Commit 61503b50 by ningjihai

加密设置

parent 7c2c59e0
...@@ -73,6 +73,11 @@ export const constantRoutes = [ ...@@ -73,6 +73,11 @@ export const constantRoutes = [
hidden: true hidden: true
}, },
{ {
path: '/classification',
component: () => import('@/views/Classification/index'),
hidden: true
},
{
path: '', path: '',
component: Layout, component: Layout,
redirect: '/index', redirect: '/index',
......
<script setup name="Classification">
import { ref, toRefs, reactive, getCurrentInstance, proxyRefs, onMounted } from 'vue'
import TreeFilter from './modules/TreeFilter.vue'
import BasicInfoTab from './modules/BasicInfoTab.vue'
import TableInfoTab from './modules/TableInfoTab.vue'
import StructureTab from './modules/StructureTab.vue'
import EncryptionTab from './modules/EncryptionTab.vue'
import { useDict } from '@/utils/dict'
import { useRouter } from 'vue-router'
const router = useRouter()
const { proxy } = getCurrentInstance()
const projectId = ref('')
onMounted(()=>{
projectId.value =proxy.$route.query.projectId
console.log('projectId',projectId.value)
})
// const props = defineProps({
// projectId: String, // 主键
// })
// 树形数据
const treeData = ref([
{
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 currentNodeData = ref(null)
const currentNodeLevel = computed(() => {
if (!currentNodeData.value) return 0
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 enableDecryptionPlugin = ref(false)
// 当前激活的Tab
const activeTab = ref('basic')
// 当前表结构数据 (匹配图片中的gen_table结构)
const currentTableStructure = ref([
{ pk: '', fieldName: 'business_name', comment: '生成业务名', fieldType: 'VARCHAR', length: '30', precision: '', algorithm: '' },
{ pk: '', fieldName: 'business_name_...', comment: '', fieldType: 'TEXT', length: '65535', precision: '', algorithm: '' },
{ pk: '', fieldName: 'class_name', comment: '实体类名称', fieldType: 'VARCHAR', length: '100', precision: '', algorithm: '' },
{ pk: '', fieldName: 'create_by', comment: '创建者', fieldType: 'VARCHAR', length: '64', precision: '', algorithm: '' },
{ pk: '', fieldName: 'create_time', comment: '创建时间', fieldType: 'DATETIME', length: '19', precision: '', algorithm: '' },
{ pk: '', fieldName: 'function_author', comment: '生成功能作者', fieldType: 'VARCHAR', length: '50', precision: '', algorithm: '' },
{ pk: '', fieldName: 'function_name', comment: '生成功能名', fieldType: 'VARCHAR', length: '50', precision: '', algorithm: '' },
{ pk: '', fieldName: 'gen_path', comment: '生成路径(不填...', fieldType: 'VARCHAR', length: '200', precision: '', algorithm: '' },
{ pk: '', fieldName: 'gen_type', comment: '生成代码方式(0...', fieldType: 'CHAR', length: '1', precision: '', algorithm: '' },
{ pk: '', fieldName: 'module_name', comment: '生成模块名', fieldType: 'VARCHAR', length: '30', precision: '', algorithm: '' }
])
// 树节点点击处理
const handleNodeClick = (data) => {
currentNodeData.value = data
activeTab.value = 'basic'
}
// 批量加密
const handleBatchEncrypt = () => {
console.log('批量加密', currentNodeData.value)
}
// 批量解密
const handleBatchDecrypt = () => {
console.log('批量解密', currentNodeData.value)
}
// 删除多余列
const handleDeleteColumns = () => {
console.log('删除多余列', currentNodeData.value)
}
// 编辑字段
const handleEditField = (field) => {
console.log('编辑字段', field)
}
defineExpose({
// handleRedInk,
// handleVoid
})
</script>
<template>
<div class="app-container scroller">
<PageTitle :back="true" @back="pageProjectManage" >
<template #title>
返回项目管理
</template>
</PageTitle>
<div class="app-container__body">
<div class="flex-container content-container">
<div class="left">
<el-card class="image-card tree-container">
<TreeFilter :tree-data="treeData" @node-click="handleNodeClick"/>
</el-card>
</div>
<div class="right flex1">
<el-card class="image-card">
<div class="header-section">
<div class="breadcrumb">
<el-breadcrumb separator="/">
<el-breadcrumb-item>若依配测系统</el-breadcrumb-item>
<el-breadcrumb-item v-if="currentNodeLevel >= 2">ry</el-breadcrumb-item>
<el-breadcrumb-item v-if="currentNodeLevel >= 3"></el-breadcrumb-item>
<el-breadcrumb-item v-if="currentNodeLevel >= 4">{{ currentNodeData?.label }}</el-breadcrumb-item>
</el-breadcrumb>
</div>
<div class="radio-group" v-if="currentNodeLevel === 4">
<el-radio-group v-model="enableDecryptionPlugin">
<el-radio :label="true">启用解密插件</el-radio>
<el-radio :label="false">不启用解密插件</el-radio>
</el-radio-group>
</div>
</div>
<!-- Tab区域 -->
<div class="tab-section">
<el-tabs v-model="activeTab">
<!-- 基本信息Tab -->
<el-tab-pane label="基本信息" name="basic">
<BasicInfoTab
:node-data="currentNodeData"
v-if="activeTab === 'basic' && currentNodeLevel === 1"
/>
<TableInfoTab
:node-data="currentNodeData"
v-if="activeTab === 'basic' && currentNodeLevel === 4"
/>
</el-tab-pane>
<!-- 数据结构Tab (仅4级节点显示) -->
<el-tab-pane label="数据结构" name="structure" v-if="currentNodeLevel === 4">
<StructureTab
:table-data="currentTableStructure"
v-if="activeTab === 'structure'"
/>
</el-tab-pane>
<!-- 字段加密配置Tab (仅4级节点显示) -->
<el-tab-pane label="字段加密配置" name="encryption" v-if="currentNodeLevel === 4">
<EncryptionTab
:table-data="currentTableStructure"
@batch-encrypt="handleBatchEncrypt"
@batch-decrypt="handleBatchDecrypt"
@delete-columns="handleDeleteColumns"
@edit-field="handleEditField"
v-if="activeTab === 'encryption'"
/>
</el-tab-pane>
</el-tabs>
</div>
</el-card>
</div>
</div>
</div>
</div>
</template>
<style lang="scss" scoped>
.flex-container {
display: flex;
}
.align-center {
align-items: center;
}
.justify-between {
justify-content: space-between;
}
.flex1 {
flex: 1;
}
.content-container {
.left {
.tree-container {
width: 300px;
}
}
.right {
margin-left: var(--container-pd);
overflow: hidden;
}
}
</style>
<template>
<div class="basic-info-tab">
<el-descriptions
title="系统基本信息"
border
:column="1"
size="medium"
>
<el-descriptions-item label="项目">若依配测系统</el-descriptions-item>
<el-descriptions-item label="数据源">若依配测系统</el-descriptions-item>
<el-descriptions-item label="数据源类型">MYSQL</el-descriptions-item>
<el-descriptions-item label="IP">172.19.1.166</el-descriptions-item>
<el-descriptions-item label="管理的SCHEMA">ry</el-descriptions-item>
</el-descriptions>
</div>
</template>
<script setup>
// 可以留空,因为数据是静态的
</script>
<style scoped>
.basic-info-tab {
padding: 20px;
background-color: white;
}
:deep(.el-descriptions__title) {
font-size: 16px;
font-weight: bold;
margin-bottom: 16px;
}
:deep(.el-descriptions__header) {
margin-bottom: 16px;
}
:deep(.el-descriptions__label) {
width: 180px;
background-color: #f5f7fa;
font-weight: bold;
color: #333;
}
:deep(.el-descriptions__content) {
background-color: white;
}
</style>
\ No newline at end of file
<template>
<el-dialog
title="设置加密规则"
v-model="dialogVisible"
width="800px"
:before-close="handleClose"
>
<div class="encryption-rule-dialog">
<!-- 上部:规则和密钥选择 -->
<div class="selection-section">
<!-- 左侧加密规则选择 -->
<div class="rule-selection">
<div class="section-title">选择加密规则</div>
<el-radio-group v-model="selectedRule" class="rule-radio-group">
<el-radio
v-for="rule in encryptionRules"
:key="rule.id"
:label="rule.id"
class="rule-radio"
>
{{ rule.name }}
</el-radio>
</el-radio-group>
</div>
<!-- 右侧加密密钥选择 -->
<div class="key-selection">
<div class="section-title">选择加密密钥</div>
<el-radio-group v-model="selectedKey" class="key-radio-group">
<el-radio
v-for="key in currentKeys"
:key="key.id"
:label="key.id"
class="key-radio"
>
{{ key.name }}
</el-radio>
</el-radio-group>
</div>
</div>
<!-- 下部:配置区域 -->
<div class="config-section">
<div class="config-row">
<!-- 加密位置 -->
<div class="config-item">
<span class="required-label">*加密位置:</span>
<el-select v-model="ruleConfig.positionType" style="width: 120px">
<el-option label="保留前" value="prefix" />
<el-option label="保留后" value="suffix" />
</el-select>
<el-input-number
v-model="ruleConfig.position"
:min="0"
:max="100"
controls-position="right"
style="width: 100px; margin-left: 10px;"
/>
<span class="unit"></span>
</div>
</div>
<div class="config-row">
<!-- 源长度 -->
<div class="config-item">
<span class="label">源长度:</span>
<el-input-number
v-model="ruleConfig.sourceLength"
:min="0"
:max="1000"
controls-position="right"
style="width: 100px;"
/>
<el-button
type="primary"
size="small"
style="margin-left: 10px;"
@click="calculateLength"
>
计算
</el-button>
</div>
<!-- 加密后长度 -->
<div class="config-item">
<span class="label">加密后长度:</span>
<span class="value">{{ encryptedLength || '--' }}</span>
</div>
</div>
<div class="action-row">
<el-checkbox v-model="supportFuzzy">支持模糊</el-checkbox>
<div class="action-buttons">
<el-button @click="resetRules">重置规则</el-button>
<el-button type="primary" @click="confirmRules">确定规则</el-button>
</div>
</div>
</div>
</div>
</el-dialog>
</template>
<script setup>
import { ref, computed, watch } from 'vue'
const props = defineProps({
visible: {
type: Boolean,
default: false
},
fieldData: {
type: Object,
default: () => ({})
}
})
const emit = defineEmits(['update:visible', 'confirm'])
// 控制弹窗显示
const dialogVisible = computed({
get: () => props.visible,
set: (value) => emit('update:visible', value)
})
// 加密规则数据
const encryptionRules = ref([
{ id: 'name', name: '姓名加密规则' },
{ id: 'gender', name: '性别加密规则' }
])
// 加密密钥数据
const encryptionKeys = ref({
name: [
{ id: 'name_key1', name: '姓名加密密钥1' },
{ id: 'name_key2', name: '姓名加密密钥2' }
],
gender: [
{ id: 'gender_key1', name: '性别加密密钥1' },
{ id: 'gender_key2', name: '性别加密密钥2' }
]
})
// 当前选中的规则
const selectedRule = ref('')
// 当前选中的密钥
const selectedKey = ref('')
// 当前显示的密钥列表
const currentKeys = computed(() => {
return encryptionKeys.value[selectedRule.value] || []
})
// 加密配置
const ruleConfig = ref({
positionType: 'prefix',
position: 0,
sourceLength: 100
})
// 加密后长度
const encryptedLength = ref('')
// 是否支持模糊
const supportFuzzy = ref(false)
// 监听规则变化,自动选择第一个密钥
watch(selectedRule, (newVal) => {
if (currentKeys.value.length > 0) {
selectedKey.value = currentKeys.value[0].id
}
}, { immediate: true })
// 计算加密后长度
const calculateLength = () => {
// 这里应该是实际的加密长度计算逻辑
encryptedLength.value = Math.floor(ruleConfig.value.sourceLength * 0.8)
}
// 重置规则
const resetRules = () => {
selectedRule.value = 'name'
ruleConfig.value = {
positionType: 'prefix',
position: 0,
sourceLength: 100
}
encryptedLength.value = ''
supportFuzzy.value = false
}
// 确认规则
const confirmRules = () => {
emit('confirm', {
rule: selectedRule.value,
key: selectedKey.value,
config: ruleConfig.value,
supportFuzzy: supportFuzzy.value,
encryptedLength: encryptedLength.value
})
dialogVisible.value = false
}
// 关闭弹窗前处理
const handleClose = (done) => {
done()
}
</script>
<style scoped>
.encryption-rule-dialog {
padding: 10px;
}
.selection-section {
display: flex;
margin-bottom: 20px;
padding-bottom: 15px;
border-bottom: 1px solid #eee;
}
.rule-selection, .key-selection {
flex: 1;
padding: 0 15px;
}
.section-title {
margin-bottom: 15px;
font-size: 14px;
font-weight: bold;
color: #333;
}
.rule-radio-group, .key-radio-group {
display: flex;
flex-direction: column;
}
.rule-radio, .key-radio {
margin: 5px 0;
}
.config-section {
padding: 0 15px;
}
.config-row {
display: flex;
align-items: center;
margin-bottom: 15px;
flex-wrap: wrap;
}
.config-item {
display: flex;
align-items: center;
margin-right: 20px;
}
.required-label {
width: 80px;
text-align: right;
margin-right: 10px;
font-size: 14px;
color: #f56c6c;
}
.label {
/* width: 60px; */
text-align: right;
margin-right: 10px;
font-size: 14px;
color: #666;
}
.unit {
margin-left: 5px;
font-size: 14px;
color: #666;
}
.value {
font-size: 14px;
color: #333;
font-weight: bold;
}
.action-row {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 20px;
}
.action-buttons {
display: flex;
gap: 10px;
}
</style>
\ No newline at end of file
<template>
<div class="encryption-tab">
<!-- 搜索区域 -->
<div class="filter-section">
<el-form :inline="true" :model="filterForm" class="filter-form">
<el-form-item label="字段名过滤:">
<el-input
v-model="filterForm.fieldName"
placeholder="字段名模糊搜索"
clearable
style="width: 200px;"
/>
</el-form-item>
<el-form-item label="状态:">
<el-select style="width: 200px;" v-model="filterForm.status" placeholder="全部" clearable>
<el-option label="已加密" value="encrypted" />
<el-option label="未加密" value="unencrypted" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleSearch">搜索</el-button>
</el-form-item>
</el-form>
</div>
<!-- 表格区域 -->
<el-table
:data="filteredTableData"
border
style="width: 100%; margin-top: 15px;"
:row-class-name="tableRowClassName"
>
<!-- 左浮动列 -->
<el-table-column prop="pk" label="主键" width="60" align="left" fixed />
<el-table-column prop="fieldName" label="字段名" width="150" align="left" fixed/>
<el-table-column prop="fieldType" label="字段类型" width="120" align="center" />
<el-table-column prop="length" label="长度" width="80" align="center" />
<el-table-column prop="algorithm" label="加密算法" width="120" align="center" />
<el-table-column label="密钥" width="120" align="center">
<template #default="{ row }">
<el-button type="text" @click="openEncryptionDialog(row)">设置</el-button>
</template>
</el-table-column>
<el-table-column prop="status" label="状态" width="100" align="center">
<template #default="{ row }">
<el-tag v-if="row.status === 'encrypted'" type="success" size="small">已加密</el-tag>
<el-tag v-else type="info" size="small">未加密</el-tag>
</template>
</el-table-column>
<!-- 右浮动列 -->
<el-table-column prop="comment" label="注释" align="left" min-width="200" />
<el-table-column label="描述信息" align="left" fixed="right">
<template #default="{ row }">
<el-input
v-model="row.description"
size="small"
:disabled="!isEditing"
@change="handleDescriptionChange(row)"
/>
</template>
</el-table-column>
<el-table-column prop="plaintextProcess" label="明文处理" align="left" fixed="right"/>
</el-table>
<!-- 操作按钮区域 -->
<div class="action-buttons">
<el-button type="primary" @click="handleBatchEncrypt">批量加密</el-button>
<el-button type="primary" @click="handleBatchDecrypt">批量解密</el-button>
<el-button type="primary" @click="handleDeleteColumns">删除多余列</el-button>
<el-button
type="primary"
@click="toggleEditMode"
>
{{ isEditing ? '保存' : '编辑' }}
</el-button>
</div>
<EncryptionRuleDialog
v-model:visible="showEncryptionDialog"
:field-data="currentField"
@confirm="handleRuleConfirm"
/>
</div>
</template>
<script setup>
import { ref, computed, reactive } from 'vue'
import EncryptionRuleDialog from './EncryptionRuleDialog.vue'
const props = defineProps({
tableData: {
type: Array,
required: true,
default: () => [
{
pk: 'PK',
fieldName: 'table_id',
fieldType: 'BIGINT',
length: '19',
algorithm: '',
status: 'unencrypted',
comment: '编号',
description: '主键ID',
plaintextProcess: ''
},
// 其他数据行...
]
}
})
const emit = defineEmits(['batch-encrypt', 'batch-decrypt', 'delete-columns', 'edit-field', 'update-field'])
// 搜索表单
const filterForm = reactive({
fieldName: '',
status: ''
})
// 编辑模式
const isEditing = ref(false)
// 表格行样式
const tableRowClassName = ({ row, rowIndex }) => {
return row.status === 'encrypted' ? 'encrypted-row' : ''
}
// 处理搜索
const handleSearch = () => {
// 搜索逻辑已在计算属性中实现
}
// 切换编辑模式
const toggleEditMode = () => {
isEditing.value = !isEditing.value
}
// 处理描述信息变更
const handleDescriptionChange = (row) => {
emit('update-field', row)
}
// 批量加密
const handleBatchEncrypt = () => {
emit('batch-encrypt')
}
// 批量解密
const handleBatchDecrypt = () => {
emit('batch-decrypt')
}
// 删除多余列
const handleDeleteColumns = () => {
emit('delete-columns')
}
// 过滤表格数据
const filteredTableData = computed(() => {
return props.tableData.filter(item => {
const matchesName = filterForm.fieldName
? item.fieldName.includes(filterForm.fieldName)
: true
const matchesStatus = filterForm.status
? item.status === filterForm.status
: true
return matchesName && matchesStatus
})
})
const showEncryptionDialog = ref(false)
const currentField = ref({})
// 处理设置密钥
const openEncryptionDialog = (row) => {
if(!isEditing.value) return
currentField.value = row
showEncryptionDialog.value = true
}
const handleRuleConfirm = (ruleData) => {
console.log('确认加密规则:', ruleData)
// 更新表格数据或调用API
}
</script>
<style scoped>
.encryption-tab {
padding: 15px;
background-color: white;
}
.filter-section {
margin-bottom: 15px;
}
.filter-form {
display: flex;
align-items: center;
}
.action-buttons {
margin-top: 20px;
text-align: center;
}
:deep(.el-table .encrypted-row) {
background-color: #f0f9eb;
}
:deep(.el-table td) {
padding: 8px 0;
}
:deep(.el-table th) {
background-color: #f5f7fa;
}
:deep(.el-form-item) {
margin-bottom: 0;
}
:deep(.el-form-item__label) {
color: #333;
font-weight: normal;
}
</style>
\ No newline at end of file
<template>
<div class="structure-tab">
<el-table
:data="tableData"
border
style="width: 100%"
:header-cell-style="{ background: '#f5f7fa', color: '#333' }"
:row-style="{ height: '40px' }"
:cell-style="{ padding: '8px 0', textAlign: 'center' }"
>
<el-table-column prop="pk" label="主键" width="120" fixed/>
<el-table-column prop="fieldName" label="字段名" width="180" />
<el-table-column prop="comment" label="注释" min-width="200" />
<el-table-column prop="fieldType" label="类型" width="120" />
<el-table-column prop="length" label="长度" width="120" />
<el-table-column prop="precision" label="精度" width="120" />
</el-table>
</div>
</template>
<script setup>
const props = defineProps({
tableData: {
type: Array,
required: true,
default: () => [
{ pk: '', fieldName: 'business_name', comment: '生成业务名', fieldType: 'VARCHAR', length: '30', precision: '' },
{ pk: '', fieldName: 'business_name_...', comment: '', fieldType: 'TEXT', length: '65535', precision: '' },
{ pk: '', fieldName: 'class_name', comment: '实体类名称', fieldType: 'VARCHAR', length: '100', precision: '' },
{ pk: '', fieldName: 'create_by', comment: '创建者', fieldType: 'VARCHAR', length: '64', precision: '' },
{ pk: '', fieldName: 'create_time', comment: '创建时间', fieldType: 'DATETIME', length: '19', precision: '' },
{ pk: '', fieldName: 'function_author', comment: '生成功能作者', fieldType: 'VARCHAR', length: '50', precision: '' },
{ pk: '', fieldName: 'function_name', comment: '生成功能名', fieldType: 'VARCHAR', length: '50', precision: '' },
{ pk: '', fieldName: 'gen_path', comment: '生成路径(不填...', fieldType: 'VARCHAR', length: '200', precision: '' },
{ pk: '', fieldName: 'gen_type', comment: '生成代码方式(0...', fieldType: 'CHAR', length: '1', precision: '' },
{ pk: '', fieldName: 'module_name', comment: '生成模块名', fieldType: 'VARCHAR', length: '30', precision: '' }
]
}
})
</script>
<style scoped>
.structure-tab {
padding: 10px;
background-color: white;
}
:deep(.el-table) {
font-size: 14px;
}
:deep(.el-table--striped .el-table__body tr.el-table__row--striped td) {
background-color: #fafafa;
}
</style>
\ No newline at end of file
<template>
<div class="table-info-tab">
<el-descriptions
title="表基本信息"
border
:column="1"
size="medium"
>
<el-descriptions-item label="字段数">224</el-descriptions-item>
<el-descriptions-item label="加密表数量">0</el-descriptions-item>
<el-descriptions-item label="加密字段数量">0</el-descriptions-item>
<el-descriptions-item label="未加密表数量">20</el-descriptions-item>
<el-descriptions-item label="未加密字段数量">224</el-descriptions-item>
<el-descriptions-item label="操作人">admin</el-descriptions-item>
<el-descriptions-item label="操作时间">2023-05-15 14:30:22</el-descriptions-item>
</el-descriptions>
</div>
</template>
<script setup>
// 可以留空,因为数据是静态的
</script>
<style scoped>
.table-info-tab {
padding: 20px;
background-color: white;
}
:deep(.el-descriptions__title) {
font-size: 16px;
font-weight: bold;
margin-bottom: 16px;
}
:deep(.el-descriptions__header) {
margin-bottom: 16px;
}
:deep(.el-descriptions__label) {
width: 180px;
background-color: #f5f7fa;
font-weight: bold;
color: #333;
}
:deep(.el-descriptions__content) {
background-color: white;
}
</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>
</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 {
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;
}
:deep(.el-tree-node__content) {
height: 36px;
}
</style>
\ No newline at end of file
...@@ -117,7 +117,7 @@ const handleEnterProject = (project) => { ...@@ -117,7 +117,7 @@ const handleEnterProject = (project) => {
console.log('进入项目:', project) console.log('进入项目:', project)
// // ElMessage.success(`进入项目 ${project.projectName}`) // // ElMessage.success(`进入项目 ${project.projectName}`)
// emit('page', 'detail', { projectId: project.id}) // emit('page', 'detail', { projectId: project.id})
router.push({ path:'/project',query: { projectId: project.id } }) router.push({ path:'/projectHome',query: { projectId: project.id } })
} }
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论