Commit b6c25be9 by 周海峰

Merge branch 'master' of https://code.palacesun.com/wuchao/nse-ui

# Conflicts:
#	src/views/classification/Classification/index.vue
parents fcb208a4 501789fd
import request from '@/utils/request'
/**
* 资产库 - 查询用户列表
* 脱敏策略 - 列表查询
* @param {*} query
* @returns
*/
export function queryUser(query) {
export function getTableList(data) {
return request({
url: '/console/user/queryAll',
method: 'get',
params: query
url: '/core/desensitizationStrategy/loadDesensitizationStrategy',
method: 'post',
data: data
})
}
/**
* 资产库 - 查询列表
* @param {*} query
* @returns
*/
export function queryAll(query) {
return request({
url: '/core/datasystem/queryAll',
url: '/console/role/queryAll',
method: 'get',
params: query
})
}
/**
* 资产库 - 数据源明细
* @param {*} query
* @returns
*/
export function initEdit(query) {
export function desensitizationruleQuery(query) {
return request({
url: '/core/datasystem/initEdit',
url: '/core/desensitizationrule/query?dataarea=',
method: 'get',
params: query
})
}
export function queryversion(data) {
return request({
url: '/core/fieldscopedata/queryversion',
method: 'post',
data: data
})
}
export function queryTask(data) {
return request({
url: '/core/fieldscopedata/queryTask',
method: 'post',
data: data
})
}
/**
* 资产库 - 查询数据库版本
* @param {*} query
* @returns
*/
export function queryDatadis(query) {
export function queryProVersion(data) {
return request({
url: '/core/fieldscopedata/queryProVersion',
method: 'post',
data: data
})
}
export function queryTaskVersion(query) {
return request({
url: '/core/datadis/queryAll',
url: '/core/fieldscopedata/queryTaskVersion',
method: 'get',
params: query
})
}
/**
* 资产库 - 数据库版本检测
* @param {*} data
* @returns
*/
export function checkVersion(data) {
export function tdatasourceQuery(data) {
return request({
url: '/core/datasystem/checkVersion',
url: '/core/tdatasource/query',
method: 'post',
data: data
})
}
/**
* 资产库 - 测试数据源名称是否重复
* @param {*} data
* @returns
*/
export function checkRepeatName(data) {
export function queryShemas(data) {
return request({
url: '/core/datasystem/checkRepeatName',
url: '/core/modifysubsettask/queryShemas',
method: 'post',
data: data
})
}
/**
* 资产库 - 测试连接
* @param {*} data
* @returns
*/
export function testConnect(data) {
export function rowsensitivelevel(tableName,data) {
return request({
url: '/core/datasystem/testConnect',
url: '/core/rowsensitivelevel/' + tableName,
method: 'post',
data: data
})
}
/**
* 资产库 - 新增修改接口
* @param {*} data
* @returns
*/
export function add(data) {
export function queryDesensitizationTables(data) {
return request({
url: '/core/datasystem/add',
url: '/core/rowsensitivelevel/queryDesensitizationTables',
method: 'post',
data: data
})
}
/**
* 资产库 - 关联检测
* @param {*} data
* @returns
*/
export function checkDatasystemUsed(data) {
export function queryOriginalList(data) {
return request({
url: '/core/datasystem/checkDatasystemUsed',
url: '/core/encryptionconfig/queryOriginalList',
method: 'post',
data: data
})
}
/**
* 资产库 - 删除数据源
* @param {*} data
* @returns
*/
export function del(data) {
export function desensitizationStrategyDetails(data) {
return request({
url: '/core/datasystem/del',
url: '/core/desensitizationStrategy/desensitizationStrategyDetails',
method: 'post',
data: data
})
}
export function save(data) {
return request({
url: '/core/desensitizationStrategy/save',
method: 'post',
data: data
})
}
export function remove(data) {
return request({
url: '/core/desensitizationStrategy/remove',
method: 'post',
data: data
})
}
/**
* 资产库 - 查询字符编码
* @param {*} query
* @returns
*/
export function queryCharset(query) {
export function disableOrEnable(data) {
return request({
url: '/core/datasystem/queryCharset',
method: 'get',
params: query
url: '/core/desensitizationStrategy/disableOrEnable',
method: 'post',
data: data
})
}
import router from '@/router'
import useAppStore from '@/store/modules/app'
import usePermissionStore from '@/store/modules/permission'
const appStore = useAppStore()
const permissionStore = usePermissionStore()
export function changeRoute() {
let menus = appStore.allnavList
console.log('allnavList',menus)
let type = appStore.navStatus.type === 'manage' ? 'project' : 'manage'
let filterMenus = type === 'manage' ? menus.filter(item => item.type === '1') : menus.filter(item => item.type === '2')
console.log('filterMenus',filterMenus)
appStore.setallNav(menus)
const type = appStore.navStatus.type === 'manage' ? 'project' : 'manage'
appStore.setNavStatus({type: type})
if(type === 'manage'){
router.push({
path: '/project/Project'
})
}
}
\ No newline at end of file
......@@ -34,9 +34,9 @@ function onSearch() {
// 重置
function onReset(formRef: FormInstance) {
queryForm.value.title = ''
queryForm.value.remark = ''
queryForm.value.status = ''
queryForm.value.strategyName = ''
queryForm.value.remarks = ''
queryForm.value.state = ''
emit('reset', formRef)
}
......@@ -49,9 +49,9 @@ function onReset(formRef: FormInstance) {
:model="queryForm"
@search="onSearch"
@reset="onReset">
<el-form-item label="策略名" prop="title">
<el-form-item label="策略名" prop="strategyName">
<el-input
v-model="queryForm.title"
v-model="queryForm.strategyName"
placeholder="请输入策略名"
clearable
/>
......@@ -59,23 +59,23 @@ function onReset(formRef: FormInstance) {
<el-form-item label="备注" prop="remark">
<el-form-item label="备注" prop="remarks">
<el-input
v-model="queryForm.remark"
v-model="queryForm.remarks"
placeholder="请输入备注"
clearable
/>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-form-item label="状态" prop="state">
<el-select
v-model="queryForm.status"
v-model="queryForm.state"
placeholder="请选择状态"
clearable
>
<el-option label="已启用" value="active" />
<el-option label="未启用" value="inactive" />
<el-option label="全部" value="" />
<el-option label="已启用" value="1" />
<el-option label="未启用" value="0" />
</el-select>
</el-form-item>
</page-wrapper-search>
......
<script setup name="ProjectManageList">
import { getCurrentInstance, reactive, ref, toRefs } from 'vue'
import { getCurrentInstance, reactive, ref, toRefs, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { useRouter } from 'vue-router'
import useAppStore from '@/store/modules/app'
import usePermissionStore from '@/store/modules/permission'
import { changeRoute } from '@/utils/switchRoute'
import QueryForm from './QueryForm.vue'
import AddStrategyDialog from './modules/AddStrategyDialog.vue'
import StrategyDetailDialog from './modules/StrategyDetailDialog.vue' // 引入详情组件
import EditStrategyDialog from './modules/EditStrategyDialog.vue' // 引入编辑策略组件
import StrategyAddDialog from './modules/StrategyAddDialog.vue' // 统一对话框组件
import {
getTableList,
queryAll,
remove,
disableOrEnable
} from '@/api/desensitizationStrategy'
const appStore = useAppStore()
const permissionStore = usePermissionStore()
const router = useRouter()
const emit = defineEmits(['page'])
const { proxy } = getCurrentInstance()
// 新增策略弹窗引用
const addStrategyDialogRef = ref()
const strategyDialogRef = ref()
onMounted(() => {
handleQuery()
queryAll().then(res => {
if (res.flag) {
roleList.value = res.data
} else {
ElMessage.error(res.msg)
}
}).catch(err => {
ElMessage.error(err.msg)
})
})
function onReset(formQuery) {
console.log('onReset')
formQuery.resetFields()
handleQuery()
}
const roleList = ref([])
function onQuery() {
handleQuery()
}
// 搜索按钮操作
function handleQuery() {
console.log('queryParams',queryParams.value)
queryParams.value.pageNum = 1
queryParams.value.pageno = 1
getList()
}
const data = reactive({
queryParams: {
pageNum: 1,
pageSize: 8
pageno: 1,
pagesize: 8,
remarks: '',
state: '',
strategyName: ''
}
})
// 表格数据
const { queryParams } = toRefs(data)
const total = ref(3)
const total = ref(0)
const loading = ref(false)
const projectId = ref(sessionStorage.getItem('projectId'))
// 查询列表
function getList() {
loading.value = true
setTimeout(() => {
getTableList({
pageno: queryParams.value.pageno,
pagesize: queryParams.value.pagesize,
projectid: projectId.value,
remarks: queryParams.value.remarks,
roleGroup: '',
state: queryParams.value.state,
strategyName: queryParams.value.strategyName
}).then(res => {
if (res.flag) {
tableData.value = res.data.list
total.value = res.data.total
}
loading.value = false
}, 3000)
})
}
function pageProjectManage() {
changeRoute()
router.push({
path: '/project/Project'
})
}
const strategyAddDialogRef = ref()
/**
* 新增策略
*/
function handleAdd(){
addStrategyDialogRef.value.openDialog()
function handleAdd() {
dialog.value.title = '添加策略'
dialog.value.type = 'addtactics'
strategyAddDialogRef.value.openDialog('addtactics')
}
const tableData = ref([
{
index: 1,
id: '1',
name: '测试',
remark: '123',
createTime: '2025-08-21 16:55:30',
creator: 'admin',
status: '1'
}
])
const strategyDetailDialogRef = ref()
const tableData = ref([])
const dialog = ref({
title: '',
type: ''
})
/**
* 查看策略详情
*/
const showDetail = (row) => {
strategyDetailDialogRef.value.openDialog(row)
dialog.value.title = '详情'
dialog.value.type = 'info'
strategyAddDialogRef.value.openDialog('detail', row)
}
// 编辑策略弹窗引用
const editStrategyDialogRef = ref()
/**
* 编辑策略
*/
const editStrategy = (row) => {
console.log('编辑策略:', row)
editStrategyDialogRef.value.openDialog(row)
dialog.value.title = '编辑策略'
dialog.value.type = 'edit'
strategyAddDialogRef.value.openDialog('edit', row)
}
/**
* 删除策略
*/
const deleteStrategy = (row) => {
ElMessageBox.confirm(
`确定删除【${row.name}】策略吗?`,
`确定删除【${row.strategyName}】策略吗?`,
'删除确认',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
beforeClose: (action, instance, done) => {
if (action === 'confirm') {
instance.confirmButtonLoading = true
// 这里调用实际删除API
setTimeout(() => {
done()
// 删除成功后刷新数据或从tableData中移除
tableData.value = tableData.value.filter(item => item.index !== row.index)
}, 1000)
} else {
done()
}
}
type: 'warning'
}
).then(() => {
ElMessage({
type: 'success',
message: '删除成功',
remove({
projectid: sessionStorage.getItem('projectId'),
strategyId: row.strategyId
}).then(res => {
if (res.flag) {
ElMessage.success('删除成功')
handleQuery() // 刷新列表
} else {
ElMessage.error(res.msg)
}
}).catch(err => {
ElMessage.error('删除失败')
})
}).catch(() => {
// 用户取消删除
})
}
/**
* 启用/禁用策略
*/
const toggleStatus = (row) => {
console.log('启用/禁用:', row)
// changeRoleStatus(row.roleId, row.status).then(() => {
// ElMessage({
// type: 'success',
// message: '操作成功',
// })
// })
const newStatus = row.state === '1' ? '0' : '1'
const action = newStatus === '1' ? '启用' : '禁用'
ElMessageBox.confirm(
`确定要${action}${row.strategyName}】策略吗?`,
`${action}确认`,
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}
).then(() => {
disableOrEnable({
projectid: sessionStorage.getItem('projectId'),
state:row.state === '1' ? '0' : '1',
strategyId:row.strategyId
}).then(res => {
if(res.flag){
ElMessage({
type: 'success',
message: `${action}成功`
})
handleQuery()
}
})
}).catch(() => {
// 用户取消操作
})
}
</script>
......@@ -162,25 +236,26 @@ const toggleStatus = (row) => {
v-model="queryParams"
@query="onQuery"
@reset="onReset"/>
<el-table
:data="tableData"
border
style="width: 100%"
:header-cell-style="{ background: '#f5f7fa', color: '#606266' }"
v-loading="loading"
>
<el-table-column prop="index" label="序号" width="80" align="center" />
<el-table-column prop="name" label="策略名称" min-width="120" />
<el-table-column prop="remark" label="备注" min-width="120" show-overflow-tooltip />
<el-table-column prop="createTime" label="创建时间" min-width="180" />
<el-table-column prop="creator" label="创建人" width="120" />
<el-table-column prop="status" label="状态" width="100" align="center">
<el-table-column prop="strategyName" label="策略名称" min-width="120" fiexd/>
<el-table-column prop="remarks" label="备注" min-width="120" show-overflow-tooltip />
<el-table-column prop="createtime" label="创建时间" min-width="180" />
<el-table-column prop="createuser" label="创建人" width="120" />
<el-table-column prop="statezh" label="状态" width="100" align="center">
<template #default="{ row }">
<el-tag :type="row.status === '1' ? 'success' : 'info'">
{{ row.status === '1' ? '已启用' : '未启用' }}
<el-tag :type="row.state === '1' ? 'success' : 'info'">
{{ row.state === '1' ? '已启用' : '未启用' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="280" align="center">
<el-table-column label="操作" width="280" align="center" fixed="right">
<template #default="{ row }">
<el-button link type="primary" size="small" @click="showDetail(row)">详情</el-button>
<el-divider direction="vertical" />
......@@ -189,7 +264,7 @@ const toggleStatus = (row) => {
<el-button link type="danger" size="small" @click="deleteStrategy(row)">删除</el-button>
<el-divider direction="vertical" />
<el-button link type="success" size="small" @click="toggleStatus(row)">
{{ row.status === '1' ? '禁用' : '启用' }}
{{ row.state === '1' ? '禁用' : '启用' }}
</el-button>
</template>
</el-table-column>
......@@ -198,20 +273,15 @@ const toggleStatus = (row) => {
<pagination
v-show="total > 0"
:total="total"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
v-model:page="queryParams.pageno"
v-model:limit="queryParams.pagesize"
@pagination="getList"
/>
</div>
</div>
<!-- 新增策略弹窗 -->
<AddStrategyDialog ref="addStrategyDialogRef" />
<!-- 详情弹窗 -->
<StrategyDetailDialog ref="strategyDetailDialogRef" />
<!-- 编辑策略弹窗 -->
<EditStrategyDialog ref="editStrategyDialogRef" />
<!-- 统一策略对话框组件 -->
<StrategyAddDialog :title="dialog.title" ref="strategyAddDialogRef" @refresh="handleQuery" />
</div>
</template>
......
<template>
<el-dialog
v-model="dialogVisible"
:title="currentStep === 1 ? '添加策略 - 步骤1/2' : '添加策略 - 步骤2/2'"
width="80%"
:before-close="handleClose"
>
<!-- 步骤1:基本信息 -->
<div v-if="currentStep === 1" class="step-content">
<el-form :model="formData" :rules="rules" ref="step1Form" label-width="120px">
<el-form-item label="策略名称" prop="name">
<el-input
v-model="formData.name"
placeholder="请输入策略名称"
clearable
/>
</el-form-item>
<el-form-item label="备注">
<el-input
v-model="formData.remark"
type="textarea"
:rows="4"
placeholder="请输入策略备注信息"
maxlength="200"
show-word-limit
/>
</el-form-item>
</el-form>
</div>
<!-- 步骤2:字段设置 -->
<div v-else class="step-content">
<div class="version-select">
<span>根据发现版本设置脱敏:</span>
<div class="flex-container align-center">
<div>
选择发现版本
</div>
<el-select class="version-select-content flex1" v-model="selectedVersion" placeholder="选择发现版本">
<el-option label="无版本" value="" />
<el-option
v-for="version in versions"
:key="version"
:label="version"
:value="version"
/>
</el-select>
</div>
</div>
<div class="field-selection">
<div class="tree-container">
<el-tree
:data="treeData"
:props="treeProps"
@node-click="handleNodeClick"
highlight-current
/>
</div>
<el-divider direction="vertical" />
<div class="table-container">
<el-table :data="tableFields" border style="width: 100%" height="400px">
<el-table-column prop="isPk" label="主键" width="80" align="center" fixed>
<template #default="{ row }">
<span v-if="row.isPk">PK</span>
</template>
</el-table-column>
<el-table-column prop="name" label="字段名" min-widthwidth="150" />
<el-table-column prop="comment" label="注释" min-width="150" />
<el-table-column prop="dataType" label="数据域" min-width="120" />
<el-table-column prop="algorithm" label="脱敏算法" min-width="180" />
<el-table-column label="操作" width="80" align="center" fixed="right">
<template #default="{ row }">
<el-button type="text" size="small" @click="handleSet(row)"
>设置</el-button
>
</template>
</el-table-column>
</el-table>
</div>
</div>
</div>
<template #footer>
<div class="dialog-footer">
<el-button @click="handleCancel">取消</el-button>
<el-button
v-if="currentStep === 1"
type="primary"
@click="handleNextStep"
>下一步</el-button
>
<el-button
v-else
type="primary"
@click="handlePrevStep"
>上一步</el-button
>
<el-button
v-if="currentStep === 2"
type="primary"
@click="handleConfirm"
>确定</el-button
>
</div>
</template>
</el-dialog>
<!-- 脱敏规则设置弹窗 -->
<DesensitizationRuleDialog
v-model="desensitizationDialogVisible"
:current-field="currentField"
@confirm="handleRuleConfirm"
/>
</template>
<script setup>
import DesensitizationRuleDialog from './DesensitizationRuleDialog.vue';
import { ref, reactive } from 'vue';
import { ElMessage } from 'element-plus';
const dialogVisible = ref(false);
const currentStep = ref(1);
const desensitizationDialogVisible = ref(false);
const currentField = ref({});
// 表单数据
const formData = reactive({
name: '',
remark: '',
});
// 表单验证规则
const rules = reactive({
name: [
{ required: true, message: '请填写策略名称', trigger: 'blur' },
],
});
// 步骤2数据
const selectedVersion = ref('');
const versions = ref(['v1.0', 'v2.0']);
const treeData = ref([
{
id: 'system1',
label: '若依测试系统1',
children: [
{
id: 'ry',
label: 'ry',
children: [
{
id: 'tables',
label: '表',
children: [
{
id: 'gen_table',
label: 'gen_table',
type: 'table'
},
{
id: 'gen_table_column',
label: 'gen_table_column',
type: 'table'
},
{
id: 'sys_config',
label: 'sys_config',
type: 'table'
},
{
id: 'sys_dept',
label: 'sys_dept',
type: 'table'
},
{
id: 'sys_dict_data',
label: 'sys_dict_data',
type: 'table'
},
{
id: 'sys_dict_type',
label: 'sys_dict_type',
type: 'table'
},
{
id: 'sys_job',
label: 'sys_job',
type: 'table'
},
{
id: 'sys_job_log',
label: 'sys_job_log',
type: 'table'
},
{
id: 'sys_logininfor',
label: 'sys_logininfor',
type: 'table'
},
{
id: 'sys_menu',
label: 'sys_menu',
type: 'table'
},
{
id: 'sys_notice',
label: 'sys_notice',
type: 'table'
},
{
id: 'sys_oper_log',
label: 'sys_oper_log',
type: 'table'
}
]
}
]
}
]
}
]);
const treeProps = {
children: 'children',
label: 'label',
};
const tableFields = ref([]);
const algorithms = ref([
{ value: 'mask', label: '掩码处理' },
{ value: 'encrypt', label: '加密处理' },
{ value: 'hash', label: '哈希处理' },
]);
// 打开弹窗
const openDialog = () => {
dialogVisible.value = true;
currentStep.value = 1;
resetForm();
};
// 重置表单
const resetForm = () => {
formData.name = '';
formData.remark = '';
selectedVersion.value = '';
tableFields.value = [];
};
// 关闭弹窗
const handleClose = () => {
dialogVisible.value = false;
};
// 取消
const handleCancel = () => {
dialogVisible.value = false;
};
// 下一步
const handleNextStep = () => {
if (!formData.name) {
ElMessage.error('请填写策略名称');
return;
}
currentStep.value = 2;
};
// 上一步
const handlePrevStep = () => {
currentStep.value = 1;
};
// 确定
const handleConfirm = () => {
// 这里调用API提交数据
console.log('提交数据:', {
...formData,
version: selectedVersion.value,
fields: tableFields.value,
});
dialogVisible.value = false;
ElMessage.success('策略添加成功');
};
// 树节点点击
const handleNodeClick = (node) => {
if (node.type === 'table') {
// 模拟根据表名获取字段
fetchTableFields(node.label);
}
};
// 获取表字段
const fetchTableFields = (tableName) => {
// 这里应该是API调用,根据表名获取字段
// 模拟数据
if (tableName === 'gen_table_column') {
tableFields.value = [
{
isPk: true,
name: 'column_id',
comment: '编号',
dataType: 'bigint',
algorithm: '',
},
{
isPk: false,
name: 'table_id',
comment: '归属表编号',
dataType: 'bigint',
algorithm: '',
},
// 其他字段...
];
} else {
tableFields.value = [];
}
};
// 设置字段
const handleSet = (row) => {
currentField.value = row;
desensitizationDialogVisible.value = true;
};
// 处理规则确认
const handleRuleConfirm = (ruleData) => {
// 更新表格中的算法字段
const fieldIndex = tableFields.value.findIndex(field => field.name === ruleData.fieldName);
if (fieldIndex !== -1) {
tableFields.value[fieldIndex].algorithm = ruleData.algorithm;
ElMessage.success(`字段 ${ruleData.fieldName} 的脱敏规则已更新`);
}
};
defineExpose({
openDialog,
});
</script>
<style scoped lang="scss">
.step-content {
min-height: 400px;
}
.version-select {
margin-bottom: 20px;
}
.field-selection {
display: flex;
height: 400px;
}
.tree-container {
width: 250px;
height: 400px;
overflow-y: auto;
padding-right: 10px;
}
.table-container {
flex: 1;
height: 100%;
overflow-y: auto;
}
.dialog-footer {
display: flex;
justify-content: flex-end;
}
.el-divider {
height: 100%;
margin: 0 10px;
}
.flex-container {
display: flex;
}
.align-center {
align-items: center;
}
.justify-between {
justify-content: space-between;
}
.flex1 {
flex: 1;
}
.version-select-content {
margin-left: 15px;
}
</style>
\ No newline at end of file
......@@ -3,391 +3,379 @@
v-model="dialogVisible"
title="设置脱敏规则"
width="800px"
destroy-on-close
:before-close="handleClose"
class="desensitization-rule-dialog"
>
<div class="desensitization-dialog">
<div class="dialog-content">
<!-- 左侧:选择数据域(按照图片样式重写) -->
<div class="data-domain-section">
<!-- <el-card></el-card> -->
<div class="selector-header">
<span class="selector-title">选择数据域</span>
<div class="rule-dialog-content">
<div class="selection-container">
<!-- 左侧:选择数据域 -->
<div class="selection-section left-section">
<div class="section-header">
<div class="section-title">选择数据域</div>
</div>
<div class="options-list">
<div class="section-content">
<div
v-for="domain in dataDomains"
:key="domain.value"
class="option-item"
:class="{ 'option-selected': selectedDomain === domain.value }"
@click="selectDomain(domain.value)"
v-for="(item,index) in dataAreaList"
:key="item.id"
class="selection-item"
:class="{ selected: selectedDataArea && (selectedDataArea.id === item.id) }"
@click="handleSelectDataArea(item)"
>
<!-- 自定义单选按钮 -->
<div class="custom-radio">
<div class="radio-outer">
<div class="radio-inner" :class="{ selected: selectedDomain === domain.value }"></div>
</div>
<div class="selection-indicator">
<div class="indicator-dot" v-if="selectedDataArea && (selectedDataArea.id === item.id)"></div>
</div>
<!-- 选项标签 -->
<span class="option-label">{{ domain.label }}</span>
<div class="selection-text">{{ item.dataarea }}</div>
</div>
</div>
</div>
<!-- <el-divider direction="vertical" /> -->
<!-- 右侧:选择字段脱敏规则 -->
<div class="algorithm-section">
<h4>选择字段脱敏规则</h4>
<div class="algorithm-list">
<el-radio-group v-model="selectedAlgorithm">
<el-radio
v-for="algorithm in currentAlgorithms"
:key="algorithm.value"
:label="algorithm.value"
class="algorithm-item"
<div class="selection-section right-section">
<div class="section-header">
<div class="section-title">选择字段脱敏规则</div>
</div>
<div class="section-content" v-if="selectedDataArea && selectedDataArea.ruleList.length > 0">
<div
v-for="(item,index) in selectedDataArea.ruleList"
:key="item.id"
class="selection-item"
:class="{ selected:selectedRule && (selectedRule.id === item.id) }"
@click="selectRule(item)"
>
{{ algorithm.label }}
</el-radio>
</el-radio-group>
<div class="selection-indicator">
<div class="indicator-dot" v-if="selectedRule && (selectedRule.id === item.id)"></div>
</div>
<div class="selection-text">{{ item.rulename }}</div>
</div>
</div>
</div>
<el-divider style="height: 1px;"/>
<!-- 底部按钮 -->
<div class="dialog-footer">
<el-button @click="resetRules">重置规则</el-button>
<el-button type="primary" @click="confirmRules">确定规则</el-button>
</div>
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="handleReset" :disabled="!selectedRule">重置规则</el-button>
<el-button type="primary" @click="handleConfirm">确定规则</el-button>
</span>
</template>
</el-dialog>
</template>
<script setup>
import { ref, computed, watch, onMounted } from 'vue'
import { ElMessage } from 'element-plus'
import { ref, reactive, watch, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import {
desensitizationruleQuery,
// queryDesensitizationRules
} from '@/api/desensitizationStrategy'
const emit = defineEmits(['confirm'])
const props = defineProps({
modelValue: {
type: Boolean,
default: false
// 数据
tschemas: {
type: String,
default: '',
},
currentField: {
type: Object,
default: () => ({})
// 当前的值
dataTableLabel: [Number, String, Array],
// 当未找到匹配的数据时,显示value
table_field_id: {
type: String,
default: '',
},
dataSystemid: {
type: String,
default: '',
}
})
const emit = defineEmits(['update:modelValue', 'confirm'])
// 弹窗显示控制
const dialogVisible = computed({
get: () => props.modelValue,
set: (value) => emit('update:modelValue', value)
})
// 数据域选项(完全按照图片顺序)
const dataDomains = ref([
{ value: 'wang', label: '小王' },
{ value: 'test', label: '测试数据域' },
{ value: 'general', label: '通用规则' },
{ value: 'decrypt', label: '解密数据' },
{ value: 'mixed_id', label: '混合证件号' },
{ value: 'black_white', label: '黑白名单' },
{ value: 'english_address', label: '英文地址' },
{ value: 'name', label: '姓名' },
{ value: 'phone', label: '手机号码' },
{ value: 'age', label: '年龄' },
{ value: 'business_license', label: '营业执照' },
{ value: 'mixed_phone', label: '混合电话号码' }
// 控制弹窗显示
const dialogVisible = ref(false)
// 当前字段数据
const currentFieldData = ref({})
// 数据域列表(根据图片中的选项)
const dataAreaList = ref([
])
// 脱敏算法选项
const algorithmOptions = {
// 通用规则
general: [
{ value: 'general_mask', label: '通用掩码' },
{ value: 'general_encrypt', label: '通用加密' },
{ value: 'general_hash', label: '通用哈希' }
],
// 姓名
name: [
{ value: 'name_mask', label: '姓名掩码' },
{ value: 'name_random', label: '姓名随机替换' },
{ value: 'name_encrypt', label: '姓名加密' }
],
// 手机号码
phone: [
{ value: 'phone_mask', label: '手机号掩码' },
{ value: 'phone_encrypt', label: '手机号加密' },
{ value: 'phone_hash', label: '手机号哈希' }
],
// 其他数据域的算法...
wang: [
{ value: 'wang_mask', label: '小王掩码' },
{ value: 'wang_encrypt', label: '小王加密' }
],
test: [
{ value: 'test_mask', label: '测试掩码' },
{ value: 'test_encrypt', label: '测试加密' }
],
// 默认算法
default: [
{ value: 'mask', label: '掩码处理' },
{ value: 'encrypt', label: '加密处理' },
{ value: 'hash', label: '哈希处理' }
]
}
// // 脱敏规则列表(根据图片中的选项)
// const desensitizationRules = ref([
// { id: '1', name: '测试算法' },
// { id: '2', name: '测试算法1' }
// ])
// 选中的数据域和算法
const selectedDomain = ref('general')
const selectedAlgorithm = ref('')
// 选中的数据域
const selectedDataArea = ref(null) // 默认选中第一个
// 选中的脱敏规则
const selectedRule = ref(null) // 默认选中第一个
// 组件挂载时加载数据
onMounted(() => {
// 计算当前可用的脱敏算法
const currentAlgorithms = computed(() => {
return algorithmOptions[selectedDomain.value] || algorithmOptions.default
})
// 选择数据域
const selectDomain = (value) => {
selectedDomain.value = value
const algorithms = currentAlgorithms.value
if (algorithms.length > 0) {
selectedAlgorithm.value = algorithms[0].value
}
const handleSelectDataArea = (val) => {
selectedDataArea.value = val
}
// 重置选择函数
const resetSelection = () => {
selectedDomain.value = 'general'
const algorithms = currentAlgorithms.value
if (algorithms.length > 0) {
selectedAlgorithm.value = algorithms[0].value
}
// 选择脱敏规则
const selectRule = (val) => {
console.log(val)
selectedRule.value = val
console.log('selectedRule.value',selectedRule.value)
}
// 根据算法推断数据域
const inferDomainFromAlgorithm = (algorithm) => {
for (const [domain, algorithms] of Object.entries(algorithmOptions)) {
if (algorithms.some(algo => algo.value === algorithm)) {
selectedDomain.value = domain
return
}
}
selectedDomain.value = 'general'
// 打开弹窗
const open = (fieldData = {}) => {
console.log('open',fieldData)
currentFieldData.value = fieldData
console.log('currentFieldData.value',currentFieldData.value)
initData(currentFieldData.value)
dialogVisible.value = true
}
// 监听当前字段变化,恢复已保存的规则
watch(() => props.currentField, (newField) => {
if (newField && newField.algorithm) {
selectedAlgorithm.value = newField.algorithm
inferDomainFromAlgorithm(newField.algorithm)
} else {
resetSelection()
// 关闭弹窗
const close = () => {
dialogVisible.value = false
resetForm()
}
// 初始化数据
const initData = (val) => {
desensitizationruleQuery().then(res=>{
dataAreaList.value = res.data
// 如果有传入的字段数据,可以预设选择
if (val.dataareanameId) {
selectedDataArea.value = dataAreaList.value.find(item => item.id === val.dataareanameId)
console.log('123123',selectedDataArea.value)
// selectedDataArea.value = val.dataarea_id
}
}, { immediate: true })
if (val.rulenameId && selectedDataArea.value) {
selectedRule.value = selectedDataArea.value.ruleList.find(item => item.id === val.rulenameId)
// 组件挂载时初始化
onMounted(() => {
resetSelection()
})
}
})
// 关闭弹窗
const handleClose = () => {
dialogVisible.value = false
// 如果没有传入数据,重置表单
// if (!fieldData || Object.keys(fieldData).length === 0) {
// resetForm()
// }
}
// 重置规则
const resetRules = () => {
resetSelection()
const handleReset = () => {
selectedRule.value = ''
ElMessage.info('规则已重置')
}
// 确定规则
const confirmRules = () => {
if (!selectedAlgorithm.value) {
// 确认规则
const handleConfirm = () => {
if (!selectedDataArea.value) {
ElMessage.warning('请选择数据域')
return
}
if (!selectedRule.value) {
ElMessage.warning('请选择脱敏规则')
return
}
emit('confirm', {
algorithm: selectedAlgorithm.value,
domain: selectedDomain.value,
fieldName: props.currentField.name
let data = {
dataSystemid:props.dataSystemid,
dataareaid: selectedDataArea.value.id,
dataareaname: selectedDataArea.value.dataarea,
ruleId: selectedRule.value.id,
rulename: selectedRule.value.rulename,
table_field_id:props.table_field_id,
tschemas:props.tschemas,
names:props.dataTableLabel
}
let str = `${props.tschemas}.${props.dataTableLabel}.${props.table_field_id}`
emit('confirm',str,data)
close()
// ElMessage.success('脱敏规则设置成功')
}
// 关闭弹窗
const handleClose = (done) => {
// 如果有未保存的更改,可以提示用户
if (selectedDataArea.value || selectedRule.value) {
ElMessageBox.confirm('确定要关闭吗?未保存的更改将会丢失', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
resetForm()
done()
}).catch(() => {
// 取消关闭
})
} else {
resetForm()
done()
}
}
ElMessage.success('脱敏规则设置成功')
dialogVisible.value = false
// 重置表单
const resetForm = () => {
dataAreaList.value = []
selectedDataArea.value = null
selectedRule.value = null
currentFieldData.value = {}
}
// 暴露方法给父组件
defineExpose({
open,
close
})
</script>
<style scoped lang="scss">
.desensitization-dialog {
.dialog-content {
display: flex;
height: 400px;
<style scoped>
.desensitization-rule-dialog {
:deep(.el-dialog__body) {
padding: 20px;
}
}
.data-domain-section {
flex: 1;
.rule-dialog-content {
padding: 10px;
}
.selection-container {
display: flex;
flex-direction: column;
padding-right: 20px;
}
gap: 20px;
height: 400px;
}
.algorithm-section {
.selection-section {
flex: 1;
border: 1px solid #e4e7ed;
border-radius: 4px;
background-color: #f8f9fa;
display: flex;
flex-direction: column;
padding-left: 20px;
}
}
.selector-header {
padding: 0 0 16px 0;
margin-bottom: 8px;
// border-bottom: 1px dashed #dcdfe6;
.selector-title {
font-size: 16px;
font-weight: 900;
color: #000000;
line-height: 1.5;
display: block;
}
}
.section-header {
padding: 15px;
border-bottom: 1px solid #e4e7ed;
background-color: #f5f5f5;
}
.options-list {
border: thin solid #dcdfe6;
border-radius: 5px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.04);
.section-title {
font-weight: bold;
color: #303133;
font-size: 14px;
text-align: center;
}
padding: 10px;
display: flex;
flex-direction: column;
gap: 0;
.section-content {
flex: 1;
padding: 10px;
overflow-y: auto;
}
}
.option-item {
.selection-item {
display: flex;
align-items: center;
padding: 12px 0;
padding: 12px 15px;
border-bottom: 1px solid #f0f0f0;
cursor: pointer;
transition: background-color 0.2s ease;
border-bottom: 1px dashed #e0e0e0;
&:last-child {
border-bottom: none;
}
&:hover {
background-color: #f8f9fa;
}
transition: all 0.2s ease;
border-radius: 4px;
margin-bottom: 4px;
}
&.option-selected {
background-color: #f0f7ff;
.selection-item:hover {
background-color: #f5f7fa;
}
.option-label {
color: #1890ff;
font-weight: 500;
}
.radio-outer {
background-color: #1890ff;
}
}
}
.selection-item.selected {
background-color: #ecf5ff;
border-color: #409EFF;
}
.custom-radio {
display: flex;
align-items: center;
justify-content: center;
margin-right: 12px;
width: 20px;
height: 20px;
flex-shrink: 0;
}
.selection-item:last-child {
border-bottom: none;
margin-bottom: 0;
}
.radio-outer {
.selection-indicator {
width: 16px;
height: 16px;
border: 2px solid #c0c4cc;
border: 2px solid #dcdfe6;
border-radius: 50%;
margin-right: 12px;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.2s ease;
}
.option-selected & {
border-color: #1890ff;
background-color: #1890ff;
}
}
.selection-item.selected .selection-indicator {
border-color: #409EFF;
}
.radio-inner {
width: 6px;
height: 6px;
.indicator-dot {
width: 8px;
height: 8px;
background-color: #409EFF;
border-radius: 50%;
background-color: transparent;
transition: background-color 0.2s ease;
&.selected {
background-color: #ffffff;
}
}
.option-label {
font-size: 14px;
color: #606266;
line-height: 1.5;
flex: 1;
user-select: none;
.option-selected & {
color: #1890ff;
}
}
}
h4 {
margin: 0 0 16px 0;
font-size: 14px;
font-weight: 600;
.selection-text {
font-size: 13px;
color: #606266;
}
.algorithm-list {
flex: 1;
overflow-y: auto;
}
.algorithm-item {
display: block;
margin: 8px 0;
padding: 8px 12px;
border-radius: 4px;
transition: background-color 0.3s;
&:hover {
background-color: #f5f7fa;
}
:deep(.el-radio__label) {
font-size: 13px;
}
}
}
.el-divider {
// height: 360px;
// margin: 0 20px;
}
.selection-item.selected .selection-text {
color: #409EFF;
font-weight: 500;
}
.dialog-footer {
.dialog-footer {
display: flex;
justify-content: flex-end;
margin-top: 20px;
gap: 12px;
}
padding: 10px 0;
}
.left-section {
border-right: 2px solid #e4e7ed;
}
.right-section {
border-left: 2px solid #e4e7ed;
}
</style>
\ No newline at end of file
<template>
<el-dialog
v-model="dialogVisible"
:title="title"
width="80%"
:before-close="handleClose"
class="strategy-add-dialog"
>
<!-- 步骤指示器 -->
<div class="steps-container">
<el-steps :active="activeStep" align-center>
<el-step title="基本信息" />
<el-step title="规则配置" />
</el-steps>
</div>
<!-- 第一步:基本信息 -->
<div v-if="activeStep === 1" class="step-content">
<el-form
ref="step1FormRef"
:model="formData"
:rules="formRules"
label-width="100px"
label-position="top"
>
<el-form-item label="策略名称" prop="strategyName" required>
<el-input
:disabled="dialogType === 'detail'"
v-model="formData.strategyName"
placeholder="请输入策略名称"
maxlength="50"
show-word-limit
/>
</el-form-item>
<el-form-item label="备注" prop="remarks">
<el-input
:disabled="dialogType === 'detail'"
v-model="formData.remarks"
type="textarea"
:rows="4"
placeholder="请输入策略备注信息"
maxlength="200"
show-word-limit
/>
</el-form-item>
</el-form>
</div>
<!-- 第二步:规则配置 -->
<div v-if="activeStep === 2" class="step-content">
<div class="version-select">
<span class="label">根据发现版本设置脱敏:</span>
<span class="label" style="margin-left: 60px;">选择发现版本:</span>
<el-tree-select
v-model="selectedVersion"
:data="editionList"
:props="treeSelectProps"
:load="loadCascader"
lazy
placeholder="选择发现版本"
clearable
style="width: 200px; margin-left: 10px;"
/>
</div>
<div class="config-container">
<!-- 左侧树形结构 -->
<div class="tree-panel">
<TreeFilter :tree-data="treeData" @node-click="handleNodeClick"/>
</div>
<!-- 右侧表格 -->
<div class="table-panel">
<el-table
:data="tableData"
border
style="width: 100%"
height="100%"
v-loading="tableLoading"
>
<el-table-column prop="id" label="主键" width="80" align="center" />
<el-table-column prop="fieldName" label="字段名" min-width="120" />
<el-table-column prop="comment" label="注释" min-width="150" show-overflow-tooltip />
<el-table-column prop="dataareaname" label="数据域" min-width="120" />
<el-table-column prop="rulename" label="脱敏算法" min-width="150" />
<el-table-column label="操作" width="120" align="center" fixed="right">
<template #default="{ row }">
<el-button link type="primary" size="small" @click="editField(row)">
设置
</el-button>
</template>
</el-table-column>
</el-table>
<div v-if="tableData.length === 0 && !tableLoading" class="empty-table">
<el-empty description="暂无数据" />
</div>
</div>
</div>
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="handleCancel">取消</el-button>
<el-button v-if="activeStep === 2" @click="activeStep = 1">上一步</el-button>
<el-button
type="primary"
v-if="activeStep === 1"
@click="handleNextStep"
>
下一步
</el-button>
<el-button
v-if="activeStep === 2"
type="primary"
:loading="saveLoading"
@click="handleNextStep"
>
确定
</el-button>
</span>
</template>
</el-dialog>
<DesensitizationRuleDialog
:dataSystemid="dataSystemid"
:tschemas="schema"
:dataTableLabel="dataTableLabel"
:table_field_id="currentFieldData?.columnname"
ref="desensitizationRuleDialogRef"
@confirm="handleRuleConfirm"
/>
</template>
<script setup>
import { ref, reactive, watch, computed, nextTick } from 'vue'
import { ElMessage,ElMessageBox } from 'element-plus'
import DesensitizationRuleDialog from './DesensitizationRuleDialog.vue'
import {
desensitizationruleQuery,
queryversion,
queryShemas,
queryOriginalList,
queryTask,
queryProVersion,
queryTaskVersion,
desensitizationStrategyDetails,
save,
queryDesensitizationTables
} from '@/api/desensitizationStrategy'
import { queryTables,query } from '@/api/classification/classification.js'
import TreeFilter from './TreeFilter.vue'
const props = defineProps({
title: {
type: String,
default: '添加策略'
},
strategyId: {
type: String,
default: ''
}
})
const emit = defineEmits(['refresh', 'update:visible'])
const dialogVisible = ref(false)
const activeStep = ref(1)
const selectedVersion = ref('')
const tableLoading = ref(false)
// 表单数据
const formData = reactive({
strategyName: '',
remarks: ''
})
const step1FormRef = ref()
// 表单验证规则
const formRules = {
strategyName: [
{ required: true, message: '请填写策略名称', trigger: 'blur' }
]
}
const treeSelectProps = {
// value: 'value',
// label: 'text',
// children: 'children',
// isLeaf: 'leaf'
}
// 版本选项
const editionList = ref([
{
value: 'none',
label: '无版本'
},
{
value: 'v1',
label: '版本1.0'
},
{
value: 'v2',
label: '版本2.0'
}
])
/**
* 发现版本懒加载
* @param node
* @param resolve
*/
const loadCascader = async (node, resolve) => {
try {
const { value, level } = node
if (level === 0) {
// 第一级节点已经加载完成,直接返回
resolve(editionList.value)
return
}
let children = []
if (value === 'TaskVersion') {
// 加载任务列表
const res = await queryTask({ projectid: sessionStorage.getItem('projectId') })
children = res.data.map(item => ({
label: item.text,
value: item.value,
loading: false,
children: [],
leaf: false
}))
} else if (value === 'ProjectVersion') {
// 加载项目版本
const res = await queryProVersion({
projectid: sessionStorage.getItem('projectId'),
versiontype: 'project'
})
if (res.data.versionname && res.data.filename) {
children = [{
label: res.data.versionname,
value: res.data.filename,
leaf: true
}]
}
} else {
// 加载任务版本
const res = await queryTaskVersion({ taskid: value })
children = res.data.map(item => ({
label: item.versionname,
value: item.filename,
leaf: true
}))
}
resolve(children)
} catch (error) {
console.error('加载版本数据失败:', error)
ElMessage.error('加载版本数据失败')
resolve([])
}
}
// 树形数据
const treeData = ref([
// {
// id: '1',
// label: '若依配测2',
// type: 'project',
// children: [
// {
// id: '1-1',
// label: 'ry',
// children: [
// { id: '1-1-1', label: 'gen_table', type: 'table' },
// { id: '1-1-2', label: 'gen_table_column', type: 'table' },
// { id: '1-1-3', label: 'sys_config', type: 'table' }
// ]
// }
// ]
// },
// {
// id: '2',
// label: '若依测试库2',
// type: 'project',
// children: [
// {
// id: '2-1',
// label: 'ry_test',
// type: 'database',
// tableCount: 15,
// children: [
// { id: '2-1-1', label: 'sys_dept', type: 'table' },
// { id: '2-1-2', label: 'sys_dict_data', type: 'table' },
// { id: '2-1-3', label: 'sys_dict_type', type: 'table' }
// ]
// }
// ]
// }
])
// 当前选中的节点数据
const currentNodeData = ref(null)
const currentNodeLevel = computed(() => {
if (!currentNodeData.value) return 1
if (currentNodeData.value.type === 'system') return 1
if (currentNodeData.value.type === 'database') return 2
if (currentNodeData.value.type === 'category') return 3
if (currentNodeData.value.type === 'table') return 4
return 0
})
const datasystem_id = ref('')
const schema = ref('')
const dataBaseLabel = ref('')
const dataTableLabel = ref('')
const dataTableColumnsLabel = ref('')
const dataSystemid = ref('')
const mapData = ref({})
// 监听 currentNodeLevel 和 currentNodeData,一级节点时自动查详情
watch([currentNodeLevel, currentNodeData], async ([level, node]) => {
// if (level === 1 && node && tab === 'basic') {
// const res = await queryDatasystemInfo({ dataSystemId: node.tid, projectId: projectId.value })
// if (res && res.data) {
// // 你可以将结果赋值到 basicInfoData 或其他变量
// basicInfoData.value = { ...basicInfoData.value, ...res.data }
// }
// }
if (level === 1 && node ) {
console.log('level1',node)
datasystem_id.value = node.tid
dataSystemid.value = node.tid
}
if (level === 2 && node ) {
console.log('level2',node)
schema.value = node.value
dataBaseLabel.value = node.label
}
if (level === 3 && node ) {
console.log('level3',node)
}
// 查询表基本信息
if (level === 4 && node ) {
console.log('level4',node)
dataTableLabel.value = node.label
schema.value = node.parent.parent.value
datasystem_id.value = node.parent.parent.parent.tid
dataSystemid.value = node.parent.parent.parent.tid
tableLoading.value = true
queryOriginalList({
table_name: node.label,
projectId: sessionStorage.getItem('projectId'),
datasystem_id: datasystem_id.value,
schema: schema.value
}).then(res=>{
if(res.flag){
tableData.value = res.data.map(item=>(({
...item,
id: item.primarykey,
fieldName: item.columnname,
comment: item.remarks,
dataareaname:'',
dataareanameId: '',
rulename: '',
rulenameId: ''
// dataDomain: item.
})))
tableLoading.value = false
if(mapData.value && dialogType.value === 'addtactics'){
for (const key in mapData.value) {
const item = mapData.value[key];
console.log(`字段键名: ${key}`);
console.log(`数据系统ID: ${item.dataSystemid}`);
console.log(`数据域ID: ${item.dataareaid}`);
console.log(`数据域名称: ${item.dataareaname}`);
console.log(`规则ID: ${item.ruleId}`);
console.log(`规则名称: ${item.rulename}`);
console.log(`表字段ID: ${item.table_field_id}`);
console.log(`模式: ${item.tschemas}`);
console.log(`表名: ${item.names}`);
console.log('----------------------');
console.log('tableData.value',tableData.value)
let index = tableData.value.findIndex(x =>x.fieldName === item.table_field_id)
console.log(index)
if(index !== -1 && datasystem_id.value === item.dataSystemid && schema.value === item.tschemas && dataTableLabel.value === item.names){
tableData.value[index].dataareaname = item.dataareaname
console.log('tableData.value',tableData.value)
tableData.value[index].rulename= item.rulename
tableData.value[index].dataareanameId = item.dataareaid
tableData.value[index].rulenameId = item.ruleId
}
}
}
console.log(123123)
if(dialogType.value === 'edit'){
console.log('infoTable.value',infoTable.value)
infoTable.value.forEach(item => {
let str = `${item.tschemas}.${item.names}.${item.table_field_id}`
let data = {
dataSystemid:item.datasystem_id,
dataareaid: item.dataareaid,
dataareaname: item.dataareaname,
ruleId: item.ruleid,
rulename: item.rulename,
table_field_id: item.table_field_id,
tschemas: item.tschemas,
names: item.names
}
mapData.value[`${str}`] = data
});
for (const key in mapData.value) {
const item = mapData.value[key];
console.log(`字段键名: ${key}`);
console.log(`数据系统ID: ${item.dataSystemid}`);
console.log(`数据域ID: ${item.dataareaid}`);
console.log(`数据域名称: ${item.dataareaname}`);
console.log(`规则ID: ${item.ruleId}`);
console.log(`规则名称: ${item.rulename}`);
console.log(`表字段ID: ${item.table_field_id}`);
console.log(`模式: ${item.tschemas}`);
console.log(`表名: ${item.names}`);
console.log('----------------------');
console.log('tableData.value',tableData.value)
let index = tableData.value.findIndex(x =>x.fieldName === item.table_field_id)
console.log(index)
if(index !== -1 && datasystem_id.value === item.dataSystemid && schema.value === item.tschemas && dataTableLabel.value === item.names){
tableData.value[index].dataareaname = item.dataareaname
console.log('tableData.value',tableData.value)
tableData.value[index].rulename= item.rulename
tableData.value[index].dataareanameId = item.dataareaid
tableData.value[index].rulenameId = item.ruleId
}
}
// {
// "strategyName": "111",
// "remarks": "",
// "roleGroup": "2222,1,系统管理员,项目管理",
// "state": "0",
// "strategyId": "e848c8be263f417992afe1a1aa44c680",
// "datasystem_id": "2c90e4d798c5650f0198c58937920003",
// "tschemas": "nse_encryption_ry_test",
// "names": "gen_table",
// "table_field_id": "business_name",
// "projectid": "df345570-d044-47b3-a2c6-0ff265f89b80",
// "dataareaid": "402882057e4c6440017e4d3b6f46005f",
// "dataareaname": "通用规则",
// "ruleid": "2c90e4cb98d9e3220198daae53850028",
// "rulename": "测试2",
// "createuser": "admin",
// "createtime": "2025-08-27 09:24:19",
// "pageno": 0,
// "pagesize": 0
// }
}
}else{
ElMessage.error(res.msg)
tableLoading.value = false
tableData.value = []
}
}).catch(err=>{
console.log(err)
ElMessage.error(err.msg)
tableLoading.value = false
tableData.value = []
})
}
console.log('mapData',mapData)
})
const selectedDataArea = ref(null)
const selectedRule = ref(null)
const handleRuleConfirm = (str,data) =>{
console.log('mapData000',mapData.value)
console.log('str',str)
console.log('data',data)
mapData.value[`${str}`] = data
console.log('mapData11111',mapData.value)
for (const key in mapData.value) {
const item = mapData.value[key];
let index = tableData.value.findIndex(x =>x.fieldName === item.table_field_id)
console.log(index)
if(index !== -1){
tableData.value[index].names = item.names
tableData.value[index].dataareaname = item.dataareaname
console.log('tableData.value',tableData.value)
tableData.value[index].rulename= item.rulename
tableData.value[index].dataareanameId = item.dataareaid
tableData.value[index].rulenameId = item.ruleId
}
}
}
// 生成唯一标识符
function uuid() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
var r = Math.random() * 16 | 0, v = c === 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
}
// 树节点点击处理
const handleNodeClick = async (data) => {
currentNodeData.value = data
// 2. 点击一层系统数据,查 schemas
if (data.type === 'system') {
const res = await queryShemas({
dataSystemId: data.tid,
dataType: data.dbtype,
projectid: sessionStorage.getItem('projectId')
})
if (res && res.data) {
data.children = res.data.map(schema => ({
id: uuid(),
...schema,
type: 'database',
label: schema.text,
parent: data,
children: [
{
id: uuid(),
label: '表',
type: 'category',
parent: {
...schema,
type: 'database',
label: schema.text,
parent: data,
tid: data.tid,
dbservername: schema.text
},
children: []
}
]
}))
treeData.value = [...treeData.value] // 触发视图更新
}
}
// 3. 点击三层 category,查表
if (data.type === 'category' && data.parent) {
const parentDb = data.parent
const res = await queryTables({
dataSystemId: parentDb.tid,
schema: parentDb.dbservername
})
if (res && res.data) {
data.children = res.data.map(table => ({
...table,
id: uuid(),
label: table.showName,
type: 'table',
parent: data
}))
treeData.value = [...treeData.value]
}
}
}
// 表格数据
const tableData = ref([])
const infoTable = ref([])
const dialogType = ref('')
// 打开对话框
const openDialog = async (mode, strategyData = null) => {
dialogType.value = mode
dialogVisible.value = true
activeStep.value = 1
selectedVersion.value = ''
desensitizationStrategyDetails({
strategyId: strategyData ? strategyData.strategyId : '',
projectid: sessionStorage.getItem('projectId')
}).then(res =>{
if(res.flag){
infoTable.value = res.data
}
})
queryversion({
projectid: sessionStorage.getItem('projectId')
}).then(res=>{
// editionList.value = res.data
editionList.value = res.data.map(item => {
let res = {
label: item.text,
value: item.value,
children: [],
leaf: item.value === 'null' || item.value === 'AllVersion'
}
!res.leaf && (res.loading = false)
return res
})
editionList.value.splice(1, 2)
console.log(editionList.value)
})
// 重置表单数据
if (mode === 'addtactics') {
formData.strategyName = ''
formData.remarks = ''
} else if (strategyData) {
// 填充编辑数据
formData.strategyName = strategyData.strategyName || ''
formData.remarks = strategyData.remarks || ''
}
// 清空表格数据
tableData.value = []
const res = await query({ project_id: sessionStorage.getItem('projectId') })
if (res && res.data) {
treeData.value = res.data.map(item => ({
...item,
label: item.sysname,
type: 'system',
children: [
]
}))
// 赋值触发监听,默认查询第一个数据源的基本信息
currentNodeData.value = treeData.value[0]
}
}
// 关闭对话框
const handleClose = (done) => {
// 如果有未保存的数据,提示用户
if ((formData.strategyName || formData.remarks) && dialogType.value !=='detail') {
ElMessageBox.confirm('确定要关闭吗?未保存的数据将会丢失', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
resetForm()
done()
}).catch(() => {
// 取消关闭
})
} else {
resetForm()
done()
}
}
// 取消操作
const handleCancel = () => {
dialogVisible.value = false
resetForm()
}
// 重置表单
const resetForm = () => {
mapData.value = ref({})
formData.strategyName = ''
formData.remarks = ''
activeStep.value = 1
selectedVersion.value = ''
tableData.value = []
}
// 下一步操作
const handleNextStep = async () => {
if (activeStep.value === 1) {
// 验证第一步表单
if (step1FormRef.value) {
const formInstance = step1FormRef.value
if (formInstance) {
try {
await formInstance.validate()
activeStep.value = 2
} catch (error) {
ElMessage.error('请填写策略名称')
}
}
}
} else {
// 第二步:保存策略
await saveStrategy()
}
}
const saveLoading = ref(false)
// 保存策略
const saveStrategy = async () => {
saveLoading.value = true
try {
// 动态生成 mapStr 并构建完整对象
const mapStr = `${dataBaseLabel.value}.${dataTableLabel.value}.${dataTableColumnsLabel.value}`;
const params = {
strategyName: formData.strategyName,
remarks: formData.remarks,
state: "0",
strategyId: props.strategyId || '',
projectId: sessionStorage.getItem('projectId'),
roleGroup: '',
map: mapData.value
};
const res = await save(params);
if (res.flag) {
ElMessage.success(props.strategyId ? '编辑成功' : '新增成功');
saveLoading.value = false
dialogVisible.value = false;
emit('refresh');
resetForm();
} else {
ElMessage.error(res.msg);
saveLoading.value = false
}
} catch (error) {
console.log(error);
saveLoading.value = false
ElMessage.error('保存失败');
}
}
const desensitizationRuleDialogRef = ref(null)
// 编辑字段
const currentFieldData = ref(null)
const editField = (row) => {
// ElMessage.info('编辑字段功能待实现')
currentFieldData.value = row
desensitizationRuleDialogRef.value.open(row)
}
// 暴露方法给父组件
defineExpose({
openDialog
})
</script>
<style scoped>
.strategy-add-dialog {
:deep(.el-dialog__body) {
padding: 20px;
}
}
.steps-container {
margin-bottom: 30px;
padding: 0 50px;
}
.step-content {
min-height: 400px;
}
.version-select {
margin-bottom: 20px;
display: flex;
align-items: center;
.label {
font-weight: bold;
color: #606266;
}
}
.config-container {
display: flex;
height: 400px;
border: 1px solid #e4e7ed;
border-radius: 4px;
}
.tree-panel {
width: 300px;
border-right: 1px solid #e4e7ed;
padding: 10px;
overflow-y: auto;
}
.table-panel {
flex: 1;
position: relative;
padding: 10px;
}
.empty-table {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.tree-node {
display: flex;
align-items: center;
.node-label {
flex: 1;
}
.node-count {
color: #909399;
font-size: 12px;
margin-left: 8px;
}
}
.dialog-footer {
display: flex;
justify-content: flex-end;
gap: 10px;
}
:deep(.el-form-item__label) {
font-weight: bold;
}
</style>
\ No newline at end of file
<template>
<div class="tree-filter-container">
<!-- 搜索框 -->
<div class="search-box">
<el-input
v-model="filterText"
placeholder="输入关键字过滤"
clearable
prefix-icon="el-icon-search"
/>
</div>
<!-- 树形结构 -->
<el-tree
ref="treeRef"
class="filter-tree"
:data="treeData"
:props="defaultProps"
:filter-node-method="filterNode"
:expand-on-click-node="false"
node-key="id"
highlight-current
default-expand-all
@node-click="handleNodeClick"
>
<template #default="{ node, data }">
<span class="custom-tree-node">
<i :class="getNodeIcon(data.type)" class="node-icon"></i>
<span>{{ node.label }}</span>
<template v-if="data.type === 'category'">
<i class="el-icon-collection icon-category" style="margin-left:6px;color:#F7BA2A;font-size:16px;"></i>
</template>
<template v-if="data.type === 'system'">
<i class="el-icon-s-platform icon-system" style="margin-left:6px;color:#409EFF;font-size:16px;"></i>
</template>
<template v-if="data.type === 'database'">
<i class="el-icon-s-data icon-database" style="margin-left:6px;color:#67C23A;font-size:16px;"></i>
</template>
<template v-if="data.type === 'table'">
<i class="el-icon-s-grid icon-table" style="margin-left:6px;color:#E6A23C;font-size:16px;"></i>
</template>
</span>
</template>
</el-tree>
</div>
</template>
<script setup>
import { ref, watch } from 'vue'
const emit = defineEmits(['node-click'])
const props = defineProps({
treeData: {
type: Array,
default: () => [
{
id: 'system',
label: '若依配测系统',
type: 'system',
children: [
{
id: 'database',
label: 'ry',
type: 'database',
children: [
{
id: 'tables',
label: '表',
type: 'category',
children: [
{ id: 'gen_table', label: 'gen_table', type: 'table' },
{ id: 'gen_table_column', label: 'gen_table_column', type: 'table' },
{ id: 'sys_config', label: 'sys_config', type: 'table' },
{ id: 'sys_dept', label: 'sys_dept', type: 'table' },
{ id: 'sys_dict_data', label: 'sys_dict_data', type: 'table' },
{ id: 'sys_dict_type', label: 'sys_dict_type', type: 'table' },
{ id: 'sys_job', label: 'sys_job', type: 'table' },
{ id: 'sys_job_log', label: 'sys_job_log', type: 'table' },
{ id: 'sys_logininfor', label: 'sys_logininfor', type: 'table' },
{ id: 'sys_menu', label: 'sys_menu', type: 'table' },
{ id: 'sys_notice', label: 'sys_notice', type: 'table' },
{ id: 'sys_oper_log', label: 'sys_oper_log', type: 'table' },
{ id: 'sys_post', label: 'sys_post', type: 'table' },
{ id: 'sys_role', label: 'sys_role', type: 'table' },
{ id: 'sys_role_dept', label: 'sys_role_dept', type: 'table' },
{ id: 'sys_role_menu', label: 'sys_role_menu', type: 'table' },
{ id: 'sys_user', label: 'sys_user', type: 'table' },
{ id: 'sys_user_online', label: 'sys_user_online', type: 'table' },
{ id: 'sys_user_post', label: 'sys_user_post', type: 'table' },
{ id: 'sys_user_role', label: 'sys_user_role', type: 'table' }
]
}
]
}
]
}
]
}
})
const filterText = ref('')
const treeRef = ref(null)
const defaultProps = {
children: 'children',
label: 'label'
}
// 根据节点类型获取图标
const getNodeIcon = (type) => {
const iconMap = {
system: 'el-icon-s-platform',
database: 'el-icon-s-data',
category: 'el-icon-folder-opened',
table: 'el-icon-s-grid'
}
return iconMap[type] || 'el-icon-document'
}
// 过滤树节点
const filterNode = (value, data) => {
if (!value) return true
return data.label.toLowerCase().includes(value.toLowerCase())
}
// 监听过滤文本变化
watch(filterText, (val) => {
treeRef.value.filter(val)
})
// 节点点击事件
const handleNodeClick = (data) => {
emit('node-click', data)
}
</script>
<style scoped>
.tree-filter-container {
max-height: 700px;
overflow-y: auto;
width: 100%;
height: 100%;
/* background-color: #f5f5f5; */
padding: 10px;
border-right: 1px solid #e6e6e6;
}
.search-box {
margin-bottom: 10px;
}
.filter-tree {
background-color: transparent;
}
.custom-tree-node {
flex: 1;
display: flex;
align-items: center;
font-size: 14px;
}
.node-icon {
margin-right: 6px;
color: #606266;
}
.icon-category {
vertical-align: middle;
}
.icon-system {
vertical-align: middle;
}
.icon-database {
vertical-align: middle;
}
.icon-table {
vertical-align: middle;
}
:deep(.el-tree-node__content) {
height: 36px;
}
</style>
\ No newline at end of file
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论