Commit 15ab1ab3 by 周海峰

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

parents 32dc9cfc 50b958db
......@@ -34,6 +34,12 @@ const usePermissionStore = defineStore(
console.log('this.sidebarRoutersold',routes)
let list = filterAsyncRouter(routes)
list.forEach(item => {
router.addRoute(item)
});
this.sidebarRouters = list
console.log('this.sidebarRouters',this.sidebarRouters)
},
......
......@@ -121,7 +121,7 @@ function handleLogin() {
let data = res
let menus = transformRoutes(data.menus)
console.log(1234,menus)
// return
// 用户数据整理
let user = data.tsysUser
......@@ -207,8 +207,8 @@ function transformRoutes(originalRoutes) {
return originalRoutes.map(route => {
// 基础转换 - 所有路由都使用Layout作为父组件
const transformedRoute = {
path: route.url.replace(/^\//, ''),
name: route.menuname.replace(/\s+/g, ''),
path: route.url,
name: route.menuname.replace(/\s+/g, '')+ new Date().getTime(),
hidden: false,
type: route.type,
component: 'Layout', // 所有路由都使用Layout
......
<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 lang="ts" name="QueryForm">
import { computed,ref,watch } from 'vue'
import type { FormInstance } from 'element-plus'
import PageWrapperSearch from '@/components/search/PageWrapperSearch.vue'
// import { useDict } from '@/utils/dict'
// import { listDept } from '@/api/system/dept'// 部门
// const { approve_status, invoice_status} = useDict('approve_status', 'invoice_status')
const emit = defineEmits(['update:modelValue', 'query', 'reset'])
const invoice_status_filter = ref([])
const employeesList = ref([])
const props = defineProps<{
modelValue: any
}>()
const queryForm = computed({
get() {
return props.modelValue
},
set(val: any) {
console.log('query computed', val)
emit('update:modelValue', val)
}
})
// 搜索
function onSearch() {
emit('query')
}
// 重置
function onReset(formRef: FormInstance) {
queryForm.value.projectName = ''
queryForm.value.remark = ''
emit('reset', formRef)
}
</script>
<template>
<!-- el-form -->
<page-wrapper-search
:model="queryForm"
@search="onSearch"
@reset="onReset">
<el-form-item label="项目名称" prop="projectName">
<el-input
v-model="queryForm.projectName"
placeholder="请输入项目名称"
clearable
/>
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input
v-model="queryForm.remark"
placeholder="请输入备注"
clearable
/>
</el-form-item>
</page-wrapper-search>
</template>
<style scoped>
</style>
<script setup lang="ts" name="projectManageIndex">
import { ref } from 'vue'
import list from './list.vue'
const widget = {
list: list
}
const page = ref('list')
const params = ref({})
function onChangePage(val: string, param?: any) {
page.value = val
params.value = param ?? {}
}
</script>
<template>
<component :is="widget[page]" v-bind="params" @page="onChangePage" />
</template>
<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
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论