mirror of
https://gh-proxy.org/https://github.com/klinsmaya/SQLBot.git
synced 2026-11-09 09:46:55 +08:00
feat: Chat UI
This commit is contained in:
@@ -16,6 +16,14 @@ export const questionApi = {
|
||||
query: (id: number) => request.get(`/chat/question/${id}`)
|
||||
}
|
||||
|
||||
export interface ChatMessage {
|
||||
role: 'user' | 'assistant'
|
||||
create_time?: Date | string
|
||||
content?: string | number
|
||||
isTyping?: boolean
|
||||
isWelcome?: boolean
|
||||
}
|
||||
|
||||
export class ChatRecord {
|
||||
id?: number
|
||||
chat_id?: number
|
||||
@@ -147,11 +155,11 @@ function startChat(data: any): Promise<ChatInfo> {
|
||||
return request.post('/chat/start', data)
|
||||
}
|
||||
|
||||
function renameChat(chat_id: number, brief: string): Promise<string> {
|
||||
function renameChat(chat_id: number | undefined, brief: string): Promise<string> {
|
||||
return request.post('/chat/rename', {id: chat_id, brief: brief})
|
||||
}
|
||||
|
||||
function deleteChat(id: number): Promise<string> {
|
||||
function deleteChat(id: number | undefined): Promise<string> {
|
||||
return request.get(`/chat/delete/${id}`)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
import type {ChatMessage} from "@/api/chat.ts";
|
||||
|
||||
defineProps<{
|
||||
msg?: ChatMessage
|
||||
}>()
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="chat-block">
|
||||
<slot>
|
||||
{{ msg?.content }}
|
||||
</slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="less">
|
||||
.chat-block {
|
||||
border-radius: 2px;
|
||||
background-color: white;
|
||||
padding: 12px;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,58 @@
|
||||
<script setup lang="ts">
|
||||
import ChatBlock from './ChatBlock.vue'
|
||||
import WelcomeBlock from './WelcomeBlock.vue'
|
||||
import {ChatInfo, type ChatMessage} from "@/api/chat.ts";
|
||||
import {computed} from "vue";
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
msg: ChatMessage
|
||||
currentChat: ChatInfo
|
||||
datasource?: number
|
||||
}>(),
|
||||
{}
|
||||
)
|
||||
|
||||
const emits = defineEmits(["update:datasource"])
|
||||
|
||||
const _datasource = computed({
|
||||
get() {
|
||||
return props.datasource
|
||||
},
|
||||
set(v) {
|
||||
emits("update:datasource", v)
|
||||
}
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="chat-row" :class="{'right-to-left': msg.role === 'user'}">
|
||||
<el-avatar shape="square" v-if="msg.role === 'assistant'">SQBot</el-avatar>
|
||||
<el-avatar shape="square" v-if="msg.role === 'user'"/>
|
||||
<ChatBlock v-if="!msg.isWelcome" :msg="msg" :class="{'row-full': msg.role === 'assistant'}">
|
||||
<slot></slot>
|
||||
</ChatBlock>
|
||||
<WelcomeBlock v-else v-model="_datasource" :current-chat="currentChat" class="row-full"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="less">
|
||||
.chat-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: flex-start;
|
||||
gap: 8px;
|
||||
|
||||
padding: 20px 20px 0;
|
||||
|
||||
&.right-to-left {
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
|
||||
.row-full {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,125 @@
|
||||
<script setup lang="ts">
|
||||
import ChatBlock from './ChatBlock.vue'
|
||||
import {ChatInfo} from "@/api/chat.ts";
|
||||
import {computed, onMounted, ref} from "vue";
|
||||
import {datasourceApi} from "@/api/datasource.ts";
|
||||
import DatasourceItemCard from '../ds/DatasourceItemCard.vue'
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
modelValue?: number
|
||||
currentChat: ChatInfo
|
||||
}>(),
|
||||
{}
|
||||
)
|
||||
|
||||
const dsList = ref<Array<any>>([])
|
||||
|
||||
const editable = computed(() => {
|
||||
return props.currentChat?.id === undefined
|
||||
})
|
||||
|
||||
const ds = computed(() => {
|
||||
for (let i = 0; i < dsList.value.length; i++) {
|
||||
const item = dsList.value[i]
|
||||
if (props.modelValue === item.id) {
|
||||
return item
|
||||
}
|
||||
}
|
||||
if (props.currentChat && props.currentChat.datasource !== undefined) {
|
||||
return {
|
||||
id: props.currentChat.datasource,
|
||||
name: props.currentChat.datasource_name ?? 'Datasource does not exist',
|
||||
type: props.currentChat.engine_type
|
||||
}
|
||||
}
|
||||
return undefined
|
||||
})
|
||||
|
||||
const emits = defineEmits(["update:modelValue"])
|
||||
|
||||
function selectDs(ds: any) {
|
||||
if (editable.value) {
|
||||
emits("update:modelValue", ds.id)
|
||||
}
|
||||
}
|
||||
|
||||
function listDs() {
|
||||
datasourceApi.list().then((res) => {
|
||||
dsList.value = res
|
||||
})
|
||||
}
|
||||
|
||||
function showDs() {
|
||||
listDs()
|
||||
|
||||
}
|
||||
|
||||
|
||||
onMounted(() => {
|
||||
listDs()
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ChatBlock>
|
||||
<div>你好,我是SQLBot,很高兴为你服务</div>
|
||||
<div class="sub">我可以帮忙查询数据、生成图表、检测数据异常、预测数据等,请选择一个数据源,开启智能问数吧~</div>
|
||||
<template v-if="editable">
|
||||
<template v-if="dsList.length>0">
|
||||
<div class="ds-select-row">
|
||||
<div>选择数据源</div>
|
||||
<el-button @click="showDs" link type="primary">查看更多</el-button>
|
||||
</div>
|
||||
<div class="ds-row-container">
|
||||
<template v-for="(item, _index) in dsList" :key="_index">
|
||||
<DatasourceItemCard :ds="item" @click="selectDs(item)" v-if="_index<3 || item?.id===modelValue"
|
||||
class="ds-card" :class="[item?.id===modelValue? 'ds-card-selected': '']"/>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
<div v-else>
|
||||
数据源为空,请新建后再开启智能问数!
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="ds-select-row">
|
||||
<div>已选择数据源</div>
|
||||
</div>
|
||||
<div class="ds-row-container">
|
||||
<DatasourceItemCard :ds="ds"/>
|
||||
</div>
|
||||
</template>
|
||||
</ChatBlock>
|
||||
</template>
|
||||
|
||||
<style scoped lang="less">
|
||||
.sub {
|
||||
color: grey;
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
.ds-select-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.ds-row-container {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
|
||||
gap: 24px;
|
||||
|
||||
}
|
||||
|
||||
.ds-card {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.ds-card-selected {
|
||||
border-color: var(--ed-color-primary-light-5);
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
@@ -19,39 +19,33 @@
|
||||
<el-container :loading="loading">
|
||||
<el-main class="chat-record-list">
|
||||
<el-scrollbar>
|
||||
<div v-for="message in computedMessages">
|
||||
{{ message.role }}: {{ message.content }}
|
||||
<div v-if="message.isWelcome">
|
||||
<el-select v-model="currentChat.datasource" :disabled="currentChat.id!==undefined">
|
||||
<el-option v-for="item in dsList"
|
||||
:value="item.id"
|
||||
:key="item.id"
|
||||
:label="item.name"/>
|
||||
</el-select>
|
||||
</div>
|
||||
</div>
|
||||
<template v-for="(message, _index) in computedMessages" :key="_index">
|
||||
<ChatRow :current-chat="currentChat" v-model:datasource="currentChat.datasource" :msg="message"/>
|
||||
</template>
|
||||
</el-scrollbar>
|
||||
</el-main>
|
||||
<el-footer>
|
||||
<div class="chat-input">
|
||||
<div class="input-wrapper">
|
||||
<el-input
|
||||
v-model="inputMessage"
|
||||
type="textarea"
|
||||
:rows="1"
|
||||
:autosize="{ minRows: 1, maxRows: 8 }"
|
||||
placeholder="Press Enter to send, Ctrl + Enter for new line"
|
||||
@keydown.enter.exact.prevent="sendMessage"
|
||||
@keydown.ctrl.enter.exact.prevent="handleCtrlEnter"
|
||||
/>
|
||||
<div class="input-actions">
|
||||
<el-button circle type="primary" class="send-btn" @click="sendMessage">
|
||||
<el-icon>
|
||||
<Position/>
|
||||
</el-icon>
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<el-footer class="chat-footer">
|
||||
<div style="height: 24px;">
|
||||
<template v-if="currentChat.datasource">
|
||||
使用数据源:{{ currentChat.datasource_name }}
|
||||
</template>
|
||||
</div>
|
||||
<div class="input-wrapper">
|
||||
<el-input
|
||||
class="input-area"
|
||||
v-model="inputMessage"
|
||||
type="textarea"
|
||||
:rows="1"
|
||||
:autosize="{ minRows: 1, maxRows: 8 }"
|
||||
placeholder="Press Enter to send, Ctrl + Enter for new line"
|
||||
@keydown.enter.exact.prevent="sendMessage"
|
||||
@keydown.ctrl.enter.exact.prevent="handleCtrlEnter"
|
||||
/>
|
||||
<el-button link type="primary" class="input-icon" @click="sendMessage">
|
||||
<el-icon size="20">
|
||||
<Position/>
|
||||
</el-icon>
|
||||
</el-button>
|
||||
</div>
|
||||
</el-footer>
|
||||
</el-container>
|
||||
@@ -62,23 +56,14 @@
|
||||
<script setup lang="ts">
|
||||
import {ref, computed, nextTick, watch, onMounted} from 'vue'
|
||||
import {Plus, Position} from '@element-plus/icons-vue'
|
||||
import {Chat, chatApi, ChatInfo, ChatRecord, questionApi} from '@/api/chat'
|
||||
import {datasourceApi} from "@/api/datasource.ts"
|
||||
import {Chat, chatApi, ChatInfo, type ChatMessage, ChatRecord, questionApi} from '@/api/chat'
|
||||
import ChatList from './ChatList.vue'
|
||||
|
||||
interface ChatMessage {
|
||||
role: 'user' | 'assistant'
|
||||
create_time?: Date | string
|
||||
content?: string | number
|
||||
isTyping?: boolean
|
||||
isWelcome?: boolean
|
||||
}
|
||||
import ChatRow from './ChatRow.vue'
|
||||
|
||||
const inputMessage = ref('')
|
||||
|
||||
const loading = ref<boolean>(false);
|
||||
const chatList = ref<Array<ChatInfo>>([])
|
||||
const dsList = ref<any>([])
|
||||
|
||||
const currentChatId = ref<number | undefined>()
|
||||
const currentChat = ref<ChatInfo>(new ChatInfo())
|
||||
@@ -131,7 +116,6 @@ const createNewChat = () => {
|
||||
currentChat.value = new ChatInfo()
|
||||
currentChatId.value = undefined
|
||||
inputMessage.value = ''
|
||||
listDs()
|
||||
}
|
||||
|
||||
function getChatList() {
|
||||
@@ -183,15 +167,9 @@ function onChatRenamed(chat: Chat) {
|
||||
}
|
||||
}
|
||||
|
||||
function listDs() {
|
||||
datasourceApi.list().then((res) => {
|
||||
dsList.value = res
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getChatList()
|
||||
listDs()
|
||||
})
|
||||
|
||||
|
||||
@@ -347,7 +325,39 @@ const handleCtrlEnter = (e: KeyboardEvent) => {
|
||||
}
|
||||
|
||||
.chat-record-list {
|
||||
padding: 20px 0 0 0;
|
||||
padding: 0 0 20px 0;
|
||||
}
|
||||
|
||||
.chat-footer {
|
||||
--ed-footer-height: 120px;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.input-wrapper {
|
||||
flex: 1;
|
||||
|
||||
position: relative;
|
||||
|
||||
.input-area {
|
||||
height: 100%;
|
||||
padding-bottom: 8px;
|
||||
|
||||
:deep(.ed-textarea__inner) {
|
||||
height: 100% !important;
|
||||
}
|
||||
}
|
||||
|
||||
.input-icon {
|
||||
min-width: unset;
|
||||
position: absolute;
|
||||
bottom: 14px;
|
||||
right: 8px;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
import {datetimeFormat} from "@/utils/utils.ts";
|
||||
import SQLServerDs from "@/assets/svg/ds/sqlServer-ds.svg";
|
||||
import ExcelDs from "@/assets/svg/ds/Excel-ds.svg";
|
||||
import PgDs from "@/assets/svg/ds/pg-ds.svg";
|
||||
import MysqlDs from "@/assets/svg/ds/mysql-ds.svg";
|
||||
import OracleDs from "@/assets/svg/ds/oracle-ds.svg";
|
||||
|
||||
defineProps<{
|
||||
ds: any
|
||||
}>()
|
||||
|
||||
const getStatus = (status: string) => {
|
||||
if (status === 'Success') {
|
||||
return 'connected'
|
||||
}
|
||||
if (status === 'Fail') {
|
||||
return 'failed'
|
||||
}
|
||||
if (status === 'Checking') {
|
||||
return 'needs-verification'
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="connection-card"
|
||||
>
|
||||
<div class="connection-icon">
|
||||
<Icon>
|
||||
<MysqlDs v-if="ds.type === 'mysql'"/>
|
||||
<SQLServerDs v-else-if="ds.type === 'sqlServer'"/>
|
||||
<PgDs v-else-if="ds.type === 'pg'"/>
|
||||
<ExcelDs v-else-if="ds.type === 'excel'"/>
|
||||
<OracleDs v-else-if="ds.type === 'oracle'"/>
|
||||
</Icon>
|
||||
</div>
|
||||
<div class="connection-details">
|
||||
<div class="connection-name">{{ ds.name }}</div>
|
||||
<div class="connection-type">{{ ds.type_name }}</div>
|
||||
<div class="connection-host">{{ ds.description }}</div>
|
||||
<div class="connection-last">{{ datetimeFormat(ds.create_time) }}</div>
|
||||
</div>
|
||||
<div class="connection-status" :class="`${getStatus(ds.status)}`">{{ ds.status }}</div>
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="less">
|
||||
.connection-card {
|
||||
background-color: white;
|
||||
border-radius: 16px;
|
||||
padding: 24px 20px;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
|
||||
display: flex;
|
||||
position: relative;
|
||||
border: 1px solid #dadce0;
|
||||
transition: all 0.2s ease;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.connection-icon {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 12px;
|
||||
background-color: #e8f0fe;
|
||||
color: var(--primary-color);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 16px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.connection-details {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
|
||||
.connection-name {
|
||||
font-weight: 600;
|
||||
font-size: 18px;
|
||||
color: #202124;
|
||||
margin-bottom: 8px;
|
||||
line-height: 1.3;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.connection-type {
|
||||
color: #5f6368;
|
||||
margin-bottom: 8px;
|
||||
font-size: 14px;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.connection-host {
|
||||
color: #5f6368;
|
||||
margin-bottom: 8px;
|
||||
font-size: 14px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.connection-last {
|
||||
color: #5f6368;
|
||||
font-size: 14px;
|
||||
margin-bottom: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
.connection-status {
|
||||
position: absolute;
|
||||
right: 20px;
|
||||
top: 18px;
|
||||
padding: 3px 8px;
|
||||
border-radius: 12px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
max-width: 50px;
|
||||
text-align: center;
|
||||
justify-content: center;
|
||||
opacity: 0.9;
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.connection-status.connected {
|
||||
background-color: #e6f4ea;
|
||||
color: #34a853;
|
||||
}
|
||||
|
||||
.connection-status.failed {
|
||||
background-color: #fce8e6;
|
||||
color: #ea4335;
|
||||
}
|
||||
|
||||
.connection-status.needs-verification {
|
||||
background-color: #fef7e0;
|
||||
color: #fbbc05;
|
||||
}
|
||||
</style>
|
||||
+50
-160
@@ -3,15 +3,17 @@
|
||||
<div class="header">
|
||||
<div class="mt-4">
|
||||
<el-input
|
||||
v-model="searchValue"
|
||||
style="max-width: 300px"
|
||||
placeholder="Search Datasource..."
|
||||
class="input-with-select"
|
||||
clearable
|
||||
@change="searchHandle"
|
||||
v-model="searchValue"
|
||||
style="max-width: 300px"
|
||||
placeholder="Search Datasource..."
|
||||
class="input-with-select"
|
||||
clearable
|
||||
@change="searchHandle"
|
||||
>
|
||||
<template #prepend>
|
||||
<el-icon><Search /></el-icon>
|
||||
<el-icon>
|
||||
<Search/>
|
||||
</el-icon>
|
||||
</template>
|
||||
</el-input>
|
||||
</div>
|
||||
@@ -20,33 +22,17 @@
|
||||
</div>
|
||||
|
||||
<div class="connections-container">
|
||||
<div
|
||||
class="connection-card"
|
||||
v-for="ds in dsList"
|
||||
>
|
||||
<div class="connection-icon">
|
||||
<Icon>
|
||||
<MysqlDs v-if="ds.type === 'mysql'"/>
|
||||
<SQLServerDs v-else-if="ds.type === 'sqlServer'"/>
|
||||
<PgDs v-else-if="ds.type === 'pg'"/>
|
||||
<ExcelDs v-else-if="ds.type === 'excel'"/>
|
||||
<OracleDs v-else-if="ds.type === 'oracle'"/>
|
||||
</Icon>
|
||||
</div>
|
||||
<div class="connection-details">
|
||||
<div class="connection-name">{{ ds.name }}</div>
|
||||
<div class="connection-type">{{ ds.type_name }}</div>
|
||||
<div class="connection-host">{{ ds.description }}</div>
|
||||
<div class="connection-last">{{ datetimeFormat(ds.create_time) }}</div>
|
||||
</div>
|
||||
<div class="connection-status" :class="`${getStatus(ds.status)}`">{{ ds.status }}</div>
|
||||
<div class="connection-actions">
|
||||
<el-button class="action-btn" circle @click="getTables(ds.id, ds.name)" :icon="List" />
|
||||
<el-button class="action-btn" circle @click="editTables(ds)" :icon="CreditCard" />
|
||||
<el-button v-if="ds.type !== 'excel'" type="primary" class="action-btn" circle @click="editDs(ds)" :icon="IconOpeEdit"/>
|
||||
<el-button type="danger" class="action-btn" circle @click="deleteDs(ds)" :icon="IconOpeDelete"/>
|
||||
</div>
|
||||
</div>
|
||||
<template v-for="ds in dsList">
|
||||
<DatasourceItemCard :ds="ds">
|
||||
<div class="connection-actions">
|
||||
<el-button class="action-btn" circle @click="getTables(ds.id, ds.name)" :icon="List"/>
|
||||
<el-button class="action-btn" circle @click="editTables(ds)" :icon="CreditCard"/>
|
||||
<el-button v-if="ds.type !== 'excel'" type="primary" class="action-btn" circle @click="editDs(ds)"
|
||||
:icon="IconOpeEdit"/>
|
||||
<el-button type="danger" class="action-btn" circle @click="deleteDs(ds)" :icon="IconOpeDelete"/>
|
||||
</div>
|
||||
</DatasourceItemCard>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<DsForm ref="dsForm" @refresh="refresh"/>
|
||||
@@ -55,18 +41,13 @@
|
||||
import IconOpeAdd from '@/assets/svg/operate/ope-add.svg'
|
||||
import IconOpeEdit from '@/assets/svg/operate/ope-edit.svg'
|
||||
import IconOpeDelete from '@/assets/svg/operate/ope-delete.svg'
|
||||
import MysqlDs from '@/assets/svg/ds/mysql-ds.svg'
|
||||
import SQLServerDs from '@/assets/svg/ds/sqlServer-ds.svg'
|
||||
import PgDs from '@/assets/svg/ds/pg-ds.svg'
|
||||
import ExcelDs from '@/assets/svg/ds/Excel-ds.svg'
|
||||
import OracleDs from '@/assets/svg/ds/oracle-ds.svg'
|
||||
import { Search, List, CreditCard } from '@element-plus/icons-vue'
|
||||
import { ref, onMounted } from 'vue'
|
||||
import {Search, List, CreditCard} from '@element-plus/icons-vue'
|
||||
import {ref, onMounted} from 'vue'
|
||||
import DsForm from './form.vue'
|
||||
import { datasourceApi } from '@/api/datasource'
|
||||
import { datetimeFormat } from '@/utils/utils'
|
||||
import { ElMessageBox } from 'element-plus-secondary'
|
||||
import { useRouter } from 'vue-router'
|
||||
import {datasourceApi} from '@/api/datasource'
|
||||
import {ElMessageBox} from 'element-plus-secondary'
|
||||
import {useRouter} from 'vue-router'
|
||||
import DatasourceItemCard from "@/views/ds/DatasourceItemCard.vue";
|
||||
|
||||
const searchValue = ref<string>('')
|
||||
const dsForm = ref()
|
||||
@@ -75,21 +56,12 @@ const allDsList = ref<any>([])// all ds list
|
||||
const router = useRouter()
|
||||
const loading = ref(false)
|
||||
|
||||
const getStatus = (status: string) => {
|
||||
if (status === 'Success') {
|
||||
return 'connected'
|
||||
}
|
||||
if (status === 'Fail') {
|
||||
return 'failed'
|
||||
}
|
||||
if (status === 'Checking') {
|
||||
return 'needs-verification'
|
||||
}
|
||||
}
|
||||
|
||||
function searchHandle() {
|
||||
if(searchValue.value) {
|
||||
dsList.value = JSON.parse(JSON.stringify(allDsList.value)).filter((item: any) => {return item.name.toLowerCase().includes(searchValue.value.toLowerCase())})
|
||||
if (searchValue.value) {
|
||||
dsList.value = JSON.parse(JSON.stringify(allDsList.value)).filter((item: any) => {
|
||||
return item.name.toLowerCase().includes(searchValue.value.toLowerCase())
|
||||
})
|
||||
} else {
|
||||
dsList.value = JSON.parse(JSON.stringify(allDsList.value))
|
||||
}
|
||||
@@ -118,22 +90,22 @@ const editTables = (item: any) => {
|
||||
|
||||
const deleteDs = (item: any) => {
|
||||
ElMessageBox.confirm(
|
||||
'Delete this datasource?',
|
||||
'Delete',
|
||||
{
|
||||
confirmButtonText: 'Confirm',
|
||||
cancelButtonText: 'Cancel',
|
||||
type: 'warning',
|
||||
}
|
||||
'Delete this datasource?',
|
||||
'Delete',
|
||||
{
|
||||
confirmButtonText: 'Confirm',
|
||||
cancelButtonText: 'Cancel',
|
||||
type: 'warning',
|
||||
}
|
||||
)
|
||||
.then(() => {
|
||||
datasourceApi.delete(item.id).then(() => {
|
||||
console.log('success')
|
||||
list()
|
||||
})
|
||||
})
|
||||
.catch(() => {
|
||||
})
|
||||
.then(() => {
|
||||
datasourceApi.delete(item.id).then(() => {
|
||||
console.log('success')
|
||||
list()
|
||||
})
|
||||
})
|
||||
.catch(() => {
|
||||
})
|
||||
}
|
||||
|
||||
const getTables = (id: number, name: string) => {
|
||||
@@ -157,7 +129,7 @@ onMounted(() => {
|
||||
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.header{
|
||||
.header {
|
||||
background-color: white;
|
||||
padding: 16px 20px;
|
||||
border-radius: 8px;
|
||||
@@ -171,102 +143,20 @@ onMounted(() => {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(380px, 1fr));
|
||||
gap: 24px;
|
||||
.connection-card {
|
||||
background-color: white;
|
||||
border-radius: 16px;
|
||||
padding: 24px 20px;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
|
||||
display: flex;
|
||||
position: relative;
|
||||
border: 1px solid #dadce0;
|
||||
transition: all 0.2s ease;
|
||||
align-items: center;
|
||||
}
|
||||
.connection-icon {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 12px;
|
||||
background-color: #e8f0fe;
|
||||
color: var(--primary-color);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 16px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.connection-details {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
.connection-name {
|
||||
font-weight: 600;
|
||||
font-size: 18px;
|
||||
color: #202124;
|
||||
margin-bottom: 8px;
|
||||
line-height: 1.3;
|
||||
display: flex;
|
||||
}
|
||||
.connection-type {
|
||||
color: #5f6368;
|
||||
margin-bottom: 8px;
|
||||
font-size: 14px;
|
||||
display: flex;
|
||||
}
|
||||
.connection-host {
|
||||
color: #5f6368;
|
||||
margin-bottom: 8px;
|
||||
font-size: 14px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.connection-last {
|
||||
color: #5f6368;
|
||||
font-size: 14px;
|
||||
margin-bottom: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
.connection-status {
|
||||
position: absolute;
|
||||
right: 20px;
|
||||
top: 18px;
|
||||
padding: 3px 8px;
|
||||
border-radius: 12px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
max-width: 50px;
|
||||
text-align: center;
|
||||
justify-content: center;
|
||||
opacity: 0.9;
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
.connection-status.connected {
|
||||
background-color: #e6f4ea;
|
||||
color: #34a853;
|
||||
}
|
||||
.connection-status.failed {
|
||||
background-color: #fce8e6;
|
||||
color: #ea4335;
|
||||
}
|
||||
.connection-status.needs-verification {
|
||||
background-color: #fef7e0;
|
||||
color: #fbbc05;
|
||||
}
|
||||
|
||||
.connection-actions {
|
||||
position: absolute;
|
||||
bottom: 20px;
|
||||
right: 20px;
|
||||
display: flex;
|
||||
|
||||
.action-btn {
|
||||
display: none;
|
||||
min-width: 0;
|
||||
}
|
||||
}
|
||||
:hover .action-btn{
|
||||
|
||||
:hover .action-btn {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user