Commit 7289be62 by ningjihai

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

parent 6f7d2487
...@@ -146,6 +146,7 @@ const toggleStatus = (row) => { ...@@ -146,6 +146,7 @@ const toggleStatus = (row) => {
</template> </template>
<template #buttons> <template #buttons>
<el-button <el-button
icon="Plus"
type="primary" type="primary"
@click="handleAdd" @click="handleAdd"
> >
......
...@@ -34,8 +34,7 @@ function onSearch() { ...@@ -34,8 +34,7 @@ function onSearch() {
// 重置 // 重置
function onReset(formRef: FormInstance) { function onReset(formRef: FormInstance) {
queryForm.value.projectName = '' queryForm.value.title = ''
queryForm.value.remark = ''
emit('reset', formRef) emit('reset', formRef)
} }
...@@ -49,20 +48,18 @@ function onReset(formRef: FormInstance) { ...@@ -49,20 +48,18 @@ function onReset(formRef: FormInstance) {
:model="queryForm" :model="queryForm"
@search="onSearch" @search="onSearch"
@reset="onReset"> @reset="onReset">
<el-form-item label="项目名称" prop="projectName"> <el-form-item label="发现任务名称" prop="title">
<el-input <el-input
v-model="queryForm.projectName" v-model="queryForm.title"
placeholder="请输入项目名称" placeholder="请输入发现任务名称"
clearable clearable
/>
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input
v-model="queryForm.remark"
placeholder="请输入备注"
clearable
/> />
</el-form-item> </el-form-item>
</page-wrapper-search> </page-wrapper-search>
</template> </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 lang="ts" name="projectManageIndex"> <script setup lang="ts" name="projectManageIndex">
import { ref } from 'vue' import { ref } from 'vue'
import list from './list.vue' import list from './list.vue'
import discoverProcess from './discoverProcess.vue'
import discoverResult from './discoverResult.vue'
const widget = { const widget = {
list: list list: list,
discoverProcess: discoverProcess,
discoverResult: discoverResult
} }
const page = ref('list') const page = ref('list')
......
<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="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
<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 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论