379 lines
14 KiB
Vue
379 lines
14 KiB
Vue
|
|
<script setup lang="ts">
|
||
|
|
import { ref } from 'vue'
|
||
|
|
|
||
|
|
interface Database {
|
||
|
|
id: number
|
||
|
|
name: string
|
||
|
|
type: string
|
||
|
|
subtables: number
|
||
|
|
status: 'connected' | 'disconnected' | 'error'
|
||
|
|
createdAt: string
|
||
|
|
description: string
|
||
|
|
}
|
||
|
|
|
||
|
|
// 数据库数据
|
||
|
|
const databases = ref<Database[]>([
|
||
|
|
{ id: 1, name: 'production-db', type: 'MySQL', subtables: 24, status: 'connected', createdAt: '2025-04-10', description: 'Main production database' },
|
||
|
|
{ id: 2, name: 'staging-db', type: 'MySQL', subtables: 18, status: 'connected', createdAt: '2025-04-08', description: 'Staging environment database' },
|
||
|
|
{ id: 3, name: 'analytics-db', type: 'ClickHouse', subtables: 56, status: 'connected', createdAt: '2025-04-05', description: 'Analytics and reporting database' },
|
||
|
|
{ id: 4, name: 'cache-db', type: 'Redis', subtables: 8, status: 'connected', createdAt: '2025-04-12', description: 'Cache layer database' },
|
||
|
|
{ id: 5, name: 'test-db', type: 'MySQL', subtables: 12, status: 'disconnected', createdAt: '2025-04-11', description: 'Testing database' },
|
||
|
|
])
|
||
|
|
|
||
|
|
// 编辑状态
|
||
|
|
const editingDb = ref<Database | null>(null)
|
||
|
|
const isEditing = ref(false)
|
||
|
|
const isCreating = ref(false)
|
||
|
|
const searchQuery = ref('')
|
||
|
|
const filterStatus = ref<string>('all')
|
||
|
|
|
||
|
|
const dbTypes = ['MySQL']
|
||
|
|
|
||
|
|
// 新建 Database 表单
|
||
|
|
const newDbForm = ref({
|
||
|
|
name: '',
|
||
|
|
type: 'MySQL',
|
||
|
|
description: '',
|
||
|
|
})
|
||
|
|
|
||
|
|
// 打开新建弹窗
|
||
|
|
const openCreate = () => {
|
||
|
|
newDbForm.value = {
|
||
|
|
name: '',
|
||
|
|
type: 'MySQL',
|
||
|
|
description: '',
|
||
|
|
}
|
||
|
|
isCreating.value = true
|
||
|
|
}
|
||
|
|
|
||
|
|
// 关闭新建弹窗
|
||
|
|
const closeCreate = () => {
|
||
|
|
isCreating.value = false
|
||
|
|
}
|
||
|
|
|
||
|
|
// 保存新建
|
||
|
|
const saveNewDb = () => {
|
||
|
|
const newId = Math.max(...databases.value.map(d => d.id)) + 1
|
||
|
|
databases.value.push({
|
||
|
|
id: newId,
|
||
|
|
name: newDbForm.value.name || 'Untitled Database',
|
||
|
|
type: newDbForm.value.type,
|
||
|
|
subtables: 0,
|
||
|
|
status: 'disconnected',
|
||
|
|
createdAt: new Date().toISOString().split('T')[0],
|
||
|
|
description: newDbForm.value.description,
|
||
|
|
})
|
||
|
|
isCreating.value = false
|
||
|
|
}
|
||
|
|
|
||
|
|
// 编辑表单数据
|
||
|
|
const editForm = ref({
|
||
|
|
name: '',
|
||
|
|
type: '',
|
||
|
|
description: '',
|
||
|
|
})
|
||
|
|
|
||
|
|
// 打开编辑弹窗
|
||
|
|
const openEdit = (db: Database) => {
|
||
|
|
editingDb.value = db
|
||
|
|
editForm.value = {
|
||
|
|
name: db.name,
|
||
|
|
type: db.type,
|
||
|
|
description: db.description,
|
||
|
|
}
|
||
|
|
isEditing.value = true
|
||
|
|
}
|
||
|
|
|
||
|
|
// 保存编辑
|
||
|
|
const saveEdit = () => {
|
||
|
|
if (editingDb.value) {
|
||
|
|
const index = databases.value.findIndex(d => d.id === editingDb.value!.id)
|
||
|
|
if (index !== -1) {
|
||
|
|
databases.value[index] = {
|
||
|
|
...databases.value[index],
|
||
|
|
...editForm.value,
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
isEditing.value = false
|
||
|
|
}
|
||
|
|
|
||
|
|
// 取消编辑
|
||
|
|
const cancelEdit = () => {
|
||
|
|
isEditing.value = false
|
||
|
|
editingDb.value = null
|
||
|
|
}
|
||
|
|
|
||
|
|
// 切换状态
|
||
|
|
const toggleStatus = (db: Database) => {
|
||
|
|
if (db.status === 'connected') {
|
||
|
|
db.status = 'disconnected'
|
||
|
|
} else if (db.status === 'disconnected') {
|
||
|
|
db.status = 'connected'
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// 删除 Database
|
||
|
|
const deleteDb = (id: number) => {
|
||
|
|
databases.value = databases.value.filter(d => d.id !== id)
|
||
|
|
}
|
||
|
|
|
||
|
|
// 过滤后的 Databases
|
||
|
|
const filteredDatabases = () => {
|
||
|
|
return databases.value.filter(db => {
|
||
|
|
const matchSearch = db.name.toLowerCase().includes(searchQuery.value.toLowerCase()) ||
|
||
|
|
db.type.toLowerCase().includes(searchQuery.value.toLowerCase())
|
||
|
|
const matchStatus = filterStatus.value === 'all' || db.status === filterStatus.value
|
||
|
|
return matchSearch && matchStatus
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
// 状态颜色
|
||
|
|
const statusClass = (status: string) => {
|
||
|
|
switch (status) {
|
||
|
|
case 'connected': return 'bg-primary-success'
|
||
|
|
case 'disconnected': return 'bg-gray-500'
|
||
|
|
case 'error': return 'bg-primary-danger'
|
||
|
|
default: return 'bg-gray-500'
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
</script>
|
||
|
|
|
||
|
|
<template>
|
||
|
|
<!-- 主内容区域 -->
|
||
|
|
<div class="p-6 min-h-screen">
|
||
|
|
<!-- 顶部导航 -->
|
||
|
|
<div class="flex justify-between items-center mb-6">
|
||
|
|
<div class="flex items-center gap-2">
|
||
|
|
<i class="fa-solid fa-database text-gray-400"></i>
|
||
|
|
<span class="font-medium">Database</span>
|
||
|
|
</div>
|
||
|
|
<button @click="openCreate" class="bg-gradient-to-r from-primary-orange to-red-500 hover:from-orange-500 hover:to-red-600 text-white px-4 py-2 rounded-lg font-medium flex items-center gap-2 transition-all">
|
||
|
|
<i class="fa-solid fa-plus"></i>
|
||
|
|
New Connection
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<!-- 搜索和筛选 -->
|
||
|
|
<div class="flex gap-4 mb-6">
|
||
|
|
<div class="flex-1 relative">
|
||
|
|
<i class="fa-solid fa-search absolute left-3 top-1/2 -translate-y-1/2 text-gray-400"></i>
|
||
|
|
<input
|
||
|
|
v-model="searchQuery"
|
||
|
|
type="text"
|
||
|
|
placeholder="Search databases..."
|
||
|
|
class="w-full bg-dark-600 border border-dark-500 rounded-lg py-2 pl-10 pr-4 text-white placeholder-gray-500 focus:outline-none focus:border-primary-orange"
|
||
|
|
>
|
||
|
|
</div>
|
||
|
|
<el-select v-model="filterStatus" placeholder="Select" class="w-40" size="large">
|
||
|
|
<el-option label="All Status" value="all" />
|
||
|
|
<el-option label="Connected" value="connected" />
|
||
|
|
<el-option label="Disconnected" value="disconnected" />
|
||
|
|
<el-option label="Error" value="error" />
|
||
|
|
</el-select>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<!-- Database 列表 -->
|
||
|
|
<div class="bg-dark-700 rounded-xl overflow-hidden">
|
||
|
|
<table class="w-full">
|
||
|
|
<thead class="bg-dark-600">
|
||
|
|
<tr>
|
||
|
|
<th class="text-left px-5 py-3 text-sm font-medium text-gray-400">Database Name</th>
|
||
|
|
<th class="text-center px-5 py-3 text-sm font-medium text-gray-400">Type</th>
|
||
|
|
<th class="text-center px-5 py-3 text-sm font-medium text-gray-400">Subtables</th>
|
||
|
|
<th class="text-center px-5 py-3 text-sm font-medium text-gray-400">Status</th>
|
||
|
|
<th class="text-center px-5 py-3 text-sm font-medium text-gray-400">Created</th>
|
||
|
|
<th class="text-center px-5 py-3 text-sm font-medium text-gray-400">Actions</th>
|
||
|
|
</tr>
|
||
|
|
</thead>
|
||
|
|
<tbody>
|
||
|
|
<tr v-for="db in filteredDatabases()" :key="db.id" class="border-t border-dark-600 hover:bg-dark-600/50 transition-colors">
|
||
|
|
<td class="px-5 py-4">
|
||
|
|
<div class="font-medium">{{ db.name }}</div>
|
||
|
|
<div class="text-sm text-gray-500">{{ db.description }}</div>
|
||
|
|
</td>
|
||
|
|
<td class="px-5 py-4 text-center">
|
||
|
|
<span class="bg-dark-500 px-2 py-1 rounded text-sm">{{ db.type }}</span>
|
||
|
|
</td>
|
||
|
|
<td class="px-5 py-4 text-center">
|
||
|
|
<span class="text-primary-cyan">{{ db.subtables }}</span>
|
||
|
|
</td>
|
||
|
|
<td class="px-5 py-4 text-center">
|
||
|
|
<span
|
||
|
|
class="px-3 py-1 rounded-full text-xs capitalize"
|
||
|
|
:class="{
|
||
|
|
'bg-green-500/20 text-green-400': db.status === 'connected',
|
||
|
|
'bg-gray-500/20 text-gray-400': db.status === 'disconnected',
|
||
|
|
'bg-red-500/20 text-red-400': db.status === 'error'
|
||
|
|
}"
|
||
|
|
>{{ db.status }}</span>
|
||
|
|
</td>
|
||
|
|
<td class="px-5 py-4 text-center text-gray-400 text-sm">{{ db.createdAt }}</td>
|
||
|
|
<td class="px-5 py-4">
|
||
|
|
<div class="flex items-center justify-center gap-2">
|
||
|
|
<button
|
||
|
|
@click="toggleStatus(db)"
|
||
|
|
class="p-2 rounded-lg hover:bg-dark-500 transition-colors"
|
||
|
|
:title="db.status === 'connected' ? 'Disconnect' : 'Connect'"
|
||
|
|
>
|
||
|
|
<i :class="['fa-solid', db.status === 'connected' ? 'fa-stop' : 'fa-play', 'text-gray-400 hover:text-white']"></i>
|
||
|
|
</button>
|
||
|
|
<button
|
||
|
|
@click="openEdit(db)"
|
||
|
|
class="p-2 rounded-lg hover:bg-dark-500 transition-colors"
|
||
|
|
title="Edit"
|
||
|
|
>
|
||
|
|
<i class="fa-solid fa-pen text-gray-400 hover:text-white"></i>
|
||
|
|
</button>
|
||
|
|
<button
|
||
|
|
@click="deleteDb(db.id)"
|
||
|
|
class="p-2 rounded-lg hover:bg-dark-500 transition-colors"
|
||
|
|
title="Delete"
|
||
|
|
>
|
||
|
|
<i class="fa-solid fa-trash text-gray-400 hover:text-primary-danger"></i>
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
</td>
|
||
|
|
</tr>
|
||
|
|
</tbody>
|
||
|
|
</table>
|
||
|
|
|
||
|
|
<!-- 空状态 -->
|
||
|
|
<div v-if="filteredDatabases().length === 0" class="py-12 text-center text-gray-500">
|
||
|
|
<i class="fa-solid fa-database text-4xl mb-3"></i>
|
||
|
|
<p>No databases found</p>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<!-- 编辑弹窗 -->
|
||
|
|
<Teleport to="body">
|
||
|
|
<div v-if="isEditing" class="fixed inset-0 bg-black/60 flex items-center justify-center z-50" @click.self="cancelEdit">
|
||
|
|
<div class="bg-dark-700 rounded-2xl w-full max-w-lg border border-dark-500 shadow-2xl">
|
||
|
|
<!-- 弹窗头部 -->
|
||
|
|
<div class="flex items-center justify-between p-5 border-b border-dark-500">
|
||
|
|
<h3 class="text-lg font-semibold">Edit Database</h3>
|
||
|
|
<button @click="cancelEdit" class="text-gray-400 hover:text-white transition-colors">
|
||
|
|
<i class="fa-solid fa-xmark text-xl"></i>
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<!-- 弹窗内容 -->
|
||
|
|
<div class="p-5 space-y-4">
|
||
|
|
<div>
|
||
|
|
<label class="block text-sm font-medium text-gray-300 mb-2">Database Name</label>
|
||
|
|
<input
|
||
|
|
v-model="editForm.name"
|
||
|
|
type="text"
|
||
|
|
class="w-full bg-dark-600 border border-dark-500 rounded-lg px-4 py-2.5 text-white focus:outline-none focus:border-primary-orange"
|
||
|
|
>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div>
|
||
|
|
<label class="block text-sm font-medium text-gray-300 mb-2">Type</label>
|
||
|
|
<el-select v-model="editForm.type" placeholder="Select" class="w-full" size="large">
|
||
|
|
<el-option v-for="type in dbTypes" :key="type" :label="type" :value="type" />
|
||
|
|
</el-select>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div>
|
||
|
|
<label class="block text-sm font-medium text-gray-300 mb-2">Description</label>
|
||
|
|
<textarea
|
||
|
|
v-model="editForm.description"
|
||
|
|
rows="3"
|
||
|
|
class="w-full bg-dark-600 border border-dark-500 rounded-lg px-4 py-2.5 text-white focus:outline-none focus:border-primary-orange resize-none"
|
||
|
|
></textarea>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<!-- 弹窗底部 -->
|
||
|
|
<div class="flex items-center justify-end gap-3 p-5 border-t border-dark-500">
|
||
|
|
<button
|
||
|
|
@click="cancelEdit"
|
||
|
|
class="px-4 py-2 rounded-lg bg-dark-600 text-gray-300 hover:bg-dark-500 transition-colors"
|
||
|
|
>
|
||
|
|
Cancel
|
||
|
|
</button>
|
||
|
|
<button
|
||
|
|
@click="saveEdit"
|
||
|
|
class="px-4 py-2 rounded-lg bg-gradient-to-r from-primary-orange to-red-500 text-white hover:from-orange-500 hover:to-red-600 transition-all"
|
||
|
|
>
|
||
|
|
Save Changes
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</Teleport>
|
||
|
|
|
||
|
|
<!-- 新建 Database 弹窗 -->
|
||
|
|
<Teleport to="body">
|
||
|
|
<div v-if="isCreating" class="fixed inset-0 bg-black/80 flex items-center justify-center z-50 p-4" @click.self="closeCreate">
|
||
|
|
<div class="bg-dark-800 rounded-2xl w-full max-w-lg border border-dark-600 shadow-2xl overflow-hidden animate-modal-in">
|
||
|
|
<!-- 弹窗头部 -->
|
||
|
|
<div class="flex items-center justify-between p-5 border-b border-dark-600 bg-dark-700/50">
|
||
|
|
<div class="flex items-center gap-3">
|
||
|
|
<div class="w-10 h-10 rounded-xl bg-gradient-to-br from-primary-orange to-red-500 flex items-center justify-center">
|
||
|
|
<i class="fa-solid fa-database text-white"></i>
|
||
|
|
</div>
|
||
|
|
<div>
|
||
|
|
<h3 class="text-xl font-semibold text-white">Create New Connection</h3>
|
||
|
|
<p class="text-sm text-gray-400">Configure your database connection</p>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
<button @click="closeCreate" class="text-gray-400 hover:text-white transition-all p-2 hover:bg-dark-600 rounded-lg">
|
||
|
|
<i class="fa-solid fa-xmark text-xl"></i>
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<!-- 弹窗内容 -->
|
||
|
|
<div class="p-5 space-y-4">
|
||
|
|
<div>
|
||
|
|
<label class="block text-sm font-medium text-gray-300 mb-2">Database Name</label>
|
||
|
|
<input
|
||
|
|
v-model="newDbForm.name"
|
||
|
|
type="text"
|
||
|
|
placeholder="Enter database name..."
|
||
|
|
class="w-full bg-dark-600 border border-dark-500 rounded-lg px-4 py-2.5 text-white placeholder-gray-500 focus:outline-none focus:border-primary-orange"
|
||
|
|
>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div>
|
||
|
|
<label class="block text-sm font-medium text-gray-300 mb-2">Type</label>
|
||
|
|
<el-select v-model="newDbForm.type" placeholder="Select" class="w-full" size="large">
|
||
|
|
<el-option v-for="type in dbTypes" :key="type" :label="type" :value="type" />
|
||
|
|
</el-select>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div>
|
||
|
|
<label class="block text-sm font-medium text-gray-300 mb-2">Description</label>
|
||
|
|
<textarea
|
||
|
|
v-model="newDbForm.description"
|
||
|
|
rows="3"
|
||
|
|
placeholder="Describe this database..."
|
||
|
|
class="w-full bg-dark-600 border border-dark-500 rounded-lg px-4 py-2.5 text-white placeholder-gray-500 focus:outline-none focus:border-primary-orange resize-none"
|
||
|
|
></textarea>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<!-- 底部操作栏 -->
|
||
|
|
<div class="flex items-center justify-end gap-3 p-5 border-t border-dark-600 bg-dark-700/50">
|
||
|
|
<button
|
||
|
|
@click="closeCreate"
|
||
|
|
class="px-4 py-2 rounded-lg bg-dark-600 text-gray-300 hover:bg-dark-500 transition-colors"
|
||
|
|
>
|
||
|
|
Cancel
|
||
|
|
</button>
|
||
|
|
<button
|
||
|
|
@click="saveNewDb"
|
||
|
|
class="px-4 py-2 rounded-lg bg-gradient-to-r from-primary-orange to-red-500 text-white hover:from-orange-500 hover:to-red-600 transition-all flex items-center gap-2"
|
||
|
|
>
|
||
|
|
<i class="fa-solid fa-plus"></i>
|
||
|
|
Create Database
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</Teleport>
|
||
|
|
</div>
|
||
|
|
</template>
|