Merge remote-tracking branch 'origin/main'

# Conflicts:
#	frontend/src/views/dashboard/preview/SQPreviewShow.vue
This commit is contained in:
wangjiahao
2025-06-11 18:20:11 +08:00
19 changed files with 826 additions and 440 deletions
+13
View File
@@ -0,0 +1,13 @@
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
insert_final_newline = false
trim_trailing_whitespace = false
+8
View File
@@ -0,0 +1,8 @@
/node_modules/
/dist/
/public/
*.md
*.woff
*.ttf
.vscode/
.DS_Store
+33
View File
@@ -0,0 +1,33 @@
module.exports = {
root: true,
env: {
node: true,
},
extends: [
'plugin:vue/vue3-recommended',
'eslint:recommended',
'@vue/typescript/recommended',
'plugin:@typescript-eslint/recommended',
'plugin:prettier/recommended',
],
parserOptions: {
ecmaVersion: 2020,
},
rules: {
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
'vue/multi-word-component-names': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'prettier/prettier': [
'warn',
{
singleQuote: true,
semi: false,
trailingComma: 'es5',
printWidth: 100,
tabWidth: 2,
endOfLine: 'auto',
},
],
},
}
+8
View File
@@ -0,0 +1,8 @@
{
"singleQuote": true,
"semi": false,
"trailingComma": "es5",
"printWidth": 100,
"tabWidth": 2,
"endOfLine": "auto"
}
+7
View File
@@ -31,12 +31,19 @@
"@types/crypto-js": "^4.2.2",
"@types/element-resize-detector": "^1.1.6",
"@types/node": "^22.14.1",
"@typescript-eslint/eslint-plugin": "^8.34.0",
"@typescript-eslint/parser": "^8.34.0",
"@vitejs/plugin-vue": "^5.2.2",
"@vue/tsconfig": "^0.7.0",
"axios": "^1.8.4",
"crypto-js": "^4.2.0",
"eslint": "^9.28.0",
"eslint-config-prettier": "^10.1.5",
"eslint-plugin-prettier": "^5.4.1",
"eslint-plugin-vue": "^10.2.0",
"less": "^4.3.0",
"pinia": "^3.0.2",
"prettier": "^3.5.3",
"typescript": "~5.7.2",
"unplugin-auto-import": "^19.1.2",
"unplugin-vue-components-secondary": "^0.24.6",
+7
View File
@@ -0,0 +1,7 @@
<svg viewBox="0 0 80 56" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M37.5 6C37.7761 6 38 6.22386 38 6.5V13.5C38 13.7761 37.7761 14 37.5 14L9.5 14C9.22386 14 9 13.7761 9 13.5V6.5C9 6.22386 9.22386 6 9.5 6L37.5 6Z" fill="#3370FF"/>
<path d="M71.5 18C71.7761 18 72 18.2239 72 18.5V25.5C72 25.7761 71.7761 26 71.5 26H9.5C9.22386 26 9 25.7761 9 25.5V18.5C9 18.2239 9.22386 18 9.5 18L71.5 18Z" fill="#3370FF"/>
<path d="M50.5 30C50.7761 30 51 30.2239 51 30.5V37.5C51 37.7761 50.7761 38 50.5 38H9.5C9.22386 38 9 37.7761 9 37.5V30.5C9 30.2239 9.22386 30 9.5 30H50.5Z" fill="#3370FF"/>
<path d="M29.5 42C29.7761 42 30 42.2239 30 42.5V49.5C30 49.7761 29.7761 50 29.5 50H9.5C9.22386 50 9 49.7761 9 49.5V42.5C9 42.2239 9.22386 42 9.5 42H29.5Z" fill="#3370FF"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M7 2.00391V53.0016V54.0016H4V2.00391H7Z" fill="#DEE0E3"/>
</svg>

After

Width:  |  Height:  |  Size: 877 B

+7
View File
@@ -0,0 +1,7 @@
<svg viewBox="0 0 80 56" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M5 51H76L76 54H4V51H5Z" fill="#DEE0E3"/>
<path d="M11 29.5C11 29.2239 11.2239 29 11.5 29H20.5C20.7761 29 21 29.2239 21 29.5V48.5C21 48.7761 20.7761 49 20.5 49H11.5C11.2239 49 11 48.7761 11 48.5V29.5Z" fill="#3370FF"/>
<path d="M27 4.5C27 4.22386 27.2239 4 27.5 4H36.5C36.7761 4 37 4.22386 37 4.5V48.5C37 48.7761 36.7761 49 36.5 49H27.5C27.2239 49 27 48.7761 27 48.5V4.5Z" fill="#3370FF"/>
<path d="M43 19.5C43 19.2239 43.2239 19 43.5 19H52.5C52.7761 19 53 19.2239 53 19.5V48.5C53 48.7761 52.7761 49 52.5 49H43.5C43.2239 49 43 48.7761 43 48.5V19.5Z" fill="#3370FF"/>
<path d="M59 36.5C59 36.2239 59.2239 36 59.5 36H68.5C68.7761 36 69 36.2239 69 36.5V48.5C69 48.7761 68.7761 49 68.5 49H59.5C59.2239 49 59 48.7761 59 48.5V36.5Z" fill="#3370FF"/>
</svg>

After

Width:  |  Height:  |  Size: 872 B

+5
View File
@@ -0,0 +1,5 @@
<svg viewBox="0 0 80 56" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M37.686 13.231C37.2791 12.8242 36.6138 12.8437 36.2315 13.2737L34.8455 14.8327C34.1868 15.5735 33.2442 16.6333 32.1089 17.9092C29.8382 20.4611 26.7969 23.8773 23.714 27.3352C17.5164 34.2865 11.2327 41.313 10.6063 41.9394C10.0205 42.5252 10.0205 43.4749 10.6063 44.0607C11.1921 44.6465 12.1419 44.6465 12.7277 44.0607C13.4346 43.3537 19.8175 36.2136 25.9533 29.3316C29.0371 25.8728 32.0791 22.4557 34.3501 19.9034C35.4725 18.642 36.4066 17.5918 37.0644 16.8521L49.5834 29.3711C49.9141 29.7018 50.43 29.7591 50.8253 29.5089L72.851 15.5689L72.8605 15.5622C73.5392 15.0872 73.7043 14.1519 73.2292 13.4732C72.7567 12.7982 71.8291 12.6313 71.1513 13.0968L50.5191 26.0642L37.686 13.231Z" fill="#3370FF"/>
<path d="M11.667 26.5C10.8386 26.5 10.167 27.1716 10.167 28C10.167 28.8284 10.8386 29.5 11.667 29.5H33.6392L42.646 40.1151C42.8119 40.3107 43.0467 40.4351 43.3017 40.4624L71.4856 43.489C72.3058 43.589 73.0528 43.0065 73.1554 42.1861C73.2582 41.364 72.6751 40.6143 71.8531 40.5115L44.4294 37.5801L35.3277 26.853C35.1377 26.6291 34.8588 26.5 34.5651 26.5H11.667Z" fill="#00D6B9"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M7 2.00391V51.0016L76 51.0016L76 54.0016H4V2.00391H7Z" fill="#DEE0E3"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

