|
@@ -0,0 +1,466 @@
|
|
|
|
+<template>
|
|
|
|
+ <div class="page">
|
|
|
|
+ <el-card shadow="never">
|
|
|
|
+ <template #header>
|
|
|
|
+ <div class="card-header">
|
|
|
|
+ <span>数据源管理</span>
|
|
|
|
+ </div>
|
|
|
|
+ </template>
|
|
|
|
+
|
|
|
|
+ <!-- 搜索表单 -->
|
|
|
|
+ <el-form :model="params" inline ref="queryRef">
|
|
|
|
+ <el-form-item label="数据源名称" prop="keyWord">
|
|
|
|
+ <el-input v-model="params.keyWord" placeholder="请输入数据源名称" clearable style="width: 200px" @keyup.enter.native="getList(1)" />
|
|
|
|
+ </el-form-item>
|
|
|
|
+ <el-form-item label="日期范围" prop="dateRange">
|
|
|
|
+ <el-date-picker
|
|
|
|
+ v-model="params.dateRange"
|
|
|
|
+ type="daterange"
|
|
|
|
+ range-separator="至"
|
|
|
|
+ start-placeholder="开始日期"
|
|
|
|
+ end-placeholder="结束日期"
|
|
|
|
+ value-format="YYYY-MM-DD"
|
|
|
|
+ style="width: 240px"
|
|
|
|
+ />
|
|
|
|
+ </el-form-item>
|
|
|
|
+ <el-form-item>
|
|
|
|
+ <el-button type="primary" @click="getList(1)">
|
|
|
|
+ <el-icon><ele-Search /></el-icon>查询
|
|
|
|
+ </el-button>
|
|
|
|
+ <el-button @click="resetQuery">
|
|
|
|
+ <el-icon><ele-Refresh /></el-icon>重置
|
|
|
|
+ </el-button>
|
|
|
|
+ <el-button type="primary" @click="addOrEdit()" v-auth="'add_datasource'">
|
|
|
|
+ <el-icon><ele-Plus /></el-icon>新增
|
|
|
|
+ </el-button>
|
|
|
|
+ </el-form-item>
|
|
|
|
+ </el-form>
|
|
|
|
+
|
|
|
|
+ <!-- 数据表格 -->
|
|
|
|
+ <el-table :data="tableData" style="width: 100%" v-loading="loading" row-key="id">
|
|
|
|
+ <el-table-column type="selection" width="55" align="center" />
|
|
|
|
+ <el-table-column prop="id" label="ID" width="80" align="center" />
|
|
|
|
+ <el-table-column prop="name" label="数据源名称" min-width="120" show-overflow-tooltip />
|
|
|
|
+ <el-table-column prop="type" label="数据库类型" width="120" />
|
|
|
|
+ <el-table-column prop="host" label="主机地址" min-width="120" show-overflow-tooltip />
|
|
|
|
+ <el-table-column prop="port" label="端口" width="80" align="center" />
|
|
|
|
+ <el-table-column prop="database" label="数据库名" min-width="120" show-overflow-tooltip />
|
|
|
|
+ <el-table-column prop="remark" label="备注" min-width="150" show-overflow-tooltip />
|
|
|
|
+ <el-table-column prop="createdAt" label="创建时间" width="160" />
|
|
|
|
+ <el-table-column label="操作" width="200" fixed="right">
|
|
|
|
+ <template #default="{ row }">
|
|
|
|
+ <el-button type="primary" link @click="testConnection(row)" v-auth="'test_datasource'">
|
|
|
|
+ 测试连接
|
|
|
|
+ </el-button>
|
|
|
|
+ <el-button type="primary" link @click="addOrEdit(row)" v-auth="'edit_datasource'">
|
|
|
|
+ 编辑
|
|
|
|
+ </el-button>
|
|
|
|
+ <el-button type="danger" link @click="deleteDataSource(row)" v-auth="'delete_datasource'">
|
|
|
|
+ 删除
|
|
|
|
+ </el-button>
|
|
|
|
+ </template>
|
|
|
|
+ </el-table-column>
|
|
|
|
+ </el-table>
|
|
|
|
+
|
|
|
|
+ <!-- 分页 -->
|
|
|
|
+ <div class="pagination-container">
|
|
|
|
+ <el-pagination
|
|
|
|
+ v-model:current-page="params.pageNum"
|
|
|
|
+ v-model:page-size="params.pageSize"
|
|
|
|
+ :page-sizes="[10, 20, 50, 100]"
|
|
|
|
+ layout="total, sizes, prev, pager, next, jumper"
|
|
|
|
+ :total="total"
|
|
|
|
+ @size-change="getList()"
|
|
|
|
+ @current-change="getList()"
|
|
|
|
+ />
|
|
|
|
+ </div>
|
|
|
|
+ </el-card>
|
|
|
|
+
|
|
|
|
+ <!-- 数据源表单对话框 -->
|
|
|
|
+ <el-dialog
|
|
|
|
+ v-model="dialogVisible"
|
|
|
|
+ :title="formData.id ? '编辑数据源' : '新增数据源'"
|
|
|
|
+ width="600px"
|
|
|
|
+ :close-on-click-modal="false"
|
|
|
|
+ :close-on-press-escape="false"
|
|
|
|
+ >
|
|
|
|
+ <el-form ref="formRef" :model="formData" :rules="rules" label-width="120px">
|
|
|
|
+ <el-form-item label="数据源名称" prop="name">
|
|
|
|
+ <el-input v-model="formData.name" placeholder="请输入数据源名称" />
|
|
|
|
+ </el-form-item>
|
|
|
|
+ <el-form-item label="数据库类型" prop="type">
|
|
|
|
+ <el-select v-model="formData.type" placeholder="请选择数据库类型" style="width: 100%">
|
|
|
|
+ <el-option label="MySQL" value="MySQL" />
|
|
|
|
+ <el-option label="PostgreSQL" value="PostgreSQL" />
|
|
|
|
+ <el-option label="SQL Server" value="SQL Server" />
|
|
|
|
+ </el-select>
|
|
|
|
+ </el-form-item>
|
|
|
|
+ <el-form-item label="主机地址" prop="host">
|
|
|
|
+ <el-input v-model="formData.host" placeholder="请输入主机地址" />
|
|
|
|
+ </el-form-item>
|
|
|
|
+ <el-form-item label="端口" prop="port">
|
|
|
|
+ <el-input-number v-model="formData.port" :min="1" :max="65535" style="width: 100%" />
|
|
|
|
+ </el-form-item>
|
|
|
|
+ <el-form-item label="用户名" prop="username">
|
|
|
|
+ <el-input v-model="formData.username" placeholder="请输入用户名" />
|
|
|
|
+ </el-form-item>
|
|
|
|
+ <el-form-item label="密码" prop="password">
|
|
|
|
+ <el-input v-model="formData.password" type="password" placeholder="请输入密码" show-password />
|
|
|
|
+ <div class="form-tip" v-if="formData.id">不修改请留空</div>
|
|
|
|
+ </el-form-item>
|
|
|
|
+ <el-form-item label="数据库名" prop="database">
|
|
|
|
+ <el-input v-model="formData.database" placeholder="请输入数据库名" />
|
|
|
|
+ </el-form-item>
|
|
|
|
+ <el-form-item label="最大打开连接数" prop="maxOpenConns">
|
|
|
|
+ <el-input-number v-model="formData.maxOpenConns" :min="1" style="width: 100%" />
|
|
|
|
+ </el-form-item>
|
|
|
|
+ <el-form-item label="最大空闲连接数" prop="maxIdleConns">
|
|
|
|
+ <el-input-number v-model="formData.maxIdleConns" :min="1" style="width: 100%" />
|
|
|
|
+ </el-form-item>
|
|
|
|
+ <el-form-item label="连接最大生存时间" prop="connMaxLifetime">
|
|
|
|
+ <el-input-number v-model="formData.connMaxLifetime" :min="1" style="width: 100%" />
|
|
|
|
+ <div class="form-tip">单位:秒</div>
|
|
|
|
+ </el-form-item>
|
|
|
|
+ <el-form-item label="备注" prop="remark">
|
|
|
|
+ <el-input v-model="formData.remark" type="textarea" :rows="3" placeholder="请输入备注" />
|
|
|
|
+ </el-form-item>
|
|
|
|
+ </el-form>
|
|
|
|
+ <template #footer>
|
|
|
|
+ <div class="dialog-footer">
|
|
|
|
+ <el-button @click="dialogVisible = false">取消</el-button>
|
|
|
|
+ <el-button type="primary" @click="submitForm">确定</el-button>
|
|
|
|
+ <el-button type="success" @click="testConnectionForm" v-if="!formData.id">测试连接</el-button>
|
|
|
|
+ </div>
|
|
|
|
+ </template>
|
|
|
|
+ </el-dialog>
|
|
|
|
+
|
|
|
|
+ <!-- 测试连接对话框 -->
|
|
|
|
+ <el-dialog
|
|
|
|
+ v-model="testDialogVisible"
|
|
|
|
+ title="测试数据源连接"
|
|
|
|
+ width="500px"
|
|
|
|
+ >
|
|
|
|
+ <el-form ref="testFormRef" :model="testFormData" :rules="testRules" label-width="120px">
|
|
|
|
+ <el-form-item label="数据库类型" prop="type">
|
|
|
|
+ <el-select v-model="testFormData.type" placeholder="请选择数据库类型" style="width: 100%">
|
|
|
|
+ <el-option label="MySQL" value="MySQL" />
|
|
|
|
+ <el-option label="PostgreSQL" value="PostgreSQL" />
|
|
|
|
+ <el-option label="SQL Server" value="SQL Server" />
|
|
|
|
+ </el-select>
|
|
|
|
+ </el-form-item>
|
|
|
|
+ <el-form-item label="主机地址" prop="host">
|
|
|
|
+ <el-input v-model="testFormData.host" placeholder="请输入主机地址" />
|
|
|
|
+ </el-form-item>
|
|
|
|
+ <el-form-item label="端口" prop="port">
|
|
|
|
+ <el-input-number v-model="testFormData.port" :min="1" :max="65535" style="width: 100%" />
|
|
|
|
+ </el-form-item>
|
|
|
|
+ <el-form-item label="用户名" prop="username">
|
|
|
|
+ <el-input v-model="testFormData.username" placeholder="请输入用户名" />
|
|
|
|
+ </el-form-item>
|
|
|
|
+ <el-form-item label="密码" prop="password">
|
|
|
|
+ <el-input v-model="testFormData.password" type="password" placeholder="请输入密码" show-password />
|
|
|
|
+ </el-form-item>
|
|
|
|
+ <el-form-item label="数据库名" prop="database">
|
|
|
|
+ <el-input v-model="testFormData.database" placeholder="请输入数据库名" />
|
|
|
|
+ </el-form-item>
|
|
|
|
+ </el-form>
|
|
|
|
+ <template #footer>
|
|
|
|
+ <div class="dialog-footer">
|
|
|
|
+ <el-button @click="testDialogVisible = false">取消</el-button>
|
|
|
|
+ <el-button type="primary" @click="submitTestConnection">测试连接</el-button>
|
|
|
|
+ </div>
|
|
|
|
+ </template>
|
|
|
|
+ </el-dialog>
|
|
|
|
+ </div>
|
|
|
|
+</template>
|
|
|
|
+
|
|
|
|
+<script lang="ts" setup>
|
|
|
|
+import { ref, reactive, onMounted } from 'vue'
|
|
|
|
+import { ElMessage, ElMessageBox } from 'element-plus'
|
|
|
|
+import request from '/@/utils/request'
|
|
|
|
+import { useSearch } from '/@/hooks/useSearch'
|
|
|
|
+
|
|
|
|
+// 定义数据源类型
|
|
|
|
+interface DataSource {
|
|
|
|
+ id?: number
|
|
|
|
+ name: string
|
|
|
|
+ type: string
|
|
|
|
+ host: string
|
|
|
|
+ port: number
|
|
|
|
+ username: string
|
|
|
|
+ password?: string
|
|
|
|
+ database: string
|
|
|
|
+ maxOpenConns?: number
|
|
|
|
+ maxIdleConns?: number
|
|
|
|
+ connMaxLifetime?: number
|
|
|
|
+ remark?: string
|
|
|
|
+ createdAt?: string
|
|
|
|
+ updatedAt?: string
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// 表单引用
|
|
|
|
+const formRef = ref()
|
|
|
|
+const testFormRef = ref()
|
|
|
|
+const queryRef = ref()
|
|
|
|
+
|
|
|
|
+// 对话框状态
|
|
|
|
+const dialogVisible = ref(false)
|
|
|
|
+const testDialogVisible = ref(false)
|
|
|
|
+
|
|
|
|
+// 总数
|
|
|
|
+const total = ref(0)
|
|
|
|
+
|
|
|
|
+// 加载状态
|
|
|
|
+const loading = ref(false)
|
|
|
|
+
|
|
|
|
+// 表单数据
|
|
|
|
+const formData = reactive<DataSource>({
|
|
|
|
+ name: '',
|
|
|
|
+ type: 'MySQL',
|
|
|
|
+ host: '',
|
|
|
|
+ port: 3306,
|
|
|
|
+ username: '',
|
|
|
|
+ password: '',
|
|
|
|
+ database: '',
|
|
|
|
+ maxOpenConns: 100,
|
|
|
|
+ maxIdleConns: 10,
|
|
|
|
+ connMaxLifetime: 3600,
|
|
|
|
+ remark: ''
|
|
|
|
+})
|
|
|
|
+
|
|
|
|
+// 测试连接表单数据
|
|
|
|
+const testFormData = reactive({
|
|
|
|
+ type: 'MySQL',
|
|
|
|
+ host: '',
|
|
|
|
+ port: 3306,
|
|
|
|
+ username: '',
|
|
|
|
+ password: '',
|
|
|
|
+ database: ''
|
|
|
|
+})
|
|
|
|
+
|
|
|
|
+// 表单验证规则
|
|
|
|
+const rules = {
|
|
|
|
+ name: [{ required: true, message: '请输入数据源名称', trigger: 'blur' }],
|
|
|
|
+ type: [{ required: true, message: '请选择数据库类型', trigger: 'change' }],
|
|
|
|
+ host: [{ required: true, message: '请输入主机地址', trigger: 'blur' }],
|
|
|
|
+ port: [{ required: true, message: '请输入端口', trigger: 'blur' }],
|
|
|
|
+ username: [{ required: true, message: '请输入用户名', trigger: 'blur' }],
|
|
|
|
+ password: [{ required: true, message: '请输入密码', trigger: 'blur' }],
|
|
|
|
+ database: [{ required: true, message: '请输入数据库名', trigger: 'blur' }]
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// 测试连接表单验证规则
|
|
|
|
+const testRules = {
|
|
|
|
+ type: [{ required: true, message: '请选择数据库类型', trigger: 'change' }],
|
|
|
|
+ host: [{ required: true, message: '请输入主机地址', trigger: 'blur' }],
|
|
|
|
+ port: [{ required: true, message: '请输入端口', trigger: 'blur' }],
|
|
|
|
+ username: [{ required: true, message: '请输入用户名', trigger: 'blur' }],
|
|
|
|
+ password: [{ required: true, message: '请输入密码', trigger: 'blur' }],
|
|
|
|
+ database: [{ required: true, message: '请输入数据库名', trigger: 'blur' }]
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// 数据源列表请求函数
|
|
|
|
+const dataSourceRequest = (params: any) => {
|
|
|
|
+ return request({
|
|
|
|
+ url: '/datasource/list',
|
|
|
|
+ method: 'get',
|
|
|
|
+ params
|
|
|
|
+ })
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// 使用通用搜索钩子
|
|
|
|
+const { params, tableData, getList } = useSearch<DataSource[]>(
|
|
|
|
+ dataSourceRequest,
|
|
|
|
+ 'data',
|
|
|
|
+ (res: any) => {
|
|
|
|
+ total.value = res.pagination?.total || 0
|
|
|
|
+ return res.data || []
|
|
|
|
+ },
|
|
|
|
+ {
|
|
|
|
+ keyWord: '',
|
|
|
|
+ dateRange: [],
|
|
|
|
+ pageNum: 1,
|
|
|
|
+ pageSize: 10
|
|
|
|
+ }
|
|
|
|
+)
|
|
|
|
+
|
|
|
|
+// 页面加载时获取列表数据
|
|
|
|
+onMounted(() => {
|
|
|
|
+ getList(1)
|
|
|
|
+})
|
|
|
|
+
|
|
|
|
+// 重置查询表单
|
|
|
|
+const resetQuery = () => {
|
|
|
|
+ queryRef.value?.resetFields()
|
|
|
|
+ getList(1)
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// 新增或编辑数据源
|
|
|
|
+const addOrEdit = (row?: DataSource) => {
|
|
|
|
+ // 重置表单
|
|
|
|
+ Object.assign(formData, {
|
|
|
|
+ id: undefined,
|
|
|
|
+ name: '',
|
|
|
|
+ type: 'MySQL',
|
|
|
|
+ host: '',
|
|
|
|
+ port: 3306,
|
|
|
|
+ username: '',
|
|
|
|
+ password: '',
|
|
|
|
+ database: '',
|
|
|
|
+ maxOpenConns: 100,
|
|
|
|
+ maxIdleConns: 10,
|
|
|
|
+ connMaxLifetime: 3600,
|
|
|
|
+ remark: ''
|
|
|
|
+ })
|
|
|
|
+
|
|
|
|
+ // 如果是编辑,填充表单数据
|
|
|
|
+ if (row) {
|
|
|
|
+ Object.assign(formData, { ...row, password: '' })
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 显示对话框
|
|
|
|
+ dialogVisible.value = true
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// 提交表单
|
|
|
|
+const submitForm = async () => {
|
|
|
|
+ // 表单验证
|
|
|
|
+ await formRef.value.validate()
|
|
|
|
+
|
|
|
|
+ try {
|
|
|
|
+ // 根据是否有ID判断是新增还是编辑
|
|
|
|
+ const url = formData.id ? '/datasource/edit' : '/datasource/add'
|
|
|
|
+ const method = formData.id ? 'put' : 'post'
|
|
|
|
+
|
|
|
|
+ // 发送请求
|
|
|
|
+ await request({
|
|
|
|
+ url,
|
|
|
|
+ method,
|
|
|
|
+ data: formData
|
|
|
|
+ })
|
|
|
|
+
|
|
|
|
+ // 关闭对话框
|
|
|
|
+ dialogVisible.value = false
|
|
|
|
+
|
|
|
|
+ // 刷新列表
|
|
|
|
+ getList()
|
|
|
|
+
|
|
|
|
+ // 提示成功
|
|
|
|
+ ElMessage.success(formData.id ? '编辑成功' : '添加成功')
|
|
|
|
+ } catch (error) {
|
|
|
|
+ console.error('提交表单失败:', error)
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// 删除数据源
|
|
|
|
+const deleteDataSource = (row: DataSource) => {
|
|
|
|
+ ElMessageBox.confirm(`确定要删除数据源「${row.name}」吗?`, '警告', {
|
|
|
|
+ confirmButtonText: '确定',
|
|
|
|
+ cancelButtonText: '取消',
|
|
|
|
+ type: 'warning'
|
|
|
|
+ }).then(async () => {
|
|
|
|
+ try {
|
|
|
|
+ // 发送删除请求
|
|
|
|
+ await request({
|
|
|
|
+ url: '/datasource/delete',
|
|
|
|
+ method: 'delete',
|
|
|
|
+ data: { ids: [row.id] }
|
|
|
|
+ })
|
|
|
|
+
|
|
|
|
+ // 刷新列表
|
|
|
|
+ getList()
|
|
|
|
+
|
|
|
|
+ // 提示成功
|
|
|
|
+ ElMessage.success('删除成功')
|
|
|
|
+ } catch (error) {
|
|
|
|
+ console.error('删除失败:', error)
|
|
|
|
+ }
|
|
|
|
+ }).catch(() => {})
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// 测试连接(从列表)
|
|
|
|
+const testConnection = (row: DataSource) => {
|
|
|
|
+ // 填充测试表单
|
|
|
|
+ Object.assign(testFormData, {
|
|
|
|
+ type: row.type,
|
|
|
|
+ host: row.host,
|
|
|
|
+ port: row.port,
|
|
|
|
+ username: row.username,
|
|
|
|
+ password: '', // 密码需要重新输入
|
|
|
|
+ database: row.database
|
|
|
|
+ })
|
|
|
|
+
|
|
|
|
+ // 显示测试对话框
|
|
|
|
+ testDialogVisible.value = true
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// 测试连接(从表单)
|
|
|
|
+const testConnectionForm = async () => {
|
|
|
|
+ // 表单验证
|
|
|
|
+ await formRef.value.validate()
|
|
|
|
+
|
|
|
|
+ // 填充测试表单
|
|
|
|
+ Object.assign(testFormData, {
|
|
|
|
+ type: formData.type,
|
|
|
|
+ host: formData.host,
|
|
|
|
+ port: formData.port,
|
|
|
|
+ username: formData.username,
|
|
|
|
+ password: formData.password,
|
|
|
|
+ database: formData.database
|
|
|
|
+ })
|
|
|
|
+
|
|
|
|
+ // 显示测试对话框
|
|
|
|
+ testDialogVisible.value = true
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// 提交测试连接
|
|
|
|
+const submitTestConnection = async () => {
|
|
|
|
+ // 表单验证
|
|
|
|
+ await testFormRef.value.validate()
|
|
|
|
+
|
|
|
|
+ try {
|
|
|
|
+ // 发送测试连接请求
|
|
|
|
+ const res = await request({
|
|
|
|
+ url: '/datasource/test',
|
|
|
|
+ method: 'post',
|
|
|
|
+ data: testFormData
|
|
|
|
+ })
|
|
|
|
+
|
|
|
|
+ // 根据测试结果显示提示
|
|
|
|
+ if (res.data.success) {
|
|
|
|
+ ElMessage.success(res.data.message || '连接成功')
|
|
|
|
+ } else {
|
|
|
|
+ ElMessage.error(res.data.message || '连接失败')
|
|
|
|
+ }
|
|
|
|
+ } catch (error) {
|
|
|
|
+ console.error('测试连接失败:', error)
|
|
|
|
+ ElMessage.error('测试连接失败')
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+</script>
|
|
|
|
+
|
|
|
|
+<style scoped>
|
|
|
|
+.card-header {
|
|
|
|
+ display: flex;
|
|
|
|
+ justify-content: space-between;
|
|
|
|
+ align-items: center;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.pagination-container {
|
|
|
|
+ margin-top: 20px;
|
|
|
|
+ display: flex;
|
|
|
|
+ justify-content: flex-end;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.form-tip {
|
|
|
|
+ font-size: 12px;
|
|
|
|
+ color: #909399;
|
|
|
|
+ margin-top: 4px;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.dialog-footer {
|
|
|
|
+ display: flex;
|
|
|
|
+ justify-content: flex-end;
|
|
|
|
+ gap: 10px;
|
|
|
|
+}
|
|
|
|
+</style>
|