Commit 83864ab4 by ningjihai

项目管理 和项目首页

parent 6a39fb68
......@@ -63,6 +63,16 @@ export const constantRoutes = [
hidden: true
},
{
path: '/projectManage',
component: () => import('@/views/projectManage/index'),
hidden: true
},
{
path: '/projectHome',
component: () => import('@/views/projectHome/index'),
hidden: true
},
{
path: '',
component: Layout,
redirect: '/index',
......
<script setup name="ProjectHome">
import { ref, toRefs, reactive, getCurrentInstance, proxyRefs, onMounted } from 'vue'
import { useDict } from '@/utils/dict'
import biao from '@/assets/images/project/biao.png'
import jiamibiao from '@/assets/images/project/jiamibiao.png'
import jiamiziduan from '@/assets/images/project/jiamiziduan.png'
import shujuyuan from '@/assets/images/project/shujuyuan.png'
import yingyong from '@/assets/images/project/yingyong.png'
import ziduan from '@/assets/images/project/ziduan.png'
import cpu from '@/assets/images/project/cpu.png'
import neicun from '@/assets/images/project/neicun.png'
import rateChart from './modules/rateChart.vue'
import DiskUsageChart from './modules/DiskUsageChart.vue'
import { useRouter } from 'vue-router'
const router = useRouter()
const { proxy } = getCurrentInstance()
// const { eo_province, eo_city, eo_district, invoice_type, tax_rate, plan_type, invoice_status } = useDict('eo_province', 'eo_city', 'eo_district', 'invoice_type', 'tax_rate', 'plan_type', 'invoice_status')
const detailLoading = ref(false)
function getInfo() {
detailLoading.value = true
// console.log(props.projectId)
setTimeout(() => {
detailLoading.value = false
}, 3000);
}
const projectId = ref('')
onMounted(()=>{
projectId.value =proxy.$route.query.projectId
console.log('projectId',projectId.value)
})
// const props = defineProps({
// projectId: String, // 主键
// })
const stats = ref([
{ label: '应用', value: 1,url: yingyong },
{ label: '数据源', value: 10, url: shujuyuan},
{ label: '表', value: 340, url:biao },
{ label: '字段', value: 4043, url: ziduan},
{ label: '加密表数量', value: 0, url: jiamibiao},
{ label: '加密字段数量', value: 0, url:jiamiziduan }
])
const cpuData = reactive({
cpuUsage: 0,
value: 8
})
const neicunData = reactive({
cpuUsage: 77,
value: 16
})
const diskData = ref([
{ name: '/run/user/0', total: 100, used: 10 },
{ name: '/boot/efi', total: 100, used: 20 },
{ name: '/boot', total: 100, used: 30 },
{ name: '/run/lock', total: 100, used: 5 },
{ name: '/dev/shm', total: 100, used: 15 },
{ name: '/', total: 100, used: 90 },
{ name: '/run', total: 100, used: 25 }
])
getInfo()
function pageProjectManage() {
router.push({
path: '/projectManage'
})
}
defineExpose({
// handleRedInk,
// handleVoid
})
</script>
<template>
<div class="app-container scroller">
<PageTitle :back="true" @back="pageProjectManage" >
<template #title>
返回项目管理
</template>
</PageTitle>
<div class="app-container__body">
<el-card class="image-card">
<template #header>
<div class="card-header">
<span>数据统计</span>
</div>
</template>
<div class="card-content">
<!-- <img src="您的图片路径" alt="数据统计" /> -->
<el-row :gutter="20">
<el-col :span="4" v-for="(item,index) in stats">
<div class="stats-content">
<img class="stats-img" :src="item.url" alt="">
<div class="stats-title">{{item.label}}</div>
<div class="stats-value">{{ item.value }}</div>
</div>
</el-col>
</el-row>
</div>
</el-card>
<el-card class="image-card">
<template #header>
<div class="card-header">
<span>系统资源</span>
</div>
</template>
<div class="card-content">
<!-- <img src="您的图片路径" alt="数据统计" /> -->
<el-row :gutter="20">
<el-col :span="8">
<div class="chart-content">
<img class="chart-logo" :src="cpu" alt="">
<div class="chart-text">
<div class="value color-blue">{{ cpuData.value }}</div>
<div class="unit color-blue"></div>
</div>
<div class="chart-title">
cpu
</div>
<div class="chart-container">
<rateChart :usage="cpuData.cpuUsage" color="#1890ff"/>
</div>
</div>
</el-col>
<el-col :span="8">
<div class="chart-content">
<img class="chart-logo" :src="neicun" alt="">
<div class="chart-text">
<div class="value color-yellowred">{{ neicunData.value }}</div>
<div class="unit color-yellowred">G</div>
</div>
<div class="chart-title">
内存
</div>
<div class="chart-container">
<rateChart :usage="neicunData.cpuUsage" color="#fa7257"/>
</div>
</div>
</el-col>
<el-col :span="8">
<div class="chart-content">
<div class="chart-container">
<DiskUsageChart :disk-data="diskData" />
</div>
</div>
</el-col>
</el-row>
</div>
</el-card>
</div>
</div>
</template>
<style lang="scss" scoped>
.image-card {
width: 100%;
// max-width: 800px;
margin: 20px auto;
}
.card-header {
font-size: 18px;
font-weight: bold;
padding: 15px;
}
.card-content {
padding: 40px 0;
.stats-content {
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
.stats-img {
height: 64px;
width: 64px;
}
.stats-title {
font-size: 16px;
color: #999;
margin-top: 20px;
}
.stats-value {
font-size: 20px;
font-weight: bold;
color: #333;
margin-top: 20px;
}
}
.chart-content {
height: 300px;
display: flex;
flex-direction: column;
align-items: center;
.chart-logo {
height: 64px;
width: 64px;
}
.chart-text {
margin-top: 10px;
display: flex;
align-items: flex-end;
.value {
font-size: 20px;
font-weight: bold;
}
.unit{
font-size: 14px;
margin-left: 2px;
}
}
.chart-title {
font-size: 14px;
color: #999;
margin-top: 5px;
}
.chart-container {
flex: 1;
width: 100%;
}
}
}
.color-blue{
color: #1890ff;
}
.color-yellowred {
color: #fa7257;
}
</style>
<template>
<div class="disk-chart" ref="chartRef"></div>
</template>
<script setup>
import { ref, onMounted, onBeforeUnmount, watch } from 'vue'
import * as echarts from 'echarts'
const colorPalette = [
'#FF6384', '#36A2EB', '#FFCE56', '#4BC0C0', '#9966FF',
'#FF9F40', '#8AC24A', '#F06292', '#7986CB', '#E57373',
'#BA68C8', '#64B5F6', '#4DB6AC', '#81C784', '#FFD54F',
'#FF8A65', '#A1887F', '#90A4AE', '#9575CD', '#4DD0E1'
]
const props = defineProps({
diskData: {
type: Array,
default: () => [
{ name: '/run/user/0', total: 100, used: 10 },
{ name: '/boot/efi', total: 100, used: 20 },
{ name: '/boot', total: 100, used: 30 },
{ name: '/run/lock', total: 100, used: 5 },
{ name: '/dev/shm', total: 100, used: 15 },
{ name: '/', total: 100, used: 90 },
{ name: '/run', total: 100, used: 25 }
]
}
})
const getDiskColor = (index) => {
return colorPalette[index % colorPalette.length]
}
const chartRef = ref(null)
let chartInstance = null
const initChart = () => {
if (!chartRef.value) return
chartInstance = echarts.init(chartRef.value)
const option = {
title: {
text: '磁盘空间',
left: 'center',
textStyle: {
color: '#333',
fontSize: 16,
fontWeight: 'bold'
}
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow'
},
formatter: function(params) {
return `${params[0].name}<br/>
已使用: ${params[0].value}G<br/>
总容量: ${params[1].value}G`
}
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: {
type: 'value',
boundaryGap: [0, 0.01],
axisLabel: {
formatter: '{value}G'
},
max: 100,
splitLine: {
show: true,
lineStyle: {
color: '#eee'
}
}
},
yAxis: {
type: 'category',
data: props.diskData.map(item => item.name),
axisLine: {
show: true,
lineStyle: {
color: '#999'
}
},
axisTick: {
show: false
}
},
series: [
{
name: '总容量',
type: 'bar',
data: props.diskData.map(item => item.total),
itemStyle: {
color: '#f5f5f5', // 浅灰色表示总容量
borderWidth: 1,
borderColor: '#d9d9d9'
},
barGap: '-100%', // 让总容量条在已使用条后面
silent: true, // 不响应鼠标事件
label: {
show: false
},
barWidth: '60%'
},
{
name: '已使用',
type: 'bar',
data: props.diskData.map((item, index) => ({
value: item.used,
itemStyle: { color: getDiskColor(index) }
})),
label: {
show: true,
position: 'right',
formatter: '{c}G',
color: '#1890ff',
fontWeight: 'bold'
},
barWidth: '60%'
},
]
}
chartInstance.setOption(option)
}
// 监听窗口变化自动调整图表大小
const resizeHandler = () => {
chartInstance?.resize()
}
// 监听props变化更新图表
watch(() => props.diskData, (newVal) => {
if (chartInstance) {
chartInstance.setOption({
yAxis: {
data: newVal.map(item => item.name)
},
series: [
{
data: newVal.map(item => item.used)
},
{
data: newVal.map(item => item.total)
}
]
})
}
}, { deep: true })
onMounted(() => {
initChart()
window.addEventListener('resize', resizeHandler)
})
onBeforeUnmount(() => {
window.removeEventListener('resize', resizeHandler)
chartInstance?.dispose()
})
</script>
<style scoped>
.disk-chart {
width: 100%;
height: 100%;
background-color: white;
border: 1px solid #f0f0f0;
border-radius: 4px;
padding: 10px;
}
</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 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:'/project',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 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论