+4
View File
@@ -0,0 +1,4 @@
<svg viewBox="0 0 80 56" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M39.1377 52.9999C52.3009 52.9999 63.0031 42.4632 63.2702 29.364C63.2758 29.0879 63.0515 28.8623 62.7753 28.8623C54.8961 28.8623 47.0169 28.8623 39.1377 28.8623C39.1377 19.4553 39.1377 11.5758 39.1377 5.22392C39.1377 4.94778 38.912 4.72409 38.636 4.72972C25.5367 4.99681 15 15.699 15 28.8623C15 42.1931 25.8068 52.9999 39.1377 52.9999Z" fill="#3370FF"/>
<path d="M46.6201 23.6894C45.3435 23.6894 44.3086 22.6545 44.3086 21.3779V3.5521C44.3086 3.25643 44.5649 3.02499 44.8583 3.06121C55.3368 4.35483 63.6432 12.6612 64.9368 23.1397C64.973 23.4331 64.7416 23.6894 64.4459 23.6894H46.6201Z" fill="#00D6B9"/>
</svg>

After

Width:  |  Height:  |  Size: 693 B

+12
View File
@@ -0,0 +1,12 @@
<svg viewBox="0 0 80 56" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13 7.16675C13 6.89061 13.2239 6.66675 13.5 6.66675H65.8333C66.1095 6.66675 66.3333 6.89061 66.3333 7.16675V17.3334H13V7.16675Z" fill="#3370FF"/>
<path d="M29 20H13V28H29V20Z" fill="#ADC6FF"/>
<path d="M47.6667 20H31.6667V28H47.6667V20Z" fill="#ADC6FF"/>
<path d="M66.3333 20H50.3333V28H66.3333V20Z" fill="#ADC6FF"/>
<path d="M29 41.3333H13V48C13 48.7364 13.597 49.3333 14.3333 49.3333H29V41.3333Z" fill="#ADC6FF"/>
<path d="M47.6667 41.3333H31.6667V49.3333H47.6667V41.3333Z" fill="#ADC6FF"/>
<path d="M66.3333 41.3333H50.3333V49.3333H65C65.7364 49.3333 66.3333 48.7364 66.3333 48V41.3333Z" fill="#ADC6FF"/>
<path d="M29 30.6667H13V38.6667H29V30.6667Z" fill="#ADC6FF"/>
<path d="M47.6667 30.6667H31.6667V38.6667H47.6667V30.6667Z" fill="#ADC6FF"/>
<path d="M66.3333 30.6667H50.3333V38.6667H66.3333V30.6667Z" fill="#ADC6FF"/>
</svg>

After

Width:  |  Height:  |  Size: 913 B

