Commit 7289be62 by ningjihai

项目管理使用动态路由注释本地路由,发现梳理发现结果,监控编辑 以及相关组件

parent 6f7d2487
......@@ -146,6 +146,7 @@ const toggleStatus = (row) => {
</template>
<template #buttons>
<el-button
icon="Plus"
type="primary"
@click="handleAdd"
>
......
......@@ -34,8 +34,7 @@ function onSearch() {
// 重置
function onReset(formRef: FormInstance) {
queryForm.value.projectName = ''
queryForm.value.remark = ''
queryForm.value.title = ''
emit('reset', formRef)
}
......@@ -49,20 +48,18 @@ function onReset(formRef: FormInstance) {
:model="queryForm"
@search="onSearch"
@reset="onReset">
<el-form-item label="项目名称" prop="projectName">
<el-form-item label="发现任务名称" prop="title">
<el-input
v-model="queryForm.projectName"
placeholder="请输入项目名称"
clearable
/>
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input
v-model="queryForm.remark"
placeholder="请输入备注"
v-model="queryForm.title"
placeholder="请输入发现任务名称"
clearable
/>
</el-form-item>
</page-wrapper-search>
</template>
......
<script setup name="DiscoverProcess">
import { getCurrentInstance, reactive, ref, toRefs } 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 WaterWaveChart from './modules/WaterWaveChart.vue'
// import AddDiscoveryDialog from './modules/AddDiscoveryDialog.vue' // 引入新增发现任务弹窗
const appStore = useAppStore()
const permissionStore = usePermissionStore()
const router = useRouter()
const emit = defineEmits(['page'])
const { proxy } = getCurrentInstance()
const props = defineProps({
discoverProcessData: {
type: Object,
default: () => ({})
}
})
function pageProjectManage() {
changeRoute()
router.push({
path: '/project/Project'
})
}
// 进度数据
const progress = ref(100) // 100% 进度
const completedCount = ref(20) // 已完成表数量
const totalCount = ref(20) // 总表数量
// 执行失败数据
const failureData = ref([
{
category: '数据连接',
severity: '高',
executeTime: '2025-08-23 10:30'
},
{
category: '权限验证',
severity: '中',
executeTime: '2025-08-23 10:25'
},
{
category: '资源限制',
severity: '低',
executeTime: '2025-08-23 10:20'
}
])
// 已完成数据
const completedData = ref([
{ discoveryNo: 1, tableName: 'gen_table', schema: 'ry', discoveryTime: '2025-08-23 04:15:23' },
{ discoveryNo: 2, tableName: 'gen_table_column', schema: 'ry', discoveryTime: '2025-08-23 04:15:24' },
{ discoveryNo: 3, tableName: 'sys_config', schema: 'ry', discoveryTime: '2025-08-23 04:15:25' },
{ discoveryNo: 4, tableName: 'sys_dept', schema: 'ry', discoveryTime: '2025-08-23 04:15:26' },
{ discoveryNo: 5, tableName: 'sys_dict_data', schema: 'ry', discoveryTime: '2025-08-23 04:15:27' },
{ discoveryNo: 6, tableName: 'sys_dict_type', schema: 'ry', discoveryTime: '2025-08-23 04:15:28' },
{ discoveryNo: 7, tableName: 'sys_job', schema: 'ry', discoveryTime: '2025-08-23 04:15:29' },
{ discoveryNo: 8, tableName: 'sys_job_log', schema: 'ry', discoveryTime: '2025-08-23 04:15:30' },
{ discoveryNo: 9, tableName: 'sys_logininfor', schema: 'ry', discoveryTime: '2025-08-23 04:15:31' },
{ discoveryNo: 10, tableName: 'sys_menu', schema: 'ry', discoveryTime: '2025-08-23 04:15:32' },
{ discoveryNo: 11, tableName: 'sys_notice', schema: 'ry', discoveryTime: '2025-08-23 04:15:33' },
{ discoveryNo: 12, tableName: 'sys_oper_log', schema: 'ry', discoveryTime: '2025-08-23 04:15:34' },
{ discoveryNo: 13, tableName: 'sys_post', schema: 'ry', discoveryTime: '2025-08-23 04:15:35' },
{ discoveryNo: 14, tableName: 'sys_role', schema: 'ry', discoveryTime: '2025-08-23 04:15:36' }
])
// 严重性标签类型
const getSeverityType = (severity) => {
const types = {
'高': 'danger',
'中': 'warning',
'低': 'info'
}
return types[severity] || 'info'
}
// 查看结果处理
const handleCatResult = () => {
console.log('查看详细结果')
emit('page','discoverResult')
}
</script>
<template>
<div class="app-container scroller">
<PageTitle :back="true" @back="pageProjectManage">
<template #title>
返回项目管理
</template>
</PageTitle>
<div class="app-container__body">
<div class="discovery-progress-container">
<div class="layout-container">
<!-- 左侧区域 -->
<div class="left-panel">
<!-- 水波纹进度图表 -->
<div class="chart-section">
<div class="chart-container">
<WaterWaveChart :usage="progress" color="#1890ff" />
</div>
<div>完成表数:{{ completedCount }}</div>
</div>
<!-- 执行失败表格 -->
<div class="failure-table-section">
<div class="section-header">
<span class="title">执行失败</span>
</div>
<el-table
:data="failureData"
style="width: 100%"
height="300px"
empty-text="暂无数据"
:header-cell-style="{ background: '#f5f7fa', color: '#606266' }"
>
<el-table-column prop="category" label="类别" min-width="120" />
<el-table-column prop="severity" label="严重性" min-width="100">
<template #default="{ row }">
<el-tag :type="getSeverityType(row.severity)" size="small">
{{ row.severity }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="executeTime" label="执行时间" min-width="140" />
</el-table>
</div>
</div>
<!-- 右侧区域 -->
<div class="right-panel">
<div class="completed-table-section">
<div class="section-header">
<span class="title">已完成</span>
<el-button
type="primary"
@click="handleCatResult"
>查看结果</el-button>
</div>
<el-table
:data="completedData"
height="600px"
style="width: 100%"
:header-cell-style="{ background: '#f5f7fa', color: '#606266' }"
empty-text="暂无数据"
>
<el-table-column prop="discoveryNo" label="发现序号" width="100" align="center" />
<el-table-column prop="tableName" label="发现表名" min-width="150" />
<el-table-column prop="schema" label="SCHEMA" width="100" />
<el-table-column prop="discoveryTime" label="发现时间" width="160" />
</el-table>
</div>
</div>
</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;
}
.flex-wrap {
flex-direction: wrap;
}
.discovery-progress-container {
padding: 20px;
background: #f5f7fa;
// min-height: 100vh;
}
.layout-container {
display: flex;
gap: 20px;
// height: calc(100vh - 100px);
}
.left-panel {
flex: 1;
display: flex;
flex-direction: column;
gap: 20px;
}
.right-panel {
flex: 1;
min-width: 0;
}
.chart-section {
background: white;
border-radius: 8px;
padding: 20px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
height: 300px;
display: flex;
align-items: center;
padding-right: 40px;
.chart-container {
height: 100%;
flex: 1;
}
}
.failure-table-section,
.completed-table-section {
background: white;
border-radius: 8px;
padding: 20px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
flex: 1;
display: flex;
flex-direction: column;
}
.section-header {
margin-bottom: 16px;
display: flex;
align-items: center;
justify-content: space-between;
.title {
font-size: 16px;
font-weight: 600;
color: #303133;
}
}
// // 表格样式调整
// :deep(.el-table) {
// border-radius: 4px;
// overflow: hidden;
// // .el-table__header-wrapper {
// // th {
// // background: #f0f7ff;
// // font-weight: 600;
// // }
// // }
// .el-table__body-wrapper {
// .el-table__row {
// &:nth-child(even) {
// background-color: #fafafa;
// }
// &:hover {
// background-color: #f5f7fa;
// }
// }
// }
// }
// // 右侧表格特殊样式
// .completed-table-section {
// :deep(.el-table) {
// .el-table__header-wrapper {
// th {
// background: #1890ff;
// // color: white;
// }
// }
// }
// }
// // 响应式设计
// @media (max-width: 1200px) {
// .layout-container {
// flex-direction: column;
// height: auto;
// }
// .left-panel,
// .right-panel {
// flex: none;
// }
// }
</style>
\ No newline at end of file
<script setup name="DiscoverResult">
import { getCurrentInstance, reactive, ref, toRefs } 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 DomainRuleDialog from './modules/DomainRuleDialog.vue'
import MatchPreviewDialog from './modules/MatchPreviewDialog.vue'
const appStore = useAppStore()
const permissionStore = usePermissionStore()
const router = useRouter()
const emit = defineEmits(['page'])
const { proxy } = getCurrentInstance()
const props = defineProps({
discoverProcessData: {
type: Object,
default: () => ({})
}
})
function pageProjectManage() {
changeRoute()
router.push({
path: '/project/Project'
})
}
// 折叠面板激活项
const activeNames = ref('gen_table')
// gen_table 数据
const genTableData = ref([
{ index: 1, field: 'business_name', fieldDesc: '', samplingInfo: '0/0/0%', dataDomain: '设置', discoveryRule: '', status: '未确认' },
{ index: 2, field: 'business_name...', fieldDesc: '', samplingInfo: '0/0/0%', dataDomain: '设置', discoveryRule: '', status: '未确认' },
{ index: 3, field: 'class_name', fieldDesc: '', samplingInfo: '0/0/0%', dataDomain: '设置', discoveryRule: '', status: '未确认' },
{ index: 4, field: 'create_by', fieldDesc: '', samplingInfo: '0/0/0%', dataDomain: '设置', discoveryRule: '', status: '未确认' },
{ index: 5, field: 'create_time', fieldDesc: '', samplingInfo: '0/0/0%', dataDomain: '设置', discoveryRule: '', status: '未确认' },
{ index: 6, field: 'function_author', fieldDesc: '', samplingInfo: '0/0/0%', dataDomain: '设置', discoveryRule: '', status: '未确认' }
])
// 其他表格数据
const allTableNames = ref([
{ name: 'gen_table', sensitiveCount: 1 },
{ name: 'ry.gen_table_column', sensitiveCount: 0 },
{ name: 'ry.sys_config', sensitiveCount: 0 },
{ name: 'ry.sys_dept', sensitiveCount: 1 },
{ name: 'ry.sys_dict_data', sensitiveCount: 0 },
{ name: 'ry.sys_dict_type', sensitiveCount: 0 },
{ name: 'ry.sys_job', sensitiveCount: 0 },
{ name: 'ry.sys_job_log', sensitiveCount: 0 },
{ name: 'ry.sys_logininfor', sensitiveCount: 0 },
{ name: 'ry.sys_menu', sensitiveCount: 0 }
])
const selectAll = ref(false)
/**
* 显示敏感字段切换
*/
const displaySensitive = ()=>{
console.log('显示敏感字段切换')
}
/**
* 批量设置规则
*/
const batchSettingRules = ()=>{
console.log('批量设置规则')
}
/**
* 确认所有
*/
const confirmAll = ()=>{
console.log('确认所有')
}
/**
* 取消所有
*/
const cancelAll = ()=>{
console.log('取消所有')
}
/**
* 保存
*/
const handleSave = ()=>{
console.log('保存')
}
/**
* 后退
*/
const handleBack = ()=>{
console.log('后退')
}
const handleChangeTableByCollapse = ()=>{
console.log('切换表格显示方式')
}
const domainRuleDialogRef = ref()
const domainRuleDialogVisible = ref(false)
const currentRowData = ref(null)
// 打开设置弹窗
const openSettingDialog = (row) => {
currentRowData.value = row
// 方法1:使用ref调用子组件方法
// domainRuleDialogRef.value?.openDialog()
// 方法2:或者直接控制visible
domainRuleDialogVisible.value = true
}
// 处理规则确认
const handleRuleConfirm = (ruleData) => {
console.log('选择的规则:', ruleData)
if (currentRowData.value) {
currentRowData.value.discoveryRule = ruleData.ruleLabel
currentRowData.value.dataDomain = ruleData.domainLabel
// 同时保存value值用于下次打开时回显
currentRowData.value.discoveryRuleValue = ruleData.rule
currentRowData.value.dataDomainValue = ruleData.domain
}
}
const matchPreviewDialogRef = ref()
const matchPreviewDialogVisible = ref(false)
const currentRowDataPreview = ref(null)
// 模拟匹配数据
const matchedData = ref([
{ value: '15888888888' },
{ value: '15888888888' },
{ value: '15888888888' },
{ value: '15888888888' },
{ value: '15888888888' },
{ value: '15888888888' },
{ value: '15888888888' },
{ value: '15888888888' },
{ value: '15888888888' },
{ value: '15888888888' }
])
const unmatchedData = ref([]) // 空数组,显示"暂无数据"
// 打开匹配预览弹窗
const handleMatch = (row) => {
console.log('匹配预览:', row)
currentRowDataPreview.value = row
// 根据行数据获取匹配结果(这里需要根据实际业务逻辑实现)
// const matchResult = getMatchResult(row)
// matchedData.value = matchResult.matched
// unmatchedData.value = matchResult.unmatched
// 打开弹窗
matchPreviewDialogVisible.value = true
}
// 获取匹配结果的方法(需要根据实际业务实现)
const getMatchResult = (row) => {
// 这里应该是您的业务逻辑,返回匹配和不匹配的数据
return {
matched: matchedData.value,
unmatched: unmatchedData.value
}
}
</script>
<template>
<div class="app-container scroller">
<PageTitle :back="true" @back="pageProjectManage">
<template #title>
返回项目管理
</template>
<template #buttons>
<el-button
type="success"
@click="batchSettingRules"
>
批量设置规则
</el-button>
<el-button
type="primary"
@click="confirmAll"
>
确认所有
</el-button>
<el-button
type="danger"
@click="cancelAll"
>
取消所有
</el-button>
<el-button
type="success"
@click="handleSave"
>
保存
</el-button>
<el-button
type="info"
@click="handleBack"
>
后退
</el-button>
<el-checkbox style="margin-left: 10px;" v-model="selectAll" @change="displaySensitive" class="select-all-checkbox">仅显示敏感字段</el-checkbox>
</template>
</PageTitle>
<div class="app-container__body">
<div class="discovery-report-container">
<!-- 上方:发现任务执行报告概览 -->
<div class="report-overview">
<h2 class="report-title">发现任务执行报告</h2>
<div class="overview-cards">
<el-descriptions :column="4" border>
<el-descriptions-item label="任务运行时间">
<span class="highlight-text">911毫秒</span>
</el-descriptions-item>
<el-descriptions-item label="任务总对象数">
<span class="highlight-text">20</span>
</el-descriptions-item>
<el-descriptions-item label="发现敏感对象数(表/文件)">
<span class="highlight-text">2</span>
</el-descriptions-item>
<el-descriptions-item label="任务总列数">
<span class="highlight-text">225</span>
</el-descriptions-item>
<el-descriptions-item label="发现敏感列数">
<span class="highlight-text">2</span>
</el-descriptions-item>
</el-descriptions>
</div>
</div>
<!-- 下方:详细数据表格 -->
<div class="detailed-data">
<el-collapse v-model="activeNames" accordion @change="handleChangeTableByCollapse">
<!-- 第一个表格 -->
<el-collapse-item :name="item.name" v-for="(item, index) in allTableNames" :key="index">
<template #title>
<span class="collapse-title">{{ item.name }}</span>
<span class="sensitive-count">{{item.sensitiveCount}}个敏感数据</span>
</template>
<el-table :data="genTableData" height="300px" style="width: 100%;">
<el-table-column prop="index" label="序号" width="60" align="center" fixed />
<el-table-column prop="field" label="字段" min-width="120" />
<el-table-column prop="fieldDesc" label="字段说明" min-width="150" />
<el-table-column label="采样数/有效采样数/匹配率" min-width="180">
<template #default="{ row }">
{{ row.samplingInfo }}
</template>
</el-table-column>
<el-table-column prop="dataDomain" label="数据域" min-width="100">
<template #default="scope">
<el-button
v-if="scope.row.discoveryRule"
type="primary"
plain
size="small"
@click="openSettingDialog(scope.row)"
>
{{ scope.row.discoveryRule }}
</el-button>
<el-button
v-else
plain
type="primary"
size="small"
@click="openSettingDialog(scope.row)"
>
设置
</el-button>
</template>
</el-table-column>
<el-table-column prop="discoveryRule" label="数据发现规则" min-width="150" />
<el-table-column prop="status" label="梳理" min-width="100">
<template #default>
<el-tag size="small">未确认</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" min-width="120" align="center" fixed="right">
<template #default="scope">
<el-button type="primary" size="small" @click="handleMatch(scope.row)">匹配预览</el-button>
</template>
</el-table-column>
</el-table>
</el-collapse-item>
</el-collapse>
</div>
</div>
</div>
<DomainRuleDialog
v-model="domainRuleDialogVisible"
:selected-data="currentRowData ? {
domain: currentRowData.dataDomainValue,
rule: currentRowData.discoveryRuleValue
} : {}"
@confirm="handleRuleConfirm"
/>
<!-- 匹配预览弹窗 -->
<MatchPreviewDialog
v-model="matchPreviewDialogVisible"
:matched-data="matchedData"
:unmatched-data="unmatchedData"
/>
</div>
</template>
<style lang="scss" scoped>
.flex-container {
display: flex;
}
.align-center {
align-items: center;
}
.justify-between {
justify-content: space-between;
}
.flex1 {
flex: 1;
}
.flex-wrap {
flex-direction: wrap;
}
.discovery-report-container {
padding: 20px;
background: #f5f7fa;
min-height: 100vh;
}
.report-overview {
background: white;
border-radius: 8px;
padding: 24px;
margin-bottom: 20px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
}
.report-title {
margin: 0 0 20px 0;
color: #303133;
font-size: 20px;
font-weight: 600;
}
.overview-content {
display: flex;
gap: 20px;
align-items: flex-start;
}
.main-cards {
flex: 1;
}
.sensitive-column-card {
background: #f0f7ff;
border: 1px solid #1890ff;
border-radius: 6px;
padding: 20px;
min-width: 150px;
text-align: center;
.card-content {
display: flex;
flex-direction: column;
gap: 8px;
}
.card-label {
color: #606266;
font-size: 14px;
}
.card-value {
color: #1890ff;
font-size: 24px;
font-weight: 600;
}
}
:deep(.el-descriptions) {
.el-descriptions__header {
display: none;
}
.el-descriptions__body {
background: #f0f7ff;
border-radius: 6px;
.el-descriptions__table {
tbody {
tr {
background: transparent;
&:hover {
background: #e6f7ff;
}
}
}
}
}
}
.highlight-text {
color: #1890ff;
font-weight: 600;
font-size: 16px;
}
.detailed-data {
background: white;
border-radius: 8px;
padding: 20px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
}
:deep(.el-collapse) {
border: none;
.el-collapse-item {
margin-bottom: 12px;
&__header {
background: #f0f7ff;
border: 1px solid #1890ff;
border-radius: 4px;
padding: 0 16px;
height: 48px;
font-weight: 600;
.collapse-title {
color: #303133;
font-size: 14px;
}
.sensitive-count {
color: #1890ff;
margin-left: 12px;
font-size: 13px;
font-weight: normal;
}
}
&__wrap {
border: none;
}
&__content {
padding: 0;
border: 1px solid #e0e0e0;
border-top: none;
border-radius: 0 0 4px 4px;
}
}
}
// 表格样式
:deep(.el-table) {
border-radius: 0 0 4px 4px;
.el-table__header-wrapper {
th {
background: #f0f7ff;
color: #606266;
font-weight: 600;
.cell {
font-weight: 600;
}
}
}
.el-table__body-wrapper {
.el-table__row {
&:nth-child(even) {
background-color: #fafafa;
}
&:hover {
background-color: #f5f7fa;
}
}
}
}
// 响应式设计
@media (max-width: 768px) {
.discovery-report-container {
padding: 12px;
}
.report-overview {
padding: 16px;
}
.overview-content {
flex-direction: column;
}
.sensitive-column-card {
width: 100%;
}
:deep(.el-descriptions) {
.el-descriptions__body {
.el-descriptions__table {
tbody {
tr {
display: flex;
flex-wrap: wrap;
.el-descriptions__cell {
flex: 1 0 50%;
min-width: 120px;
}
}
}
}
}
}
}
</style>
\ No newline at end of file
<script setup lang="ts" name="projectManageIndex">
import { ref } from 'vue'
import list from './list.vue'
import discoverProcess from './discoverProcess.vue'
import discoverResult from './discoverResult.vue'
const widget = {
list: list
list: list,
discoverProcess: discoverProcess,
discoverResult: discoverResult
}
const page = ref('list')
......
<script setup name="ProjectManageList">
import { getCurrentInstance, reactive, ref, toRefs } 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 AddDiscoveryDialog from './modules/AddDiscoveryDialog.vue' // 引入新增发现任务弹窗
import SmartDiscoveryDialog from './modules/SmartDiscoveryDialog.vue'
const appStore = useAppStore()
const permissionStore = usePermissionStore()
const router = useRouter()
const emit = defineEmits(['page'])
const { proxy } = getCurrentInstance()
// 新增发现任务弹窗引用
const addDiscoveryDialogRef = ref()
function onReset(formQuery) {
console.log('onReset')
formQuery.resetFields()
handleQuery()
}
function onQuery() {
handleQuery()
}
// 搜索按钮操作
function handleQuery() {
console.log('queryParams',queryParams.value)
queryParams.value.pageNum = 1
getList()
}
// 表格数据
const tableData = ref([
{
id: 1,
taskName: '22',
result: '0/225',
dbType: 'MYSQL',
operator: 'admin',
createTime: '2025-08-21 16:55:30',
updateTime: '2025-08-21 16:55:30',
remark: '',
projectid: "ec132fb8-04f0-4c0f-8460-c2a0ac4015d5",
resultid: "R_1755845361051",
datasystemid: "ff80818198986db40198c5933e5902e7",
},
{
id: 2,
taskName: '小王',
result: '2/225',
dbType: 'MYSQL',
operator: 'admin',
createTime: '2025-08-21 16:55:30',
updateTime: '2025-08-21 16:55:30',
remark: ''
}
])
const data = reactive({
queryParams: {
pageNum: 1,
pageSize: 8
}
})
// 表格数据
const { queryParams } = toRefs(data)
const total = ref(3)
const loading = ref(false)
// 查询列表
function getList() {
loading.value = true
setTimeout(() => {
loading.value = false
}, 3000)
}
function pageProjectManage() {
changeRoute()
router.push({
path: '/project/Project'
})
}
/**
* 新增发现任务
*/
function handleAdd(){
console.log('新增发现任务')
addDiscoveryDialogRef.value.openDialog()
}
// 操作处理函数
const handleEdit = (row) => {
console.log('编辑:', row)
addDiscoveryDialogRef.value.openDialog(row)
ElMessage.info(`编辑任务: ${row.taskName}`)
}
const handleDiscover = (row) => {
console.log('发现:', row)
ElMessage.success(`开始发现任务: ${row.taskName}`)
emit('page', 'discoverProcess', { discoverProcessData: row})
// router.push({
// path: '/user/discoverProcess',
// query: {
// taskid: row.id,
// resultid: row.resultid,
// datasystemid: row.datasystemid,
// discoverTaskName: row.tname,
// btnStatus: '1'
// }
// })
}
const handleMonitor = (row) => {
console.log('监控:', row)
ElMessage.warning(`监控任务: ${row.taskName}`)
emit('page', 'discoverProcess', { discoverProcessData: row})
}
const smartDiscoveryDialogRef = ref()
const smartData = ref(null)
const handleMoreCommand = (command, row) => {
switch (command) {
case 'smartDiscover':
smartData.value = row
handleSmartDiscover(row)
ElMessage.info(`智能发现: ${row.taskName}`)
break
case 'viewHistory':
ElMessage.info(`查看历史: ${row.taskName}`)
break
case 'viewResult':
ElMessage.info(`查看结果: ${row.taskName}`)
console.log('查看详细结果')
emit('page','discoverResult')
break
case 'delete':
ElMessage.error(`删除任务: ${row.taskName}`)
break
}
}
const smartDiscoveryDialogVisible = ref(false)
// 打开智能发现弹窗
const handleSmartDiscover = (row) => {
// 可以传入特定的发现版本数据
const discoveryData = [
{ version: row.resultid || '12123' } // 使用任务的结果ID或默认值
]
// 优先使用ref方式
if (smartDiscoveryDialogRef.value?.openDialog) {
smartDiscoveryDialogRef.value.openDialog()
}
// 备用:使用v-model方式
else {
smartDiscoveryDialogVisible.value = true
}
}
const disCoverSmart = (val) => {
console.log(val)
emit('page', 'discoverProcess', { discoverProcessData: smartData.value})
}
const handleAddEditConfirm = (submitData, isEditMode) =>{
console.log('提交数据:', submitData)
console.log('是否是编辑模式:', isEditMode)
ElMessage.success(`${isEditMode ? '编辑' : '添加'}任务成功`)
}
</script>
<template>
<div class="app-container scroller">
<PageTitle :back="true" @back="pageProjectManage">
<template #title>
返回项目管理
</template>
<template #buttons>
<el-button
type="primary"
icon="Plus"
@click="handleAdd"
>
新增发现任务
</el-button>
</template>
</PageTitle>
<div class="app-container__body">
<div>
<query-form
ref="QueryFormRef"
v-model="queryParams"
@query="onQuery"
@reset="onReset"/>
<el-table
:data="tableData"
border
style="width: 100%"
:header-cell-style="{ background: '#f5f7fa', color: '#606266' }"
>
<!-- 发现任务名称列(左侧浮动) -->
<el-table-column
prop="taskName"
label="发现任务名称"
min-width="180"
align="left"
fixed
>
<template #default="{ row }">
<span class="task-name">{{ row.taskName }}</span>
</template>
</el-table-column>
<!-- 梳理结果列 -->
<el-table-column
prop="result"
label="梳理结果"
min-width="120"
>
<template #default="{ row }">
<span class="result-text">{{ row.result }}</span>
</template>
</el-table-column>
<!-- 数据库类型列 -->
<el-table-column
prop="dbType"
label="数据库类型"
width="110"
>
<template #default="{ row }">
<el-tag type="primary">{{ row.dbType }}</el-tag>
</template>
</el-table-column>
<!-- 操作人列 -->
<el-table-column
prop="operator"
label="操作人"
min-width="120"
>
<template #default="{ row }">
<span>{{ row.operator }}</span>
</template>
</el-table-column>
<!-- 创建时间列 -->
<el-table-column
prop="createTime"
label="创建时间"
min-width="200"
>
<template #default="{ row }">
<span class="time-text">{{ row.createTime }}</span>
</template>
</el-table-column>
<!-- 修改时间列 -->
<el-table-column
prop="updateTime"
label="修改时间"
min-width="200"
>
<template #default="{ row }">
<span class="time-text">{{ row.updateTime }}</span>
</template>
</el-table-column>
<!-- 备注列 -->
<el-table-column
prop="remark"
label="备注"
width="120"
>
<template #default="{ row }">
<span class="remark-text">{{ row.remark || '-' }}</span>
</template>
</el-table-column>
<!-- 操作列(右侧浮动) -->
<el-table-column
label="操作"
width="320"
fixed="right"
align="center"
>
<template #default="{ row }">
<div class="operation-buttons">
<!-- 编辑按钮 -->
<el-button
link
type="primary"
size="small"
@click="handleEdit(row)"
class="op-btn"
>
编辑
</el-button>
<!-- 发现按钮 -->
<el-button
link
type="success"
size="small"
@click="handleDiscover(row)"
class="op-btn"
>
发现
</el-button>
<!-- 监控按钮 -->
<el-button
link
type="warning"
size="small"
@click="handleMonitor(row)"
class="op-btn"
>
监控
</el-button>
<!-- 更多操作下拉菜单 -->
<el-dropdown
@command="(command) => handleMoreCommand(command, row)"
trigger="click"
>
<el-button
link
type="info"
size="small"
class="more-btn"
>
更多
<el-icon class="el-icon--right">
<arrow-down />
</el-icon>
</el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item command="smartDiscover">
<span class="dropdown-item smart-discover">智能发现</span>
</el-dropdown-item>
<el-dropdown-item command="viewHistory">
<span class="dropdown-item">查看历史</span>
</el-dropdown-item>
<el-dropdown-item command="viewResult">
<span class="dropdown-item">查看结果</span>
</el-dropdown-item>
<el-dropdown-item command="delete" divided>
<span class="dropdown-item delete-item">删除</span>
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total > 0"
:total="total"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</div>
</div>
<!-- 新增/编辑发现任务弹窗 -->
<AddDiscoveryDialog ref="addDiscoveryDialogRef" @confirm="handleAddEditConfirm" />
<SmartDiscoveryDialog
v-model="smartDiscoveryDialogVisible"
ref="smartDiscoveryDialogRef"
@discover="disCoverSmart"
/>
</div>
</template>
<style lang="scss" scoped>
.table-item {
margin-top: var(--container-pd);
margin-bottom: 20px;
}
.item-content {
margin: 8px 0;
.label {
width: 80px;
color: #888;
}
}
.flex-container {
display: flex;
}
.align-center {
align-items: center;
}
.justify-between {
justify-content: space-between;
}
.flex1 {
flex: 1;
}
.flex-wrap {
flex-direction: wrap;
}
.discovery-table-container {
width: 100%;
background: #ffffff;
border-radius: 4px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
:deep(.el-table) {
.el-table__header-wrapper {
th {
background: #f0f7ff !important;
font-weight: 600;
}
}
.el-table__body-wrapper {
.el-table__row {
&:nth-child(even) {
background-color: #fafafa;
}
&:hover {
background-color: #f5f7fa !important;
}
}
}
}
}
.task-name {
font-weight: 500;
color: #303133;
}
.result-text {
color: #e6a23c;
font-weight: 500;
}
.time-text {
color: #909399;
font-size: 12px;
}
.remark-text {
color: #909399;
font-style: italic;
}
.operation-buttons {
display: flex;
align-items: center;
justify-content: center;
gap: 4px;
.op-btn {
padding: 4px 8px;
font-size: 12px;
min-height: auto;
&:hover {
background-color: #f5f7fa;
border-radius: 4px;
}
}
.more-btn {
padding: 4px 8px;
font-size: 12px;
min-height: auto;
&:hover {
background-color: #f5f7fa;
border-radius: 4px;
}
}
}
.dropdown-item {
font-size: 12px;
padding: 4px 0;
&.smart-discover {
color: #67c23a;
font-weight: 500;
}
&.delete-item {
color: #f56c6c;
}
}
:deep(.el-dropdown-menu__item) {
padding: 8px 16px;
&:hover {
background-color: #f5f7fa;
}
}
// 响应式设计
@media (max-width: 1200px) {
.discovery-table-container {
overflow-x: auto;
:deep(.el-table) {
min-width: 1000px;
}
}
.operation-buttons {
flex-wrap: wrap;
gap: 2px;
.op-btn, .more-btn {
padding: 2px 4px;
font-size: 11px;
}
}
}
</style>
\ No newline at end of file
<template>
<el-dialog
v-model="dialogVisible"
:title="dialogTitle"
width="800px"
:before-close="handleClose"
>
<!-- 步骤导航 - 按照图片样式重新设计 -->
<div class="steps-navigation">
<div class="steps-container">
<div class="step-item" :class="{ 'step-active': currentStep === 1, 'step-completed': currentStep > 1 }">
<div class="step-number">1</div>
<div class="step-title">新建任务</div>
<div class="step-status">{{ currentStep === 1 ? '进行中' : currentStep > 1 ? '已完成' : '待进行' }}</div>
</div>
<div class="step-connector"></div>
<div class="step-item" :class="{ 'step-active': currentStep === 2, 'step-completed': currentStep > 2 }">
<div class="step-number">2</div>
<div class="step-title">选择数据源</div>
<div class="step-status">{{ currentStep === 2 ? '进行中' : currentStep > 2 ? '已完成' : '待进行' }}</div>
</div>
<div class="step-connector"></div>
<div class="step-item" :class="{ 'step-active': currentStep === 3, 'step-completed': currentStep === 3 }">
<div class="step-number">3</div>
<div class="step-title">配置规则</div>
<div class="step-status">{{ currentStep === 3 ? '进行中' : '待进行' }}</div>
</div>
</div>
</div>
<!-- 步骤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="tname" required>
<el-input
v-model="formData.tname"
placeholder="请输入发现任务名称"
clearable
maxlength="50"
/>
</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-if="currentStep === 2" class="step-content">
<div class="data-source-section">
<div class="data-source-header">
<span class="label">数据源:</span>
<el-select v-model="selectedDataSource" placeholder="请选择数据源">
<el-option
v-for="source in dataSources"
:key="source.value"
:label="source.label"
:value="source.value"
/>
</el-select>
</div>
<div class="selection-content">
<!-- 左侧SCHEMA列表 -->
<div class="schema-list">
<div class="schema-header">
<h4>SCHEMA列表</h4>
<el-input
v-model="schemaSearch"
placeholder="搜索SCHEMA"
size="small"
prefix-icon="Search"
clearable
class="search-input"
/>
</div>
<div class="list-container">
<div
v-for="schema in filteredSchemaList"
:key="schema.id"
class="schema-item"
:class="{ 'schema-selected': selectedSchema === schema.id }"
@click="selectSchema(schema)"
>
<span class="schema-name">{{ schema.name }}</span>
</div>
</div>
</div>
<el-divider direction="vertical" />
<!-- 右侧表列表 -->
<div class="table-list">
<div class="table-header">
<div class="table-header-top">
<h4>表列表</h4>
<span class="selected-count">{{ selectedTables.length }}/{{ filteredTableList.length }}</span>
</div>
<div class="table-header-bottom">
<el-checkbox v-model="selectAll" @change="handleSelectAll" class="select-all-checkbox">全选</el-checkbox>
<el-input
v-model="tableSearch"
placeholder="搜索表"
size="small"
prefix-icon="Search"
clearable
class="search-input"
/>
</div>
</div>
<div class="list-container">
<el-checkbox-group v-model="selectedTables">
<div
v-for="table in filteredTableList"
:key="table.id"
class="table-item"
>
<el-checkbox :label="table.id">
<span class="table-name">{{ table.name }}</span>
</el-checkbox>
</div>
</el-checkbox-group>
</div>
</div>
</div>
</div>
</div>
<!-- 步骤3:配置规则 -->
<div v-else class="step-content">
<el-form :model="ruleConfig" :rules="ruleRules" ref="step3Form" label-width="120px">
<el-form-item label="采样方式" prop="samplingMethod" required>
<el-radio-group v-model="ruleConfig.samplingMethod">
<el-radio label="sequential">顺序</el-radio>
<el-radio label="random">随机</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="最大采样数" prop="maxSamples" required>
<el-input-number
v-model="ruleConfig.maxSamples"
:min="1"
:max="10000"
controls-position="right"
/>
</el-form-item>
<el-form-item label="匹配率" prop="matchingRate" required>
<div class="matching-rate-input">
<el-input
v-model="ruleConfig.matchingRate"
placeholder="50"
style="width: 100px"
/>
<span class="percentage-suffix">百分比%</span>
</div>
</el-form-item>
<el-form-item label="敏感类型" required>
<div class="sensitive-types">
<el-tag
v-for="type in sensitiveTypes"
:key="type"
closable
@close="removeSensitiveType(type)"
class="sensitive-tag"
>
{{ type }}
</el-tag>
<el-button type="primary" text @click="openSensitiveTypeDialog" class="add-sensitive-btn">
+ 添加敏感类型
</el-button>
</div>
</el-form-item>
</el-form>
</div>
<!-- 底部按钮 -->
<template #footer>
<div class="dialog-footer">
<el-button @click="handleCancel">取消</el-button>
<el-button
v-if="currentStep > 1"
@click="handlePrevStep"
>
上一步
</el-button>
<el-button
v-if="currentStep < 3"
type="primary"
@click="handleNextStep"
>
下一步
</el-button>
<el-button
v-if="currentStep === 3"
type="primary"
@click="handleConfirm"
>
确定
</el-button>
</div>
</template>
<!-- 敏感类型选择弹窗 -->
<!-- <SensitiveTypeDialog
v-model="sensitiveTypeDialogVisible"
@confirm="handleSensitiveTypeConfirm"
/> -->
<SensitiveTypeDialog
ref="sensitiveDialogRef"
v-model="sensitiveTypeDialogVisible"
:selected-types="selectedRuleIds"
@confirm="handleRuleConfirm"
/>
</el-dialog>
</template>
<script setup>
import { ref, reactive, computed } from 'vue'
import { ElMessage } from 'element-plus'
import SensitiveTypeDialog from './SensitiveTypeDialog.vue'
const emit = defineEmits(['confirm'])
const dialogVisible = ref(false)
const currentStep = ref(1)
const isEditMode = ref(false)
const currentEditId = ref(null)
const sensitiveTypeDialogVisible = ref(false)
const schemaSearch = ref('')
const tableSearch = ref('')
const sensitiveDialogRef = ref()
const selectedRuleIds = ref(['email', 'mixed_id']) // 已选规则的ID
const selectedRules = ref([
{ id: 'email', domain: '电子邮件', ruleName: '邮箱规则1', sample: '605396113@qq.com' },
{ id: 'mixed_id', domain: '混合证件号', ruleName: '混合证件号规则1', sample: '310000000100116, 9131000...' }
])
const sensitiveTypes = ref(['电子邮件', '混合证件号'])
// 步骤1数据
const formData = reactive({
tname: '123',
remark: ''
})
const rules = {
tname: [
{ required: true, message: '请填写发现任务名称', trigger: 'blur' }
]
}
// 步骤2数据
const selectedDataSource = ref('system1')
const dataSources = ref([
{ value: 'system1', label: '若依测试系统1' }
])
const schemaList = ref([
{ id: 'ry', name: 'ry' }
])
const selectedSchema = ref('ry')
const tableList = ref([
{ id: 'gen_table', name: 'gen_table' },
{ id: 'gen_table_column', name: 'gen_table_column' },
{ id: 'sys_config', name: 'sys_config' },
{ id: 'sys_dept', name: 'sys_dept' },
{ id: 'sys_dict_data', name: 'sys_dict_data' },
{ id: 'sys_dict_type', name: 'sys_dict_type' },
{ id: 'sys_job', name: 'sys_job' },
{ id: 'sys_job_log', name: 'sys_job_log' },
{ id: 'sys_logininfor', name: 'sys_logininfor' },
{ id: 'sys_menu', name: 'sys_menu' },
{ id: 'sys_notice', name: 'sys_notice' },
{ id: 'sys_oper_log', name: 'sys_oper_log' },
{ id: 'sys_post', name: 'sys_post' },
{ id: 'sys_role', name: 'sys_role' },
{ id: 'sys_role_dept', name: 'sys_role_dept' },
{ id: 'sys_role_menu', name: 'sys_role_menu' },
{ id: 'sys_user', name: 'sys_user' },
{ id: 'sys_user_post', name: 'sys_user_post' },
{ id: 'sys_user_role', name: 'sys_user_role' }
])
const selectedTables = ref(tableList.value.map(table => table.id))
const selectAll = ref(true)
// 步骤3数据
const ruleConfig = reactive({
samplingMethod: 'sequential',
maxSamples: 1000,
matchingRate: 50
})
const ruleRules = {
samplingMethod: [
{ required: true, message: '请选择采样方式', trigger: 'change' }
],
maxSamples: [
{ required: true, message: '请输入最大采样数', trigger: 'blur' }
],
matchingRate: [
{ required: true, message: '请输入匹配率', trigger: 'blur' }
]
}
// 计算属性
const dialogTitle = computed(() => {
return `新增发现任务 - 步骤${currentStep.value}/3`
})
const filteredSchemaList = computed(() => {
if (!schemaSearch.value) return schemaList.value
return schemaList.value.filter(schema =>
schema.name.toLowerCase().includes(schemaSearch.value.toLowerCase())
)
})
const filteredTableList = computed(() => {
if (!tableSearch.value) return tableList.value
return tableList.value.filter(table =>
table.name.toLowerCase().includes(tableSearch.value.toLowerCase())
)
})
// 修改openDialog方法
const openDialog = (row = null) => {
dialogVisible.value = true
currentStep.value = 1
isEditMode.value = !!row
if (row) {
// 编辑模式
currentEditId.value = row.id
// 填充步骤1数据
formData.tname = row.tname
formData.remark = row.remark
// 填充步骤2数据(根据实际业务数据调整)
selectedDataSource.value = row.dataSource || 'system1'
selectedSchema.value = row.schema || 'ry'
selectedTables.value = row.tables || []
// 填充步骤3数据
if (row.ruleConfig) {
ruleConfig.samplingMethod = row.ruleConfig.samplingMethod || 'sequential'
ruleConfig.maxSamples = row.ruleConfig.maxSamples || 1000
ruleConfig.matchingRate = row.ruleConfig.matchingRate || 50
}
// 设置敏感类型
sensitiveTypes.value = row.sensitiveTypes || []
} else {
// 新增模式
resetForm()
}
}
const resetForm = () => {
formData.tname = '123'
formData.remark = ''
selectedDataSource.value = 'system1'
selectedSchema.value = 'ry'
selectedTables.value = tableList.value.map(table => table.id)
selectAll.value = true
sensitiveTypes.value = ['电子邮件', '混合证件号']
schemaSearch.value = ''
tableSearch.value = ''
}
const handleClose = () => {
dialogVisible.value = false
}
const handleCancel = () => {
dialogVisible.value = false
}
const handleNextStep = async () => {
if (currentStep.value === 1) {
if (!formData.tname.trim()) {
ElMessage.error('请填写发现任务名称')
return
}
} else if (currentStep.value === 2) {
if (!selectedDataSource.value) {
ElMessage.error('请选择数据源')
return
}
if (!selectedSchema.value) {
ElMessage.error('请选择SCHEMA')
return
}
if (selectedTables.value.length === 0) {
ElMessage.error('请至少选择一张表')
return
}
}
currentStep.value++
}
const handlePrevStep = () => {
currentStep.value--
}
const selectSchema = (schema) => {
selectedSchema.value = schema.id
}
const handleSelectAll = (value) => {
if (value) {
selectedTables.value = tableList.value.map(table => table.id)
} else {
selectedTables.value = []
}
}
const openSensitiveTypeDialog = () => {
sensitiveDialogRef.value.openDialog()
// sensitiveTypeDialogVisible.value = true
}
const handleRuleConfirm = (rules) => {
selectedRules.value = rules
selectedRuleIds.value = rules.map(rule => rule.id)
sensitiveTypes.value = rules.map(rule => rule.domain)
}
const removeRule = (ruleId) => {
selectedRules.value = selectedRules.value.filter(rule => rule.id !== ruleId)
selectedRuleIds.value = selectedRuleIds.value.filter(id => id !== ruleId)
}
const removeSensitiveType = (type) => {
sensitiveTypes.value = sensitiveTypes.value.filter(t => t !== type)
}
const handleConfirm = () => {
if (sensitiveTypes.value.length === 0) {
ElMessage.error('请至少添加一个敏感类型')
return
}
const submitData = {
...formData,
dataSource: selectedDataSource.value,
schema: selectedSchema.value,
tables: selectedTables.value,
ruleConfig: { ...ruleConfig },
sensitiveTypes: [...sensitiveTypes.value]
}
// 如果是编辑模式,添加id
if (isEditMode.value) {
submitData.id = currentEditId.value
}
console.log('提交数据:', submitData)
// ElMessage.success(isEditMode.value ? '发现任务更新成功' : '发现任务创建成功')
dialogVisible.value = false
// 触发父组件更新
emit('confirm', submitData, isEditMode.value)
}
defineExpose({
openDialog
})
</script>
<style scoped lang="scss">
.steps-navigation {
margin-bottom: 24px;
padding: 0 20px;
.steps-container {
display: flex;
align-items: center;
justify-content: center;
}
.step-item {
display: flex;
flex-direction: column;
align-items: center;
min-width: 100px;
&.step-active {
.step-number {
background: #1890ff;
color: white;
border-color: #1890ff;
}
.step-title {
color: #1890ff;
font-weight: 600;
}
.step-status {
color: #1890ff;
}
}
&.step-completed {
.step-number {
background: #52c41a;
color: white;
border-color: #52c41a;
}
.step-title {
color: #52c41a;
}
.step-status {
color: #52c41a;
}
}
}
.step-number {
width: 24px;
height: 24px;
border-radius: 50%;
border: 2px solid #d9d9d9;
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
margin-bottom: 8px;
}
.step-title {
font-size: 14px;
color: #666;
margin-bottom: 4px;
}
.step-status {
font-size: 12px;
color: #999;
}
.step-connector {
width: 60px;
height: 2px;
background: #d9d9d9;
margin: 0 10px;
.step-completed + & {
background: #52c41a;
}
}
}
.step-content {
min-height: 300px;
padding: 0 20px;
}
.data-source-section {
.data-source-header {
display: flex;
align-items: center;
margin-bottom: 16px;
.label {
width: 60px;
font-weight: 500;
margin-right: 12px;
color: #606266;
}
}
.selection-content {
display: flex;
height: 400px;
gap: 20px;
}
.schema-list,
.table-list {
flex: 1;
display: flex;
flex-direction: column;
}
.schema-list {
width: 200px;
.schema-header {
margin-bottom: 12px;
h4 {
margin: 0 0 8px 0;
font-size: 14px;
font-weight: 600;
color: #606266;
}
}
}
.table-list {
.table-header {
display: flex;
flex-direction: column;
gap: 8px;
margin-bottom: 12px;
.table-header-top {
display: flex;
justify-content: space-between;
align-items: center;
h4 {
margin: 0;
font-size: 14px;
font-weight: 600;
color: #606266;
}
.selected-count {
font-size: 12px;
color: #909399;
}
}
.table-header-bottom {
display: flex;
align-items: center;
gap: 8px;
.select-all-checkbox {
white-space: nowrap;
margin-right: 8px;
}
.search-input {
flex: 1;
min-width: 120px;
}
}
}
}
.list-container {
flex: 1;
overflow-y: auto;
border: 1px solid #dcdfe6;
border-radius: 4px;
padding: 8px;
}
.schema-item {
padding: 10px 12px;
cursor: pointer;
border-radius: 4px;
margin-bottom: 4px;
transition: all 0.2s ease;
font-size: 13px;
color: #606266;
&:hover {
background-color: #f5f7fa;
}
&.schema-selected {
background-color: #ffd6b3;
color: #e67e22;
font-weight: 500;
}
}
.table-item {
padding: 8px 0;
:deep(.el-checkbox) {
width: 100%;
.el-checkbox__label {
font-size: 13px;
color: #606266;
}
}
}
}
.matching-rate-input {
display: flex;
align-items: center;
.percentage-suffix {
margin-left: 8px;
color: #606266;
}
}
.sensitive-types {
display: flex;
flex-wrap: wrap;
gap: 8px;
align-items: center;
.sensitive-tag {
background-color: #f0f7ff;
border-color: #1890ff;
color: #1890ff;
}
.add-sensitive-btn {
margin-left: 8px;
}
}
.dialog-footer {
display: flex;
justify-content: flex-end;
gap: 12px;
}
.el-divider {
margin: 0 20px;
height: 360px;
background-color: #e0e0e0;
}
.search-input {
:deep(.el-input__inner) {
height: 32px;
line-height: 32px;
font-size: 12px;
}
:deep(.el-input__icon) {
line-height: 32px;
}
}
</style>
\ No newline at end of file
<template>
<el-dialog
v-model="dialogVisible"
title="设置数据域与字段发现规则"
width="700px"
:before-close="handleClose"
>
<div class="domain-rule-dialog">
<!-- 搜索区域 -->
<div class="search-section">
<el-input
v-model="domainSearch"
placeholder="请输入数据域名称"
size="small"
prefix-icon="Search"
clearable
/>
</div>
<div class="content-section">
<!-- 左侧数据域列表 -->
<div class="domain-list">
<div class="section-title">选择数据域</div>
<div class="list-container">
<el-radio-group v-model="selectedDomain" @change="handleDomainChange">
<div
v-for="domain in filteredDomains"
:key="domain.value"
class="domain-item"
>
<el-radio :label="domain.value">
{{ domain.label }}
</el-radio>
</div>
</el-radio-group>
</div>
</div>
<!-- 右侧字段发现规则 -->
<div class="rule-list">
<div class="section-title">选择字段发现规则</div>
<div class="list-container">
<el-radio-group v-model="selectedRule">
<div
v-for="rule in currentRules"
:key="rule.value"
class="rule-item"
>
<el-radio :label="rule.value">
{{ rule.label }}
</el-radio>
</div>
</el-radio-group>
</div>
</div>
</div>
<!-- 底部按钮 -->
<div class="dialog-footer">
<el-button @click="handleReset">重置规则</el-button>
<el-button type="primary" @click="handleConfirm">确定规则</el-button>
</div>
</div>
</el-dialog>
</template>
<script setup>
import { ref, computed, watch } from 'vue'
import { ElMessage } from 'element-plus'
const props = defineProps({
// 已选择的规则数据 { domain: '', rule: '' }
selectedData: {
type: Object,
default: () => ({})
}
})
const emit = defineEmits(['update:modelValue', 'confirm'])
const dialogVisible = computed({
get: () => props.modelValue,
set: (value) => emit('update:modelValue', value)
})
const domainSearch = ref('')
const selectedDomain = ref('')
const selectedRule = ref('')
// 数据域选项(根据图片内容)
const domains = ref([
{ value: 'general', label: '通用规则' },
{ value: 'decrypt', label: '解密数据' },
{ value: 'mixed_id', label: '混合证件号' },
{ value: 'email', label: '电子邮件' },
{ value: 'social_security', label: '社保卡号' },
{ value: 'business_license', label: '营业执照' },
{ value: 'postal_code', label: '邮政编码' },
{ value: 'chinese_address', label: '中文地址' },
{ value: 'mixed_phone', label: '混合电话号码' },
{ value: 'name', label: '姓名' },
{ value: 'bank_card', label: '银行卡号' },
{ value: 'ip_address', label: 'IP地址' }
])
// 字段发现规则映射
const rulesMapping = {
general: [
{ value: 'general_rule_1', label: '通用规则查询' },
{ value: 'general_rule_2', label: '通用数据发现' }
],
decrypt: [
{ value: 'decrypt_rule_1', label: '解密数据查询' },
{ value: 'decrypt_rule_2', label: '数据解密规则' }
],
mixed_id: [
{ value: 'mixed_id_rule_1', label: '按混合证件号字段查询' }
],
email: [
{ value: 'email_rule_1', label: '邮箱字段查询' },
{ value: 'email_rule_2', label: '电子邮件规则' }
],
social_security: [
{ value: 'ss_rule_1', label: '社保卡号查询' }
],
business_license: [
{ value: 'bl_rule_1', label: '营业执照查询' }
],
postal_code: [
{ value: 'pc_rule_1', label: '邮政编码查询' }
],
chinese_address: [
{ value: 'address_rule_1', label: '中文地址查询' }
],
mixed_phone: [
{ value: 'phone_rule_1', label: '混合电话查询' }
],
name: [
{ value: 'name_rule_1', label: '姓名字段查询' }
],
bank_card: [
{ value: 'bank_rule_1', label: '银行卡号查询' }
],
ip_address: [
{ value: 'ip_rule_1', label: 'IP地址查询' }
]
}
// 过滤后的数据域列表
const filteredDomains = computed(() => {
if (!domainSearch.value) return domains.value
return domains.value.filter(domain =>
domain.label.toLowerCase().includes(domainSearch.value.toLowerCase())
)
})
// 当前选中的数据域对应的规则
const currentRules = computed(() => {
return rulesMapping[selectedDomain.value] || []
})
// 打开弹窗时初始化
const initDialog = () => {
if (props.selectedData.domain) {
selectedDomain.value = props.selectedData.domain
selectedRule.value = props.selectedData.rule || ''
} else {
// 默认选择第一个数据域和第一个规则
selectedDomain.value = domains.value[0]?.value || ''
if (selectedDomain.value && currentRules.value.length > 0) {
selectedRule.value = currentRules.value[0].value
}
}
}
// 处理数据域变化
const handleDomainChange = (domainValue) => {
if (rulesMapping[domainValue] && rulesMapping[domainValue].length > 0) {
selectedRule.value = rulesMapping[domainValue][0].value
} else {
selectedRule.value = ''
}
}
// 重置规则
const handleReset = () => {
selectedDomain.value = domains.value[0]?.value || ''
if (selectedDomain.value && currentRules.value.length > 0) {
selectedRule.value = currentRules.value[0].value
}
domainSearch.value = ''
}
// 确定规则
const handleConfirm = () => {
if (!selectedDomain.value) {
ElMessage.warning('请选择数据域')
return
}
if (!selectedRule.value) {
ElMessage.warning('请选择字段发现规则')
return
}
const selectedDomainObj = domains.value.find(d => d.value === selectedDomain.value)
const selectedRuleObj = currentRules.value.find(r => r.value === selectedRule.value)
emit('confirm', {
domain: selectedDomain.value,
domainLabel: selectedDomainObj?.label || '',
rule: selectedRule.value,
ruleLabel: selectedRuleObj?.label || ''
})
dialogVisible.value = false
}
// 关闭弹窗
const handleClose = () => {
dialogVisible.value = false
}
// 监听弹窗显示状态
watch(dialogVisible, (newVal) => {
if (newVal) {
initDialog()
}
})
const openDialog = () => {
initDialog()
dialogVisible.value = true
}
// 暴露方法
defineExpose({
openDialog
})
</script>
<style scoped lang="scss">
.domain-rule-dialog {
.search-section {
margin-bottom: 16px;
:deep(.el-input__inner) {
height: 32px;
}
}
.content-section {
display: flex;
gap: 20px;
height: 400px;
}
.domain-list,
.rule-list {
flex: 1;
display: flex;
flex-direction: column;
}
.section-title {
font-size: 14px;
font-weight: 600;
color: #303133;
margin-bottom: 12px;
padding-left: 4px;
}
.list-container {
flex: 1;
border: 1px solid #dcdfe6;
border-radius: 4px;
padding: 12px;
overflow-y: auto;
}
.domain-item,
.rule-item {
padding: 8px 0;
:deep(.el-radio) {
width: 100%;
.el-radio__label {
font-size: 13px;
}
}
}
.domain-item {
&:hover {
background-color: #f5f7fa;
border-radius: 4px;
}
}
.rule-item {
&:hover {
background-color: #f5f7fa;
border-radius: 4px;
}
}
.dialog-footer {
display: flex;
justify-content: flex-end;
gap: 12px;
margin-top: 20px;
padding-top: 16px;
border-top: 1px solid #e0e0e0;
}
}
:deep(.el-radio-group) {
display: block;
}
</style>
\ No newline at end of file
<template>
<el-dialog
v-model="dialogVisible"
title="查看匹配"
width="900px"
:before-close="handleClose"
>
<div class="match-preview-dialog">
<div class="match-content">
<!-- 左侧:匹配的数据 -->
<div class="match-section">
<div class="section-header">
<span class="section-title">匹配的数据</span>
</div>
<el-table
:data="matchedData"
style="width: 100%"
height="400"
:header-cell-style="{ background: '#f0f7ff', color: '#606266', fontWeight: 'bold' }"
empty-text="暂无匹配数据"
>
<el-table-column prop="value" label="匹配的数据" align="center">
<template #default="{ row }">
<span class="match-value">{{ row.value }}</span>
</template>
</el-table-column>
</el-table>
</div>
<!-- 右侧:不匹配的数据 -->
<div class="match-section">
<div class="section-header">
<span class="section-title">不匹配的数据</span>
</div>
<el-table
:data="unmatchedData"
style="width: 100%"
height="400"
:header-cell-style="{ background: '#f0f7ff', color: '#606266', fontWeight: 'bold' }"
empty-text="暂无数据"
>
<el-table-column prop="value" label="不匹配的数据" align="center">
<template #default="{ row }">
<span class="unmatch-value">{{ row.value }}</span>
</template>
</el-table-column>
</el-table>
</div>
</div>
<!-- 底部按钮 -->
<div class="dialog-footer">
<el-button type="primary" @click="handleClose">关闭</el-button>
</div>
</div>
</el-dialog>
</template>
<script setup>
import { ref, computed } from 'vue'
const props = defineProps({
modelValue: {
type: Boolean,
default: false
},
// 匹配的数据
matchedData: {
type: Array,
default: () => []
},
// 不匹配的数据
unmatchedData: {
type: Array,
default: () => []
}
})
const emit = defineEmits(['update:modelValue'])
const dialogVisible = computed({
get: () => props.modelValue,
set: (value) => emit('update:modelValue', value)
})
// 关闭弹窗
const handleClose = () => {
dialogVisible.value = false
}
// 暴露方法
defineExpose({
openDialog: () => {
dialogVisible.value = true
}
})
</script>
<style scoped lang="scss">
.match-preview-dialog {
.match-content {
display: flex;
gap: 20px;
margin-bottom: 20px;
}
.match-section {
flex: 1;
background: #fafafa;
border-radius: 6px;
overflow: hidden;
}
.section-header {
background: #f0f7ff;
padding: 12px 16px;
border-bottom: 1px solid #dcdfe6;
.section-title {
font-size: 14px;
font-weight: 600;
color: #303133;
}
}
:deep(.el-table) {
border-radius: 0;
.el-table__header-wrapper {
th {
background: #f0f7ff !important;
}
}
.el-table__body-wrapper {
.el-table__row {
&:nth-child(even) {
background-color: #fafafa;
}
&:hover {
background-color: #f5f7fa;
}
}
}
}
.match-value {
color: #52c41a;
font-weight: 500;
}
.unmatch-value {
color: #ff4d4f;
font-weight: 500;
}
.dialog-footer {
display: flex;
justify-content: center;
padding-top: 16px;
border-top: 1px solid #e0e0e0;
}
}
// 响应式设计
@media (max-width: 768px) {
.match-preview-dialog {
.match-content {
flex-direction: column;
}
.match-section {
min-height: 200px;
}
}
}
</style>
\ No newline at end of file
<template>
<el-dialog
v-model="dialogVisible"
title="选择规则"
width="800px"
:before-close="handleClose"
>
<div class="sensitive-type-dialog">
<!-- 搜索区域 -->
<div class="search-section">
<el-input
v-model="searchText"
placeholder="搜索规则"
size="small"
prefix-icon="Search"
clearable
class="search-input"
/>
</div>
<!-- 规则表格 -->
<div class="rules-table-container">
<el-table
ref="tableRef"
:data="filteredRules"
style="width: 100%"
:header-cell-style="{ background: '#f0f7ff', color: '#606266' }"
:cell-style="{ padding: '8px 0' }"
@selection-change="handleSelectionChange"
>
<!-- 选择列 -->
<el-table-column type="selection" width="55" align="center" />
<!-- 数据域列 -->
<el-table-column prop="domain" label="数据域" width="120">
<template #default="{ row }">
<span class="domain-text">{{ row.domain }}</span>
</template>
</el-table-column>
<!-- 规则名列 -->
<el-table-column prop="ruleName" label="规则名" min-width="150">
<template #default="{ row }">
<el-select
v-model="row.selectedRule"
placeholder="请选择"
size="small"
style="width: 100%"
:disabled="!isRowSelected(row)"
>
<el-option
v-for="option in row.options"
:key="option.value"
:label="option.label"
:value="option.value"
/>
</el-select>
</template>
</el-table-column>
<!-- 样例列 -->
<el-table-column prop="sample" label="样例" min-width="200">
<template #default="{ row }">
<div class="sample-content">
<div class="sample-text">{{ row.sample }}</div>
<div class="sample-description" v-if="row.description">
{{ row.description }}
</div>
</div>
</template>
</el-table-column>
</el-table>
</div>
<!-- 底部按钮 -->
<div class="dialog-footer">
<el-button @click="handleCancel">取消</el-button>
<el-button type="primary" @click="handleConfirm">确定</el-button>
</div>
</div>
</el-dialog>
</template>
<script setup>
import { ref, computed, watch, nextTick } from 'vue'
import { ElMessage } from 'element-plus'
const props = defineProps({
modelValue: {
type: Boolean,
default: false
},
selectedTypes: {
type: Array,
default: () => []
}
})
const emit = defineEmits(['update:modelValue', 'confirm'])
const dialogVisible = computed({
get: () => props.modelValue,
set: (value) => emit('update:modelValue', value)
})
const searchText = ref('')
const tableRef = ref()
const selectedRules = ref([])
// 规则数据(根据图片内容)
const rulesData = ref([
{
id: 'mixed_data',
domain: '混合数据域',
options: [
{ value: '566546', label: '566546' },
{ value: '345', label: '345' }
],
selectedRule: '566546',
sample: '345',
description: '按混合证件号字段查询'
},
{
id: 'smart_data',
domain: '智能数据域',
options: [
{ value: 'option1', label: '请选择' },
{ value: 'option2', label: '智能规则1' },
{ value: 'option3', label: '智能规则2' }
],
selectedRule: 'option1',
sample: '',
description: ''
},
{
id: 'general_rule',
domain: '通用规则',
options: [
{ value: 'general1', label: '通用规则1' },
{ value: 'general2', label: '通用规则2' }
],
selectedRule: 'general1',
sample: '310000000100116, 9131000...',
description: '按混合证件号字段查询'
},
{
id: 'decrypt_data',
domain: '解密数据',
options: [
{ value: 'decrypt1', label: '解密规则1' },
{ value: 'decrypt2', label: '解密规则2' }
],
selectedRule: 'decrypt1',
sample: '',
description: ''
},
{
id: 'mixed_id',
domain: '混合证件号',
options: [
{ value: 'mixed_id1', label: '混合证件号规则1' },
{ value: 'mixed_id2', label: '混合证件号规则2' }
],
selectedRule: 'mixed_id1',
sample: '310000000100116, 9131000...',
description: '按混合证件号字段查询'
},
{
id: 'email',
domain: '电子邮件',
options: [
{ value: 'email1', label: '邮箱规则1' },
{ value: 'email2', label: '邮箱规则2' }
],
selectedRule: 'email1',
sample: '605396113@qq.com',
description: '按邮箱字段查找'
},
{
id: 'social_security',
domain: '社保卡号',
options: [
{ value: 'ss1', label: '社保规则1' },
{ value: 'ss2', label: '社保规则2' }
],
selectedRule: 'ss1',
sample: '11',
description: '按邮箱字段查找'
},
{
id: 'business_license',
domain: '营业执照',
options: [
{ value: 'bl1', label: '营业执照规则1' },
{ value: 'bl2', label: '营业执照规则2' }
],
selectedRule: 'bl1',
sample: '310000000100116, 9131000...',
description: '按营业执照字段查询'
},
{
id: 'postal_code',
domain: '邮政编码',
options: [
{ value: 'pc1', label: '邮编规则1' },
{ value: 'pc2', label: '邮编规则2' }
],
selectedRule: 'pc1',
sample: '321300, 322300, 316100',
description: '邮政编码字段查找'
},
{
id: 'chinese_address',
domain: '中文地址',
options: [
{ value: 'address1', label: '地址规则1' },
{ value: 'address2', label: '地址规则2' }
],
selectedRule: 'address1',
sample: '北京市朝阳区建国路93号万',
description: '中文地址字段查找'
}
])
// 过滤后的规则列表
const filteredRules = computed(() => {
if (!searchText.value) return rulesData.value
return rulesData.value.filter(rule =>
rule.domain.toLowerCase().includes(searchText.value.toLowerCase()) ||
rule.sample.toLowerCase().includes(searchText.value.toLowerCase()) ||
rule.description.toLowerCase().includes(searchText.value.toLowerCase()) ||
rule.options.some(option =>
option.label.toLowerCase().includes(searchText.value.toLowerCase())
)
)
})
// 检查行是否被选中
const isRowSelected = (row) => {
return selectedRules.value.some(selected => selected.id === row.id)
}
// 处理选择变化
const handleSelectionChange = (selection) => {
selectedRules.value = selection
}
// 初始化选中状态
const initSelectedRules = () => {
console.log(123)
nextTick(() => {
if (tableRef.value) {
// 清除所有选择
tableRef.value.clearSelection()
console.log('props.selectedTypes',props.selectedTypes)
// 设置初始选中
rulesData.value.forEach(rule => {
console.log('props.selectedTypes',props.selectedTypes)
if (props.selectedTypes.includes(rule.id)) {
const row = rulesData.value.find(r => r.id === rule.id)
if (row) {
tableRef.value.toggleRowSelection(row, true)
}
}
})
}
})
}
// 打开弹窗
const openDialog = () => {
dialogVisible.value = true
initSelectedRules()
}
// 关闭弹窗
const handleClose = () => {
dialogVisible.value = false
}
// 取消选择
const handleCancel = () => {
dialogVisible.value = false
}
// 确认选择
const handleConfirm = () => {
const validSelectedRules = selectedRules.value.filter(rule =>
rule.selectedRule && rule.selectedRule !== 'option1' // 排除"请选择"
)
if (validSelectedRules.length === 0) {
ElMessage.warning('请至少选择一个有效规则')
return
}
const result = validSelectedRules.map(rule => ({
id: rule.id,
domain: rule.domain,
ruleName: rule.selectedRule,
sample: rule.sample,
description: rule.description
}))
emit('confirm', result)
dialogVisible.value = false
}
// 监听对话框显示状态
watch(dialogVisible, (newVal) => {
if (newVal) {
initSelectedRules()
}
})
defineExpose({
openDialog
})
</script>
<style scoped lang="scss">
.sensitive-type-dialog {
.search-section {
margin-bottom: 16px;
.search-input {
width: 100%;
:deep(.el-input__inner) {
height: 32px;
}
}
}
.rules-table-container {
border: 1px solid #dcdfe6;
border-radius: 4px;
overflow: hidden;
margin-bottom: 20px;
max-height: 400px;
overflow-y: auto;
:deep(.el-table) {
.el-table__header-wrapper {
th {
background: #f0f7ff !important;
font-weight: 600;
padding: 8px 0;
}
}
.el-table__body-wrapper {
.el-table__row {
&.current-row {
background-color: #f0f7ff !important;
}
&:hover {
background-color: #f5f7fa !important;
}
}
}
}
}
.domain-text {
font-weight: 500;
color: #303133;
}
.sample-content {
display: flex;
flex-direction: column;
gap: 4px;
}
.sample-text {
font-size: 12px;
color: #606266;
word-break: break-all;
}
.sample-description {
font-size: 11px;
color: #909399;
font-style: italic;
}
.dialog-footer {
display: flex;
justify-content: flex-end;
gap: 12px;
padding-top: 16px;
border-top: 1px solid #e0e0e0;
}
}
// 选中行的样式
:deep(.el-table) {
.current-row {
td {
background-color: #f0f7ff !important;
}
}
.el-checkbox__input.is-disabled {
.el-checkbox__inner {
background-color: #f5f7fa;
border-color: #dcdfe6;
}
}
}
// 响应式设计
@media (max-width: 768px) {
.sensitive-type-dialog {
.rules-table-container {
:deep(.el-table) {
.el-table__header {
th {
padding: 6px 0;
font-size: 12px;
}
}
}
}
}
}
</style>
\ No newline at end of file
<template>
<el-dialog
v-model="dialogVisible"
title="智能发现"
width="500px"
:before-close="handleClose"
>
<div class="smart-discovery-dialog">
<!-- 表格内容 -->
<div class="discovery-table">
<el-table
height="400"
:data="tableData"
style="width: 100%"
:header-cell-style="{ background: '#f5f7fa', color: '#606266', fontWeight: 'bold' }"
empty-text="暂无发现版本"
>
<!-- 发现版本列 -->
<el-table-column prop="version" label="发现版本" >
<template #default="{ row }">
<div class="version-info">
<span class="version-label">任务版本:</span>
<span class="version-value">{{ row.version }}</span>
</div>
</template>
</el-table-column>
<!-- 操作列 -->
<el-table-column prop="action" label="操作" width="120" align="center">
<template #default="{ row }">
<el-button
type="primary"
link
size="small"
@click="handleSmartDiscover(row)"
class="discover-btn"
>
智能发现
</el-button>
</template>
</el-table-column>
</el-table>
</div>
<!-- 底部按钮 -->
<div class="dialog-footer">
<el-button @click="handleClose">关闭</el-button>
</div>
</div>
</el-dialog>
</template>
<script setup>
import { ref, computed } from 'vue'
import { ElMessage } from 'element-plus'
const props = defineProps({
modelValue: {
type: Boolean,
default: false
},
// 发现版本数据
discoveryData: {
type: Array,
default: () => []
}
})
const emit = defineEmits(['update:modelValue', 'discover'])
const dialogVisible = computed({
get: () => props.modelValue,
set: (value) => emit('update:modelValue', value)
})
// 表格数据
const tableData = ref([
{ version: '12123' }
])
// 处理智能发现
const handleSmartDiscover = (row) => {
console.log('开始智能发现:', row.version)
emit('discover', row)
ElMessage.success(`开始对版本 ${row.version} 进行智能发现`)
dialogVisible.value = false
}
// 关闭弹窗
const handleClose = () => {
dialogVisible.value = false
}
// 监听外部传入的数据
watch(() => props.discoveryData, (newData) => {
if (newData && newData.length > 0) {
tableData.value = newData
}
}, { immediate: true })
// 暴露方法
defineExpose({
openDialog: (data = []) => {
if (data && data.length > 0) {
tableData.value = data
}
dialogVisible.value = true
}
})
</script>
<style scoped lang="scss">
.smart-discovery-dialog {
.discovery-table {
margin-bottom: 20px;
border: 1px solid #e0e0e0;
border-radius: 4px;
overflow: hidden;
}
.version-info {
padding: 8px 0;
.version-label {
color: #606266;
font-size: 13px;
}
.version-value {
color: #303133;
font-weight: 500;
font-size: 13px;
}
}
.discover-btn {
font-weight: 500;
&:hover {
color: #1890ff;
}
}
.dialog-footer {
display: flex;
justify-content: flex-end;
padding-top: 16px;
border-top: 1px solid #e0e0e0;
}
}
// 表格样式调整
:deep(.el-table) {
border-radius: 4px;
.el-table__header-wrapper {
th {
background: #f5f7fa !important;
}
}
.el-table__body-wrapper {
.el-table__row {
&:hover {
background-color: #f5f7fa;
}
}
}
}
// 响应式设计
@media (max-width: 768px) {
.smart-discovery-dialog {
.discovery-table {
overflow-x: auto;
}
:deep(.el-table) {
min-width: 300px;
}
}
}
</style>
\ No newline at end of file
<template>
<div class="cpu-usage-chart" ref="chartRef"></div>
</template>
<script setup>
import { ref, onMounted, onBeforeUnmount, watch } from 'vue'
import * as echarts from 'echarts'
const props = defineProps({
usage: {
type: Number,
default: 0
},
color: {
type: String,
default: '#1890ff'
}
})
const chartRef = ref(null)
let chartInstance = null
const initChart = () => {
if (!chartRef.value) return
chartInstance = echarts.init(chartRef.value)
const option = {
series: [{
type: 'gauge',
startAngle: 90,
endAngle: -270,
pointer: { show: false },
progress: {
show: true,
overlap: false,
roundCap: true,
clip: false,
itemStyle: {
color: props.color // 进度条颜色 - 蓝色
}
},
axisLine: {
lineStyle: {
width: 10,
color: [[1, '#f0f0f0']] // 背景环颜色 - 浅灰色
}
},
axisTick: { show: false },
splitLine: { show: false },
axisLabel: { show: false },
detail: {
valueAnimation: true,
offsetCenter: [0, '0%'],
formatter: '{value}%',
color: props.color, // 百分比数字颜色 - 蓝色
fontSize: 24,
fontWeight: 'bold'
},
title: {
offsetCenter: [0, '30%'],
color: '#666', // "发现进度"文字颜色 - 灰色
fontSize: 14
},
data: [{
value: props.usage,
name: '发现进度'
}]
}],
// title: {
// text: 'cpu',
// left: 'center',
// top: '10%',
// textStyle: {
// color: '#666', // "cpu"文字颜色 - 灰色
// fontSize: 14
// }
// }
}
chartInstance.setOption(option)
}
// 监听窗口变化自动调整图表大小
const resizeHandler = () => {
chartInstance?.resize()
}
// 监听props变化更新图表
watch(() => props.usage, (newVal) => {
if (chartInstance) {
chartInstance.setOption({
series: [{
data: [{
value: newVal,
name: '发现进度'
}]
}]
})
}
})
onMounted(() => {
initChart()
window.addEventListener('resize', resizeHandler)
})
onBeforeUnmount(() => {
window.removeEventListener('resize', resizeHandler)
chartInstance?.dispose()
})
</script>
<style scoped>
.cpu-usage-chart {
width: 100%;
height: 100%;
background-color: white; /* 白色背景 */
}
</style>
\ No newline at end of file
<template>
<el-dialog
v-model="dialogVisible"
title="下载插件"
width="1000px"
>
<div class="download-plugin-dialog">
<!-- 加密网关配置 -->
<div class="section">
<div class="section-title">加密网关配置(规则拉取插件下载区域)</div>
<div class="section-content">
<div class="readonly-input-group">
<span class="input-label">加密网关平台:</span>
<el-input v-model="gatewayConfig.url" disabled class="input-field" />
<el-input v-model="gatewayConfig.port" disabled class="input-field port-input" />
<el-button type="primary" icon="Download" @click="downloadGatewayPlugin">下载</el-button>
</div>
</div>
</div>
<!-- 服务端加解密 -->
<div class="section">
<div class="section-title">服务端加解密(参数展示区域)</div>
<div class="section-content">
<div class="readonly-input-group">
<span class="input-label">项目ID:</span>
<el-input :value="projectId" disabled class="input-field" />
</div>
</div>
</div>
<!-- 应用项目配置 -->
<div class="section">
<div class="section-title">应用项目配置(规则推送插件下载区域)</div>
<div class="section-content">
<div class="add-project-btn">
<el-button type="primary" plain @click="addProjectConfig">
<el-icon><Plus /></el-icon>
点击添加应用项目
</el-button>
</div>
<div class="project-config-list">
<el-table :data="projectConfigs" border style="width: 100%">
<el-table-column prop="name" label="项目名称" width="180">
<template #default="{ row, $index }">
<el-input v-model="row.name" placeholder="请输入项目名称" />
</template>
</el-table-column>
<el-table-column prop="url" label="地址">
<template #default="{ row, $index }">
<el-input v-model="row.url" placeholder="请输入地址 https://xxx.xxx.x.xxx" />
</template>
</el-table-column>
<el-table-column prop="port" label="端口" width="120">
<template #default="{ row, $index }">
<el-input v-model="row.port" placeholder="请输入端口 5544" />
</template>
</el-table-column>
<el-table-column label="操作" width="300" align="center">
<template #default="{ row, $index }">
<el-button type="danger" icon="Delete" @click="removeProjectConfig($index)">删除</el-button>
<el-button type="primary" icon="Download" @click="downloadProjectPlugin($index)">下载</el-button>
</template>
</el-table-column>
</el-table>
</div>
</div>
</div>
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="handleConfirm">确定</el-button>
</span>
</template>
</el-dialog>
</template>
<script setup>
import { ref, watch, computed } from 'vue'
import { Download, Plus, Delete } from '@element-plus/icons-vue'
import { ElMessage } from 'element-plus'
const props = defineProps({
visible: {
type: Boolean,
default: false
},
projectId: {
type: String,
required: true
}
})
const emit = defineEmits(['update:visible', 'confirm'])
// 控制弹窗显示
const dialogVisible = computed({
get: () => props.visible,
set: (value) => emit('update:visible', value)
})
// 加密网关配置
const gatewayConfig = ref({
url: '',
port: ''
})
// 应用项目配置列表
const projectConfigs = ref([])
// 根据项目ID获取网关配置
const fetchGatewayConfig = async () => {
// 模拟API调用,实际项目中替换为真实API
return new Promise(resolve => {
setTimeout(() => {
resolve({
url: 'https://172.19.1.166',
port: '9005'
})
}, 300)
})
}
// 添加项目配置
const addProjectConfig = () => {
projectConfigs.value.push({
name: '',
url: '',
port: ''
})
}
// 移除项目配置
const removeProjectConfig = (index) => {
projectConfigs.value.splice(index, 1)
}
// 下载网关插件
const downloadGatewayPlugin = () => {
ElMessage.success('开始下载加密网关插件')
// 实际项目中实现下载逻辑
}
// 下载项目插件
const downloadProjectPlugin = (index) => {
const project = projectConfigs.value[index]
if (!project.name || !project.url || !project.port) {
ElMessage.warning('请先填写完整的项目配置')
return
}
ElMessage.success(`开始下载项目 ${project.name} 的插件`)
// 实际项目中实现下载逻辑
}
// 确认操作
const handleConfirm = () => {
// 验证项目配置
for (const project of projectConfigs.value) {
if (!project.name || !project.url || !project.port) {
ElMessage.warning('请填写完整的项目配置')
return
}
}
emit('confirm', {
projectId: props.projectId,
projectConfigs: projectConfigs.value
})
dialogVisible.value = false
}
// 监听项目ID变化,获取网关配置
watch(() => props.projectId, async (newVal) => {
if (newVal) {
const config = await fetchGatewayConfig()
gatewayConfig.value = config
}
}, { immediate: true })
</script>
<style scoped>
.download-plugin-dialog {
padding: 10px;
}
.section {
margin-bottom: 20px;
}
.section-title {
font-weight: bold;
margin-bottom: 10px;
color: #333;
}
.section-content {
padding: 10px;
background-color: #f9f9f9;
border-radius: 4px;
}
.readonly-input-group {
display: flex;
align-items: center;
margin-bottom: 10px;
}
.input-label {
width: 120px;
text-align: right;
padding-right: 10px;
font-size: 14px;
}
.input-field {
flex: 1;
margin-right: 10px;
}
.port-input {
width: 100px;
}
.add-project-btn {
margin-bottom: 15px;
}
.project-config-list {
border-top: 1px solid #eee;
padding-top: 10px;
}
.project-config-item {
padding: 15px;
margin-bottom: 15px;
background-color: #fff;
border-radius: 4px;
border: 1px solid #eee;
position: relative;
}
.action-buttons {
display: flex;
justify-content: flex-end;
margin-top: 10px;
gap: 10px;
}
:deep(.el-form-item) {
margin-bottom: 10px;
}
</style>
\ No newline at end of file
<template>
<el-dialog
v-model="dialogVisible"
title="导出"
width="500px"
:before-close="handleClose"
>
<div class="export-dialog">
<!-- 树形选择器 -->
<el-tree
ref="treeRef"
:data="treeData"
node-key="id"
show-checkbox
:props="defaultProps"
:default-expand-all="true"
@check="handleNodeCheck"
/>
</div>
<template #footer>
<span class="dialog-footer">
<el-button type="primary" @click="handleBackup">加密规则备份</el-button>
<el-button type="primary" @click="handleConfirm">确定</el-button>
</span>
</template>
</el-dialog>
</template>
<script setup>
import { ref, watch } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
const props = defineProps({
visible: {
type: Boolean,
default: false
},
projectId: {
type: String,
required: true
}
})
const emit = defineEmits(['update:visible', 'confirm', 'backup'])
// 控制弹窗显示
const dialogVisible = computed({
get: () => props.visible,
set: (value) => emit('update:visible', value)
})
// 树形数据
const treeData = ref([])
const treeRef = ref(null)
// 树形配置
const defaultProps = {
children: 'children',
label: 'name'
}
// 加密规则备份
const handleBackup = () => {
emit('backup', props.projectId)
ElMessage.info('加密规则备份功能待实现')
}
// 确认导出
const handleConfirm = () => {
const checkedNodes = treeRef.value.getCheckedNodes()
const checkedKeys = treeRef.value.getCheckedKeys()
if (checkedKeys.length === 0) {
ElMessage.warning('请至少选择一个导出项')
return
}
emit('confirm', {
projectId: props.projectId,
checkedNodes,
checkedKeys
})
dialogVisible.value = false
}
// 节点选中处理
const handleNodeCheck = (nodeData, checkStatus) => {
// 如果选中父节点,自动选中所有子节点
// if (checkStatus.checkedKeys.includes(nodeData.id) && nodeData.children) {
// treeRef.value.setCheckedNodes(nodeData.children, true)
// }
}
// 关闭前处理
const handleClose = (done) => {
done()
}
// 根据项目ID获取树形数据
const fetchTreeData = async (projectId) => {
// 模拟API调用,实际项目中替换为真实API
return new Promise(resolve => {
setTimeout(() => {
resolve({
id: 'root',
name: '若依配测系统',
children: [
{
id: 'ry',
name: 'ry',
children: [
{
id: 'tables',
name: '表',
children: [
{ id: 'gen_table', name: 'gen_table' },
{ id: 'gen_table_column', name: 'gen_table_column' },
{ id: 'sys_config', name: 'sys_config' },
{ id: 'sys_dept', name: 'sys_dept' },
{ id: 'sys_dict_data', name: 'sys_dict_data' },
{ id: 'sys_dict_type', name: 'sys_dict_type' },
{ id: 'sys_job', name: 'sys_job' },
{ id: 'sys_job_log', name: 'sys_job_log' },
{ id: 'sys_logininfor', name: 'sys_logininfor' },
{ id: 'sys_menu', name: 'sys_menu' },
{ id: 'sys_notice', name: 'sys_notice' },
{ id: 'sys_oper_log', name: 'sys_oper_log' },
{ id: 'sys_post', name: 'sys_post' },
{ id: 'sys_role', name: 'sys_role' },
{ id: 'sys_role_dept', name: 'sys_role_dept' },
{ id: 'sys_role_menu', name: 'sys_role_menu' },
{ id: 'sys_user', name: 'sys_user' },
{ id: 'sys_user_online', name: 'sys_user_online' },
{ id: 'sys_user_post', name: 'sys_user_post' },
{ id: 'sys_user_role', name: 'sys_user_role' }
]
}
]
}
]
})
}, 300)
})
}
// 监听项目ID变化,获取树形数据
watch(() => props.projectId, async (newVal) => {
if (newVal) {
const data = await fetchTreeData(newVal)
treeData.value = [data]
}
}, { immediate: true })
</script>
<style scoped>
.export-dialog {
padding: 10px;
}
.dialog-footer {
display: flex;
justify-content: space-between;
}
:deep(.el-tree) {
max-height: 400px;
overflow-y: auto;
}
:deep(.el-tree-node__content) {
height: 36px;
}
</style>
\ No newline at end of file
<template>
<el-dialog
v-model="dialogVisible"
:title="dialogTitle"
:before-close="handleClose"
>
<div class="dialog-content">
<!-- 左侧表单 -->
<div class="form-container">
<!-- 第一步:基本信息 -->
<div v-if="activeStep === 1">
<el-form
ref="basicInfoForm"
:model="formData"
:rules="formRules"
label-width="100px"
label-position="top"
>
<el-form-item label="项目名称" prop="projectName" required>
<el-input
v-model="formData.projectName"
placeholder="请输入项目名称"
maxlength="200"
show-word-limit
/>
</el-form-item>
<el-form-item label="项目备注" prop="remark">
<el-input
v-model="formData.remark"
type="textarea"
:rows="4"
placeholder="请输入项目备注信息"
maxlength="200"
show-word-limit
/>
</el-form-item>
<el-form-item label="项目类型" prop="projectType">
<el-radio-group v-model="formData.projectType">
<el-radio label="normal">普通项目</el-radio>
<el-radio label="udf">UDF项目</el-radio>
</el-radio-group>
</el-form-item>
</el-form>
</div>
<!-- 第二步:数据库配置 -->
<div v-if="activeStep === 2">
<div class="step-title">数据库</div>
<div class="filter-container">
<el-select v-model="dbFilter.type" placeholder="全部" class="filter-select">
<el-option label="全部" value="all" />
<el-option label="MySQL" value="mysql" />
<el-option label="Oracle" value="oracle" />
<el-option label="SQL Server" value="sqlserver" />
</el-select>
<el-input
v-model="dbFilter.keyword"
placeholder="输入数据源名称搜索"
class="filter-input"
clearable
>
<template #prefix>
<el-icon><search /></el-icon>
</template>
</el-input>
</div>
<div class="db-select-container">
<!-- 左侧可选数据库 -->
<div class="db-list">
<div class="db-list-header">
<el-checkbox v-model="selectAll" @change="handleSelectAll">全选</el-checkbox>
</div>
<el-scrollbar height="300px">
<el-checkbox-group v-model="selectedDbs">
<div
v-for="db in filteredDatabases"
:key="db.id"
class="db-item"
>
<el-checkbox :label="db.id">
{{ db.name }}
</el-checkbox>
</div>
</el-checkbox-group>
</el-scrollbar>
</div>
<!-- 右侧已选数据库 -->
<div class="db-list selected">
<div class="db-list-header">
<span>已选择数据源(数据库)</span>
<el-button type="text" @click="handleClearSelected">清空</el-button>
</div>
<el-scrollbar height="300px">
<div
v-for="db in selectedDbDetails"
:key="db.id"
class="selected-db-item"
>
<div class="db-name">{{ db.name }}</div>
<div class="db-ip">{{ db.ip }}</div>
<div class="db-username">用户名: {{ db.username }}</div>
</div>
</el-scrollbar>
</div>
</div>
</div>
<!-- 第三步:Schema选择 -->
<div v-if="activeStep === 3">
<div class="step-title">Schema选择</div>
<div class="schema-container">
<!-- 左侧数据源 -->
<div class="datasource-section">
<div class="section-title">数据源</div>
<el-scrollbar height="400px">
<div class="datasource-list">
<div
v-for="db in selectedDbDetails"
:key="db.id"
class="datasource-item"
:class="{ active: selectedDataSource === db.id }"
@click="selectDataSource(db.id)"
>
{{ db.id }} {{ db.name }}
</div>
</div>
</el-scrollbar>
</div>
<!-- 右侧Schema选择 -->
<div class="schema-section">
<div class="schema-header">
<div class="section-title">SCHEMA</div>
<div class="schema-select-all">
<el-checkbox
v-model="selectAllSchemas"
@change="handleSelectAllSchemas"
>
全选
</el-checkbox>
<span class="schema-count">{{ selectedSchemas.length }}/{{ allSchemas.length }}</span>
</div>
</div>
<el-scrollbar height="400px">
<el-checkbox-group v-model="selectedSchemas">
<div
v-for="schema in allSchemas"
:key="schema"
class="schema-item"
>
<el-checkbox :label="schema">
{{ schema }}
</el-checkbox>
</div>
</el-checkbox-group>
</el-scrollbar>
</div>
</div>
</div>
<!-- 第四步:数据域选择 -->
<div v-if="activeStep === 4">
<div class="step-title">数据域选择</div>
<div class="domain-container">
<!-- 左侧可选择数据域 -->
<div class="available-domains">
<div class="section-header">
<div class="section-title">可选择数据域</div>
</div>
<div class="domain-group" v-for="group in domainGroups" :key="group.name">
<div class="group-header">
<el-checkbox
v-model="group.selectedAll"
@change="(val) => toggleGroupSelection(group, val)"
>
{{ group.name }}
</el-checkbox>
</div>
<el-scrollbar height="300px">
<el-checkbox-group v-model="selectedDomains">
<div
v-for="domain in group.domains"
:key="domain.id"
class="domain-item"
>
<el-checkbox :label="domain.id">
<div class="domain-content">
<div class="domain-name">{{ domain.name }}</div>
<div v-if="domain.desc" class="domain-desc">{{ domain.desc }}</div>
</div>
</el-checkbox>
</div>
</el-checkbox-group>
</el-scrollbar>
</div>
</div>
<!-- 右侧已选择数据域 -->
<div class="selected-domains">
<div class="section-header">
<div class="section-title">已选择数据域</div>
<div class="selection-info">
<span class="count">{{ selectedDomains.length }}</span>
<el-button type="text" @click="clearSelectedDomains">清空</el-button>
</div>
</div>
<el-scrollbar height="400px">
<div
v-for="domain in selectedDomainDetails"
:key="domain.id"
class="selected-domain-item"
>
<div class="domain-name">{{ domain.name }}</div>
<div v-if="domain.desc" class="domain-desc">{{ domain.desc }}</div>
</div>
</el-scrollbar>
</div>
</div>
</div>
</div>
<!-- 右侧步骤条 -->
<div class="steps-container">
<el-steps direction="vertical" :active="activeStep">
<el-step :title="stepTitles[0]" />
<el-step :title="stepTitles[1]" />
<el-step :title="stepTitles[2]" />
<el-step :title="stepTitles[3]" />
</el-steps>
</div>
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="handlePrevStep">上一步</el-button>
<el-button type="primary" @click="handleNextStep">
{{ activeStep === 4 ? '完成' : '下一步' }}
</el-button>
</span>
</template>
</el-dialog>
</template>
<script setup>
import { ref, computed, watch } from 'vue'
import { ElMessageBox } from 'element-plus'
import { Search } from '@element-plus/icons-vue'
const props = defineProps({
visible: {
type: Boolean,
default: false
},
projectData: {
type: Object,
default: () => ({
projectName: '',
remark: '',
projectType: 'normal',
databases: [],
schemas: [],
domains: []
})
},
mode: {
type: String,
default: 'add',
validator: (value) => ['add', 'edit'].includes(value)
}
})
const emit = defineEmits(['update:visible', 'submit'])
// 控制弹窗显示
const dialogVisible = computed({
get: () => props.visible,
set: (value) => emit('update:visible', value)
})
// 表单数据
const formData = ref({
projectName: '',
remark: '',
projectType: 'normal',
databases: [],
schemas: [],
domains: []
})
// 步骤条相关
const activeStep = ref(1)
const stepTitles = ref([
'基本信息',
'数据源配置',
'Schema选择',
'数据域选择'
])
// 弹窗标题
const dialogTitle = computed(() => {
return props.mode === 'add' ? '新增项目' : '编辑项目'
})
// 第二步:数据库配置相关
const dbFilter = ref({
type: 'all',
keyword: ''
})
const allDatabases = ref([
{ id: 1, name: '若依配测系统', ip: '172.19.1.166', username: 'root', type: 'mysql' },
{ id: 2, name: '测试数据库1', ip: '192.168.1.100', username: 'admin', type: 'mysql' },
{ id: 3, name: '生产数据库', ip: '10.0.0.1', username: 'dba', type: 'oracle' },
{ id: 4, name: '开发数据库', ip: '172.16.1.50', username: 'dev', type: 'mysql' },
{ id: 5, name: '备份数据库', ip: '172.16.1.51', username: 'backup', type: 'sqlserver' }
])
const selectedDbs = ref([])
const selectAll = ref(false)
// 第三步:Schema选择相关
const selectedDataSource = ref(null)
const allSchemas = ref([
'information_schema',
'mysql',
'performance_schema',
'ry',
'sys'
])
const selectedSchemas = ref([])
const selectAllSchemas = ref(false)
// 第四步:数据域选择相关
const domainGroups = ref([
{
name: '混合数据域',
selectedAll: false,
domains: [
{ id: 'domain1', name: '566546', desc: '' },
{ id: 'domain2', name: '通用规则', desc: '' },
{ id: 'domain3', name: '解密数据', desc: '' },
{ id: 'domain4', name: '混合证件号', desc: '按混合证件号字段查询' }
]
},
{
name: '智能数据域',
selectedAll: false,
domains: [
{ id: 'domain5', name: '智能推荐', desc: '' },
{ id: 'domain6', name: '电子邮件', desc: '按邮箱字段查找' },
{ id: 'domain7', name: '社保卡号', desc: '' }
]
},
{
name: '测试数据域',
selectedAll: false,
domains: [
{ id: 'domain8', name: '发现规则1', desc: '' },
{ id: 'domain9', name: '营业执照', desc: '按营业执照字段查询' },
{ id: 'domain10', name: '邮政编码', desc: '邮政编码字段查找' }
]
}
])
const selectedDomains = ref(['domain2', 'domain3', 'domain4', 'domain6', 'domain7', 'domain9', 'domain10'])
// 计算属性
const filteredDatabases = computed(() => {
return allDatabases.value.filter(db => {
const typeMatch = dbFilter.value.type === 'all' || db.type === dbFilter.value.type
const keywordMatch = db.name.includes(dbFilter.value.keyword)
return typeMatch && keywordMatch
})
})
const selectedDbDetails = computed(() => {
return allDatabases.value.filter(db => selectedDbs.value.includes(db.id))
})
const selectedDomainDetails = computed(() => {
const allDomains = domainGroups.value.flatMap(group => group.domains)
return allDomains.filter(domain => selectedDomains.value.includes(domain.id))
})
// 方法
const handleSelectAll = (val) => {
if (val) {
selectedDbs.value = filteredDatabases.value.map(db => db.id)
} else {
selectedDbs.value = []
}
}
const handleClearSelected = () => {
selectedDbs.value = []
selectAll.value = false
}
const selectDataSource = (dbId) => {
selectedDataSource.value = dbId
}
const handleSelectAllSchemas = (val) => {
if (val) {
selectedSchemas.value = [...allSchemas.value]
} else {
selectedSchemas.value = []
}
}
const toggleGroupSelection = (group, selected) => {
const groupDomainIds = group.domains.map(d => d.id)
if (selected) {
const newSelected = [...new Set([...selectedDomains.value, ...groupDomainIds])]
selectedDomains.value = newSelected
} else {
selectedDomains.value = selectedDomains.value.filter(id => !groupDomainIds.includes(id))
}
}
const clearSelectedDomains = () => {
selectedDomains.value = []
domainGroups.value.forEach(group => {
group.selectedAll = false
})
}
// 上一步
const handlePrevStep = () => {
if (activeStep.value > 1) {
activeStep.value--
}
}
// 下一步/完成
const handleNextStep = async () => {
if (activeStep.value < 4) {
activeStep.value++
} else {
// 最后一步提交数据
formData.value.databases = selectedDbDetails.value
formData.value.schemas = selectedSchemas.value
formData.value.domains = selectedDomainDetails.value
emit('submit', formData.value)
dialogVisible.value = false
}
}
// 取消
const handleCancel = () => {
dialogVisible.value = false
}
// 关闭前处理
const handleClose = (done) => {
ElMessageBox.confirm('确定要关闭吗?未保存的更改将会丢失', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消'
}).then(() => {
done()
}).catch(() => {
// 取消关闭
})
}
// 监听数据变化
watch(selectedDbs, (newVal) => {
selectAll.value = newVal.length > 0 && newVal.length === filteredDatabases.value.length
})
watch(selectedSchemas, (newVal) => {
selectAllSchemas.value = newVal.length === allSchemas.value.length
}, { deep: true })
watch(selectedDomains, (newVal) => {
domainGroups.value.forEach(group => {
const groupDomainIds = group.domains.map(d => d.id)
group.selectedAll = groupDomainIds.every(id => newVal.includes(id))
})
}, { deep: true })
// 初始化时自动选择第一个数据源
watch(selectedDbDetails, (newVal) => {
if (newVal.length > 0 && !selectedDataSource.value) {
selectedDataSource.value = newVal[0].id
}
}, { immediate: true })
// 监听传入的项目数据变化
watch(() => props.projectData, (newVal) => {
if (props.mode === 'edit') {
formData.value = { ...newVal }
// 初始化选中状态
selectedDbs.value = newVal.databases.map(db => db.id)
selectedSchemas.value = [...newVal.schemas]
selectedDomains.value = newVal.domains.map(domain => domain.id)
}
}, { immediate: true, deep: true })
</script>
<style scoped>
.dialog-content {
display: flex;
min-height: 500px;
}
.form-container {
flex: 1;
padding-right: 30px;
}
.steps-container {
width: 150px;
padding-left: 30px;
border-left: 1px solid #eee;
}
.step-title {
font-size: 16px;
font-weight: bold;
margin-bottom: 20px;
}
/* 第二步样式 */
.filter-container {
display: flex;
margin-bottom: 15px;
gap: 10px;
}
.filter-select {
width: 120px;
}
.filter-input {
flex: 1;
}
.db-select-container {
display: flex;
gap: 20px;
}
.db-list {
flex: 1;
border: 1px solid #ebeef5;
border-radius: 4px;
}
.db-list.selected {
flex: 1;
}
.db-list-header {
padding: 10px 15px;
border-bottom: 1px solid #ebeef5;
display: flex;
justify-content: space-between;
align-items: center;
}
.db-item {
padding: 10px 15px;
border-bottom: 1px solid #f5f5f5;
}
.selected-db-item {
padding: 12px 15px;
border-bottom: 1px solid #f5f5f5;
}
.db-name {
font-weight: bold;
margin-bottom: 5px;
}
.db-ip {
color: #666;
font-size: 13px;
margin-bottom: 3px;
}
.db-username {
color: #999;
font-size: 12px;
}
/* 第三步样式 */
.schema-container {
display: flex;
gap: 20px;
margin-top: 20px;
}
.datasource-section, .schema-section {
flex: 1;
border: 1px solid #ebeef5;
border-radius: 4px;
background-color: #f8f8f8;
}
.section-title {
padding: 10px 15px;
font-weight: bold;
border-bottom: 1px solid #ebeef5;
background-color: #f5f5f5;
}
.datasource-list {
padding: 5px 0;
}
.datasource-item {
padding: 10px 15px;
cursor: pointer;
border-bottom: 1px solid #f0f0f0;
}
.datasource-item:hover {
background-color: #f0f7ff;
}
.datasource-item.active {
background-color: #e6f7ff;
color: #1890ff;
}
.schema-header {
display: flex;
justify-content: space-between;
align-items: center;
padding-right: 15px;
}
.schema-select-all {
display: flex;
align-items: center;
gap: 10px;
}
.schema-count {
color: #999;
font-size: 13px;
}
.schema-item {
padding: 10px 15px;
border-bottom: 1px solid #f0f0f0;
}
/* 第四步样式 */
.domain-container {
display: flex;
gap: 20px;
margin-top: 20px;
}
.available-domains, .selected-domains {
flex: 1;
border: 1px solid #ebeef5;
border-radius: 4px;
background-color: #f8f8f8;
}
.section-header {
padding: 10px 15px;
border-bottom: 1px solid #ebeef5;
background-color: #f5f5f5;
display: flex;
justify-content: space-between;
align-items: center;
}
.section-title {
font-weight: bold;
}
.selection-info {
display: flex;
align-items: center;
gap: 10px;
}
.count {
color: #1890ff;
font-weight: bold;
}
.domain-group {
margin-bottom: 20px;
}
.group-header {
padding: 10px 15px;
background-color: #f0f0f0;
border-bottom: 1px solid #e8e8e8;
}
.domain-item {
padding: 12px 15px;
border-bottom: 1px solid #f0f0f0;
}
.domain-content {
margin-left: 8px;
}
.domain-name {
font-weight: 500;
}
.domain-desc {
color: #999;
font-size: 12px;
margin-top: 4px;
}
.selected-domain-item {
padding: 12px 15px;
border-bottom: 1px solid #f0f0f0;
}
.selected-domain-item .domain-name {
font-weight: bold;
}
.selected-domain-item .domain-desc {
color: #666;
font-size: 13px;
margin-top: 4px;
}
.dialog-footer {
display: flex;
justify-content: flex-end;
gap: 10px;
}
:deep(.el-step__title) {
font-size: 14px;
}
:deep(.el-form-item__label) {
font-weight: bold;
padding-bottom: 8px;
}
</style>
\ No newline at end of file
<script setup name="ProjectManageList">
import { getCurrentInstance, reactive, ref, toRefs } from 'vue'
import { ElMessage } from 'element-plus'
import QueryForm from './QueryForm.vue'
import ProjectEditDialog from './ProjectEditDialog.vue'
import DownloadPluginDialog from './DownloadPluginDialog.vue'
import ExportDialog from './ExportDialog.vue'
import { useRouter } from 'vue-router'
const router = useRouter()
const emit = defineEmits(['page'])
const { proxy } = getCurrentInstance()
function onReset(formQuery) {
console.log('onReset')
formQuery.resetFields()
handleQuery()
}
function onQuery() {
handleQuery()
}
// 搜索按钮操作
function handleQuery() {
queryParams.value.pageNum = 1
getList()
}
const data = reactive({
queryParams: {
pageNum: 1,
pageSize: 8,
projectName: '',
remark: ''
}
})
// 表格数据
const { queryParams } = toRefs(data)
const tableList = ref([
{
id: '1',
projectName: '若依配测系统1',
datasource: 'MySQL',
domain: '电商',
findRule: '自动发现'
},
{
id: '2',
projectName: '若依配测系统2',
datasource: 'Oracle',
domain: '金融',
findRule: '手动配置'
},
{
id: '3',
projectName: '若依配测系统3',
datasource: 'SQL Server',
domain: '医疗',
findRule: '规则匹配'
}
])
const total = ref(3)
const loading = ref(false)
// 查询列表
function getList() {
loading.value = true
setTimeout(() => {
loading.value = false
}, 3000);
}
// 弹窗相关
const dialogVisible = ref(false)
const dialogMode = ref('add')
const currentProject = ref(null)
// 显示新增对话框
const showAddDialog = () => {
dialogMode.value = 'add'
currentProject.value = null
dialogVisible.value = true
}
// 处理项目操作
const handleCommandProject = (command, project) => {
switch(command) {
case '1': // 进入
handleEnterProject(project)
break
case '2': // 编辑
handleEditProject(project)
break
case '3': // 删除
handleDeleteProject(project)
break
case '4': // 导入
handleImportProject(project)
break
case '5': // 导出
handleExportProject(project)
break
case '6': // 下载插件
handleDownloadPlugin(project)
break
default:
console.warn('未知命令:', command)
}
}
// 具体操作方法
const handleEnterProject = (project) => {
console.log('进入项目:', project)
// // ElMessage.success(`进入项目 ${project.projectName}`)
// emit('page', 'detail', { projectId: project.id})
router.push({ path:'/projectHome',query: { projectId: project.id } })
}
const handleEditProject = (project) => {
dialogMode.value = 'edit'
currentProject.value = { ...project }
dialogVisible.value = true
}
// 删除项目
const handleDeleteProject = (project) => {
}
// 导入项目
const handleImportProject = (project) => {
ElMessage.info('导入功能待实现')
}
// 导出项目
const handleExportProject = (project) => {
// ElMessage.success(`开始导出项目 "${project.projectName}"`)
openExportDialog(project)
}
// 下载插件
const handleDownloadPlugin = (project) => {
// ElMessage.success(`开始下载 "${project.projectName}" 的插件`)
currentProjectId.value = project.id // 假设项目对象中有id字段
downloadDialogVisible.value = true
console.log(123)
}
// 处理确认操作
const handleDownloadConfirm = (data) => {
console.log('确认下载插件:', data)
// 这里可以处理保存操作或调用API
}
// 提交表单
const handleSubmit = (formData) => {
if (dialogMode.value === 'add') {
// 模拟新增
const newProject = {
id: tableList.value.length + 1,
projectName: formData.projectName,
datasource: '待配置',
domain: '待配置',
findRule: '待配置'
}
tableList.value.push(newProject)
total.value++
ElMessage.success('新增项目成功')
} else {
// 模拟编辑
const index = tableList.value.findIndex(p => p.id === currentProject.value.id)
if (index !== -1) {
tableList.value[index].projectName = formData.projectName
ElMessage.success('编辑项目成功')
}
}
}
const downloadDialogVisible = ref(false)
const currentProjectId = ref('')
const exportDialogVisible = ref(false)
const currentExportProjectId = ref('')
// 打开导出弹窗
const openExportDialog = (project) => {
currentExportProjectId.value = project.id
exportDialogVisible.value = true
}
// 处理导出确认
const handleExportConfirm = (data) => {
console.log('导出数据:', data)
}
// 处理加密规则备份
const handleBackup = (projectId) => {
console.log('备份项目:', projectId)
}
</script>
<template>
<div class="app-container scroller">
<PageTitle @back="$emit('page', 'list')">
<template #title>
项目管理
</template>
<template #buttons>
<el-button
type="primary"
icon="Plus"
@click="showAddDialog"
>
新增
</el-button>
</template>
</PageTitle>
<div class="app-container__body">
<div>
<query-form
ref="QueryFormRef"
v-model="queryParams"
@query="onQuery"
@reset="onReset"/>
<el-row :gutter="20">
<el-col class="table-item" :span="6" v-for="(item, index) in tableList" :key="index">
<el-card shadow="always">
<template #header>
<div class="flex-container justify-between align-center">
{{ item.projectName }}
<el-dropdown placement="bottom-end" @command="(command) => handleCommandProject(command, item)">
<el-button type="primary" icon="Operation">
操作
</el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item command="1">进入</el-dropdown-item>
<el-dropdown-item command="2">编辑</el-dropdown-item>
<el-dropdown-item command="3">删除</el-dropdown-item>
<el-dropdown-item command="4">导入</el-dropdown-item>
<el-dropdown-item command="5">导出</el-dropdown-item>
<el-dropdown-item command="6">下载插件</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</template>
<div class="item-content flex-container align-center">
<div class="label">数据源:</div>
<div class="value flex1">{{ item.datasource }}</div>
</div>
<div class="item-content flex-container align-center">
<div class="label">数据域:</div>
<div class="value flex1">{{ item.domain }}</div>
</div>
<div class="item-content flex-container align-center">
<div class="label">发现规则:</div>
<div class="value flex1">{{ item.findRule }}</div>
</div>
</el-card>
</el-col>
</el-row>
<pagination
v-show="total > 0"
:total="total"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</div>
</div>
<ProjectEditDialog
v-model:visible="dialogVisible"
:mode="dialogMode"
:project-data="currentProject"
@submit="handleSubmit"
/>
<DownloadPluginDialog
v-model:visible="downloadDialogVisible"
:project-id="currentProjectId"
@confirm="handleDownloadConfirm"
/>
<ExportDialog
v-model:visible="exportDialogVisible"
:project-id="currentExportProjectId"
@confirm="handleExportConfirm"
@backup="handleBackup"
/>
</div>
</template>
<style lang="scss" scoped>
.table-item {
margin-top: var(--container-pd);
margin-bottom: 20px;
}
.item-content {
margin: 8px 0;
.label {
width: 80px;
color: #888;
}
}
.flex-container {
display: flex;
}
.align-center {
align-items: center;
}
.justify-between {
justify-content: space-between;
}
.flex1 {
flex: 1;
}
</style>
\ No newline at end of file
<script setup name="ProjectManageList">
import { getCurrentInstance, reactive, ref, toRefs } 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 AddDiscoveryDialog from './modules/AddDiscoveryDialog.vue' // 引入新增发现任务弹窗
const appStore = useAppStore()
const permissionStore = usePermissionStore()
const router = useRouter()
const emit = defineEmits(['page'])
const { proxy } = getCurrentInstance()
function pageProjectManage() {
changeRoute()
router.push({
path: '/project/Project'
})
}
</script>
<template>
<div class="app-container scroller">
<PageTitle :back="true" @back="pageProjectManage">
<template #title>
返回项目管理
</template>
</PageTitle>
<div class="app-container__body">
<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;
}
.flex-wrap {
flex-direction: wrap;
}
</style>
\ No newline at end of file
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论