+65 -4
View File
@@ -9,7 +9,8 @@
"back": "Back",
"confirm": "Confirm",
"cancel": "Cancel",
"system_manage": "System Management"
"system_manage": "System Management",
"next": "Next"
},
"dashboard": {
"length_1_64_characters": "Name field length must be 1-64 characters",
@@ -43,7 +44,6 @@
"ds": {
"title": "Data Source",
"add": "Add Data Source",
"edit": "Edit Data Source",
"delete": "Delete Data Source",
"name": "Data Source Name",
"type": "Data Source Type",
@@ -51,6 +51,67 @@
"actions": "Actions",
"test_connection": "Test Connection",
"connection_success": "Connection Successful",
"connection_failed": "Connection Failed, Please Check Configuration"
"connection_failed": "Connection Failed, Please Check Configuration",
"Search Datasource": "Search Datasource",
"tables": "Tables",
"no_data_tip": "No data, select a table from left",
"comment": "Comment",
"table_schema": "Table Schema",
"preview": "Preview",
"preview_tip": "Preview 100 items",
"field": {
"name": "Name",
"type": "Type",
"comment": "Comment",
"custom_comment": "Custom Comment",
"status": "Status"
},
"edit": {
"table_comment": "Edit Table Comment",
"field_comment": "Edit Field Comment",
"table_comment_label": "Table Comment",
"field_comment_label": "Field Comment"
},
"form": {
"title": {
"add": "Add Datasource",
"edit": "Edit Datasource",
"choose_tables": "Choose Tables"
},
"base_info": "Base Info",
"choose_tables": "Choose Tables",
"name": "Name",
"description": "Description",
"host": "Host/Ip",
"port": "Port",
"username": "Username",
"password": "Password",
"database": "Database",
"connect_mode": "Connect Mode",
"service_name": "Service Name",
"extra_jdbc": "Extra JDBC String",
"schema": "Schema",
"get_schema": "Get Schema",
"file": "File",
"upload": "Upload",
"upload_tip": "Only support .xls, .xlsx, .csv, size less than 50MB.",
"version_tip": {
"sqlServer": "Supported version: 2012+",
"oracle": "Supported version: 12+",
"mysql": "Supported version: 5.6+",
"pg": "Supported version: 9.6+"
},
"selected": "Selected: {0}/{1}",
"validate": {
"name_required": "Please input name",
"name_length": "Length should be 1 to 50",
"type_required": "Please choose database type",
"host_required": "Please input host",
"port_required": "Please input port",
"database_required": "Please input database",
"mode_required": "Please choose mode",
"schema_required": "Please input schema"
}
}
}
}
}
+65 -4
View File
@@ -11,7 +11,8 @@
"cancel": "取消",
"system_manage": "系统管理",
"update_success": "更新",
"save_success": "保存成功"
"save_success": "保存成功",
"next": "下一步"
},
"dashboard": {
"length_1_64_characters": "名称字段长度1-64个字符",
@@ -45,7 +46,6 @@
"ds": {
"title": "数据源",
"add": "添加数据源",
"edit": "编辑数据源",
"delete": "删除数据源",
"name": "数据源名称",
"type": "数据源类型",
@@ -53,6 +53,67 @@
"actions": "操作",
"test_connection": "测试连接",
"connection_success": "连接成功",
"connection_failed": "连接失败,请检查配置"
"connection_failed": "连接失败,请检查配置",
"Search Datasource": "搜索数据源",
"tables": "数据表",
"no_data_tip": "暂无数据,请从左侧选择数据表",
"comment": "注释",
"table_schema": "表结构",
"preview": "预览",
"preview_tip": "预览100条数据",
"field": {
"name": "字段名",
"type": "类型",
"comment": "注释",
"custom_comment": "自定义注释",
"status": "状态"
},
"edit": {
"table_comment": "编辑表注释",
"field_comment": "编辑字段注释",
"table_comment_label": "表注释",
"field_comment_label": "字段注释"
},
"form": {
"title": {
"add": "添加数据源",
"edit": "编辑数据源",
"choose_tables": "选择数据表"
},
"base_info": "基本信息",
"choose_tables": "选择数据表",
"name": "名称",
"description": "描述",
"host": "主机/IP",
"port": "端口",
"username": "用户名",
"password": "密码",
"database": "数据库",
"connect_mode": "连接方式",
"service_name": "服务名",
"extra_jdbc": "额外 JDBC 参数",
"schema": "模式",
"get_schema": "获取模式",
"file": "文件",
"upload": "上传",
"upload_tip": "仅支持 .xls, .xlsx, .csv 格式,大小不超过50MB",
"version_tip": {
"sqlServer": "支持版本: 2012+",
"oracle": "支持版本: 12+",
"mysql": "支持版本: 5.6+",
"pg": "支持版本: 9.6+"
},
"selected": "已选择: {0}/{1}",
"validate": {
"name_required": "请输入名称",
"name_length": "长度应在1到50个字符之间",
"type_required": "请选择数据库类型",
"host_required": "请输入主机地址",
"port_required": "请输入端口号",
"database_required": "请输入数据库名",
"mode_required": "请选择连接方式",
"schema_required": "请输入模式名"
}
}
}
}
}
+10 -13
View File
@@ -7,8 +7,6 @@ import { store } from './index'
const { wsCache } = useCache()
interface UserState {
token: string
uid: string
@@ -17,7 +15,7 @@ interface UserState {
language: string
exp: number
time: number
[key: string]: string | number
[key: string]: string | number
}
export const UserStore = defineStore('user', {
@@ -29,7 +27,7 @@ export const UserStore = defineStore('user', {
oid: '',
language: 'zh-CN',
exp: 0,
time: 0
time: 0,
}
},
getters: {
@@ -53,10 +51,9 @@ export const UserStore = defineStore('user', {
},
getTime(): number {
return this.time
}
},
},
actions: {
async login(formData: { username: string; password: string }) {
const res: any = await AuthApi.login(formData)
this.setToken(res.access_token)
@@ -66,13 +63,13 @@ export const UserStore = defineStore('user', {
this.clear()
},
async info () {
async info() {
const res: any = await AuthApi.info()
const res_data = res || {}
const keys = ['uid', 'name', 'oid', 'language', 'exp', 'time'] as const
keys.forEach(key => {
keys.forEach((key) => {
const dkey = key === 'uid' ? 'id' : key
const value = res_data[dkey]
if (key === 'exp' || key === 'time') {
@@ -122,11 +119,11 @@ export const UserStore = defineStore('user', {
},
clear() {
const keys: string[] = ['token', 'uid', 'name', 'oid', 'language', 'exp', 'time']
keys.forEach(key => wsCache.delete('user.' + key))
}
}
keys.forEach((key) => wsCache.delete('user.' + key))
},
},
})
export const useUserStore = () => {
return UserStore(store)
}
}
+174 -24
View File
@@ -2,10 +2,16 @@
import type {ChatMessage} from "@/api/chat.ts";
import {computed, nextTick, ref} from "vue";
import type {TabsPaneContext} from 'element-plus-secondary'
import {Loading} from "@element-plus/icons-vue";
import Component from "./component/Component.vue"
import ChartComponent from "./component/ChartComponent.vue"
import type {ChartTypes} from "@/views/chat/component/BaseChart.ts";
import {ArrowDown} from "@element-plus/icons-vue";
import ICON_BAR from "@/assets/svg/chart/bar.svg"
import ICON_COLUMN from "@/assets/svg/chart/column.svg"
import ICON_LINE from "@/assets/svg/chart/line.svg"
import ICON_PIE from "@/assets/svg/chart/pie.svg"
import ICON_TABLE from "@/assets/svg/chart/table.svg"
import {find} from "lodash-es";
const props = defineProps<{
message?: ChatMessage
@@ -29,10 +35,6 @@ const renderChartThinking = computed(() => {
return props.message?.record?.chart_answer
})
const handleClick = (tab: TabsPaneContext, event: Event) => {
console.log(tab, event)
}
const dataObject = computed<{
fields: Array<string>,
data: Array<{ [key: string]: any }>,
@@ -92,10 +94,59 @@ const chartType = computed<ChartTypes>({
}
})
function clickTab(type: "chart" | "sql") {
settings.value.type = type
}
const chartSelectRef = ref()
function openChartSelect() {
chartSelectRef.value.toggleMenu()
}
const chartTypeList = computed(() => {
const _list = [{
value: "table",
icon: ICON_TABLE
}]
if (chartObject.value) {
switch (chartObject.value.type) {
case "table":
break
case "column":
case "bar":
case "line":
_list.push({
value: "column",
icon: ICON_COLUMN
})
_list.push({
value: "bar",
icon: ICON_BAR
})
_list.push({
value: "line",
icon: ICON_LINE
})
break
case "pie":
_list.push({
value: "pie",
icon: ICON_PIE
})
}
}
return _list;
})
const currentChartTypeIcon = computed(() => {
return find(chartTypeList.value, c => c.value === chartType.value)?.icon ?? ICON_TABLE
})
const chartRef = ref()
function onTypeChange(type: string) {
console.log(type)
function onTypeChange() {
nextTick(() => {
chartRef.value?.destroyChart()
chartRef.value?.renderChart()
@@ -112,21 +163,36 @@ function onTypeChange(type: string) {
<div v-if="message.isTyping">Thinking ...</div>
<div v-if="chartObject.title && !message.isTyping">{{ chartObject.title }}</div>
</div>
<el-tabs v-model="settings.type" class="type-tabs" @tab-click="handleClick" tab-position="top">
<el-tab-pane label="Chart" name="chart">
<template #label>
<el-select v-model="chartType" style="width: 80px" :disabled="settings.type!== 'chart'"
@change="onTypeChange">
<el-option value="table">table</el-option>
<el-option value="column">column</el-option>
<el-option value="bar">bar</el-option>
<el-option value="line">line</el-option>
<el-option value="pie">pie</el-option>
</el-select>
</template>
</el-tab-pane>
<el-tab-pane label="SQL" name="sql"></el-tab-pane>
</el-tabs>
<div class="tab-container">
<div class="base-chart-choose-btn" @click="clickTab('chart')">
<div class="chart-choose-btn"
:class="{'active': settings.type === 'chart', 'no-click': settings.type !== 'chart'}"
@click="openChartSelect">
<el-icon size="34">
<component :is="currentChartTypeIcon"/>
</el-icon>
<el-icon>
<ArrowDown/>
</el-icon>
</div>
<el-select v-model="chartType" class="inner-select" :disabled="settings.type!== 'chart'"
ref="chartSelectRef"
@change="onTypeChange">
<el-option v-for="t in chartTypeList " :key="t.value" :value="t.value">
<div class="inner-chart-option">
<el-icon size="34">
<component :is="t.icon"/>
</el-icon>
{{ t.value }}
</div>
</el-option>
</el-select>
</div>
<div class="chart-choose-btn" :class="{'active': settings.type !== 'chart'}" @click="clickTab('sql')">
SQL
</div>
</div>
</el-header>
<el-container direction="vertical">
<template v-if="message.record">
@@ -163,7 +229,7 @@ function onTypeChange(type: string) {
<div>
<div v-if="message.record.chart" class="chart-base-container">
<div>
<Component
<ChartComponent
ref="chartRef"
v-if="message.record.id"
:id="message.record.id"
@@ -205,4 +271,88 @@ function onTypeChange(type: string) {
padding: 20px;
background: rgba(224, 224, 226, 0.29);
}
.tab-container {
display: flex;
flex-direction: row;
align-items: center;
gap: 4px;
}
.base-chart-choose-btn {
cursor: pointer;
color: var(--ed-color-primary);
font-size: 16px;
font-weight: 500;
height: 34px;
border-radius: 4px;
position: relative;
&:hover {
background: var(--ed-color-primary-light-5);
color: var(--ed-color-primary-light-9);
}
.inner-select {
position: absolute;
:deep(.ed-select__wrapper) {
height: 0;
padding: unset;
min-height: unset;
line-height: unset;
}
:deep(.ed-select__suffix) {
display: none;
}
:deep(.ed-select__selected-item) {
display: none;
}
}
}
.inner-chart-option {
display: flex;
align-items: center;
flex-direction: row;
padding-right: 4px;
}
.chart-choose-btn {
cursor: pointer;
color: var(--ed-color-primary);
font-size: 16px;
font-weight: 500;
height: 34px;
border-radius: 4px;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
padding: 0 4px;
&.no-click {
pointer-events: none;
}
&.active {
background: var(--ed-color-primary-light-7);
color: var(--ed-color-primary);
}
&:hover {
background: var(--ed-color-primary-light-5);
color: var(--ed-color-primary-light-9);
}
}
</style>
+52 -10
View File
@@ -4,13 +4,14 @@ import {ChatInfo} from "@/api/chat.ts";
import {computed, onMounted, ref} from "vue";
import {datasourceApi} from "@/api/datasource.ts";
import DatasourceItemCard from '../ds/DatasourceItemCard.vue'
import { useI18n } from 'vue-i18n'
const { t } = useI18n()
import {useI18n} from 'vue-i18n'
const {t} = useI18n()
const props = withDefaults(defineProps<{
modelValue?: number
currentChat: ChatInfo
}>(),
{}
modelValue?: number
currentChat: ChatInfo
}>(),
{}
)
const dsList = ref<Array<any>>([])
@@ -50,9 +51,27 @@ function listDs() {
})
}
function showDs() {
listDs()
const dialogVisible = ref(false)
const DatasourceListRef = ref()
const innerDs = ref()
function showDs() {
innerDs.value = props.modelValue
listDs()
dialogVisible.value = true
}
function selectDsInDialog(ds: any) {
innerDs.value = ds.id
}
function confirmSelectDs() {
if (innerDs.value) {
emits("update:modelValue", innerDs.value)
dialogVisible.value = false
}
}
@@ -75,7 +94,7 @@ onMounted(() => {
</div>
<div class="ds-row-container">
<template v-for="(item, _index) in dsList" :key="_index">
<DatasourceItemCard :ds="item" @click="selectDs(item)"
<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>
@@ -93,6 +112,28 @@ onMounted(() => {
</div>
</template>
</div>
<el-drawer v-model="dialogVisible" title="Choose Datasource" ref="DatasourceListRef" direction="btt"
size="100%">
<el-scrollbar>
<div class="ds-row-container">
<template v-for="(item, _index) in dsList" :key="_index">
<DatasourceItemCard :ds="item" @click="selectDsInDialog(item)"
class="ds-card" :class="[item?.id===innerDs? 'ds-card-selected': '']"/>
</template>
</div>
</el-scrollbar>
<template #footer>
<div class="dialog-footer">
<el-button @click="dialogVisible = false">Cancel</el-button>
<el-button type="primary" @click="confirmSelectDs">
Confirm
</el-button>
</div>
</template>
</el-drawer>
</ChatBlock>
</template>
@@ -126,7 +167,8 @@ onMounted(() => {
.ds-card-selected {
box-shadow: 0 1px 3px var(--ed-color-primary-light-5);
border: 1px solid var(--ed-color-primary-light-5);
}
</style>
</style>
+93 -109
View File
@@ -5,89 +5,80 @@
<el-button text style="color: #fff" :icon="ArrowLeft" @click="back()" />
{{ props.dsName }}
</div>
<!-- <el-button type="primary" @click="save()"> Save </el-button> -->
</div>
<div class="container">
<div class="left-side">
<div style="display: flex;justify-content: space-between;align-items: center;">
<span>Tables</span>
<el-button style="padding: 12px;" text @click="editTables(ds)" :icon="CreditCard"/>
<div style="display: flex; justify-content: space-between; align-items: center">
<span>{{ t('ds.tables') }}</span>
<el-button style="padding: 12px" text @click="editTables(ds)" :icon="CreditCard" />
</div>
<el-input
style="margin: 16px 0"
v-model="searchValue"
placeholder="Search"
:placeholder="t('ds.Search Datasource')"
/>
<div>
<div
v-for="item in tableList"
class="list-item_primary"
@click="clickTable(item)"
>
<div v-for="item in tableList" class="list-item_primary" @click="clickTable(item)">
{{ item.table_name }}
</div>
</div>
</div>
<div class="right-side">
<div v-if="fieldList.length === 0">
No data, select a table from left
{{ t('ds.no_data_tip') }}
</div>
<div v-else>
<div
style="
display: flex;
justify-content: space-between;
align-items: center;
"
>
<div
style="display: flex; justify-content: start; align-items: center"
>
<div style="display: flex; justify-content: space-between; align-items: center">
<div style="display: flex; justify-content: start; align-items: center">
<span>{{ currentTable.table_name }}</span>
<el-divider direction="vertical" />
<span>Comment:</span>
<span>{{ t('ds.comment') }}:</span>
<span>{{ currentTable.custom_comment }}</span>
<el-button style="margin-left: 10px;" text class="action-btn" :icon="IconOpeEdit" @click="editTable"/>
<el-button
style="margin-left: 10px"
text
class="action-btn"
:icon="IconOpeEdit"
@click="editTable"
/>
</div>
</div>
<el-tabs
v-model="activeName"
class="demo-tabs"
@tab-click="handleClick"
>
<el-tab-pane label="Table Schema" name="schema">
<el-tabs v-model="activeName" class="demo-tabs" @tab-click="handleClick">
<el-tab-pane :label="t('ds.table_schema')" name="schema">
<el-table :data="fieldList" style="width: 100%">
<el-table-column prop="field_name" label="Name" width="180" />
<el-table-column prop="field_type" label="Type" width="180" />
<el-table-column prop="field_comment" label="Comment" />
<el-table-column label="Custom Comment">
<el-table-column prop="field_name" :label="t('ds.field.name')" width="180" />
<el-table-column prop="field_type" :label="t('ds.field.type')" width="180" />
<el-table-column prop="field_comment" :label="t('ds.field.comment')" />
<el-table-column :label="t('ds.field.custom_comment')">
<template #default="scope">
<div class="field-comment">
<span>{{ scope.row.custom_comment }}</span>
<el-button text class="action-btn" :icon="IconOpeEdit" @click="editField(scope.row)"/>
<el-button
text
class="action-btn"
:icon="IconOpeEdit"
@click="editField(scope.row)"
/>
</div>
</template>
</el-table-column>
<el-table-column label="Status" width="180">
<el-table-column :label="t('ds.field.status')" width="180">
<template #default="scope">
<div style="display: flex; align-items: center">
<el-switch v-model="scope.row.checked" size="small" @change="changeStatus(scope.row)"/>
<el-switch
v-model="scope.row.checked"
size="small"
@change="changeStatus(scope.row)"
/>
</div>
</template>
</el-table-column>
</el-table>
</el-tab-pane>
<el-tab-pane label="Preview" name="preview">
<div style="margin: 16px 0;">Preview 100 items</div>
<el-table
:data="previewData.data"
style="width: 100%; height: 600px"
>
<el-table-column
v-for="c in previewData.fields"
:prop="c"
:label="c"
/>
<el-tab-pane :label="t('ds.preview')" name="preview">
<div style="margin: 16px 0">{{ t('ds.preview_tip') }}</div>
<el-table :data="previewData.data" style="width: 100%; height: 600px">
<el-table-column v-for="c in previewData.fields" :prop="c" :label="c" />
</el-table>
</el-tab-pane>
</el-tabs>
@@ -97,85 +88,78 @@
<el-dialog
v-model="tableDialog"
title="Edit Table Comment"
:title="t('ds.edit.table_comment')"
width="600"
:destroy-on-close="true"
:close-on-click-modal="false"
@closed="closeTable"
>
<div>Table Comment</div>
<div>{{ t('ds.edit.table_comment_label') }}</div>
<el-input v-model="currentTable.custom_comment" />
<div style="display: flex; justify-content: flex-end; margin-top: 20px">
<el-button @click="closeTable">Cancel</el-button>
<el-button
type="primary"
@click="saveTable"
>Confirm</el-button
>
<el-button @click="closeTable">{{ t('common.cancel') }}</el-button>
<el-button type="primary" @click="saveTable">{{ t('common.confirm') }}</el-button>
</div>
</el-dialog>
<el-dialog
v-model="fieldDialog"
title="Edit Field Comment"
:title="t('ds.edit.field_comment')"
width="600"
:destroy-on-close="true"
:close-on-click-modal="false"
@closed="closeField"
>
<div>Field Comment</div>
<div>{{ t('ds.edit.field_comment_label') }}</div>
<el-input v-model="currentField.custom_comment" />
<div style="display: flex; justify-content: flex-end; margin-top: 20px">
<el-button @click="closeField">Cancel</el-button>
<el-button
type="primary"
@click="saveField"
>Confirm</el-button
>
<el-button @click="closeField">{{ t('common.cancel') }}</el-button>
<el-button type="primary" @click="saveField">{{ t('common.confirm') }}</el-button>
</div>
</el-dialog>
</div>
<DsForm ref="dsForm" @refresh="refresh"/>
<DsForm ref="dsForm" @refresh="refresh" />
</template>
<script lang="tsx" setup>
import { ref } from "vue";
import { datasourceApi } from "@/api/datasource";
import { onMounted } from "vue";
import { ArrowLeft } from "@element-plus/icons-vue";
import type { TabsPaneContext } from "element-plus-secondary";
<script setup lang="tsx">
import { ref } from 'vue'
import { useI18n } from 'vue-i18n'
import { datasourceApi } from '@/api/datasource'
import { onMounted } from 'vue'
import { ArrowLeft } from '@element-plus/icons-vue'
import type { TabsPaneContext } from 'element-plus-secondary'
import IconOpeEdit from '@/assets/svg/operate/ope-edit.svg'
import {CreditCard} from '@element-plus/icons-vue'
import { CreditCard } from '@element-plus/icons-vue'
import DsForm from './form.vue'
const props = defineProps({
dsId: { type: [Number], required: true },
dsName: { type: [String], required: true },
});
})
const dsId = ref<Number>(0);
const searchValue = ref("");
const tableList = ref<any>([]);
const currentTable = ref<any>({});
const currentField = ref<any>({});
const fieldList = ref<any>([]);
const previewData = ref<any>({});
const { t } = useI18n()
const activeName = ref("schema");
const dsId = ref<Number>(0)
const searchValue = ref('')
const tableList = ref<any>([])
const currentTable = ref<any>({})
const currentField = ref<any>({})
const fieldList = ref<any>([])
const previewData = ref<any>({})
const activeName = ref('schema')
const tableDialog = ref<boolean>(false)
const fieldDialog = ref<boolean>(false)
const dsForm = ref()
const ds = ref<any>({})
const buildData = () => {
return { table: currentTable.value, fields: fieldList.value };
};
return { table: currentTable.value, fields: fieldList.value }
}
const back = () => {
history.back();
};
history.back()
}
// const save = () => {
// datasourceApi.edit(buildData()).then(() => {
@@ -199,8 +183,8 @@ const saveTable = () => {
datasourceApi.saveTable(currentTable.value).then(() => {
closeTable()
ElMessage({
message: "Edit Success",
type: "success",
message: 'Edit Success',
type: 'success',
showClose: true,
})
})
@@ -224,30 +208,30 @@ const saveField = () => {
datasourceApi.saveField(currentField.value).then(() => {
closeField()
ElMessage({
message: "Edit Success",
type: "success",
message: 'Edit Success',
type: 'success',
showClose: true,
})
})
}
const clickTable = (table: any) => {
currentTable.value = table;
currentTable.value = table
datasourceApi.fieldList(table.id).then((res) => {
fieldList.value = res;
fieldList.value = res
datasourceApi.previewData(dsId.value, buildData()).then((res) => {
previewData.value = res;
});
});
};
previewData.value = res
})
})
}
const handleClick = (tab: TabsPaneContext) => {
if (tab.paneName === "preview") {
if (tab.paneName === 'preview') {
datasourceApi.previewData(dsId.value, buildData()).then((res) => {
previewData.value = res;
});
previewData.value = res
})
}
};
}
const editTables = (item: any) => {
dsForm.value.open(item, true)
@@ -258,19 +242,19 @@ const refresh = () => {
}
const init = () => {
dsId.value = props.dsId;
dsId.value = props.dsId
datasourceApi.getDs(dsId.value).then((res) => {
ds.value = res
fieldList.value = [];
fieldList.value = []
datasourceApi.tableList(props.dsId).then((res) => {
tableList.value = res;
});
tableList.value = res
})
})
}
onMounted(() => {
init()
});
})
</script>
<style lang="less" scoped>
@@ -288,7 +272,7 @@ onMounted(() => {
}
.title {
color: #fff;
font-family: var(--de-custom_font, "PingFang");
font-family: var(--de-custom_font, 'PingFang');
font-size: 16px;
font-weight: 400;
display: flex;
@@ -316,11 +300,11 @@ onMounted(() => {
}
}
.field-comment{
display: flex;
.field-comment {
display: flex;
align-items: center;
.action-btn{
.action-btn {
margin-left: 10px;
}
}
+211 -231
View File
@@ -8,7 +8,7 @@
@closed="close"
modal-class="add-datasource_dialog"
>
<template #header="">
<template #header>
<div style="display: flex">
<div style="margin-right: 24px">{{ dialogTitle }}</div>
<el-steps
@@ -18,32 +18,22 @@
custom
style="max-width: 400px; flex: 1"
>
<el-step title="Base Info" />
<el-step title="Choose Tables" />
<el-step :title="t('ds.form.base_info')" />
<el-step :title="t('ds.form.choose_tables')" />
</el-steps>
</div>
</template>
<div v-show="active === 0" class="container">
<el-form
:model="form"
label-position="top"
label-width="auto"
ref="dsFormRef"
:rules="rules"
>
<el-form-item label="Name" prop="name">
<el-form :model="form" label-position="top" label-width="auto" ref="dsFormRef" :rules="rules">
<el-form-item :label="t('ds.form.name')" prop="name">
<el-input v-model="form.name" />
</el-form-item>
<el-form-item label="Description">
<el-form-item :label="t('ds.form.description')">
<el-input v-model="form.description" :rows="2" type="textarea" />
</el-form-item>
<el-form-item label="Type" prop="type">
<el-select
v-model="form.type"
placeholder="Select Type"
:disabled="!isCreate"
>
<el-form-item :label="t('ds.type')" prop="type">
<el-select v-model="form.type" placeholder="Select Type" :disabled="!isCreate">
<el-option
v-for="item in dsType"
:key="item.value"
@@ -62,49 +52,53 @@
:before-upload="beforeUpload"
:on-success="onSuccess"
>
<el-button :disabled="!isCreate">Upload</el-button>
<el-button :disabled="!isCreate">{{ t('ds.form.upload.button') }}</el-button>
<template #tip>
<div class="el-upload__tip">
Only support .xls, .xlsx, .csv, size less than 50MB.
</div>
<div class="el-upload__tip">{{ t('ds.form.upload.tip') }}</div>
</template>
</el-upload>
</el-form-item>
</div>
<div v-else>
<el-form-item label="Host/Ip" prop="host">
<el-form-item :label="t('ds.form.host')" prop="host">
<el-input v-model="form.host" />
</el-form-item>
<el-form-item label="Port" prop="port">
<el-form-item :label="t('ds.form.port')" prop="port">
<el-input v-model="form.port" />
</el-form-item>
<el-form-item label="Username">
<el-form-item :label="t('ds.form.username')">
<el-input v-model="form.username" />
</el-form-item>
<el-form-item label="Password">
<el-form-item :label="t('ds.form.password')">
<el-input v-model="form.password" type="password" show-password />
</el-form-item>
<el-form-item label="Database" prop="database">
<el-form-item :label="t('ds.form.database')" prop="database">
<el-input v-model="form.database" />
</el-form-item>
<el-form-item label="Connect Mode" v-if="form.type === 'oracle'" prop="mode">
<el-form-item
:label="t('ds.form.connect_mode')"
v-if="form.type === 'oracle'"
prop="mode"
>
<el-radio-group v-model="form.mode">
<el-radio value="service_name">Service Name</el-radio>
<el-radio value="sid">SID</el-radio>
<el-radio value="service_name">{{ t('ds.form.mode.service_name') }}</el-radio>
<el-radio value="sid">{{ t('ds.form.mode.sid') }}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="Extra JDBC String">
<el-form-item :label="t('ds.form.extra_jdbc')">
<el-input v-model="form.extraJdbc" />
</el-form-item>
<el-form-item label="Schema" v-if="haveSchema.includes(form.type)" prop="dbSchema">
<el-form-item
:label="t('ds.form.schema')"
v-if="haveSchema.includes(form.type)"
prop="dbSchema"
>
<el-input v-model="form.dbSchema" />
<el-button link type="primary" :icon="Plus" v-if="false"
>Get Schema</el-button
>
<el-button link type="primary" :icon="Plus" v-if="false">Get Schema</el-button>
</el-form-item>
<span v-if="form.type === 'sqlServer'">Supported version: 2012+</span>
<span v-else-if="form.type === 'oracle'">Supported version: 12+</span>
<span v-else-if="form.type === 'mysql'">Supported version: 5.6+</span>
<span v-if="form.type === 'sqlServer'"> Supported version: 2012+ </span>
<span v-else-if="form.type === 'oracle'"> Supported version: 12+ </span>
<span v-else-if="form.type === 'mysql'"> Supported version: 5.6+ </span>
<span v-else-if="form.type === 'pg'">Supported version: 9.6+</span>
</div>
</el-form>
@@ -130,242 +124,228 @@
</template>
</FixedSizeList>
</el-checkbox-group>
<span>Selected: {{ checkList.length }}/{{ tableList.length }}</span>
<span>{{ t('ds.form.selected', [checkList.length, tableList.length]) }}</span>
</div>
<div style="display: flex; justify-content: flex-end; margin-top: 20px">
<el-button @click="close">Cancel</el-button>
<el-button @click="close">{{ t('common.cancel') }}</el-button>
<el-button v-show="!isCreate && !isEditTable && form.type !== 'excel'" @click="check">
{{ t('ds.test_connection') }}
</el-button>
<el-button v-show="active === 0 && isCreate" type="primary" @click="next(dsFormRef)">
{{ t('common.next') }}
</el-button>
<el-button v-show="active === 1 && isCreate" @click="preview">
{{ t('ds.preview') }}
</el-button>
<el-button
v-show="!isCreate && !isEditTable && form.type !== 'excel'"
@click="check"
>Test Connect</el-button
>
<el-button
v-show="active === 0 && isCreate"
type="primary"
@click="next(dsFormRef)"
>Next</el-button
>
<el-button v-show="active === 1 && isCreate" @click="preview"
>Preview</el-button
>
<el-button
:loading="saveLoading"
:loading="saveLoading"
v-show="active === 1 || !isCreate"
type="primary"
@click="save(dsFormRef)"
>Save</el-button
>
{{ t('common.save') }}
</el-button>
</div>
</el-dialog>
</template>
<script lang="ts" setup>
import { ref, reactive } from "vue";
import { datasourceApi } from "@/api/datasource";
import { encrypted, decrypted } from "./js/aes";
import { ElMessage } from "element-plus-secondary";
import type { FormInstance, FormRules } from "element-plus-secondary";
import FixedSizeList from "element-plus-secondary/es/components/virtual-list/src/components/fixed-size-list.mjs";
import { Plus } from "@element-plus/icons-vue";
import { useCache } from "@/utils/useCache";
import { dsType, haveSchema } from "@/views/ds/js/ds-type";
import { ref, reactive } from 'vue'
import { useI18n } from 'vue-i18n'
import { datasourceApi } from '@/api/datasource'
import { encrypted, decrypted } from './js/aes'
import { ElMessage } from 'element-plus-secondary'
import type { FormInstance, FormRules } from 'element-plus-secondary'
import FixedSizeList from 'element-plus-secondary/es/components/virtual-list/src/components/fixed-size-list.mjs'
import { Plus } from '@element-plus/icons-vue'
import { useCache } from '@/utils/useCache'
import { dsType, haveSchema } from '@/views/ds/js/ds-type'
const { wsCache } = useCache();
const dsFormRef = ref<FormInstance>();
const emit = defineEmits(["refresh"]);
const active = ref(0);
const isCreate = ref(true);
const isEditTable = ref(false);
const checkList = ref<any>([]);
const tableList = ref<any>([]);
const excelUploadSuccess = ref(false);
const tableListLoading = ref(false);
const token = wsCache.get("user.token");
const headers = ref<any>({ "X-SQLBOT-TOKEN": `Bearer ${token}` });
const dialogTitle = ref("");
const { wsCache } = useCache()
const dsFormRef = ref<FormInstance>()
const emit = defineEmits(['refresh'])
const active = ref(0)
const isCreate = ref(true)
const isEditTable = ref(false)
const checkList = ref<any>([])
const tableList = ref<any>([])
const excelUploadSuccess = ref(false)
const tableListLoading = ref(false)
const token = wsCache.get('user.token')
const headers = ref<any>({ 'X-SQLBOT-TOKEN': `Bearer ${token}` })
const dialogTitle = ref('')
const getUploadURL = import.meta.env.VITE_API_BASE_URL + '/datasource/uploadExcel'
const saveLoading = ref<boolean>(false)
const { t } = useI18n()
const rules = reactive<FormRules>({
name: [
{ required: true, message: "Please input name", trigger: "blur" },
{ min: 1, max: 50, message: "Length should be 1 to 50", trigger: "blur" },
{ required: true, message: t('ds.form.validate.name_required'), trigger: 'blur' },
{ min: 1, max: 50, message: t('ds.form.validate.name_length'), trigger: 'blur' },
],
type: [
{ required: true, message: "Please choose database type" ,trigger: "change"}
],
host: [
{ required: true, message: "Please input host" ,trigger: "blur"}
],
port: [
{ required: true, message: "Please input port" ,trigger: "blur"}
],
database: [
{ required: true, message: "Please input database" ,trigger: "blur"}
],
mode: [
{ required: true, message: "Please choose mode" ,trigger: "change"}
],
dbSchema: [
{ required: true, message: "Please input schema" ,trigger: "blur"}
]
});
type: [{ required: true, message: t('ds.form.validate.type_required'), trigger: 'change' }],
host: [{ required: true, message: 'Please input host', trigger: 'blur' }],
port: [{ required: true, message: 'Please input port', trigger: 'blur' }],
database: [{ required: true, message: 'Please input database', trigger: 'blur' }],
mode: [{ required: true, message: 'Please choose mode', trigger: 'change' }],
dbSchema: [{ required: true, message: 'Please input schema', trigger: 'blur' }],
})
const dialogVisible = ref<boolean>(false);
const dialogVisible = ref<boolean>(false)
const form = ref<any>({
name: "",
description: "",
type: "mysql",
configuration: "",
driver: "",
host: "",
name: '',
description: '',
type: 'mysql',
configuration: '',
driver: '',
host: '',
port: 0,
username: "",
password: "",
database: "",
extraJdbc: "",
dbSchema: "",
filename: "",
username: '',
password: '',
database: '',
extraJdbc: '',
dbSchema: '',
filename: '',
sheets: [],
mode: "service_name",
});
mode: 'service_name',
})
const close = () => {
dialogVisible.value = false;
isCreate.value = true;
active.value = 0;
isEditTable.value = false;
checkList.value = [];
tableList.value = [];
excelUploadSuccess.value = false;
dialogVisible.value = false
isCreate.value = true
active.value = 0
isEditTable.value = false
checkList.value = []
tableList.value = []
excelUploadSuccess.value = false
saveLoading.value = false
};
}
const open = (item: any, editTable: boolean = false) => {
isEditTable.value = false;
isEditTable.value = false
if (item) {
dialogTitle.value = "Edit Datasource";
isCreate.value = false;
form.value.id = item.id;
form.value.name = item.name;
form.value.description = item.description;
form.value.type = item.type;
form.value.configuration = item.configuration;
dialogTitle.value = editTable ? t('ds.form.title.choose_tables') : t('ds.form.title.edit')
isCreate.value = false
form.value.id = item.id
form.value.name = item.name
form.value.description = item.description
form.value.type = item.type
form.value.configuration = item.configuration
if (item.configuration) {
const configuration = JSON.parse(decrypted(item.configuration));
form.value.host = configuration.host;
form.value.port = configuration.port;
form.value.username = configuration.username;
form.value.password = configuration.password;
form.value.database = configuration.database;
form.value.extraJdbc = configuration.extraJdbc;
form.value.dbSchema = configuration.dbSchema;
form.value.filename = configuration.filename;
form.value.sheets = configuration.sheets;
form.value.mode = configuration.mode;
const configuration = JSON.parse(decrypted(item.configuration))
form.value.host = configuration.host
form.value.port = configuration.port
form.value.username = configuration.username
form.value.password = configuration.password
form.value.database = configuration.database
form.value.extraJdbc = configuration.extraJdbc
form.value.dbSchema = configuration.dbSchema
form.value.filename = configuration.filename
form.value.sheets = configuration.sheets
form.value.mode = configuration.mode
}
if (editTable) {
dialogTitle.value = "Choose Tables";
active.value = 1;
isEditTable.value = true;
isCreate.value = false;
dialogTitle.value = 'Choose Tables'
active.value = 1
isEditTable.value = true
isCreate.value = false
// request tables and check tables
datasourceApi.tableList(item.id).then((res) => {
checkList.value = res.map((ele: any) => {
return ele.table_name;
});
if (item.type === "excel") {
tableList.value = form.value.sheets;
return ele.table_name
})
if (item.type === 'excel') {
tableList.value = form.value.sheets
} else {
tableListLoading.value = true;
tableListLoading.value = true
const requestObj = buildConf()
datasourceApi
.getTablesByConf(requestObj)
.then((table) => {
tableList.value = table;
tableList.value = table
checkList.value = checkList.value.filter((ele: string) => {
return table
.map((ele: any) => {
return ele.tableName;
return ele.tableName
})
.includes(ele);
});
.includes(ele)
})
})
.finally(() => {
tableListLoading.value = false;
});
tableListLoading.value = false
})
}
});
})
}
} else {
dialogTitle.value = "Add Datasource";
isCreate.value = true;
isEditTable.value = false;
checkList.value = [];
tableList.value = [];
dialogTitle.value = t('ds.form.title.add')
isCreate.value = true
isEditTable.value = false
checkList.value = []
tableList.value = []
form.value = {
name: "",
description: "",
type: "mysql",
configuration: "",
driver: "",
host: "",
name: '',
description: '',
type: 'mysql',
configuration: '',
driver: '',
host: '',
port: 0,
username: "",
password: "",
database: "",
extraJdbc: "",
dbSchema: "",
filename: "",
username: '',
password: '',
database: '',
extraJdbc: '',
dbSchema: '',
filename: '',
sheets: [],
mode: "service_name",
mode: 'service_name',
}
}
dialogVisible.value = true;
};
dialogVisible.value = true
}
const save = async (formEl: FormInstance | undefined) => {
if (!formEl) return;
if (!formEl) return
await formEl.validate((valid) => {
if (valid) {
saveLoading.value = true
const list = tableList.value
.filter((ele: any) => {
return checkList.value.includes(ele.tableName);
return checkList.value.includes(ele.tableName)
})
.map((ele: any) => {
return { table_name: ele.tableName, table_comment: ele.tableComment };
});
return { table_name: ele.tableName, table_comment: ele.tableComment }
})
const requestObj = buildConf();
const requestObj = buildConf()
if (form.value.id) {
if (!isEditTable.value) {
// only update datasource config info
datasourceApi.update(requestObj).then((res) => {
console.log(res);
close();
emit("refresh");
});
console.log(res)
close()
emit('refresh')
})
} else {
// save table and field
datasourceApi.chooseTables(form.value.id, list).then(() => {
close();
emit("refresh");
});
close()
emit('refresh')
})
}
} else {
requestObj.tables = list;
requestObj.tables = list
datasourceApi.add(requestObj).then((res: any) => {
console.log(res);
close();
emit("refresh");
});
console.log(res)
close()
emit('refresh')
})
}
}
});
};
})
}
const buildConf = () => {
form.value.configuration = encrypted(
@@ -395,79 +375,79 @@ const buildConf = () => {
delete obj.sheets
delete obj.mode
return obj
};
}
const check = () => {
const requestObj = buildConf();
const requestObj = buildConf()
datasourceApi.check(requestObj).then((res: any) => {
if (res) {
ElMessage({
message: "Connect success",
type: "success",
message: t('ds.form.connect.success'),
type: 'success',
showClose: true,
});
})
} else {
ElMessage({
message: "Connect failed",
type: "error",
message: t('ds.form.connect.failed'),
type: 'error',
showClose: true,
});
})
}
});
};
})
}
const next = async (formEl: FormInstance | undefined) => {
if (!formEl) return;
if (!formEl) return
await formEl.validate((valid) => {
if (valid) {
if (form.value.type === "excel") {
if (form.value.type === 'excel') {
// next, show tables
if (excelUploadSuccess.value) {
active.value++;
active.value++
}
} else {
// check status if success do next
const requestObj = buildConf();
const requestObj = buildConf()
datasourceApi.check(requestObj).then((res: boolean) => {
if (res) {
active.value++;
active.value++
// request tables
datasourceApi.getTablesByConf(requestObj).then((res) => {
tableList.value = res;
});
tableList.value = res
})
} else {
ElMessage({
message: "Connect failed",
type: "error",
message: 'Connect failed',
type: 'error',
showClose: true,
});
})
}
});
})
}
}
});
};
})
}
const preview = () => {
active.value--;
};
active.value--
}
const beforeUpload = (rawFile: any) => {
if (rawFile.size / 1024 / 1024 > 50) {
ElMessage.error("File size can not exceed 50MB!");
return false;
ElMessage.error('File size can not exceed 50MB!')
return false
}
return true;
};
return true
}
const onSuccess = (response: any) => {
form.value.filename = response.data.filename;
form.value.sheets = response.data.sheets;
tableList.value = response.data.sheets;
excelUploadSuccess.value = true;
};
form.value.filename = response.data.filename
form.value.sheets = response.data.sheets
tableList.value = response.data.sheets
excelUploadSuccess.value = true
}
defineExpose({ open });
defineExpose({ open })
</script>
<style lang="less">
.add-datasource_dialog {
+52 -45
View File
@@ -3,59 +3,78 @@
<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="t('ds.Search Datasource')"
class="input-with-select"
clearable
@change="searchHandle"
>
<template #prepend>
<el-icon>
<Search/>
<Search />
</el-icon>
</template>
</el-input>
</div>
<el-button class="border-radius_8" type="primary" :icon="IconOpeAdd" @click="editDs(undefined)">Add Datasource</el-button>
<el-button
class="border-radius_8"
type="primary"
:icon="IconOpeAdd"
@click="editDs(undefined)"
>{{ t('ds.add') }}</el-button
>
</div>
<div class="connections-container">
<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 type="primary" class="action-btn" circle @click="editDs(ds)" :icon="IconOpeEdit"/>
<el-button type="danger" class="action-btn" circle @click="deleteDs(ds)" :icon="IconOpeDelete"/>
<el-button class="action-btn" circle @click="getTables(ds.id, ds.name)" :icon="List" />
<el-button
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"/>
<DsForm ref="dsForm" @refresh="refresh" />
</template>
<script lang="ts" setup>
import { ref, onMounted } from 'vue'
import { useI18n } from 'vue-i18n'
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 {Search, List} from '@element-plus/icons-vue'
import {ref, onMounted} from 'vue'
import { Search, List } from '@element-plus/icons-vue'
import DsForm from './form.vue'
import {datasourceApi} from '@/api/datasource'
import {ElMessageBox} from 'element-plus-secondary'
import {useRouter} from 'vue-router'
import DatasourceItemCard from "@/views/ds/DatasourceItemCard.vue";
import { datasourceApi } from '@/api/datasource'
import { ElMessageBox } from 'element-plus-secondary'
import { useRouter } from 'vue-router'
import DatasourceItemCard from '@/views/ds/DatasourceItemCard.vue'
const { t } = useI18n()
const searchValue = ref<string>('')
const dsForm = ref()
const dsList = ref<any>([])// show ds list
const allDsList = ref<any>([])// all ds list
const dsList = ref<any>([]) // show ds list
const allDsList = ref<any>([]) // all ds list
const router = useRouter()
const loading = ref(false)
function searchHandle() {
if (searchValue.value) {
dsList.value = JSON.parse(JSON.stringify(allDsList.value)).filter((item: any) => {
@@ -83,28 +102,18 @@ const editDs = (item: any) => {
dsForm.value.open(item)
}
// const editTables = (item: any) => {
// dsForm.value.open(item, true)
// }
const deleteDs = (item: any) => {
ElMessageBox.confirm(
'Delete this datasource?',
'Delete',
{
confirmButtonText: 'Confirm',
cancelButtonText: 'Cancel',
type: 'warning',
}
)
.then(() => {
datasourceApi.delete(item.id).then(() => {
console.log('success')
list()
})
})
.catch(() => {
ElMessageBox.confirm(t('ds.delete'), t('common.confirm'), {
confirmButtonText: t('common.confirm'),
cancelButtonText: t('common.cancel'),
type: 'warning',
})
.then(() => {
datasourceApi.delete(item.id).then(() => {
refresh()
})
})
.catch(() => {})
}
const getTables = (id: number, name: string) => {
@@ -114,7 +123,6 @@ const getTables = (id: number, name: string) => {
onMounted(() => {
list()
})
</script>
<style lang="less" scoped>
.header {
@@ -152,5 +160,4 @@ onMounted(() => {
display: flex;
}
}
</style>
</style>