feat: eslint

This commit is contained in:
ulleo
2025-06-12 12:09:30 +08:00
parent 751cad5158
commit 8c1f0ebeec
87 changed files with 2640 additions and 2489 deletions
+3 -1
View File
@@ -5,4 +5,6 @@
*.woff
*.ttf
.vscode/
.DS_Store
.DS_Store
eslint.config.cjs
.eslintrc.js
+1 -1
View File
@@ -30,4 +30,4 @@ module.exports = {
},
],
},
}
}
+59
View File
@@ -0,0 +1,59 @@
const { globalIgnores } = require('eslint/config')
const eslint = require('@eslint/js')
const eslintConfigPrettier = require('eslint-config-prettier')
const eslintPluginPrettierRecommended = require('eslint-plugin-prettier/recommended')
const eslintPluginVue = require('eslint-plugin-vue')
const globals = require('globals')
const typescriptEslint = require('typescript-eslint')
module.exports = typescriptEslint.config(
{
languageOptions: {
globals: globals.browser,
ecmaVersion: 'latest',
parserOptions: {
parser: typescriptEslint.parser,
},
},
extends: [
eslint.configs.recommended,
...typescriptEslint.configs.recommended,
...eslintPluginVue.configs['flat/recommended'],
],
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',
'vue/no-required-prop-with-default': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/ban-ts-comment': 'warn',
'prettier/prettier': [
'warn',
{
singleQuote: true,
semi: false,
trailingComma: 'es5',
printWidth: 100,
tabWidth: 2,
endOfLine: 'auto',
},
],
},
},
globalIgnores([
'node_modules/',
'dist/',
'public/',
'eslint.config.cjs',
'.eslintrc.js',
'**/*.md',
'**/*.woff',
'**/*.ttf',
'**/.vscode/',
'**/.DS_Store',
]),
eslintConfigPrettier,
eslintPluginPrettierRecommended
)
+11 -1
View File
@@ -3,10 +3,17 @@
"private": true,
"version": "0.0.0",
"type": "module",
"eslintConfig": {
"env": {
"browser": true,
"node": true
}
},
"scripts": {
"dev": "vite",
"build": "vue-tsc -b && vite build",
"preview": "vite preview"
"preview": "vite preview",
"lint": "eslint . --ext .vue,.js,.ts,.jsx,.tsx --fix"
},
"dependencies": {
"@antv/g2": "^5.3.3",
@@ -28,6 +35,7 @@
},
"devDependencies": {
"@element-plus/icons-vue": "^2.3.1",
"@eslint/migrate-config": "^1.5.0",
"@types/crypto-js": "^4.2.2",
"@types/element-resize-detector": "^1.1.6",
"@types/node": "^22.14.1",
@@ -41,10 +49,12 @@
"eslint-config-prettier": "^10.1.5",
"eslint-plugin-prettier": "^5.4.1",
"eslint-plugin-vue": "^10.2.0",
"globals": "^16.2.0",
"less": "^4.3.0",
"pinia": "^3.0.2",
"prettier": "^3.5.3",
"typescript": "~5.7.2",
"typescript-eslint": "^8.34.0",
"unplugin-auto-import": "^19.1.2",
"unplugin-vue-components-secondary": "^0.24.6",
"vite": "^6.3.1",
+1 -3
View File
@@ -13,6 +13,4 @@ const elLanguage = computed(() => {
</el-config-provider>
</template>
<style scoped>
</style>
<style scoped></style>
+3 -2
View File
@@ -1,10 +1,11 @@
import { request } from '@/utils/request'
export const userApi = {
pager: (pageNumber: number, pageSize: number) => request.get(`/user/pager/${pageNumber}/${pageSize}`),
pager: (pageNumber: number, pageSize: number) =>
request.get(`/user/pager/${pageNumber}/${pageSize}`),
add: (data: any) => request.post('/settings/terminology', data),
edit: (data: any) => request.put('/settings/terminology', data),
delete: (id: number) => request.delete(`/settings/terminology/${id}`),
query: (id: number) => request.get(`/settings/terminology/${id}`),
language: (data: any) => request.put('/user/language', data),
}
}
+220 -129
View File
@@ -1,185 +1,276 @@
import {request} from '@/utils/request'
import {getDate} from "@/utils/utils.ts";
import { request } from '@/utils/request'
import { getDate } from '@/utils/utils.ts'
export const questionApi = {
pager: (pageNumber: number, pageSize: number) => request.get(`/chat/question/pager/${pageNumber}/${pageSize}`),
/* add: (data: any) => new Promise((resolve, reject) => {
pager: (pageNumber: number, pageSize: number) =>
request.get(`/chat/question/pager/${pageNumber}/${pageSize}`),
/* add: (data: any) => new Promise((resolve, reject) => {
request.post('/chat/question', data, { responseType: 'stream', timeout: 0, onDownloadProgress: p => {
resolve(p)
}}).catch(e => reject(e))
}), */
// add: (data: any) => request.post('/chat/question', data),
add: (data: any) => request.fetchStream('/chat/question', data),
edit: (data: any) => request.put('/chat/question', data),
delete: (id: number) => request.delete(`/chat/question/${id}`),
query: (id: number) => request.get(`/chat/question/${id}`)
// add: (data: any) => request.post('/chat/question', data),
add: (data: any) => request.fetchStream('/chat/question', data),
edit: (data: any) => request.put('/chat/question', data),
delete: (id: number) => request.delete(`/chat/question/${id}`),
query: (id: number) => request.get(`/chat/question/${id}`),
}
export interface ChatMessage {
role: 'user' | 'assistant'
create_time?: Date | string
content?: string | number
record?: ChatRecord
isTyping?: boolean
isWelcome?: boolean
role: 'user' | 'assistant'
create_time?: Date | string
content?: string | number
record?: ChatRecord
isTyping?: boolean
isWelcome?: boolean
}
export class ChatRecord {
id?: number
chat_id?: number
create_time?: Date | string
finish_time?: Date | string
question?: string
sql_answer?: string
sql?: string
data?: string
chart_answer?: string
chart?: string
finish?: boolean = false
error?: string
run_time: number = 0
id?: number
chat_id?: number
create_time?: Date | string
finish_time?: Date | string
question?: string
sql_answer?: string
sql?: string
data?: string
chart_answer?: string
chart?: string
finish?: boolean = false
error?: string
run_time: number = 0
constructor()
constructor(id: number, chat_id: number, create_time: Date | string, finish_time: Date | string | undefined, question: string, sql_answer: string | undefined, sql: string | undefined, data: string | undefined, chart_answer: string | undefined, chart: string | undefined, finish: boolean, error: string | undefined, run_time: number)
constructor(id?: number, chat_id?: number, create_time?: Date | string, finish_time?: Date | string, question?: string, sql_answer?: string, sql?: string, data?: string, chart_answer?: string, chart?: string, finish?: boolean, error?: string, run_time?: number) {
this.id = id
this.chat_id = chat_id
this.create_time = getDate(create_time)
this.finish_time = getDate(finish_time)
this.question = question
this.sql_answer = sql_answer
this.sql = sql
this.data = data
this.chart_answer = chart_answer
this.chart = chart
this.finish = finish
this.error = error
this.run_time = run_time ?? 0
}
constructor()
constructor(
id: number,
chat_id: number,
create_time: Date | string,
finish_time: Date | string | undefined,
question: string,
sql_answer: string | undefined,
sql: string | undefined,
data: string | undefined,
chart_answer: string | undefined,
chart: string | undefined,
finish: boolean,
error: string | undefined,
run_time: number
)
constructor(
id?: number,
chat_id?: number,
create_time?: Date | string,
finish_time?: Date | string,
question?: string,
sql_answer?: string,
sql?: string,
data?: string,
chart_answer?: string,
chart?: string,
finish?: boolean,
error?: string,
run_time?: number
) {
this.id = id
this.chat_id = chat_id
this.create_time = getDate(create_time)
this.finish_time = getDate(finish_time)
this.question = question
this.sql_answer = sql_answer
this.sql = sql
this.data = data
this.chart_answer = chart_answer
this.chart = chart
this.finish = finish
this.error = error
this.run_time = run_time ?? 0
}
}
export class Chat {
id?: number
create_time?: Date | string
create_by?: number
brief?: string
chat_type?: string
datasource?: number
engine_type?: string
id?: number
create_time?: Date | string
create_by?: number
brief?: string
chat_type?: string
datasource?: number
engine_type?: string
constructor()
constructor(id: number, create_time: Date | string, create_by: number, brief: string, chat_type: string, datasource: number, engine_type: string)
constructor(id?: number, create_time?: Date | string, create_by?: number, brief?: string, chat_type?: string, datasource?: number, engine_type?: string) {
this.id = id
constructor()
constructor(
id: number,
create_time: Date | string,
create_by: number,
brief: string,
chat_type: string,
datasource: number,
engine_type: string
)
constructor(
id?: number,
create_time?: Date | string,
create_by?: number,
brief?: string,
chat_type?: string,
datasource?: number,
engine_type?: string
) {
this.id = id
this.create_time = getDate(create_time)
this.create_by = create_by
this.brief = brief
this.chat_type = chat_type
this.datasource = datasource
this.engine_type = engine_type
}
}
export class ChatInfo extends Chat {
datasource_name?: string
datasource_exists: boolean = true
records: Array<ChatRecord> = []
constructor()
constructor(chat: Chat)
constructor(
id: number,
create_time: Date | string,
create_by: number,
brief: string,
chat_type: string,
datasource: number,
engine_type: string,
datasource_name: string,
datasource_exists: boolean,
records: Array<ChatRecord>
)
constructor(
param1?: number | Chat,
create_time?: Date | string,
create_by?: number,
brief?: string,
chat_type?: string,
datasource?: number,
engine_type?: string,
datasource_name?: string,
datasource_exists: boolean = true,
records: Array<ChatRecord> = []
) {
super()
if (param1 !== undefined) {
if (param1 instanceof Chat) {
this.id = param1.id
this.create_time = getDate(param1.create_time)
this.create_by = param1.create_by
this.brief = param1.brief
this.chat_type = param1.chat_type
this.datasource = param1.datasource
this.engine_type = param1.engine_type
} else {
this.id = param1
this.create_time = getDate(create_time)
this.create_by = create_by
this.brief = brief
this.chat_type = chat_type
this.datasource = datasource
this.engine_type = engine_type
}
}
}
export class ChatInfo extends Chat {
datasource_name?: string
datasource_exists: boolean = true
records: Array<ChatRecord> = []
constructor()
constructor(chat: Chat)
constructor(id: number, create_time: Date | string, create_by: number, brief: string, chat_type: string, datasource: number, engine_type: string, datasource_name: string, datasource_exists: boolean, records: Array<ChatRecord>)
constructor(param1?: number | Chat, create_time?: Date | string, create_by?: number, brief?: string, chat_type?: string, datasource?: number, engine_type?: string, datasource_name?: string, datasource_exists: boolean = true, records: Array<ChatRecord> = []) {
super()
if (param1 !== undefined) {
if (param1 instanceof Chat) {
this.id = param1.id
this.create_time = getDate(param1.create_time)
this.create_by = param1.create_by
this.brief = param1.brief
this.chat_type = param1.chat_type
this.datasource = param1.datasource
this.engine_type = param1.engine_type
} else {
this.id = param1
this.create_time = getDate(create_time)
this.create_by = create_by
this.brief = brief
this.chat_type = chat_type
this.datasource = datasource
this.engine_type = engine_type
}
}
this.datasource_name = datasource_name
this.datasource_exists = datasource_exists
this.records = records
}
this.datasource_name = datasource_name
this.datasource_exists = datasource_exists
this.records = records
}
}
function toChatRecord(data?: any): ChatRecord | undefined {
if (!data) {
return undefined
}
return new ChatRecord(data.id, data.chat_id, data.create_time, data.finish_time, data.question, data.sql_answer, data.sql, data.data, data.chart_answer, data.chart, data.finish, data.error, data.run_time)
if (!data) {
return undefined
}
return new ChatRecord(
data.id,
data.chat_id,
data.create_time,
data.finish_time,
data.question,
data.sql_answer,
data.sql,
data.data,
data.chart_answer,
data.chart,
data.finish,
data.error,
data.run_time
)
}
function toChatRecordList(list: any = []): ChatRecord[] {
const records: Array<ChatRecord> = []
for (let i = 0; i < list.length; i++) {
const record = toChatRecord(list[i])
if (record) {
records.push(record)
}
const records: Array<ChatRecord> = []
for (let i = 0; i < list.length; i++) {
const record = toChatRecord(list[i])
if (record) {
records.push(record)
}
return records
}
return records
}
function toChatInfo(data?: any): ChatInfo | undefined {
if (!data) {
return undefined
}
return new ChatInfo(data.id, data.create_time, data.create_by, data.brief, data.chat_type, data.datasource, data.engine_type, data.datasource_name, data.datasource_exists, toChatRecordList(data.records))
if (!data) {
return undefined
}
return new ChatInfo(
data.id,
data.create_time,
data.create_by,
data.brief,
data.chat_type,
data.datasource,
data.engine_type,
data.datasource_name,
data.datasource_exists,
toChatRecordList(data.records)
)
}
function toChatInfoList(list: any[] = []): ChatInfo[] {
const infos: Array<ChatInfo> = []
for (let i = 0; i < list.length; i++) {
const chatInfo = toChatInfo(list[i])
if (chatInfo) {
infos.push(chatInfo)
}
const infos: Array<ChatInfo> = []
for (let i = 0; i < list.length; i++) {
const chatInfo = toChatInfo(list[i])
if (chatInfo) {
infos.push(chatInfo)
}
return infos
}
return infos
}
function list(): Promise<Array<ChatInfo>> {
return request.get('/chat/list')
return request.get('/chat/list')
}
function get(id: number): Promise<ChatInfo> {
return request.get(`/chat/get/${id}`)
return request.get(`/chat/get/${id}`)
}
function startChat(data: any): Promise<ChatInfo> {
return request.post('/chat/start', data)
return request.post('/chat/start', data)
}
function renameChat(chat_id: number | undefined, brief: string): Promise<string> {
return request.post('/chat/rename', {id: chat_id, brief: brief})
return request.post('/chat/rename', { id: chat_id, brief: brief })
}
function deleteChat(id: number | undefined): Promise<string> {
return request.get(`/chat/delete/${id}`)
return request.get(`/chat/delete/${id}`)
}
export const chatApi = {
toChatRecord,
toChatRecordList,
toChatInfo,
toChatInfoList,
list,
get,
startChat,
renameChat,
deleteChat
}
toChatRecord,
toChatRecordList,
toChatInfo,
toChatInfoList,
list,
get,
startChat,
renameChat,
deleteChat,
}
+11 -10
View File
@@ -1,13 +1,14 @@
import { request } from '@/utils/request'
export const dashboardApi = {
list_resource: (params: any) => request.post('/dashboard/list_resource',params),
load_resource: (params:any) => request.post('/dashboard/load_resource',params),
create_resource: (params: any) => request.post('/dashboard/create_resource',params),
update_resource: (params: any) => request.post('/dashboard/update_resource',params),
create_canvas: (params: any) => request.post('/dashboard/create_canvas',params),
update_canvas: (params: any) => request.post('/dashboard/update_canvas',params),
check_name: (params: any) => request.post('/dashboard/check_name',params),
delete_resource: (params: any) => request.delete(`/dashboard/delete_resource/${params.id}`,params),
move_resource: (params: any) => request.delete(`/dashboard/move_resource/${params.id}`,params),
}
list_resource: (params: any) => request.post('/dashboard/list_resource', params),
load_resource: (params: any) => request.post('/dashboard/load_resource', params),
create_resource: (params: any) => request.post('/dashboard/create_resource', params),
update_resource: (params: any) => request.post('/dashboard/update_resource', params),
create_canvas: (params: any) => request.post('/dashboard/create_canvas', params),
update_canvas: (params: any) => request.post('/dashboard/update_canvas', params),
check_name: (params: any) => request.post('/dashboard/check_name', params),
delete_resource: (params: any) =>
request.delete(`/dashboard/delete_resource/${params.id}`, params),
move_resource: (params: any) => request.delete(`/dashboard/move_resource/${params.id}`, params),
}
+11 -10
View File
@@ -5,17 +5,18 @@ export const datasourceApi = {
add: (data: any) => request.post('/datasource/add', data),
list: () => request.get('/datasource/list'),
update: (data: any) => request.post('/datasource/update', data),
delete: (id: Number) => request.post(`/datasource/delete/${id}`),
getTables: (id: Number) => request.post(`/datasource/getTables/${id}`),
delete: (id: number) => request.post(`/datasource/delete/${id}`),
getTables: (id: number) => request.post(`/datasource/getTables/${id}`),
getTablesByConf: (data: any) => request.post('/datasource/getTablesByConf', data),
getFields: (id: Number, table_name: string) => request.post(`/datasource/getFields/${id}/${table_name}`),
execSql: (id: Number, sql: string) => request.post(`/datasource/execSql/${id}/${sql}`),
chooseTables: (id: Number, data: any) => request.post(`/datasource/chooseTables/${id}`, data),
tableList: (id: Number) => request.post(`/datasource/tableList/${id}`),
fieldList: (id: Number) => request.post(`/datasource/fieldList/${id}`),
getFields: (id: number, table_name: string) =>
request.post(`/datasource/getFields/${id}/${table_name}`),
execSql: (id: number, sql: string) => request.post(`/datasource/execSql/${id}/${sql}`),
chooseTables: (id: number, data: any) => request.post(`/datasource/chooseTables/${id}`, data),
tableList: (id: number) => request.post(`/datasource/tableList/${id}`),
fieldList: (id: number) => request.post(`/datasource/fieldList/${id}`),
edit: (data: any) => request.post('/datasource/editLocalComment', data),
previewData: (id: Number, data: any) => request.post(`/datasource/previewData/${id}`, data),
previewData: (id: number, data: any) => request.post(`/datasource/previewData/${id}`, data),
saveTable: (data: any) => request.post('/datasource/editTable', data),
saveField: (data: any) => request.post('/datasource/editField', data),
getDs: (id: Number) => request.post(`/datasource/get/${id}`),
}
getDs: (id: number) => request.post(`/datasource/get/${id}`),
}
+11 -9
View File
@@ -1,13 +1,15 @@
import { request } from '@/utils/request'
export const AuthApi = {
login: (credentials: { username: string; password: string }) => request.post<{
data: any; token: string
}>('/login/access-token', credentials, {
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
}),
login: (credentials: { username: string; password: string }) =>
request.post<{
data: any
token: string
}>('/login/access-token', credentials, {
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
}),
logout: () => request.post('/auth/logout'),
info: () => request.get('/user/info')
}
info: () => request.get('/user/info'),
}
+4 -3
View File
@@ -1,9 +1,10 @@
import { request } from '@/utils/request'
export const termApi = {
pager: (pageNumber: number, pageSize: number) => request.get(`/settings/terminology/pager/${pageNumber}/${pageSize}`),
pager: (pageNumber: number, pageSize: number) =>
request.get(`/settings/terminology/pager/${pageNumber}/${pageSize}`),
add: (data: any) => request.post('/settings/terminology', data),
edit: (data: any) => request.put('/settings/terminology', data),
delete: (id: number) => request.delete(`/settings/terminology/${id}`),
query: (id: number) => request.get(`/settings/terminology/${id}`)
}
query: (id: number) => request.get(`/settings/terminology/${id}`),
}
+3 -2
View File
@@ -1,10 +1,11 @@
import { request } from '@/utils/request'
export const modelApi = {
pager: (pageNumber: number, pageSize: number) => request.get(`/system/aimodel/pager/${pageNumber}/${pageSize}`),
pager: (pageNumber: number, pageSize: number) =>
request.get(`/system/aimodel/pager/${pageNumber}/${pageSize}`),
add: (data: any) => request.post('/system/aimodel', data),
edit: (data: any) => request.put('/system/aimodel', data),
delete: (id: number) => request.delete(`/system/aimodel/${id}`),
query: (id: number) => request.get(`/system/aimodel/${id}`),
status: (data: any) => request.patch('/system/aimodel/status', data),
}
}
@@ -1,5 +1,5 @@
<template>
<el-dropdown @command="changeLanguage" trigger="hover">
<el-dropdown trigger="hover" @command="changeLanguage">
<div class="lang-switch">
<span>{{ selectedLanguage === 'zh-CN' ? '中文' : 'English' }}</span>
<el-icon class="el-icon--right">
@@ -11,7 +11,10 @@
<el-dropdown-item command="en" :class="{ 'selected-lang': selectedLanguage === 'en' }">
English
</el-dropdown-item>
<el-dropdown-item command="zh-CN" :class="{ 'selected-lang': selectedLanguage === 'zh-CN' }">
<el-dropdown-item
command="zh-CN"
:class="{ 'selected-lang': selectedLanguage === 'zh-CN' }"
>
中文
</el-dropdown-item>
</el-dropdown-menu>
@@ -38,7 +41,7 @@ const changeLanguage = (lang: string) => {
userStore.setLanguage(lang)
const param = {
language: lang
language: lang,
}
userApi.language(param)
}
@@ -50,7 +53,7 @@ const changeLanguage = (lang: string) => {
align-items: center;
cursor: pointer;
color: var(--el-text-color-primary);
.el-icon--right {
margin-left: 8px;
font-size: 12px;
@@ -60,4 +63,4 @@ const changeLanguage = (lang: string) => {
.selected-lang {
color: var(--el-color-primary);
}
</style>
</style>
+1 -1
View File
@@ -3,7 +3,7 @@ import { ElIcon } from 'element-plus-secondary'
import Icon from './src/Icon.vue'
const hIcon = (name: string) => {
return h(ElIcon, null, {
default: () => h(name)
default: () => h(name),
})
}
export { Icon, hIcon }
@@ -3,20 +3,20 @@ import { computed } from 'vue'
const props = defineProps({
prefix: {
type: String,
default: 'icon'
default: 'icon',
},
name: {
type: String,
default: ''
default: '',
},
className: {
type: String,
default: ''
default: '',
},
staticContent: {
type: String,
default: ''
}
default: '',
},
})
const svgClass = computed(() => {
if (props.className) {
@@ -28,11 +28,11 @@ const svgClass = computed(() => {
<template>
<div
class="svg-container"
v-if="staticContent"
v-html="staticContent"
class="svg-container"
:class="svgClass"
aria-hidden="true"
v-html="staticContent"
></div>
<slot v-else />
</template>
@@ -48,7 +48,7 @@ const svgClass = computed(() => {
.svg-container {
width: 100%;
height: 100%;
svg {
width: 100%;
height: 100%;
@@ -5,7 +5,7 @@
</template>
<style lang="less" scoped>
.single-page_layout {
width: 100vw;
height: 100vh;
width: 100vw;
height: 100vh;
}
</style>
+70 -49
View File
@@ -1,6 +1,9 @@
<template>
<div class="app-container" :class="{ 'app-topbar-container': topLayout }">
<div class="main-menu" :class="{ 'main-menu-sidebar': !topLayout, 'main-menu-topbar': topLayout }">
<div
class="main-menu"
:class="{ 'main-menu-sidebar': !topLayout, 'main-menu-topbar': topLayout }"
>
<div class="logo">SQLBot</div>
<!-- <div v-if="!topLayout || !showSubmenu"
@@ -28,40 +31,43 @@
</el-select>
</div> -->
<el-menu
v-if="!topLayout || !showSubmenu"
:default-active="activeMenu"
class="menu-container"
:mode="topLayout ? 'horizontal' : 'vertical'"
v-if="!topLayout || !showSubmenu"
:default-active="activeMenu"
class="menu-container"
:mode="topLayout ? 'horizontal' : 'vertical'"
>
<el-menu-item v-for="item in routerList" :key="item.path" :index="item.path" @click="menuSelect">
<el-menu-item
v-for="item in routerList"
:key="item.path"
:index="item.path"
@click="menuSelect"
>
<el-icon v-if="item.meta.icon">
<component :is="resolveIcon(item.meta.icon)"/>
<component :is="resolveIcon(item.meta.icon)" />
</el-icon>
<span>{{ t(`menu.${item.meta.title}`) }}</span>
</el-menu-item>
</el-menu>
<div v-else class="top-bar-title">
<span class="split"/>
<span class="split" />
<span>{{ t('common.system_manage') }}</span>
</div>
<div class="main-topbar-right" v-if="topLayout">
<div class="top-back-area" v-if="showSubmenu">
<div v-if="topLayout" class="main-topbar-right">
<div v-if="showSubmenu" class="top-back-area">
<el-button type="primary" text="primary" @click="backMain">
<el-icon class="el-icon--right">
<ArrowLeftBold/>
<ArrowLeftBold />
</el-icon>
Back
</el-button>
</div>
<el-tooltip :content="t('common.system_manage')" placement="bottom" v-else>
<el-tooltip v-else :content="t('common.system_manage')" placement="bottom">
<div class="header-icon-btn" @click="toSystem">
<el-icon>
<iconsystem/>
<iconsystem />
</el-icon>
</div>
</el-tooltip>
@@ -76,7 +82,7 @@
<el-dropdown-item @click="switchLayout">Switch Layout</el-dropdown-item>
<el-dropdown-item @click="logout">Logout</el-dropdown-item>
<el-dropdown-item>
<language-selector/>
<language-selector />
</el-dropdown-item>
</el-dropdown-menu>
</template>
@@ -84,15 +90,15 @@
</div>
</div>
<div class="main-content" :class="{'main-content-with-bar': topLayout}">
<div class="header-container" v-if="!topLayout">
<div class="main-content" :class="{ 'main-content-with-bar': topLayout }">
<div v-if="!topLayout" class="header-container">
<div class="header">
<h1>{{ currentPageTitle }}</h1>
<div class="header-actions">
<el-tooltip content="System manage" placement="bottom">
<div class="header-icon-btn" @click="toSystem">
<el-icon>
<iconsystem/>
<iconsystem />
</el-icon>
<span>{{ t('common.system_manage') }}</span>
</div>
@@ -108,9 +114,8 @@
<el-dropdown-item @click="switchLayout">Switch Layout</el-dropdown-item>
<el-dropdown-item @click="logout">Logout</el-dropdown-item>
<el-dropdown-item>
<language-selector/>
<language-selector />
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
@@ -120,13 +125,18 @@
<div v-if="sysRouterList.length && showSubmenu" class="sub-menu-container">
<el-menu
:default-active="activeMenu"
class="el-menu-demo"
:mode="!topLayout ? 'horizontal' : 'vertical'"
:default-active="activeMenu"
class="el-menu-demo"
:mode="!topLayout ? 'horizontal' : 'vertical'"
>
<el-menu-item v-for="item in sysRouterList" :key="item.path" :index="item.path" @click="menuSelect">
<el-menu-item
v-for="item in sysRouterList"
:key="item.path"
:index="item.path"
@click="menuSelect"
>
<el-icon v-if="item.meta.icon">
<component :is="resolveIcon(item.meta.icon)"/>
<component :is="resolveIcon(item.meta.icon)" />
</el-icon>
<span>{{ t(`menu.${item.meta.title}`) }}</span>
</el-menu-item>
@@ -135,20 +145,20 @@
<div v-if="sysRouterList.length && showSubmenu" class="sys-page-content">
<div class="sys-inner-container">
<router-view/>
<router-view />
</div>
</div>
<div v-else class="page-content">
<router-view/>
<router-view />
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import {ref, computed, onMounted} from 'vue'
import {useRouter, useRoute} from 'vue-router'
import {useUserStore} from '@/stores/user'
import { ref, computed, onMounted } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { useUserStore } from '@/stores/user'
import ds from '@/assets/svg/ds.svg'
import dashboard from '@/assets/svg/dashboard.svg'
import chat from '@/assets/svg/chat.svg'
@@ -156,13 +166,13 @@ import iconsetting from '@/assets/svg/setting.svg'
import iconsystem from '@/assets/svg/system.svg'
import icon_user from '@/assets/svg/icon_user.svg'
import icon_ai from '@/assets/svg/icon_ai.svg'
import {ArrowLeftBold} from '@element-plus/icons-vue'
import {useCache} from '@/utils/useCache'
import {useI18n} from 'vue-i18n'
import { ArrowLeftBold } from '@element-plus/icons-vue'
import { useCache } from '@/utils/useCache'
import { useI18n } from 'vue-i18n'
import LanguageSelector from '@/components/Language-selector/index.vue'
const {t} = useI18n()
const {wsCache} = useCache()
const { t } = useI18n()
const { wsCache } = useCache()
const topLayout = ref(false)
const router = useRouter()
const route = useRoute()
@@ -170,13 +180,23 @@ const userStore = useUserStore()
const name = ref('admin')
const activeMenu = computed(() => route.path)
const routerList = computed(() => {
return router.getRoutes().filter(route => {
return !route.path.includes('canvas') &&!route.path.includes('preview') && route.path !== '/login' && !route.path.includes('/system') && !route.redirect && route.path !== '/:pathMatch(.*)*' && !route.path.includes('dsTable')
return router.getRoutes().filter((route) => {
return (
!route.path.includes('canvas') &&
!route.path.includes('preview') &&
route.path !== '/login' &&
!route.path.includes('/system') &&
!route.redirect &&
route.path !== '/:pathMatch(.*)*' &&
!route.path.includes('dsTable')
)
})
})
const sysRouterList = computed(() => {
const result = router.getRoutes().filter(route => route.path.includes('/system') && !route.redirect)
const result = router
.getRoutes()
.filter((route) => route.path.includes('/system') && !route.redirect)
return result
})
@@ -197,12 +217,12 @@ const currentPageTitle = computed(() => {
})
const resolveIcon = (iconName: any) => {
const icons: Record<string, any> = {
'ds': ds,
'dashboard': dashboard,
'chat': chat,
'setting': iconsetting,
'icon_user': icon_user,
'icon_ai': icon_ai,
ds: ds,
dashboard: dashboard,
chat: chat,
setting: iconsetting,
icon_user: icon_user,
icon_ai: icon_ai,
}
return typeof icons[iconName] === 'function' ? icons[iconName]() : icons[iconName]
}
@@ -289,7 +309,7 @@ onMounted(() => {
border-bottom: none;
&:not(.ed-menu--vertical) {
margin-left: 32px
margin-left: 32px;
}
}
}
@@ -434,7 +454,9 @@ onMounted(() => {
margin: 0;
padding: 0;
height: 60px;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
font-family:
-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell,
'Open Sans', 'Helvetica Neue', sans-serif;
.header {
height: 36px;
@@ -496,7 +518,6 @@ onMounted(() => {
}
}
.page-content {
flex: 1;
overflow-y: auto;
@@ -552,4 +573,4 @@ onMounted(() => {
}
}
}
</style>
</style>
+2 -3
View File
@@ -1,4 +1,3 @@
export interface SelectOption {
label: string
value: string | number
@@ -17,6 +16,6 @@ export const modelTypeOptions: SelectOption[] = [
export const getModelTypeName = (value: any) => {
const tv = parseInt(value)
const item = modelTypeOptions.find(item => item.value === tv)
const item = modelTypeOptions.find((item) => item.value === tv)
return item?.label || ''
}
}
+5 -5
View File
@@ -14,12 +14,12 @@ const getDefaultLocale = () => {
const messages = {
en: {
...en,
el: elementEnLocale
el: elementEnLocale,
},
'zh-CN': {
...zhCN,
el: elementZhLocale
}
el: elementZhLocale,
},
}
export const i18n = createI18n({
@@ -27,9 +27,9 @@ export const i18n = createI18n({
locale: getDefaultLocale(),
fallbackLocale: 'en',
globalInjection: true,
messages
messages,
})
export const getElementLocale = () => {
return i18n.global.locale.value === 'en' ? elementEnLocale : elementZhLocale
}
}
+48 -48
View File
@@ -1,86 +1,86 @@
import { createRouter, createWebHashHistory } from "vue-router";
import { createRouter, createWebHashHistory } from 'vue-router'
import Layout from "@/components/layout/index.vue";
import SinglePage from "@/components/layout/SinglePage.vue";
import login from "@/views/login/index.vue";
import chat from "@/views/chat/index.vue";
import ds from "@/views/ds/index.vue";
import DashboardEditor from "@/views/dashboard/editor/index.vue";
import DashboardPreview from "@//views/dashboard/preview/SQPreviewSingle.vue";
import Dashboard from "@/views/dashboard/index.vue";
import Model from "@/views/system/model/index.vue";
import Layout from '@/components/layout/index.vue'
import SinglePage from '@/components/layout/SinglePage.vue'
import login from '@/views/login/index.vue'
import chat from '@/views/chat/index.vue'
import ds from '@/views/ds/index.vue'
import DashboardEditor from '@/views/dashboard/editor/index.vue'
import DashboardPreview from '@//views/dashboard/preview/SQPreviewSingle.vue'
import Dashboard from '@/views/dashboard/index.vue'
import Model from '@/views/system/model/index.vue'
// import User from "@/views/system/user/index.vue";
import { watchRouter } from "./watch";
import { watchRouter } from './watch'
const router = createRouter({
history: createWebHashHistory(),
routes: [
{
path: "/login",
name: "login",
path: '/login',
name: 'login',
component: login,
},
{
path: "/chat",
path: '/chat',
component: Layout,
redirect: "/chat/index",
redirect: '/chat/index',
children: [
{
path: "index",
name: "chat",
path: 'index',
name: 'chat',
component: chat,
meta: { title: "Data Q&A", icon: "chat" },
meta: { title: 'Data Q&A', icon: 'chat' },
},
],
},
{
path: "/dsTable",
path: '/dsTable',
component: SinglePage,
children: [
{
path: ":dsId/:dsName",
name: "dsTable",
component: () => import("@/views/ds/TableList.vue"),
path: ':dsId/:dsName',
name: 'dsTable',
component: () => import('@/views/ds/TableList.vue'),
props: true,
},
],
},
{
path: "/ds",
path: '/ds',
component: Layout,
redirect: "/ds/index",
redirect: '/ds/index',
children: [
{
path: "index",
name: "ds",
path: 'index',
name: 'ds',
component: ds,
meta: { title: "Data Connections", icon: "ds" },
meta: { title: 'Data Connections', icon: 'ds' },
},
],
},
{
path: "/dashboard",
path: '/dashboard',
component: Layout,
redirect: "/dashboard/index",
redirect: '/dashboard/index',
children: [
{
path: "index",
name: "dashboard",
path: 'index',
name: 'dashboard',
component: Dashboard,
meta: { title: "Dashboard", icon: "dashboard" },
meta: { title: 'Dashboard', icon: 'dashboard' },
},
],
},
{
path: "/canvas",
name: "canvas",
path: '/canvas',
name: 'canvas',
component: DashboardEditor,
meta: { title: "canvas", icon: "dashboard" },
meta: { title: 'canvas', icon: 'dashboard' },
},
{
path: "/dashboard-preview",
name: "preview",
path: '/dashboard-preview',
name: 'preview',
component: DashboardPreview,
meta: { title: "DashboardPreview", icon: "dashboard" },
meta: { title: 'DashboardPreview', icon: 'dashboard' },
},
/* {
path: "/setting",
@@ -96,25 +96,25 @@ const router = createRouter({
],
}, */
{
path: "/system",
path: '/system',
component: Layout,
redirect: "/system/model",
redirect: '/system/model',
children: [
/* {
/* {
path: "user",
name: "user",
component: User,
meta: { title: "User Management", icon: "icon_user" },
}, */
{
path: "model",
name: "model",
path: 'model',
name: 'model',
component: Model,
meta: { title: "AI Model Configuration", icon: "icon_ai" },
meta: { title: 'AI Model Configuration', icon: 'icon_ai' },
},
],
}
},
],
});
watchRouter(router);
export default router;
})
watchRouter(router)
export default router
+66 -70
View File
@@ -1,76 +1,72 @@
import {defineStore} from 'pinia'
import {store} from "@/stores";
import { defineStore } from 'pinia'
import { store } from '@/stores'
export const dashboardStore = defineStore('dashboard', {
state: () => {
return {
tabCollisionActiveId: null,
tabMoveInActiveId: null,
curComponent: null,
curComponentId: null,
canvasStyleData: {},
componentData: [],
canvasViewInfo: {},
fullscreenFlag: false,
dataPrepareState: false,
dashboardInfo: {
id: null,
name: null,
pid: null,
workspaceId: null,
status: null,
dataState: null,
createName: null,
updateName: null,
createTime: null,
updateTime: null,
contentId: null,
type: null
}
}
},
getters: {
getCurComponent(): any {
return this.curComponent
}
},
actions: {
setCurComponent(value: any) {
this.curComponent = value
this.curComponentId = value && value.id ? value.id : null
},
setDashboardInfo(value: any) {
this.dashboardInfo = value
},
setComponentData(value: any) {
this.componentData = value
},
setCanvasStyleData(value: any) {
this.canvasStyleData = value
},
setTabCollisionActiveId(tabId: any) {
this.tabCollisionActiveId = tabId
},
setTabMoveInActiveId(tabId: any) {
this.tabMoveInActiveId = tabId
},
updateDashboardInfo(params: any) {
Object.keys(params).forEach((key: string) => {
// @ts-ignore
this.dashboardInfo[key] = params[key]
})
},
setCanvasViewInfo(params: any) {
this.canvasViewInfo = params
},
canvasDataInit() {
}
state: () => {
return {
tabCollisionActiveId: null,
tabMoveInActiveId: null,
curComponent: null,
curComponentId: null,
canvasStyleData: {},
componentData: [],
canvasViewInfo: {},
fullscreenFlag: false,
dataPrepareState: false,
dashboardInfo: {
id: null,
name: null,
pid: null,
workspaceId: null,
status: null,
dataState: null,
createName: null,
updateName: null,
createTime: null,
updateTime: null,
contentId: null,
type: null,
},
}
},
getters: {
getCurComponent(): any {
return this.curComponent
},
},
actions: {
setCurComponent(value: any) {
this.curComponent = value
this.curComponentId = value && value.id ? value.id : null
},
setDashboardInfo(value: any) {
this.dashboardInfo = value
},
setComponentData(value: any) {
this.componentData = value
},
setCanvasStyleData(value: any) {
this.canvasStyleData = value
},
setTabCollisionActiveId(tabId: any) {
this.tabCollisionActiveId = tabId
},
setTabMoveInActiveId(tabId: any) {
this.tabMoveInActiveId = tabId
},
updateDashboardInfo(params: any) {
Object.keys(params).forEach((key: string) => {
// @ts-ignore
this.dashboardInfo[key] = params[key]
})
},
setCanvasViewInfo(params: any) {
this.canvasViewInfo = params
},
canvasDataInit() {},
},
})
export const dashboardStoreWithOut = () => {
return dashboardStore(store)
}
return dashboardStore(store)
}
+122 -126
View File
@@ -1,149 +1,145 @@
import {defineStore, storeToRefs} from 'pinia'
import { defineStore, storeToRefs } from 'pinia'
import _ from 'lodash'
import {dashboardStoreWithOut} from "@/stores/dashboard/dashboard.ts";
import {store} from "@/stores";
import { dashboardStoreWithOut } from '@/stores/dashboard/dashboard.ts'
import { store } from '@/stores'
const dashboardStore = dashboardStoreWithOut()
const {
dashboardInfo,
componentData,
canvasStyleData,
canvasViewInfo,
dataPrepareState
} = storeToRefs(dashboardStore)
const { dashboardInfo, componentData, canvasStyleData, canvasViewInfo, dataPrepareState } =
storeToRefs(dashboardStore)
let defaultCanvasInfo = {
componentData: [],
canvasStyleData: {},
canvasViewInfo: {},
cacheViewIdInfo: {
snapshotCacheViewCalc: [],
snapshotCacheViewRender: []
}
componentData: [],
canvasStyleData: {},
canvasViewInfo: {},
cacheViewIdInfo: {
snapshotCacheViewCalc: [],
snapshotCacheViewRender: [],
},
}
// Storage snapshot structure {componentData:[],canvasStyleData:{},canvasViewInfo:{}}
export const snapshotStore = defineStore('snapshot', {
state: () => {
return {
snapshotDisableTime: 1, // Snapshot disable time (resolves style changes caused by redo/undo)
styleChangeTimes: -1, // Component style modification count
cacheStyleChangeTimes: 0, // Unsaved component style modification count in the dashboard
snapshotCacheTimes: 0, // Current unsnapshot modification count (timed cache, cached every 5 seconds; used for minor changes like style adjustments)
snapshotData: [], // Editor snapshot data
snapshotIndex: -1 // Snapshot index
}
},
actions: {
initSnapShot() {
this.styleChangeTimes = -1
this.cacheStyleChangeTimes = 0
this.snapshotCacheTimes = 0
this.snapshotData = []
this.snapshotIndex = -1
},
//Regularly check the number of changes, and if there are any changes, perform mirroring processing
snapshotCatchToStore() {
if (this.snapshotCacheTimes) {
this.recordSnapshot()
}
},
recordSnapshotCache() {
if (dataPrepareState.value) {
this.snapshotCacheTimes++
}
},
undo() {
if (this.snapshotIndex > 0) {
this.snapshotIndex--
const componentSnapshot = _.cloneDeep(this.snapshotData[this.snapshotIndex]) || getDefaultCanvasInfo()
// @ts-ignore
componentSnapshot.dashboardInfo.id = dashboardInfo.value.id
// @ts-ignore
componentSnapshot.dashboardInfo.pid = dashboardInfo.value.pid
// @ts-ignore
componentSnapshot.dashboardInfo.dataState = dashboardInfo.value.dataState
// @ts-ignore
componentSnapshot.dashboardInfo.contentId = dashboardInfo.value.contentId
this.snapshotPublish(componentSnapshot)
this.styleChangeTimes++
this.snapshotDisableTime = Date.now() + 3000
dashboardStore.setCurComponent(null)
}
},
redo() {
if (this.snapshotIndex < this.snapshotData.length - 1) {
this.snapshotIndex++
const snapshotInfo = _.cloneDeep(this.snapshotData[this.snapshotIndex])
// @ts-ignore
snapshotInfo.dashboardInfo.id = dashboardInfo.value.id
// @ts-ignore
snapshotInfo.dashboardInfo.pid = dashboardInfo.value.pid
// @ts-ignore
snapshotInfo.dashboardInfo.dataState = dashboardInfo.value.dataState
// @ts-ignore
snapshotInfo.dashboardInfo.contentId = dashboardInfo.value.contentId
this.snapshotPublish(snapshotInfo)
this.styleChangeTimes++
this.snapshotDisableTime = Date.now() + 3000
dashboardStore.setCurComponent(null)
}
},
snapshotPublish(snapshotInfo: any) {
dashboardStore.updateDashboardInfo(snapshotInfo.dashboardInfo)
dashboardStore.setComponentData(snapshotInfo.componentData)
dashboardStore.setCanvasStyleData(snapshotInfo.canvasStyleData)
dashboardStore.setCanvasViewInfo(snapshotInfo.canvasViewInfo)
},
resetStyleChangeTimes() {
this.styleChangeTimes = 0
this.snapshotCacheTimes = 0
},
resetSnapshot() {
this.styleChangeTimes = -1
this.cacheStyleChangeTimes = 0
this.snapshotCacheTimes = 0
this.snapshotData = []
this.snapshotIndex = -1
this.recordSnapshot()
},
recordSnapshot() {
if (dataPrepareState.value) {
this.styleChangeTimes = ++this.styleChangeTimes
// Add a new snapshot
// @ts-ignore
this.snapshotData[++this.snapshotIndex] = {
componentData: _.cloneDeep(componentData.value),
canvasStyleData: _.cloneDeep(canvasStyleData.value),
canvasViewInfo: _.cloneDeep(canvasViewInfo.value),
dashboardInfo: _.cloneDeep(dashboardInfo.value)
}
// During the undo process, when adding a new snapshot, the snapshots following it should be cleaned up
if (this.snapshotIndex < this.snapshotData.length - 1) {
this.snapshotData = this.snapshotData.slice(0, this.snapshotIndex + 1)
}
// Clean up cache counters
this.snapshotCacheTimes = 0
}
}
state: () => {
return {
snapshotDisableTime: 1, // Snapshot disable time (resolves style changes caused by redo/undo)
styleChangeTimes: -1, // Component style modification count
cacheStyleChangeTimes: 0, // Unsaved component style modification count in the dashboard
snapshotCacheTimes: 0, // Current unsnapshot modification count (timed cache, cached every 5 seconds; used for minor changes like style adjustments)
snapshotData: [], // Editor snapshot data
snapshotIndex: -1, // Snapshot index
}
},
actions: {
initSnapShot() {
this.styleChangeTimes = -1
this.cacheStyleChangeTimes = 0
this.snapshotCacheTimes = 0
this.snapshotData = []
this.snapshotIndex = -1
},
//Regularly check the number of changes, and if there are any changes, perform mirroring processing
snapshotCatchToStore() {
if (this.snapshotCacheTimes) {
this.recordSnapshot()
}
},
recordSnapshotCache() {
if (dataPrepareState.value) {
this.snapshotCacheTimes++
}
},
undo() {
if (this.snapshotIndex > 0) {
this.snapshotIndex--
const componentSnapshot =
_.cloneDeep(this.snapshotData[this.snapshotIndex]) || getDefaultCanvasInfo()
// @ts-ignore
componentSnapshot.dashboardInfo.id = dashboardInfo.value.id
// @ts-ignore
componentSnapshot.dashboardInfo.pid = dashboardInfo.value.pid
// @ts-ignore
componentSnapshot.dashboardInfo.dataState = dashboardInfo.value.dataState
// @ts-ignore
componentSnapshot.dashboardInfo.contentId = dashboardInfo.value.contentId
this.snapshotPublish(componentSnapshot)
this.styleChangeTimes++
this.snapshotDisableTime = Date.now() + 3000
dashboardStore.setCurComponent(null)
}
},
redo() {
if (this.snapshotIndex < this.snapshotData.length - 1) {
this.snapshotIndex++
const snapshotInfo = _.cloneDeep(this.snapshotData[this.snapshotIndex])
// @ts-ignore
snapshotInfo.dashboardInfo.id = dashboardInfo.value.id
// @ts-ignore
snapshotInfo.dashboardInfo.pid = dashboardInfo.value.pid
// @ts-ignore
snapshotInfo.dashboardInfo.dataState = dashboardInfo.value.dataState
// @ts-ignore
snapshotInfo.dashboardInfo.contentId = dashboardInfo.value.contentId
this.snapshotPublish(snapshotInfo)
this.styleChangeTimes++
this.snapshotDisableTime = Date.now() + 3000
dashboardStore.setCurComponent(null)
}
},
snapshotPublish(snapshotInfo: any) {
dashboardStore.updateDashboardInfo(snapshotInfo.dashboardInfo)
dashboardStore.setComponentData(snapshotInfo.componentData)
dashboardStore.setCanvasStyleData(snapshotInfo.canvasStyleData)
dashboardStore.setCanvasViewInfo(snapshotInfo.canvasViewInfo)
},
resetStyleChangeTimes() {
this.styleChangeTimes = 0
this.snapshotCacheTimes = 0
},
resetSnapshot() {
this.styleChangeTimes = -1
this.cacheStyleChangeTimes = 0
this.snapshotCacheTimes = 0
this.snapshotData = []
this.snapshotIndex = -1
this.recordSnapshot()
},
recordSnapshot() {
if (dataPrepareState.value) {
this.styleChangeTimes = ++this.styleChangeTimes
// Add a new snapshot
// @ts-ignore
this.snapshotData[++this.snapshotIndex] = {
componentData: _.cloneDeep(componentData.value),
canvasStyleData: _.cloneDeep(canvasStyleData.value),
canvasViewInfo: _.cloneDeep(canvasViewInfo.value),
dashboardInfo: _.cloneDeep(dashboardInfo.value),
}
// During the undo process, when adding a new snapshot, the snapshots following it should be cleaned up
if (this.snapshotIndex < this.snapshotData.length - 1) {
this.snapshotData = this.snapshotData.slice(0, this.snapshotIndex + 1)
}
// Clean up cache counters
this.snapshotCacheTimes = 0
}
},
},
})
export function setDefaultComponentData(data = []) {
defaultCanvasInfo.componentData = data
defaultCanvasInfo.componentData = data
}
function getDefaultCanvasInfo() {
return defaultCanvasInfo
return defaultCanvasInfo
}
export function setDefaultCanvasInfo(data: any) {
defaultCanvasInfo = data
defaultCanvasInfo = data
}
export const snapshotStoreWithOut = () => {
return snapshotStore(store)
return snapshotStore(store)
}
+37 -38
View File
@@ -1,54 +1,53 @@
// @ts-ignore
import SnowflakeID from 'snowflake-id';
import SnowflakeID from 'snowflake-id'
const snowflake = new SnowflakeID({
mid: 42,
offset: (2010 - 1970) * 365 * 24 * 3600 * 1000
});
mid: 42,
offset: (2010 - 1970) * 365 * 24 * 3600 * 1000,
})
export const guid = (prefix?: string) => {
if (prefix) {
return `${prefix}_${snowflake.generate()}`;
} else {
return snowflake.generate()
}
if (prefix) {
return `${prefix}_${snowflake.generate()}`
} else {
return snowflake.generate()
}
}
export interface CanvasItem {
_dragId: string | number
_dragId: string | number
x: number
y: number
sizeX: number
sizeY: number
[key: string]: any
}
export type CanvasCoord = {
x1: number
y1: number
x2: number
y2: number
c1: number
c2: number
el: {
x: number
y: number
sizeX: number
sizeY: number
_dragId: string | number
[key: string]: any
}
export type CanvasCoord = {
x1: number
y1: number
x2: number
y2: number
c1: number
c2: number
el: {
x: number
y: number
sizeX: number
sizeY: number
_dragId: string | number
[key: string]: any
}
}
}
export type DashboardInfo = {
dataState: string,
optType: string,
id: number,
name: string,
pid: number,
status: number,
type: string,
mobileLayout: boolean,
}
dataState: string
optType: string
id: number
name: string
pid: number
status: number
type: string
mobileLayout: boolean
}
+1 -1
View File
@@ -29,4 +29,4 @@ export function formatTimestamp(timestamp: number, format: string = 'YYYY-MM-DD'
return match
}
})
}
}
+25 -42
View File
@@ -5,7 +5,7 @@ import axios, {
type AxiosRequestConfig,
type AxiosResponse,
type InternalAxiosRequestConfig,
type CancelTokenSource
type CancelTokenSource,
} from 'axios'
import { useCache } from '@/utils/useCache'
@@ -52,9 +52,9 @@ class HttpService {
timeout: 100000,
headers: {
'Content-Type': 'application/json',
...config?.headers
...config?.headers,
},
...config
...config,
})
this.setupInterceptors()
@@ -72,7 +72,7 @@ class HttpService {
// Request logging
// console.log(`[Request] ${config.method?.toUpperCase()} ${config.url}`)
return config
},
(error) => {
@@ -83,7 +83,7 @@ class HttpService {
this.instance.interceptors.response.use(
(response: AxiosResponse) => {
// console.log(`[Response] ${response.config.url}`, response.data)
// Return raw response if configured
if ((response.config as FullRequestConfig).requestOptions?.rawResponse) {
return response
@@ -103,20 +103,18 @@ class HttpService {
async (error: AxiosError) => {
const config = error.config as FullRequestConfig & { __retryCount?: number }
const requestOptions = config?.requestOptions || {}
// Retry logic for specific status codes
const shouldRetry =
error.response?.status === 502 &&
const shouldRetry =
error.response?.status === 502 &&
(config.__retryCount || 0) < (requestOptions.retryCount || 3)
if (shouldRetry) {
config.__retryCount = (config.__retryCount || 0) + 1
// Exponential backoff
await new Promise(resolve =>
setTimeout(resolve, 1000 * (config.__retryCount || 1))
)
await new Promise((resolve) => setTimeout(resolve, 1000 * (config.__retryCount || 1)))
return this.instance.request(config)
}
@@ -132,7 +130,7 @@ class HttpService {
private handleError(error: AxiosError) {
let errorMessage = 'Request error'
if (error.response) {
switch (error.response.status) {
case 400:
@@ -173,7 +171,7 @@ class HttpService {
ElMessage({
message: errorMessage,
type: 'error',
showClose: true
showClose: true,
})
}
@@ -188,7 +186,7 @@ class HttpService {
public request<T = any>(config: FullRequestConfig): Promise<T> {
return this.instance.request({
cancelToken: this.cancelTokenSource.token,
...config
...config,
})
}
@@ -198,15 +196,11 @@ class HttpService {
}
// POST request
public post<T = any>(
url: string,
data?: any,
config?: FullRequestConfig
): Promise<T> {
public post<T = any>(url: string, data?: any, config?: FullRequestConfig): Promise<T> {
return this.request({ ...config, method: 'POST', url, data })
}
public fetchStream(url: string ,data?: any): Promise<any> {
public fetchStream(url: string, data?: any): Promise<any> {
const token = wsCache.get('user.token')
const heads: any = {
'Content-Type': 'application/json',
@@ -223,11 +217,7 @@ class HttpService {
}
// PUT request
public put<T = any>(
url: string,
data?: any,
config?: FullRequestConfig
): Promise<T> {
public put<T = any>(url: string, data?: any, config?: FullRequestConfig): Promise<T> {
return this.request({ ...config, method: 'PUT', url, data })
}
@@ -237,11 +227,7 @@ class HttpService {
}
// PATCH request
public patch<T = any>(
url: string,
data?: any,
config?: FullRequestConfig
): Promise<T> {
public patch<T = any>(url: string, data?: any, config?: FullRequestConfig): Promise<T> {
return this.request({ ...config, method: 'PATCH', url, data })
}
@@ -257,27 +243,24 @@ class HttpService {
return this.post(url, formData, {
headers: {
'Content-Type': 'multipart/form-data'
'Content-Type': 'multipart/form-data',
},
...config
...config,
})
}
// Download file
public download(
url: string,
config?: FullRequestConfig
): Promise<Blob> {
public download(url: string, config?: FullRequestConfig): Promise<Blob> {
return this.request<Blob>({
...config,
method: 'GET',
url,
responseType: 'blob'
responseType: 'blob',
})
}
}
// Create singleton instance
export const request = new HttpService({
baseURL: import.meta.env.VITE_API_BASE_URL
})
baseURL: import.meta.env.VITE_API_BASE_URL,
})
+2 -2
View File
@@ -4,10 +4,10 @@ type CacheType = 'sessionStorage' | 'localStorage'
export const useCache = (type: CacheType = 'localStorage') => {
const wsCache: WebStorageCache = new WebStorageCache({
storage: type
storage: type,
})
return {
wsCache
wsCache,
}
}
+20 -20
View File
@@ -1,24 +1,24 @@
import dayjs from "dayjs";
import dayjs from 'dayjs'
const getCheckDate = (timestamp: any) => {
if (!timestamp) return false
const dt = new Date(timestamp)
if (isNaN(dt.getTime())) return false
return dt
if (!timestamp) return false
const dt = new Date(timestamp)
if (isNaN(dt.getTime())) return false
return dt
}
export const datetimeFormat = (timestamp: any) => {
const dt = getCheckDate(timestamp)
if (!dt) return timestamp
const dt = getCheckDate(timestamp)
if (!dt) return timestamp
const y = dt.getFullYear()
const m = (dt.getMonth() + 1 + '').padStart(2, '0')
const d = (dt.getDate() + '').padStart(2, '0')
const hh = (dt.getHours() + '').padStart(2, '0')
const mm = (dt.getMinutes() + '').padStart(2, '0')
const ss = (dt.getSeconds() + '').padStart(2, '0')
const y = dt.getFullYear()
const m = (dt.getMonth() + 1 + '').padStart(2, '0')
const d = (dt.getDate() + '').padStart(2, '0')
const hh = (dt.getHours() + '').padStart(2, '0')
const mm = (dt.getMinutes() + '').padStart(2, '0')
const ss = (dt.getSeconds() + '').padStart(2, '0')
return `${y}-${m}-${d} ${hh}:${mm}:${ss}`
return `${y}-${m}-${d} ${hh}:${mm}:${ss}`
}
/**
@@ -28,10 +28,10 @@ export const datetimeFormat = (timestamp: any) => {
* @param time
*/
export function getDate(time?: Date | string | number) {
if (!time) return undefined
if (time instanceof Date) return time
if (typeof time === 'string') {
return dayjs(time).toDate()
}
return new Date(time)
if (!time) return undefined
if (time instanceof Date) return time
if (typeof time === 'string') {
return dayjs(time).toDate()
}
return new Date(time)
}
+5 -3
View File
@@ -2,8 +2,10 @@
<div class="welcome-container">
<el-card class="welcome-card">
<h1 class="welcome-title">Welcome</h1>
<p class="welcome-message">You have successfully logged into the system and can now start using various functions.</p>
<el-button type="primary" @click="logout" class="logout-btn">Logout</el-button>
<p class="welcome-message">
You have successfully logged into the system and can now start using various functions.
</p>
<el-button type="primary" class="logout-btn" @click="logout">Logout</el-button>
</el-card>
</div>
</template>
@@ -49,4 +51,4 @@ const logout = () => {
}
}
}
</style>
</style>
+79 -74
View File
@@ -1,27 +1,26 @@
<script setup lang="ts">
import type {ChatMessage} from "@/api/chat.ts";
import {computed, nextTick, ref} from "vue";
import {Loading} from "@element-plus/icons-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";
import type { ChatMessage } from '@/api/chat.ts'
import { computed, nextTick, ref } from 'vue'
import { Loading } from '@element-plus/icons-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
}>()
const settings = ref<{
type: "chart" | "sql"
type: 'chart' | 'sql'
showAnswer: boolean
}>({
type: "chart",
type: 'chart',
showAnswer: false,
})
@@ -36,8 +35,8 @@ const renderChartThinking = computed(() => {
})
const dataObject = computed<{
fields: Array<string>,
data: Array<{ [key: string]: any }>,
fields: Array<string>
data: Array<{ [key: string]: any }>
}>(() => {
if (props.message?.record?.data) {
return JSON.parse(props.message.record.data)
@@ -49,11 +48,11 @@ const chartObject = computed<{
type: ChartTypes
title: string
axis: {
x: { name: string, value: string },
y: { name: string, value: string },
series: { name: string, value: string }
x: { name: string; value: string }
y: { name: string; value: string }
series: { name: string; value: string }
}
columns: Array<{ name: string, value: string }>
columns: Array<{ name: string; value: string }>
}>(() => {
if (props.message?.record?.chart) {
return JSON.parse(props.message.record.chart)
@@ -87,14 +86,14 @@ const chartType = computed<ChartTypes>({
if (currentChartType.value) {
return currentChartType.value
}
return chartObject.value.type ?? "table"
return chartObject.value.type ?? 'table'
},
set(v) {
currentChartType.value = v
}
},
})
function clickTab(type: "chart" | "sql") {
function clickTab(type: 'chart' | 'sql') {
settings.value.type = type
}
@@ -105,43 +104,45 @@ function openChartSelect() {
}
const chartTypeList = computed(() => {
const _list = [{
value: "table",
icon: ICON_TABLE
}]
const _list = [
{
value: 'table',
icon: ICON_TABLE,
},
]
if (chartObject.value) {
switch (chartObject.value.type) {
case "table":
case 'table':
break
case "column":
case "bar":
case "line":
case 'column':
case 'bar':
case 'line':
_list.push({
value: "column",
icon: ICON_COLUMN
value: 'column',
icon: ICON_COLUMN,
})
_list.push({
value: "bar",
icon: ICON_BAR
value: 'bar',
icon: ICON_BAR,
})
_list.push({
value: "line",
icon: ICON_LINE
value: 'line',
icon: ICON_LINE,
})
break
case "pie":
case 'pie':
_list.push({
value: "pie",
icon: ICON_PIE
value: 'pie',
icon: ICON_PIE,
})
}
}
return _list;
return _list
})
const currentChartTypeIcon = computed(() => {
return find(chartTypeList.value, c => c.value === chartType.value)?.icon ?? ICON_TABLE
return find(chartTypeList.value, (c) => c.value === chartType.value)?.icon ?? ICON_TABLE
})
const chartRef = ref()
@@ -152,44 +153,51 @@ function onTypeChange() {
chartRef.value?.renderChart()
})
}
</script>
<template>
<el-container v-if="message">
<el-header style="display: flex; align-items: center; flex-direction: row;">
<div style="flex:1">
<el-header style="display: flex; align-items: center; flex-direction: row">
<div style="flex: 1">
<div v-if="message.isTyping">Thinking ...</div>
<div v-if="chartObject.title && !message.isTyping">{{ chartObject.title }}</div>
</div>
<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">
<div
class="chart-choose-btn"
:class="{ active: settings.type === 'chart', 'no-click': settings.type !== 'chart' }"
@click="openChartSelect"
>
<el-icon size="34">
<component :is="currentChartTypeIcon"/>
<component :is="currentChartTypeIcon" />
</el-icon>
<el-icon>
<ArrowDown/>
<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">
<el-select
ref="chartSelectRef"
v-model="chartType"
class="inner-select"
:disabled="settings.type !== 'chart'"
@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"/>
<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')">
<div
class="chart-choose-btn"
:class="{ active: settings.type !== 'chart' }"
@click="clickTab('sql')"
>
SQL
</div>
</div>
@@ -201,7 +209,7 @@ function onTypeChange() {
<template #title>
Inference process
<el-icon v-if="props.message?.isTyping">
<Loading/>
<Loading />
</el-icon>
</template>
<div>
@@ -230,15 +238,16 @@ function onTypeChange() {
<div v-if="message.record.chart" class="chart-base-container">
<div>
<ChartComponent
ref="chartRef"
v-if="message.record.id"
:id="message.record.id"
:type="chartType"
:columns="chartObject?.columns"
:x="xAxis"
:y="yAxis"
:series="series"
:data="dataObject.data"/>
v-if="message.record.id"
:id="message.record.id"
ref="chartRef"
:type="chartType"
:columns="chartObject?.columns"
:x="xAxis"
:y="yAxis"
:series="series"
:data="dataObject.data"
/>
</div>
</div>
</div>
@@ -296,7 +305,6 @@ function onTypeChange() {
}
.inner-select {
position: absolute;
:deep(.ed-select__wrapper) {
@@ -313,9 +321,7 @@ function onTypeChange() {
:deep(.ed-select__selected-item) {
display: none;
}
}
}
.inner-chart-option {
@@ -326,7 +332,6 @@ function onTypeChange() {
}
.chart-choose-btn {
cursor: pointer;
color: var(--ed-color-primary);
font-size: 16px;
@@ -355,4 +360,4 @@ function onTypeChange() {
color: var(--ed-color-primary-light-9);
}
}
</style>
</style>
+2 -6
View File
@@ -1,11 +1,9 @@
<script setup lang="ts">
import type {ChatMessage} from "@/api/chat.ts";
import type { ChatMessage } from '@/api/chat.ts'
defineProps<{
msg?: ChatMessage
}>()
</script>
<template>
@@ -34,11 +32,9 @@ defineProps<{
.chat-block-content {
padding: 12px;
}
}
.chat-block-footer {
}
}
</style>
</style>
+76 -81
View File
@@ -1,123 +1,121 @@
<script setup lang="ts">
import {Delete, EditPen, MoreFilled} from '@element-plus/icons-vue'
import {type Chat, chatApi} from "@/api/chat.ts";
import {computed} from "vue";
import { Delete, EditPen, MoreFilled } from '@element-plus/icons-vue'
import { type Chat, chatApi } from '@/api/chat.ts'
import { computed } from 'vue'
const props = withDefaults(defineProps<{
currentChatId?: number
chatList: Array<Chat>
loading?: boolean
}>(), {
chatList: () => [],
loading: false
})
const emits = defineEmits(['chatSelected', 'chatRenamed', 'chatDeleted', "update:loading"])
const props = withDefaults(
defineProps<{
currentChatId?: number
chatList: Array<Chat>
loading?: boolean
}>(),
{
chatList: () => [],
loading: false,
}
)
const emits = defineEmits(['chatSelected', 'chatRenamed', 'chatDeleted', 'update:loading'])
const _loading = computed({
get() {
return props.loading
},
set(v) {
emits("update:loading", v)
}
emits('update:loading', v)
},
})
function onClickHistory(chat: Chat) {
emits("chatSelected", chat)
emits('chatSelected', chat)
}
function handleCommand(command: string | number | object, chat: Chat) {
if (chat && chat.id !== undefined) {
switch (command) {
case "rename":
case 'rename':
ElMessageBox.prompt('Please enter new brief', 'Rename', {
confirmButtonText: 'OK',
cancelButtonText: 'Cancel',
customStyle: {'padding-left': 'unset', padding: 'var(--ed-messagebox-padding-primary)'},
customStyle: { 'padding-left': 'unset', padding: 'var(--ed-messagebox-padding-primary)' },
inputValue: chat.brief,
inputValidator: (value: string) => {
if (!value) return false
if (value.trim().length == 0) return false
return true
}
})
.then(({value}) => {
_loading.value = true
chatApi.renameChat(chat.id, value)
.then((res) => {
ElMessage({
type: 'success',
message: 'Successfully renamed chat',
})
emits('chatRenamed', {id: chat.id, brief: res})
})
.catch((err) => {
ElMessage({
type: 'error',
message: err.message,
})
console.error(err)
})
.finally(() => {
_loading.value = false
})
},
}).then(({ value }) => {
_loading.value = true
chatApi
.renameChat(chat.id, value)
.then((res) => {
ElMessage({
type: 'success',
message: 'Successfully renamed chat',
})
emits('chatRenamed', { id: chat.id, brief: res })
})
.catch((err) => {
ElMessage({
type: 'error',
message: err.message,
})
console.error(err)
})
.finally(() => {
_loading.value = false
})
})
break
case "delete":
ElMessageBox.confirm(
'This action will permanently delete the chat. Continue?',
'Warning',
{
confirmButtonText: 'OK',
cancelButtonText: 'Cancel',
type: 'warning',
}
)
.then(() => {
_loading.value = true
chatApi.deleteChat(chat.id)
.then((res) => {
ElMessage({
type: 'success',
message: res,
})
emits('chatDeleted', chat.id)
})
.catch((err) => {
ElMessage({
type: 'error',
message: err.message,
})
console.error(err)
})
.finally(() => {
_loading.value = false
})
case 'delete':
ElMessageBox.confirm('This action will permanently delete the chat. Continue?', 'Warning', {
confirmButtonText: 'OK',
cancelButtonText: 'Cancel',
type: 'warning',
}).then(() => {
_loading.value = true
chatApi
.deleteChat(chat.id)
.then((res) => {
ElMessage({
type: 'success',
message: res,
})
emits('chatDeleted', chat.id)
})
.catch((err) => {
ElMessage({
type: 'error',
message: err.message,
})
console.error(err)
})
.finally(() => {
_loading.value = false
})
})
break
}
}
}
</script>
<template>
<el-scrollbar ref="chatListRef">
<div class="chat-list-inner">
<template v-for="chat in chatList" :key="chat.id">
<div class="chat-list-item" :class="{'active': currentChatId === chat.id}" @click="onClickHistory(chat)">
<div
class="chat-list-item"
:class="{ active: currentChatId === chat.id }"
@click="onClickHistory(chat)"
>
<span class="title">{{ chat.brief ?? 'Untitled' }}</span>
<el-button class="icon-more" link type="primary" @click.stop>
<el-dropdown trigger="click" @command="(cmd: any)=> handleCommand(cmd, chat)">
<el-dropdown trigger="click" @command="(cmd: any) => handleCommand(cmd, chat)">
<el-icon>
<MoreFilled/>
<MoreFilled />
</el-icon>
<template #dropdown>
<el-dropdown-menu>
@@ -135,7 +133,6 @@ function handleCommand(command: string | number | object, chat: Chat) {
<style scoped lang="less">
.chat-list-inner {
--hover-color: var(--ed-color-primary-light-9);
--active-color: var(--hover-color);
@@ -189,8 +186,6 @@ function handleCommand(command: string | number | object, chat: Chat) {
&.active {
background-color: var(--active-color);
}
}
}
</style>
</style>
+21 -23
View File
@@ -1,46 +1,46 @@
<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";
import {UserFilled} from "@element-plus/icons-vue"
import { ChatInfo, type ChatMessage } from '@/api/chat.ts'
import { computed } from 'vue'
import { UserFilled } from '@element-plus/icons-vue'
const props = withDefaults(defineProps<{
msg: ChatMessage
currentChat: ChatInfo
datasource?: number
}>(),
{}
const props = withDefaults(
defineProps<{
msg: ChatMessage
currentChat: ChatInfo
datasource?: number
}>(),
{}
)
const emits = defineEmits(["update:datasource"])
const emits = defineEmits(['update:datasource'])
const _datasource = computed({
get() {
return props.datasource
},
set(v) {
emits("update:datasource", v)
}
emits('update:datasource', v)
},
})
</script>
<template>
<div class="chat-row" :class="{'right-to-left': msg.role === 'user'}">
<el-avatar class="ai-avatar" shape="square" v-if="msg.role === 'assistant'">SQLBot</el-avatar>
<el-avatar class="user-avatar" shape="square" v-if="msg.role === 'user'">
<div class="chat-row" :class="{ 'right-to-left': msg.role === 'user' }">
<el-avatar v-if="msg.role === 'assistant'" class="ai-avatar" shape="square">SQLBot</el-avatar>
<el-avatar v-if="msg.role === 'user'" class="user-avatar" shape="square">
<el-icon>
<UserFilled/>
<UserFilled />
</el-icon>
</el-avatar>
<ChatBlock v-if="!msg.isWelcome" :msg="msg" :class="{'row-full': msg.role === 'assistant'}">
<ChatBlock v-if="!msg.isWelcome" :msg="msg" :class="{ 'row-full': msg.role === 'assistant' }">
<slot></slot>
<template #footer>
<slot name="footer"></slot>
</template>
</ChatBlock>
<WelcomeBlock v-else v-model="_datasource" :current-chat="currentChat" class="row-full"/>
<WelcomeBlock v-else v-model="_datasource" :current-chat="currentChat" class="row-full" />
</div>
</template>
@@ -68,8 +68,6 @@ const _datasource = computed({
}
.user-avatar {
background: var(--ed-color-primary)
background: var(--ed-color-primary);
}
</style>
</style>
+35 -29
View File
@@ -1,13 +1,14 @@
<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 { 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'
import { useI18n } from 'vue-i18n'
const {t} = useI18n()
const props = withDefaults(defineProps<{
const { t } = useI18n()
const props = withDefaults(
defineProps<{
modelValue?: number
currentChat: ChatInfo
}>(),
@@ -31,17 +32,17 @@ const ds = computed(() => {
return {
id: props.currentChat.datasource,
name: props.currentChat.datasource_name ?? 'Datasource does not exist',
type: props.currentChat.engine_type
type: props.currentChat.engine_type,
}
}
return undefined
})
const emits = defineEmits(["update:modelValue"])
const emits = defineEmits(['update:modelValue'])
function selectDs(ds: any) {
if (editable.value) {
emits("update:modelValue", ds.id)
emits('update:modelValue', ds.id)
}
}
@@ -69,16 +70,14 @@ function selectDsInDialog(ds: any) {
function confirmSelectDs() {
if (innerDs.value) {
emits("update:modelValue", innerDs.value)
emits('update:modelValue', innerDs.value)
dialogVisible.value = false
}
}
onMounted(() => {
listDs()
})
</script>
<template>
@@ -87,15 +86,20 @@ onMounted(() => {
<div>{{ t('qa.greeting') }}</div>
<div class="sub">{{ t('qa.description') }}</div>
<template v-if="editable">
<template v-if="dsList.length>0">
<template v-if="dsList.length > 0">
<div class="ds-select-row">
<div>{{ t('qa.select_datasource') }}</div>
<el-button @click="showDs" link type="primary">{{ t('qa.view_more') }}</el-button>
<el-button link type="primary" @click="showDs">{{ t('qa.view_more') }}</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': '']"/>
<DatasourceItemCard
v-if="_index < 3 || item?.id === modelValue"
:ds="item"
class="ds-card"
:class="[item?.id === modelValue ? 'ds-card-selected' : '']"
@click="selectDs(item)"
/>
</template>
</div>
</template>
@@ -108,32 +112,37 @@ onMounted(() => {
<div>{{ t('qa.selected_datasource') }}</div>
</div>
<div class="ds-row-container">
<DatasourceItemCard :ds="ds"/>
<DatasourceItemCard :ds="ds" />
</div>
</template>
</div>
<el-drawer v-model="dialogVisible" title="Choose Datasource" ref="DatasourceListRef" direction="btt"
size="100%">
<el-drawer
ref="DatasourceListRef"
v-model="dialogVisible"
title="Choose Datasource"
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': '']"/>
<DatasourceItemCard
:ds="item"
class="ds-card"
:class="[item?.id === innerDs ? 'ds-card-selected' : '']"
@click="selectDsInDialog(item)"
/>
</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>
<el-button type="primary" @click="confirmSelectDs"> Confirm </el-button>
</div>
</template>
</el-drawer>
</ChatBlock>
</template>
@@ -158,7 +167,6 @@ onMounted(() => {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
gap: 24px;
}
.ds-card {
@@ -169,6 +177,4 @@ onMounted(() => {
box-shadow: 0 1px 3px var(--ed-color-primary-light-5);
border: 1px solid var(--ed-color-primary-light-5);
}
</style>
+20 -22
View File
@@ -1,34 +1,32 @@
export interface ChartAxis {
name: string,
value: string,
type?: 'x' | 'y' | 'series'
name: string
value: string
type?: 'x' | 'y' | 'series'
}
export interface ChartData {
[key: string]: any
[key: string]: any
}
export type ChartTypes = "table" | "bar" | 'column' | "line" | "pie"
export type ChartTypes = 'table' | 'bar' | 'column' | 'line' | 'pie'
export abstract class BaseChart {
id: string;
_name: string = 'base-chart';
axis: Array<ChartAxis> = [];
data: Array<ChartData> = [];
id: string
_name: string = 'base-chart'
axis: Array<ChartAxis> = []
data: Array<ChartData> = []
constructor(id: string, name: string) {
this.id = id;
this._name = name;
}
constructor(id: string, name: string) {
this.id = id
this._name = name
}
init(axis: Array<ChartAxis>, data: Array<ChartData>): void {
this.axis = axis;
this.data = data;
}
init(axis: Array<ChartAxis>, data: Array<ChartData>): void {
this.axis = axis
this.data = data
}
abstract render(): void
abstract render(): void
abstract destroy(): void
}
abstract destroy(): void
}
@@ -1,31 +1,28 @@
import {BaseChart} from "@/views/chat/component/BaseChart.ts";
import {Chart} from '@antv/g2';
import { BaseChart } from '@/views/chat/component/BaseChart.ts'
import { Chart } from '@antv/g2'
export abstract class BaseG2Chart extends BaseChart {
chart: Chart
chart: Chart;
constructor(id: string, name: string) {
super(id, name)
this.chart = new Chart({
container: id,
autoFit: true,
})
constructor(id: string, name: string) {
super(id, name)
this.chart = new Chart({
container: id,
autoFit: true,
})
this.chart.theme({
view: {
viewFill: '#FFFFFF',
},
})
}
this.chart.theme({
view: {
viewFill: '#FFFFFF',
},
})
}
render() {
this.chart?.render()
}
render() {
this.chart?.render()
}
destroy() {
this.chart?.destroy()
}
}
destroy() {
this.chart?.destroy()
}
}
@@ -1,41 +1,44 @@
<script setup lang="ts">
import {computed, onMounted, onUnmounted} from "vue";
import {getChartInstance} from "@/views/chat/component/index.ts";
import type {BaseChart, ChartAxis, ChartData} from "@/views/chat/component/BaseChart.ts";
import { computed, onMounted, onUnmounted } from 'vue'
import { getChartInstance } from '@/views/chat/component/index.ts'
import type { BaseChart, ChartAxis, ChartData } from '@/views/chat/component/BaseChart.ts'
const params = withDefaults(defineProps<{
id: string | number
type: string
data?: Array<ChartData>
columns?: Array<ChartAxis>
x?: Array<ChartAxis>
y?: Array<ChartAxis>
series?: Array<ChartAxis>
}>(), {
data: () => [],
columns: () => [],
x: () => [],
y: () => [],
series: () => [],
})
const params = withDefaults(
defineProps<{
id: string | number
type: string
data?: Array<ChartData>
columns?: Array<ChartAxis>
x?: Array<ChartAxis>
y?: Array<ChartAxis>
series?: Array<ChartAxis>
}>(),
{
data: () => [],
columns: () => [],
x: () => [],
y: () => [],
series: () => [],
}
)
const chartId = computed(() => {
return "chart-component-" + params.id
return 'chart-component-' + params.id
})
const axis = computed(() => {
const _list: Array<ChartAxis> = []
params.columns.forEach(column => {
_list.push({name: column.name, value: column.value})
params.columns.forEach((column) => {
_list.push({ name: column.name, value: column.value })
})
params.x.forEach(column => {
_list.push({name: column.name, value: column.value, type: 'x'})
params.x.forEach((column) => {
_list.push({ name: column.name, value: column.value, type: 'x' })
})
params.y.forEach(column => {
_list.push({name: column.name, value: column.value, type: 'y'})
params.y.forEach((column) => {
_list.push({ name: column.name, value: column.value, type: 'y' })
})
params.series.forEach(column => {
_list.push({name: column.name, value: column.value, type: 'series'})
params.series.forEach((column) => {
_list.push({ name: column.name, value: column.value, type: 'series' })
})
return _list
})
@@ -70,11 +73,10 @@ onMounted(() => {
onUnmounted(() => {
destroyChart()
})
</script>
<template>
<div class="chart-container" :id="chartId"></div>
<div :id="chartId" class="chart-container"></div>
</template>
<style scoped lang="less">
@@ -84,4 +86,4 @@ onUnmounted(() => {
min-height: 360px;
min-width: 360px;
}
</style>
</style>
+27 -29
View File
@@ -1,34 +1,32 @@
import {BaseG2Chart} from "@/views/chat/component/BaseG2Chart.ts";
import type {ChartAxis, ChartData} from "@/views/chat/component/BaseChart.ts";
import { BaseG2Chart } from '@/views/chat/component/BaseG2Chart.ts'
import type { ChartAxis, ChartData } from '@/views/chat/component/BaseChart.ts'
export class Bar extends BaseG2Chart {
constructor(id: string) {
super(id, 'bar')
}
constructor(id: string) {
super(id, "bar");
init(axis: Array<ChartAxis>, data: Array<ChartData>) {
super.init(axis, data)
const x = this.axis.filter((item) => item.type === 'x')
const y = this.axis.filter((item) => item.type === 'y')
if (x.length == 0 || y.length == 0) {
return
}
init(axis: Array<ChartAxis>, data: Array<ChartData>) {
super.init(axis, data);
const x = this.axis.filter(item => item.type === "x");
const y = this.axis.filter(item => item.type === "y");
if (x.length == 0 || y.length == 0) {
return;
}
this.chart?.interval()
.coordinate({transform: [{type: 'transpose'}]})
.data(data)
.encode('x', x[0].value)
.encode('y', y[0].value)
.scale('x', {
nice: true,
})
.scale('y', {
nice: true,
})
.interaction('elementHighlight', {background: true});
}
}
this.chart
?.interval()
.coordinate({ transform: [{ type: 'transpose' }] })
.data(data)
.encode('x', x[0].value)
.encode('y', y[0].value)
.scale('x', {
nice: true,
})
.scale('y', {
nice: true,
})
.interaction('elementHighlight', { background: true })
}
}
@@ -1,34 +1,32 @@
import {BaseG2Chart} from "@/views/chat/component/BaseG2Chart.ts";
import type {ChartAxis, ChartData} from "@/views/chat/component/BaseChart.ts";
import { BaseG2Chart } from '@/views/chat/component/BaseG2Chart.ts'
import type { ChartAxis, ChartData } from '@/views/chat/component/BaseChart.ts'
export class Column extends BaseG2Chart {
constructor(id: string) {
super(id, 'column')
}
constructor(id: string) {
super(id, "column");
init(axis: Array<ChartAxis>, data: Array<ChartData>) {
super.init(axis, data)
const x = this.axis.filter((item) => item.type === 'x')
const y = this.axis.filter((item) => item.type === 'y')
if (x.length == 0 || y.length == 0) {
return
}
init(axis: Array<ChartAxis>, data: Array<ChartData>) {
super.init(axis, data);
const x = this.axis.filter(item => item.type === "x");
const y = this.axis.filter(item => item.type === "y");
if (x.length == 0 || y.length == 0) {
return;
}
this.chart?.interval()
.data(data)
.encode('x', x[0].value)
.encode('y', y[0].value)
.scale('x', {
nice: true,
})
.scale('y', {
nice: true,
})
.interaction('elementHighlight', {background: true});
}
}
this.chart
?.interval()
.data(data)
.encode('x', x[0].value)
.encode('y', y[0].value)
.scale('x', {
nice: true,
})
.scale('y', {
nice: true,
})
.interaction('elementHighlight', { background: true })
}
}
@@ -1,44 +1,40 @@
import {BaseG2Chart} from "@/views/chat/component/BaseG2Chart.ts";
import type {ChartAxis, ChartData} from "@/views/chat/component/BaseChart.ts";
import { BaseG2Chart } from '@/views/chat/component/BaseG2Chart.ts'
import type { ChartAxis, ChartData } from '@/views/chat/component/BaseChart.ts'
export class Line extends BaseG2Chart {
constructor(id: string) {
super(id, 'line')
}
constructor(id: string) {
super(id, "line");
init(axis: Array<ChartAxis>, data: Array<ChartData>) {
super.init(axis, data)
const x = this.axis.filter((item) => item.type === 'x')
const y = this.axis.filter((item) => item.type === 'y')
if (x.length == 0 || y.length == 0) {
return
}
init(axis: Array<ChartAxis>, data: Array<ChartData>) {
super.init(axis, data);
this.chart
?.data(data)
.encode('x', x[0].value)
.encode('y', y[0].value)
.scale('x', {
nice: true,
})
.scale('y', {
nice: true,
})
const x = this.axis.filter(item => item.type === "x");
const y = this.axis.filter(item => item.type === "y");
this.chart?.line().label({
text: 'value',
style: {
dx: -10,
dy: -12,
},
})
if (x.length == 0 || y.length == 0) {
return;
}
this.chart?.data(data)
.encode('x', x[0].value)
.encode('y', y[0].value)
.scale('x', {
nice: true,
})
.scale('y', {
nice: true,
});
this.chart?.line().label({
text: 'value',
style: {
dx: -10,
dy: -12,
},
});
this.chart?.point()
.style('fill', 'white')
.tooltip(false);
}
}
this.chart?.point().style('fill', 'white').tooltip(false)
}
}
+22 -26
View File
@@ -1,32 +1,28 @@
import {BaseG2Chart} from "@/views/chat/component/BaseG2Chart.ts";
import type {ChartAxis, ChartData} from "@/views/chat/component/BaseChart.ts";
import { BaseG2Chart } from '@/views/chat/component/BaseG2Chart.ts'
import type { ChartAxis, ChartData } from '@/views/chat/component/BaseChart.ts'
export class Pie extends BaseG2Chart {
constructor(id: string) {
super(id, 'pie')
}
constructor(id: string) {
super(id, "pie");
init(axis: Array<ChartAxis>, data: Array<ChartData>) {
super.init(axis, data)
const y = this.axis.filter((item) => item.type === 'y')
const series = this.axis.filter((item) => item.type === 'series')
if (series.length == 0 || y.length == 0) {
return
}
init(axis: Array<ChartAxis>, data: Array<ChartData>) {
super.init(axis, data);
const y = this.axis.filter(item => item.type === "y");
const series = this.axis.filter(item => item.type === "series");
this.chart.coordinate({ type: 'theta', outerRadius: 0.8 })
if (series.length == 0 || y.length == 0) {
return;
}
this.chart.coordinate({type: 'theta', outerRadius: 0.8});
this.chart?.interval()
.transform({type: 'stackY'})
.data(data)
.encode('y', y[0].value)
.encode('color', series[0].value)
.legend('color', {position: 'bottom', layout: {justifyContent: 'center'}})
;
}
}
this.chart
?.interval()
.transform({ type: 'stackY' })
.data(data)
.encode('y', y[0].value)
.encode('color', series[0].value)
.legend('color', { position: 'bottom', layout: { justifyContent: 'center' } })
}
}
@@ -1,79 +1,77 @@
import {BaseChart, type ChartAxis, type ChartData} from "@/views/chat/component/BaseChart.ts";
import {TableSheet, type S2Options, type S2DataConfig, type S2MountContainer} from "@antv/s2";
import {debounce} from "lodash-es";
import { BaseChart, type ChartAxis, type ChartData } from '@/views/chat/component/BaseChart.ts'
import { TableSheet, type S2Options, type S2DataConfig, type S2MountContainer } from '@antv/s2'
import { debounce } from 'lodash-es'
export class Table extends BaseChart {
table?: TableSheet = undefined
table?: TableSheet = undefined
container: S2MountContainer | null = null
container: S2MountContainer | null = null
debounceRender: any
debounceRender: any
resizeObserver: ResizeObserver
resizeObserver: ResizeObserver
constructor(id: string) {
super(id, 'table')
this.container = document.getElementById(id)
constructor(id: string) {
super(id, "table");
this.container = document.getElementById(id);
this.debounceRender = debounce(async (width?: number, height?: number) => {
if (this.table) {
this.table.changeSheetSize(width, height)
await this.table.render(false)
}
}, 200)
this.debounceRender = debounce(async (width?: number, height?: number) => {
if (this.table) {
this.table.changeSheetSize(width, height);
await this.table.render(false);
}
}, 200);
this.resizeObserver = new ResizeObserver(([entry] = []) => {
const [size] = entry.borderBoxSize || []
this.debounceRender(size.inlineSize, size.blockSize)
})
this.resizeObserver = new ResizeObserver(([entry] = []) => {
const [size] = entry.borderBoxSize || [];
this.debounceRender(size.inlineSize, size.blockSize);
});
if (this.container?.parentElement) {
this.resizeObserver.observe(this.container.parentElement)
}
}
if (this.container?.parentElement) {
this.resizeObserver.observe(this.container.parentElement);
}
init(axis: Array<ChartAxis>, data: Array<ChartData>) {
super.init(axis, data)
const s2DataConfig: S2DataConfig = {
fields: {
columns: this.axis?.map((a) => a.value) ?? [],
},
meta:
this.axis?.map((a) => {
return {
field: a.value,
name: a.name,
}
}) ?? [],
data: this.data,
}
init(axis: Array<ChartAxis>, data: Array<ChartData>) {
super.init(axis, data);
const s2DataConfig: S2DataConfig = {
fields: {
columns: this.axis?.map(a => a.value) ?? [],
},
meta: this.axis?.map(a => {
return {
field: a.value,
name: a.name
}
}) ?? [],
data: this.data,
};
const s2Options: S2Options = {
width: 600,
height: 360,
placeholder: {
cell: '-',
empty: {
icon: 'Empty',
description: 'No Data',
},
},
};
if (this.container) {
this.table = new TableSheet(this.container, s2DataConfig, s2Options);
}
const s2Options: S2Options = {
width: 600,
height: 360,
placeholder: {
cell: '-',
empty: {
icon: 'Empty',
description: 'No Data',
},
},
}
render() {
this.table?.render()
if (this.container) {
this.table = new TableSheet(this.container, s2DataConfig, s2Options)
}
}
destroy() {
this.table?.destroy()
this.resizeObserver?.disconnect()
}
render() {
this.table?.render()
}
}
destroy() {
this.table?.destroy()
this.resizeObserver?.disconnect()
}
}
+22 -23
View File
@@ -1,33 +1,32 @@
import {BaseChart} from "@/views/chat/component/BaseChart.ts";
import {Bar} from "@/views/chat/component/charts/Bar.ts";
import {Column} from "@/views/chat/component/charts/Column.ts";
import {Line} from "@/views/chat/component/charts/Line.ts";
import {Table} from "@/views/chat/component/charts/Table.ts";
import {Pie} from "@/views/chat/component/charts/Pie.ts";
import { BaseChart } from '@/views/chat/component/BaseChart.ts'
import { Bar } from '@/views/chat/component/charts/Bar.ts'
import { Column } from '@/views/chat/component/charts/Column.ts'
import { Line } from '@/views/chat/component/charts/Line.ts'
import { Table } from '@/views/chat/component/charts/Table.ts'
import { Pie } from '@/views/chat/component/charts/Pie.ts'
const CHART_TYPE_MAP: { [key: string]: any } = {
"table": Table,
"column": Column,
"bar": Bar,
"line": Line,
"pie": Pie
table: Table,
column: Column,
bar: Bar,
line: Line,
pie: Pie,
}
const isParent = (type: any, parentType: any) => {
let _type = type
while (_type) {
if (_type === parentType) {
return true
}
_type = _type.__proto__
let _type = type
while (_type) {
if (_type === parentType) {
return true
}
return false
_type = _type.__proto__
}
return false
}
export function getChartInstance(type: string, id: string): BaseChart | undefined {
if (isParent(CHART_TYPE_MAP[type], BaseChart)) {
return new CHART_TYPE_MAP[type](id) as BaseChart;
}
return undefined
if (isParent(CHART_TYPE_MAP[type], BaseChart)) {
return new CHART_TYPE_MAP[type](id) as BaseChart
}
return undefined
}
+188 -192
View File
@@ -5,19 +5,19 @@
<el-header class="chat-list-header">
<el-button type="primary" @click="createNewChat">
<el-icon>
<Plus/>
<Plus />
</el-icon>
{{ t('qa.New Conversation') }}
</el-button>
</el-header>
<el-main class="chat-list">
<ChatList
:current-chat-id="currentChatId"
v-model:loading="loading"
:chat-list="chatList"
@chat-selected="onClickHistory"
@chat-deleted="onChatDeleted"
@chat-renamed="onChatRenamed"
v-model:loading="loading"
:current-chat-id="currentChatId"
:chat-list="chatList"
@chat-selected="onClickHistory"
@chat-deleted="onChatDeleted"
@chat-renamed="onChatRenamed"
/>
</el-main>
</el-container>
@@ -27,14 +27,14 @@
<el-scrollbar ref="chatListRef">
<template v-for="(message, _index) in computedMessages" :key="_index">
<ChatRow
:current-chat="currentChat"
v-model:datasource="currentChat.datasource"
:msg="message"
v-model:datasource="currentChat.datasource"
:current-chat="currentChat"
:msg="message"
>
<template v-if="message.role === 'assistant'">
<ChatAnswer :message="message"/>
<ChatAnswer :message="message" />
</template>
<template #footer v-if="message.role === 'assistant'">
<template v-if="message.role === 'assistant'" #footer>
<!--<div>Suggestion</div>-->
</template>
</ChatRow>
@@ -43,33 +43,31 @@
</el-main>
<el-footer class="chat-footer">
<div style="height: 24px">
<template
v-if="currentChat.datasource && currentChat.datasource_name"
>
<template v-if="currentChat.datasource && currentChat.datasource_name">
{{ t('ds.title') }}:{{ currentChat.datasource_name }}
</template>
</div>
<div class="input-wrapper">
<el-input
:disabled="isTyping"
class="input-area"
v-model="inputMessage"
type="textarea"
:rows="1"
:autosize="{ minRows: 1, maxRows: 8 }"
:placeholder="t('qa.question_placeholder')"
@keydown.enter.exact.prevent="sendMessage"
@keydown.ctrl.enter.exact.prevent="handleCtrlEnter"
v-model="inputMessage"
:disabled="isTyping"
class="input-area"
type="textarea"
:rows="1"
:autosize="{ minRows: 1, maxRows: 8 }"
:placeholder="t('qa.question_placeholder')"
@keydown.enter.exact.prevent="sendMessage"
@keydown.ctrl.enter.exact.prevent="handleCtrlEnter"
/>
<el-button
link
type="primary"
class="input-icon"
@click="sendMessage"
:disabled="isTyping"
link
type="primary"
class="input-icon"
:disabled="isTyping"
@click="sendMessage"
>
<el-icon size="20">
<Position/>
<Position />
</el-icon>
</el-button>
</div>
@@ -79,17 +77,17 @@
</template>
<script setup lang="ts">
import {computed, nextTick, onMounted, ref} from "vue";
import {Plus, Position} from "@element-plus/icons-vue";
import {Chat, chatApi, ChatInfo, type ChatMessage, ChatRecord, questionApi,} from "@/api/chat";
import ChatList from "./ChatList.vue";
import ChatRow from "./ChatRow.vue";
import ChatAnswer from "./ChatAnswer.vue";
import {useI18n} from 'vue-i18n'
import { computed, nextTick, onMounted, ref } from 'vue'
import { Plus, Position } from '@element-plus/icons-vue'
import { Chat, chatApi, ChatInfo, type ChatMessage, ChatRecord, questionApi } from '@/api/chat'
import ChatList from './ChatList.vue'
import ChatRow from './ChatRow.vue'
import ChatAnswer from './ChatAnswer.vue'
import { useI18n } from 'vue-i18n'
const {t} = useI18n()
const { t } = useI18n()
const inputMessage = ref("");
const inputMessage = ref('')
const chatListRef = ref()
@@ -97,94 +95,94 @@ function scrollToBottom() {
nextTick(() => {
chatListRef.value?.scrollTo({
top: chatListRef.value.wrapRef.scrollHeight,
behavior: 'smooth'
behavior: 'smooth',
})
})
}
const loading = ref<boolean>(false);
const chatList = ref<Array<ChatInfo>>([]);
const loading = ref<boolean>(false)
const chatList = ref<Array<ChatInfo>>([])
const currentChatId = ref<number | undefined>();
const currentChat = ref<ChatInfo>(new ChatInfo());
const isTyping = ref<boolean>(false);
const currentChatId = ref<number | undefined>()
const currentChat = ref<ChatInfo>(new ChatInfo())
const isTyping = ref<boolean>(false)
const computedMessages = computed<Array<ChatMessage>>(() => {
const welcome: ChatMessage = {
role: "assistant",
role: 'assistant',
create_time: currentChat.value?.create_time,
content: currentChat.value?.datasource,
isTyping: false,
isWelcome: true,
};
const messages: Array<ChatMessage> = [welcome];
}
const messages: Array<ChatMessage> = [welcome]
if (currentChatId.value === undefined) {
return messages;
return messages
}
for (let i = 0; i < currentChat.value.records.length; i++) {
const record = currentChat.value.records[i];
const record = currentChat.value.records[i]
if (record.question !== undefined) {
messages.push({
role: "user",
role: 'user',
create_time: record.create_time,
content: record.question,
});
})
}
messages.push({
role: "assistant",
role: 'assistant',
create_time: record.create_time,
record: record,
isTyping: i === currentChat.value.records.length - 1 && isTyping.value,
});
})
}
return messages;
});
return messages
})
const createNewChat = () => {
currentChat.value = new ChatInfo();
currentChatId.value = undefined;
inputMessage.value = "";
};
currentChat.value = new ChatInfo()
currentChatId.value = undefined
inputMessage.value = ''
}
function getChatList() {
loading.value = true;
loading.value = true
chatApi
.list()
.then((res) => {
chatList.value = chatApi.toChatInfoList(res);
})
.finally(() => {
loading.value = false;
});
.list()
.then((res) => {
chatList.value = chatApi.toChatInfoList(res)
})
.finally(() => {
loading.value = false
})
}
function onClickHistory(chat: Chat) {
currentChat.value = new ChatInfo(chat);
currentChat.value = new ChatInfo(chat)
if (chat !== undefined && chat.id !== undefined && !loading.value) {
currentChatId.value = chat.id;
loading.value = true;
currentChatId.value = chat.id
loading.value = true
chatApi
.get(chat.id)
.then((res) => {
const info = chatApi.toChatInfo(res);
if (info) {
currentChat.value = info;
.get(chat.id)
.then((res) => {
const info = chatApi.toChatInfo(res)
if (info) {
currentChat.value = info
scrollToBottom()
}
})
.finally(() => {
loading.value = false;
});
scrollToBottom()
}
})
.finally(() => {
loading.value = false
})
}
}
function onChatDeleted(id: number) {
for (let i = 0; i < chatList.value.length; i++) {
if (chatList.value[i].id === id) {
chatList.value.splice(i, 1);
return;
chatList.value.splice(i, 1)
return
}
}
}
@@ -192,87 +190,86 @@ function onChatDeleted(id: number) {
function onChatRenamed(chat: Chat) {
chatList.value.forEach((c: Chat) => {
if (c.id === chat.id) {
c.brief = chat.brief;
c.brief = chat.brief
}
});
})
if (currentChat.value.id === chat.id) {
currentChat.value.brief = chat.brief;
currentChat.value.brief = chat.brief
}
}
onMounted(() => {
getChatList();
});
getChatList()
})
const sendMessage = async () => {
if (!inputMessage.value.trim()) return;
if (computedMessages.value[0].content === undefined) return;
if (!inputMessage.value.trim()) return
if (computedMessages.value[0].content === undefined) return
loading.value = true;
isTyping.value = true;
loading.value = true
isTyping.value = true
const currentRecord = new ChatRecord();
currentRecord.create_time = new Date();
currentRecord.chat_id = currentChatId.value;
currentRecord.question = inputMessage.value;
currentRecord.sql_answer = "";
currentRecord.sql = "";
currentRecord.chart_answer = "";
currentRecord.chart = "";
const currentRecord = new ChatRecord()
currentRecord.create_time = new Date()
currentRecord.chat_id = currentChatId.value
currentRecord.question = inputMessage.value
currentRecord.sql_answer = ''
currentRecord.sql = ''
currentRecord.chart_answer = ''
currentRecord.chart = ''
currentChat.value.records.push(currentRecord);
inputMessage.value = "";
currentChat.value.records.push(currentRecord)
inputMessage.value = ''
let error = false;
let error = false
if (currentChatId.value === undefined) {
await chatApi
.startChat({
question: currentRecord.question.trim(),
datasource: currentChat.value.datasource,
})
.then((res) => {
const chat = chatApi.toChatInfo(res);
if (chat !== undefined) {
chatList.value.unshift(chat);
currentChatId.value = chat.id;
chat.records.push(currentRecord);
currentChat.value = chat;
} else {
error = true;
}
})
.catch((e) => {
isTyping.value = false;
error = true;
console.error(e);
})
.finally(() => {
loading.value = false;
});
.startChat({
question: currentRecord.question.trim(),
datasource: currentChat.value.datasource,
})
.then((res) => {
const chat = chatApi.toChatInfo(res)
if (chat !== undefined) {
chatList.value.unshift(chat)
currentChatId.value = chat.id
chat.records.push(currentRecord)
currentChat.value = chat
} else {
error = true
}
})
.catch((e) => {
isTyping.value = false
error = true
console.error(e)
})
.finally(() => {
loading.value = false
})
}
if (error) return;
if (error) return
try {
const response = await questionApi.add({
question: currentRecord.question,
chat_id: currentChatId.value,
});
const reader = response.body.getReader();
const decoder = new TextDecoder();
})
const reader = response.body.getReader()
const decoder = new TextDecoder()
let sql_answer = ''
let chart_answer = ''
while (true) {
const {done, value} = await reader.read();
console.log(done);
const { done, value } = await reader.read()
console.log(done)
if (done) {
isTyping.value = false;
break;
isTyping.value = false
break
}
const chunk = decoder.decode(value);
const chunk = decoder.decode(value)
let _list = [chunk]
@@ -290,84 +287,83 @@ const sendMessage = async () => {
}
}
console.log(_list);
console.log(_list)
for (const str of _list) {
const data = JSON.parse(str);
const data = JSON.parse(str)
switch (data.type) {
case "id":
currentChat.value.records[currentChat.value.records.length - 1].id = data.id;
break;
case "info":
console.log(data.msg);
break;
case "error":
currentRecord.error = data.content;
isTyping.value = false;
break;
case "sql-result":
sql_answer += data.content;
currentChat.value.records[currentChat.value.records.length - 1].sql_answer = sql_answer;
break;
case "sql":
currentChat.value.records[currentChat.value.records.length - 1].sql = data.content;
break;
case "sql-data":
currentChat.value.records[currentChat.value.records.length - 1].data = data.content;
break;
case "chart-result":
chart_answer += data.content;
currentChat.value.records[currentChat.value.records.length - 1].chart_answer = chart_answer;
break;
case "chart":
currentChat.value.records[currentChat.value.records.length - 1].chart = data.content;
break;
case "finish":
isTyping.value = false;
break;
case 'id':
currentChat.value.records[currentChat.value.records.length - 1].id = data.id
break
case 'info':
console.log(data.msg)
break
case 'error':
currentRecord.error = data.content
isTyping.value = false
break
case 'sql-result':
sql_answer += data.content
currentChat.value.records[currentChat.value.records.length - 1].sql_answer = sql_answer
break
case 'sql':
currentChat.value.records[currentChat.value.records.length - 1].sql = data.content
break
case 'sql-data':
currentChat.value.records[currentChat.value.records.length - 1].data = data.content
break
case 'chart-result':
chart_answer += data.content
currentChat.value.records[currentChat.value.records.length - 1].chart_answer =
chart_answer
break
case 'chart':
currentChat.value.records[currentChat.value.records.length - 1].chart = data.content
break
case 'finish':
isTyping.value = false
break
}
await nextTick()
}
}
} catch (error) {
if (!currentRecord.error) {
currentRecord.error = "";
currentRecord.error = ''
}
if (currentRecord.error.trim().length !== 0) {
currentRecord.error = currentRecord.error + "\n";
currentRecord.error = currentRecord.error + '\n'
}
currentRecord.error = currentRecord.error + "Error:" + error;
console.error("Error:", error);
isTyping.value = false;
currentRecord.error = currentRecord.error + 'Error:' + error
console.error('Error:', error)
isTyping.value = false
}
};
}
//@ts-ignore
const formatMessage = (content: string) => {
if (!content) return "";
if (!content) return ''
return content
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/\n/g, "<br>")
.replace(/ {2}/g, "&nbsp;&nbsp;");
};
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/\n/g, '<br>')
.replace(/ {2}/g, '&nbsp;&nbsp;')
}
const handleCtrlEnter = (e: KeyboardEvent) => {
const textarea = e.target as HTMLTextAreaElement;
const start = textarea.selectionStart;
const end = textarea.selectionEnd;
const value = textarea.value;
const textarea = e.target as HTMLTextAreaElement
const start = textarea.selectionStart
const end = textarea.selectionEnd
const value = textarea.value
inputMessage.value = value.substring(0, start) + "\n" + value.substring(end);
inputMessage.value = value.substring(0, start) + '\n' + value.substring(end)
nextTick(() => {
textarea.selectionStart = textarea.selectionEnd = start + 1;
});
};
textarea.selectionStart = textarea.selectionEnd = start + 1
})
}
</script>
<style lang="less" scoped>
+55 -34
View File
@@ -1,24 +1,21 @@
import { computed, onWatcherCleanup, unref, watch, ref } from 'vue';
import { computed, onWatcherCleanup, unref, watch, ref } from 'vue'
import type { Ref, VNode } from 'vue'
function isString(str: any): str is string {
return typeof str === 'string';
return typeof str === 'string'
}
function useState<T, R = Ref<T>>(
defaultStateValue?: T | (() => T),
): [R, (val: T) => void] {
function useState<T, R = Ref<T>>(defaultStateValue?: T | (() => T)): [R, (val: T) => void] {
const initValue: T =
typeof defaultStateValue === 'function' ? (defaultStateValue as any)() : defaultStateValue;
typeof defaultStateValue === 'function' ? (defaultStateValue as any)() : defaultStateValue
const innerValue = ref(initValue) as Ref<T>;
const innerValue = ref(initValue) as Ref<T>
function triggerChange(newValue: T) {
innerValue.value = newValue;
innerValue.value = newValue
}
return [innerValue as unknown as R, triggerChange];
return [innerValue as unknown as R, triggerChange]
}
/**
* Return typed content and typing status when typing is enabled.
@@ -28,40 +25,64 @@ const useTypedEffect = (
content: Ref<VNode | object | string>,
typingEnabled: Ref<boolean>,
typingStep: Ref<number>,
typingInterval: Ref<number>,
typingInterval: Ref<number>
): [typedContent: Ref<VNode | object | string>, isTyping: Ref<boolean>] => {
const [prevContent, setPrevContent] = useState<VNode | object | string>('');
const [typingIndex, setTypingIndex] = useState<number>(1);
const [prevContent, setPrevContent] = useState<VNode | object | string>('')
const [typingIndex, setTypingIndex] = useState<number>(1)
const mergedTypingEnabled = computed(() => typingEnabled.value && isString(content.value));
const mergedTypingEnabled = computed(() => typingEnabled.value && isString(content.value))
// Reset typing index when content changed
watch(content, () => {
const prevContentValue = unref(prevContent);
setPrevContent(content.value);
const prevContentValue = unref(prevContent)
setPrevContent(content.value)
if (!mergedTypingEnabled.value && isString(content.value)) {
setTypingIndex(content.value.length);
} else if (isString(content.value) && isString(prevContentValue) && content.value.indexOf(prevContentValue) !== 0) {
setTypingIndex(1);
setTypingIndex(content.value.length)
} else if (
isString(content.value) &&
isString(prevContentValue) &&
content.value.indexOf(prevContentValue) !== 0
) {
setTypingIndex(1)
}
});
})
// Start typing
watch([typingIndex, typingEnabled, content], () => {
if (mergedTypingEnabled.value && isString(content.value) && unref(typingIndex) < content.value.length) {
const id = setTimeout(() => {
setTypingIndex(unref(typingIndex) + typingStep.value);
}, typingInterval.value);
watch(
[typingIndex, typingEnabled, content],
() => {
if (
mergedTypingEnabled.value &&
isString(content.value) &&
unref(typingIndex) < content.value.length
) {
const id = setTimeout(() => {
setTypingIndex(unref(typingIndex) + typingStep.value)
}, typingInterval.value)
onWatcherCleanup(() => {
clearTimeout(id);
});
}
}, { immediate: true });
onWatcherCleanup(() => {
clearTimeout(id)
})
}
},
{ immediate: true }
)
const mergedTypingContent = computed(() => mergedTypingEnabled.value && isString(content.value) ? content.value.slice(0, unref(typingIndex)) : content.value);
const mergedTypingContent = computed(() =>
mergedTypingEnabled.value && isString(content.value)
? content.value.slice(0, unref(typingIndex))
: content.value
)
return [mergedTypingContent, computed(() => mergedTypingEnabled.value && isString(content.value) && unref(typingIndex) < content.value.length)];
};
return [
mergedTypingContent,
computed(
() =>
mergedTypingEnabled.value &&
isString(content.value) &&
unref(typingIndex) < content.value.length
),
]
}
export default useTypedEffect;
export default useTypedEffect
+130 -122
View File
@@ -1,100 +1,100 @@
<script setup lang="ts">
import {ref, nextTick, toRefs, type PropType, onMounted, getCurrentInstance} from 'vue'
import { ref, nextTick, toRefs, type PropType, onMounted, getCurrentInstance } from 'vue'
import _ from 'lodash'
import {dashboardStoreWithOut} from '@/stores/dashboard/dashboard'
import {type CanvasCoord, type CanvasItem} from '@/utils/canvas.ts'
import { dashboardStoreWithOut } from '@/stores/dashboard/dashboard'
import { type CanvasCoord, type CanvasItem } from '@/utils/canvas.ts'
import CanvasShape from './CanvasShape.vue'
import {findComponent} from "@/views/dashboard/components/component-list.ts";
import {storeToRefs} from "pinia";
import { findComponent } from '@/views/dashboard/components/component-list.ts'
import { storeToRefs } from 'pinia'
const dashboardStore = dashboardStoreWithOut()
const canvasLocked = ref(false) // Is the canvas movement locked, Default false
const emits = defineEmits(["parentAddItemBox"]);
const {curComponentId, curComponent} = storeToRefs(dashboardStore)
const emits = defineEmits(['parentAddItemBox'])
const { curComponentId, curComponent } = storeToRefs(dashboardStore)
// @ts-ignore
let currentInstance
// Props
const props = defineProps({
canvasId: {
type: String,
default: 'canvas-main'
default: 'canvas-main',
},
parentConfigItem: {
type: Object as PropType<CanvasItem>,
required: false
required: false,
},
dashboardInfo: {
type: Object,
required: false
required: false,
},
canvasStyleData: {
type: Object,
required: false
required: false,
},
canvasComponentData: {
type: Array as PropType<CanvasItem[]>,
required: true
required: true,
},
baseWidth: {
type: Number,
default: 100
default: 100,
},
baseHeight: {
type: Number,
default: 50
default: 50,
},
baseMarginLeft: {
type: Number,
default: 20
default: 20,
},
baseMarginTop: {
type: Number,
default: 20
default: 20,
},
draggable: {
type: Boolean,
default: true
default: true,
},
dragStart: {
type: Function,
default: () => {
return {}
}
},
},
dragging: {
type: Function,
default: () => {
return {}
}
},
},
dragEnd: {
type: Function,
default: () => {
return {}
}
},
},
resizable: {
type: Boolean,
default: true
default: true,
},
resizeStart: {
type: Function,
default: () => {
return {}
}
},
},
resizing: {
type: Function,
default: () => {
return {}
}
},
},
resizeEnd: {
type: Function,
default: () => {
return {}
}
}
},
},
})
const {
@@ -104,10 +104,9 @@ const {
baseMarginLeft,
baseMarginTop,
draggable,
resizable
resizable,
} = toRefs(props)
// DOM ref
const containerRef = ref<HTMLElement | null>(null)
const renderOk = ref(false)
@@ -173,7 +172,7 @@ function resetPositionBox() {
for (let i = 0; i < rows; i++) {
const row = []
for (let j = 0; j < maxCell.value; j++) {
row.push({el: false})
row.push({ el: false })
}
positionBox.value.push(row)
}
@@ -206,7 +205,7 @@ function fillPositionBox(maxY: number) {
if (pb[j] === undefined) {
const row = []
for (let i = 0; i < itemMaxX; i++) {
row.push({el: false})
row.push({ el: false })
}
pb.push(row)
}
@@ -288,7 +287,7 @@ function init() {
function resizePlayer(item: CanvasItem, newSize: any) {
removeItemFromPositionBox(item)
const belowItems = findBelowItems(item) as CanvasItem[]
_.forEach(belowItems, upItem => {
_.forEach(belowItems, (upItem) => {
const canGoUpRows = canItemGoUp(upItem)
if (canGoUpRows > 0) {
moveItemUp(upItem, canGoUpRows)
@@ -365,7 +364,7 @@ function movePlayer(item: CanvasItem, position: any) {
// Find the item below
let belowItems = findBelowItems(item) as CanvasItem[]
// Traverse the items below and move them
belowItems.forEach(upItem => {
belowItems.forEach((upItem) => {
const canGoUpRows = canItemGoUp(upItem)
if (canGoUpRows > 0) {
moveItemUp(upItem, canGoUpRows)
@@ -391,7 +390,7 @@ function movePlayer(item: CanvasItem, position: any) {
}
function removeItemById(id: number) {
const index = canvasComponentData.value.findIndex(item => item.id === id);
const index = canvasComponentData.value.findIndex((item) => item.id === id)
if (index >= 0) {
removeItem(index)
}
@@ -401,7 +400,7 @@ function removeItem(index: number) {
const item = canvasComponentData.value[index] as CanvasItem
removeItemFromPositionBox(item)
const belowItems = findBelowItems(item) as CanvasItem[]
belowItems.forEach(upItem => {
belowItems.forEach((upItem) => {
const canGoUpRows = canItemGoUp(upItem)
if (canGoUpRows > 0) {
moveItemUp(upItem, canGoUpRows)
@@ -413,17 +412,19 @@ function removeItem(index: number) {
function getNextDragId() {
if (!canvasComponentData.value || canvasComponentData.value.length === 0) {
return 0;
return 0
}
//@ts-ignore
const validIds = canvasComponentData.value.map(item => item._dragId).filter(id => id != null && id !== ''); // 过滤 null、undefined 和空字符串
const validIds = canvasComponentData.value
.map((item) => item._dragId)
.filter((id) => id != null && id !== '') // 过滤 null、undefined 和空字符串
if (validIds.length === 0) {
return 0;
return 0
}
//@ts-ignore
const maxDragId = Math.max(...validIds);
return maxDragId + 1;
const maxDragId = Math.max(...validIds)
return maxDragId + 1
}
function addItem(item: CanvasItem, index: any) {
@@ -431,7 +432,7 @@ function addItem(item: CanvasItem, index: any) {
index = canvasComponentData.value.length
}
item._dragId = item.id || getNextDragId()
checkItemPosition(item, {x: item.x, y: item.y})
checkItemPosition(item, { x: item.x, y: item.y })
emptyTargetCell(item)
addItemToPositionBox(item)
const canGoUpRows = canItemGoUp(item)
@@ -448,7 +449,7 @@ function changeToCoord(left: number, top: number, width: number, height: number)
y1: top,
y2: top + height,
c1: left + width / 2,
c2: top + height / 2
c2: top + height / 2,
}
}
@@ -461,25 +462,25 @@ function changeToCoord(left: number, top: number, width: number, height: number)
function findClosetCoords(item: CanvasItem, tCoord: CanvasCoord) {
if (isOverlay) return
let collisionsItem: { centerDistance: number; coord: CanvasCoord }[] = []
coordinates.value.forEach(nowCoord => {
coordinates.value.forEach((nowCoord) => {
// Avoid comparing yourself to others
if (item._dragId === nowCoord.el._dragId) {
return
}
// Determine whether a collision has occurred
if (
tCoord.x2 < nowCoord.x1 ||
tCoord.x1 > nowCoord.x2 ||
tCoord.y2 < nowCoord.y1 ||
tCoord.y1 > nowCoord.y2
tCoord.x2 < nowCoord.x1 ||
tCoord.x1 > nowCoord.x2 ||
tCoord.y2 < nowCoord.y1 ||
tCoord.y1 > nowCoord.y2
) {
return
} else {
collisionsItem.push({
centerDistance: Math.sqrt(
Math.pow(tCoord.c1 - nowCoord.c1, 2) + Math.pow(tCoord.c2 - nowCoord.c2, 2)
Math.pow(tCoord.c1 - nowCoord.c1, 2) + Math.pow(tCoord.c2 - nowCoord.c2, 2)
),
coord: nowCoord
coord: nowCoord,
})
}
})
@@ -490,7 +491,7 @@ function findClosetCoords(item: CanvasItem, tCoord: CanvasCoord) {
collisionsItem = collisionsItem.sort((a, b) => a.centerDistance - b.centerDistance)
movePlayer(item, {
x: collisionsItem[0].coord.el.x,
y: collisionsItem[0].coord.el.y
y: collisionsItem[0].coord.el.y,
})
setTimeout(() => {
@@ -515,7 +516,7 @@ function makeCoordinate(item: CanvasItem) {
y2: top + height,
c1: left + width / 2,
c2: top + height / 2,
el: item
el: item,
}
coordinates.value.push(coord)
@@ -538,11 +539,11 @@ function changeItemCoord(item: CanvasItem) {
y2: top + height,
c1: left + width / 2,
c2: top + height / 2,
el: item
el: item,
}
// Find and update the corresponding coordinates
const index = coordinates.value.findIndex(o => o.el._dragId === item._dragId)
const index = coordinates.value.findIndex((o) => o.el._dragId === item._dragId)
if (index !== -1) {
coordinates.value.splice(index, 1, coord)
}
@@ -555,7 +556,7 @@ function changeItemCoord(item: CanvasItem) {
function emptyTargetCell(item: CanvasItem) {
let belowItems = findBelowItems(item) as CanvasItem[]
belowItems.forEach(downItem => {
belowItems.forEach((downItem) => {
if (downItem._dragId === item._dragId) return
let moveSize = item.y + item.sizeY - downItem.y
if (moveSize > 0) {
@@ -573,9 +574,9 @@ function canItemGoUp(item: CanvasItem) {
for (let row = item.y - 2; row >= 0; row--) {
for (let cell = item.x - 1; cell < item.x - 1 + item.sizeX; cell++) {
if (
positionBox.value[row] &&
positionBox.value[row][cell] &&
positionBox.value[row][cell].el
positionBox.value[row] &&
positionBox.value[row][cell] &&
positionBox.value[row][cell].el
) {
return upperRows
}
@@ -607,7 +608,7 @@ function moveItemDown(item: CanvasItem, size: number) {
}
const targetPosition = {
y: item.y + size
y: item.y + size,
}
setPlayerPosition(item, targetPosition)
@@ -663,7 +664,7 @@ function moveItemUp(item: CanvasItem, size: number) {
const belowItems = findBelowItems(item) as CanvasItem[]
setPlayerPosition(item, {
y: item.y - size
y: item.y - size,
})
addItemToPositionBox(item)
@@ -728,7 +729,6 @@ function containerMouseDown(e: MouseEvent) {
e.preventDefault()
e.stopPropagation()
}
}
function getNowPosition(addSizeX: number, addSizeY: number, moveXSize: number, moveYSize: number) {
@@ -756,12 +756,10 @@ function getNowPosition(addSizeX: number, addSizeY: number, moveXSize: number, m
if (hasR) {
nowSizeX = Math.max(nowSizeX + addSizeX, 1)
nowOriginWidth = Math.max(nowOriginWidth + moveXSize, baseWidth.value)
}
if (hasB) {
nowSizeY = Math.max(nowSizeY + addSizeY, 1)
nowOriginHeight = Math.max(nowOriginHeight + moveYSize, baseHeight.value)
}
if (hasL) {
@@ -782,7 +780,7 @@ function getNowPosition(addSizeX: number, addSizeY: number, moveXSize: number, m
// Move the lowest position from point L
nowOriginY = Math.min(Math.max(nowOriginY + moveYSize, 1), nowBottomOriginY)
}
return {nowSizeX, nowSizeY, nowX, nowY, nowOriginWidth, nowOriginHeight, nowOriginX, nowOriginY}
return { nowSizeX, nowSizeY, nowX, nowY, nowOriginWidth, nowOriginHeight, nowOriginX, nowOriginY }
}
function startMove(e: MouseEvent, item: CanvasItem, index: number) {
@@ -798,9 +796,9 @@ function startMove(e: MouseEvent, item: CanvasItem, index: number) {
let className = target.className || ''
if (
!className.includes('dragHandle') &&
!className.includes('item') &&
!className.includes('resizeHandle')
!className.includes('dragHandle') &&
!className.includes('item') &&
!className.includes('resizeHandle')
) {
return
}
@@ -850,14 +848,14 @@ function startMove(e: MouseEvent, item: CanvasItem, index: number) {
const moveYSize = e.pageY - infoBox.value.startY
const addSizeX =
moveXSize % cellWidth.value > cellWidth.value / 4
? parseInt(String(moveXSize / cellWidth.value + 1))
: parseInt(String(moveXSize / cellWidth.value))
moveXSize % cellWidth.value > cellWidth.value / 4
? parseInt(String(moveXSize / cellWidth.value + 1))
: parseInt(String(moveXSize / cellWidth.value))
const addSizeY =
moveYSize % cellHeight.value > cellHeight.value / 4
? parseInt(String(moveYSize / cellHeight.value + 1))
: parseInt(String(moveYSize / cellHeight.value))
moveYSize % cellHeight.value > cellHeight.value / 4
? parseInt(String(moveYSize / cellHeight.value + 1))
: parseInt(String(moveYSize / cellHeight.value))
// Determine the resizing direction based on the coordinate points
const {
nowSizeX,
@@ -867,11 +865,11 @@ function startMove(e: MouseEvent, item: CanvasItem, index: number) {
nowOriginWidth,
nowOriginHeight,
nowOriginX,
nowOriginY
nowOriginY,
} = getNowPosition(addSizeX, addSizeY, moveXSize, moveYSize)
debounce(() => {
resizePlayer(resizeItem, {sizeX: nowSizeX, sizeY: nowSizeY, x: nowX, y: nowY})
resizePlayer(resizeItem, { sizeX: nowSizeX, sizeY: nowSizeY, x: nowX, y: nowY })
}, 10)
infoBox.value.cloneItem.style.width = `${nowOriginWidth}px`
@@ -898,25 +896,25 @@ function startMove(e: MouseEvent, item: CanvasItem, index: number) {
if (canvasLocked.value) return
// Adjust the accuracy of moving coordinate changes
const newX = Math.max(
Math.floor(
(nowCloneItemX - 20 + infoBox.value.cloneItem.offsetWidth / 96 - baseMarginLeft.value) /
cellWidth.value +
1
),
1
Math.floor(
(nowCloneItemX - 20 + infoBox.value.cloneItem.offsetWidth / 96 - baseMarginLeft.value) /
cellWidth.value +
1
),
1
)
// Adjust the accuracy of moving coordinate changes
const newY = Math.max(
Math.floor(
(nowCloneItemY + infoBox.value.cloneItem.offsetHeight / 96 - baseMarginTop.value) /
cellHeight.value +
1
),
1
Math.floor(
(nowCloneItemY + infoBox.value.cloneItem.offsetHeight / 96 - baseMarginTop.value) /
cellHeight.value +
1
),
1
)
debounce(() => {
if (newX !== infoBox.value.oldX || newY !== infoBox.value.oldY) {
movePlayer(moveItem, {x: newX, y: newY})
movePlayer(moveItem, { x: newX, y: newY })
infoBox.value.oldX = newX
infoBox.value.oldY = newY
}
@@ -926,18 +924,20 @@ function startMove(e: MouseEvent, item: CanvasItem, index: number) {
window.addEventListener('mousemove', itemMouseMove)
// Need to execute before mouseup
const itemCanvasChange = () => {
// The current canvas movement is in a locked state, indicating that there are components that require canvas switching
if (canvasLocked.value) {
const moveItem = infoBox.value.moveItem
// Get the SQTab currently being moved in
const curActiveMoveInSQTab = canvasComponentData?.value.find(item => item.component === 'SQTab' && item.collisionActive === true);
const curActiveMoveInSQTab = canvasComponentData?.value.find(
(item) => item.component === 'SQTab' && item.collisionActive === true
)
if (curActiveMoveInSQTab) {
if (curActiveMoveInSQTab.moveInActive) {
// @ts-ignore
const refTabInstance = currentInstance.refs['shape_component_' + curActiveMoveInSQTab.id][0]
const refTabInstance =
currentInstance.refs['shape_component_' + curActiveMoveInSQTab.id][0]
refTabInstance.addTabItem(moveItem)
removeItemById(moveItem.id)
}
@@ -951,7 +951,6 @@ function startMove(e: MouseEvent, item: CanvasItem, index: number) {
removeItemById(moveItem.id)
props.parentConfigItem.moveOutActive = false
}
}
canvasLocked.value = false
}
@@ -986,7 +985,7 @@ function nowItemStyle(item: CanvasItem) {
width: cellWidth.value * item.sizeX - baseMarginLeft.value + 'px',
height: cellHeight.value * item.sizeY - baseMarginTop.value + 'px',
left: cellWidth.value * (item.x - 1) + baseMarginLeft.value + 'px',
top: cellHeight.value * (item.y - 1) + baseMarginTop.value + 'px'
top: cellHeight.value * (item.y - 1) + baseMarginTop.value + 'px',
}
}
@@ -1048,20 +1047,20 @@ function getItemMatrixPosition(item: CanvasItem) {
tl: item.x - 1,
tr: item.sizeX + item.x - 1,
tt: item.y - 1,
tb: item.sizeY + item.y - 1
tb: item.sizeY + item.y - 1,
}
}
// Get style location
function getItemStylePosition(item: CanvasItem) {
const {tw, th, tl, tr, tt, tb} = getItemMatrixPosition(item)
const { tw, th, tl, tr, tt, tb } = getItemMatrixPosition(item)
return {
tw: tw * cellWidth.value - baseMarginLeft.value,
tl: cellWidth.value * tl + baseMarginLeft.value,
tr: cellWidth.value * tr + baseMarginLeft.value,
th: th * cellHeight.value - baseMarginTop.value,
tt: cellHeight.value * tt + baseMarginTop.value,
tb: cellHeight.value * tb + baseMarginTop.value
tb: cellHeight.value * tb + baseMarginTop.value,
}
}
@@ -1074,7 +1073,7 @@ function tabMoveInCheck(cloneRefItem) {
//4. Release this cloneItem from the main canvas and remove the canvas with the added item
const moveItem = infoBox.value.moveItem
if (moveItem && moveItem !== 'SQTab') {
canvasComponentData.value.forEach(item => {
canvasComponentData.value.forEach((item) => {
// Determine the position of SQTab components around moveItem
if (item.id !== moveItem.id && item.component === 'SQTab') {
}
@@ -1083,27 +1082,28 @@ function tabMoveInCheck(cloneRefItem) {
}
function tabMoveOutCheckSQ() {
const {cloneItem, moveItem} = infoBox.value
const { cloneItem, moveItem } = infoBox.value
if (cloneItem && moveItem && props.canvasId.includes('tab') && props.parentConfigItem) {
const left = cloneItem.offsetLeft
const width = cloneItem.offsetWidth
const top = cloneItem.offsetTop
const {tw} = getItemStylePosition(props.parentConfigItem)
props.parentConfigItem.moveOutActive = left < -tabMoveOutXOffset || top < -tabMoveOutYOffset || (left + width - tw) > tabMoveOutXOffset;
const { tw } = getItemStylePosition(props.parentConfigItem)
props.parentConfigItem.moveOutActive =
left < -tabMoveOutXOffset || top < -tabMoveOutYOffset || left + width - tw > tabMoveOutXOffset
canvasLocked.value = props.parentConfigItem.moveOutActive
}
}
function tabMoveInCheckSQ() {
const {cloneItem, moveItem} = infoBox.value
const { cloneItem, moveItem } = infoBox.value
if (cloneItem && moveItem && moveItem !== 'SQTab') {
const width = cloneItem.offsetWidth
const height = cloneItem.offsetHeight
const left = cloneItem.offsetLeft
const top = cloneItem.offsetTop
canvasComponentData.value.forEach(item => {
canvasComponentData.value.forEach((item) => {
if (item.id !== moveItem.id && item.component === 'SQTab') {
const {tw, th, tl, tt} = getItemStylePosition(item)
const { tw, th, tl, tt } = getItemStylePosition(item)
// Collision effective area inspection
const collisionT = tt + tabMoveInYOffset
const collisionL = tl + collisionGap - width
@@ -1117,8 +1117,8 @@ function tabMoveInCheckSQ() {
const trAndTr = collisionT <= top && collisionL + collisionW >= left + width
// Near the lower right corner area
const brAndBr =
collisionT + collisionH >= top + height && collisionL + collisionW >= left + width
item.collisionActive = tfAndTf && bfAndBf && trAndTr && brAndBr;
collisionT + collisionH >= top + height && collisionL + collisionW >= left + width
item.collisionActive = tfAndTf && bfAndBf && trAndTr && brAndBr
canvasLocked.value = item.collisionActive // Contains collision move in operation, locking canvas
//Move into effective area for inspection
@@ -1147,7 +1147,6 @@ onMounted(() => {
currentInstance = getCurrentInstance()
})
defineExpose({
getRenderState,
init,
@@ -1161,30 +1160,39 @@ defineExpose({
changeToCoord,
removeItem,
findClosetCoords,
makeCoordinate
makeCoordinate,
})
</script>
<template>
<div class="dragAndResize" ref="containerRef" @click="containerClick" @mousedown="containerMouseDown($event)"
@mouseup="endMove()"
@mousemove="moving()">
<div
ref="containerRef"
class="dragAndResize"
@click="containerClick"
@mousedown="containerMouseDown($event)"
@mouseup="endMove()"
@mousemove="moving()"
>
<template v-if="renderOk">
<CanvasShape v-for="(item, index) in canvasComponentData"
:active="curComponentId === item.id"
:config-item="item"
:draggable="draggable"
:item-index="index"
:move-animate="moveAnimate"
:start-move="startMove"
:start-resize="startResize"
:key="'item' + index"
:style="nowItemStyle(item)">
<component :ref="'shape_component_'+item.id"
class="sql-component slot-component dragHandle"
:is="findComponent(item.component)"
:config-item="item"
@parentAddItemBox=" (subItem:any) => addItemBox(subItem)">
<CanvasShape
v-for="(item, index) in canvasComponentData"
:key="'item' + index"
:active="curComponentId === item.id"
:config-item="item"
:draggable="draggable"
:item-index="index"
:move-animate="moveAnimate"
:start-move="startMove"
:start-resize="startResize"
:style="nowItemStyle(item)"
>
<component
:is="findComponent(item.component)"
:ref="'shape_component_' + item.id"
class="sql-component slot-component dragHandle"
:config-item="item"
@parent-add-item-box="(subItem: any) => addItemBox(subItem)"
>
</component>
</CanvasShape>
</template>
@@ -1,43 +1,43 @@
<script setup lang="ts">
import {type CanvasItem} from '@/utils/canvas.ts'
import {ref, toRefs, type PropType} from 'vue'
import ResizeHandle from "@/views/dashboard/canvas/ResizeHandle.vue";
import { type CanvasItem } from '@/utils/canvas.ts'
import { ref, toRefs, type PropType } from 'vue'
import ResizeHandle from '@/views/dashboard/canvas/ResizeHandle.vue'
const shapeRef = ref(null)
// Props
const props = defineProps({
configItem: {
type: Object as PropType<CanvasItem>,
required: true
required: true,
},
active: {
type: Boolean,
default: false
default: false,
},
itemIndex: {
type: Number,
required: true
required: true,
},
moveAnimate: {
type: Boolean,
required: true
required: true,
},
draggable: {
type: Boolean,
required: true
required: true,
},
startMove: {
type: Function,
default: () => {
return {}
}
},
},
startResize: {
type: Function,
default: () => {
return {}
}
}
},
},
})
const { draggable } = toRefs(props)
@@ -48,21 +48,30 @@ const shapeClick = (e: MouseEvent) => {
</script>
<template>
<div :class="{
item: true,
itemActive: active,
itemCursorDefault: configItem.component === 'SQTab',
moveAnimation: moveAnimate,
movingItem: configItem.isPlayer,
canNotDrag: !draggable
}" @click="shapeClick" @mousedown="startMove($event, configItem, itemIndex)" ref="shapeRef">
<div
ref="shapeRef"
:class="{
item: true,
itemActive: active,
itemCursorDefault: configItem.component === 'SQTab',
moveAnimation: moveAnimate,
movingItem: configItem.isPlayer,
canNotDrag: !draggable,
}"
@click="shapeClick"
@mousedown="startMove($event, configItem, itemIndex)"
>
<slot></slot>
<resize-handle v-if="active"
:start-resize="(event : MouseEvent, point: string) => startResize(event, point, configItem, itemIndex)">
<resize-handle
v-if="active"
:start-resize="
(event: MouseEvent, point: string) => startResize(event, point, configItem, itemIndex)
"
>
</resize-handle>
</div>
</template>
<style scoped lang="less">
@import '../css/CanvasStyle.less';
</style>
</style>
@@ -1,12 +1,12 @@
<script setup lang="ts">
import type {PropType} from "vue";
import type { PropType } from 'vue'
defineProps({
sizeList: {
type: Array as PropType<number[]>,
required: false,
default: [12, 12, 12, 12]
}
default: [12, 12, 12, 12],
},
})
const shadowStyle = (size: number, index: number) => {
@@ -15,40 +15,42 @@ const shadowStyle = (size: number, index: number) => {
top: 0,
left: 0,
width: '100%',
height: `${size}px`
height: `${size}px`,
}
} else if (index === 1) {
return {
top: 0,
right: 0,
width: `${size}px`,
height: '100%'
height: '100%',
}
} else if (index === 2) {
return {
bottom: 0,
left: 0,
width: '100%',
height: `${size}px`
height: `${size}px`,
}
} else if (index === 3) {
return {
top: 0,
left: 0,
width: `${size}px`,
height: '100%'
height: '100%',
}
}
}
</script>
<template>
<div class="dragHandle dragArea" v-for="(size,index) in sizeList" :style="shadowStyle(size,index)"></div>
<div
v-for="(size, index) in sizeList"
class="dragHandle dragArea"
:style="shadowStyle(size, index)"
></div>
</template>
<style scoped lang="less">
.dragArea {
opacity: 0.05;
}
@@ -59,4 +61,4 @@ const shadowStyle = (size: number, index: number) => {
cursor: move;
z-index: 3;
}
</style>
</style>
@@ -4,8 +4,8 @@ defineProps({
type: Function,
default: () => {
return {}
}
}
},
},
})
const cursors = {
lt: 'nw',
@@ -15,7 +15,7 @@ const cursors = {
rb: 'se',
b: 's',
lb: 'sw',
l: 'w'
l: 'w',
}
function getPointStyle(point: string) {
@@ -40,7 +40,7 @@ function getPointStyle(point: string) {
// The points on both sides are centered in height
if (hasL || hasR) {
newLeft = hasL ? '0px' : '100%'
newTop ='50%'
newTop = '50%'
}
}
@@ -50,7 +50,7 @@ function getPointStyle(point: string) {
left: `${newLeft}`,
top: `${newTop}`,
// @ts-expect-error
cursor: `${cursors[point]}-resize`
cursor: `${cursors[point]}-resize`,
}
}
@@ -58,12 +58,15 @@ const pointList = ['lt', 't', 'rt', 'r', 'rb', 'b', 'lb', 'l']
</script>
<template>
<div v-for="point in pointList"
:key="point" class="resizeHandle"
:style="getPointStyle(point)"
@mousedown="startResize($event, point)"></div>
<div
v-for="point in pointList"
:key="point"
class="resizeHandle"
:style="getPointStyle(point)"
@mousedown="startResize($event, point)"
></div>
</template>
<style scoped lang="less">
@import '../css/CanvasStyle.less';
</style>
</style>
@@ -1,8 +1,6 @@
<template>
<div class="info-card">
<div class="info-title">
Dashboard ID
</div>
<div class="info-title">Dashboard ID</div>
<div class="info-content">{{ dashboardInfo.id }}</div>
<div v-if="dashboardInfo.createName" class="info-title">Creator</div>
<div v-if="dashboardInfo.createName" class="info-content">{{ dashboardInfo.createName }}</div>
@@ -19,12 +17,11 @@
</template>
<script lang="ts" setup>
defineProps({
dashboardInfo: {
type: Object,
required: true
}
required: true,
},
})
const timestampFormatDate = (value: any) => {
@@ -12,16 +12,16 @@ defineProps({
type: String as PropType<
'input' | 'select' | 'table' | 'none' | 'noneWhite' | 'tree' | 'error'
>,
default: 'table'
default: 'table',
},
imageSize: {
type: Number,
default: 125
default: 125,
},
description: {
type: String,
default: ''
}
default: '',
},
})
const getAssetsFile = {
input: nothingInput,
@@ -30,14 +30,14 @@ const getAssetsFile = {
noneWhite: nothingNone,
tree: nothingTree,
error,
none
none,
}
</script>
<template>
<el-empty
class="empty-info"
:imageSize="imageSize"
:image-size="imageSize"
:description="description"
:image="getAssetsFile[imgType]"
>
@@ -1,7 +1,7 @@
<script lang="ts" setup>
import {Icon} from '@/components/icon-custom'
import { Icon } from '@/components/icon-custom'
import icon_more_outlined from '@/assets/svg/icon_more_outlined.svg'
import type {Placement} from 'element-plus-secondary'
import type { Placement } from 'element-plus-secondary'
export interface Menu {
svgName?: string
@@ -12,18 +12,18 @@ export interface Menu {
}
withDefaults(
defineProps<{
menuList: Menu[]
placement?: Placement
iconName?: any
iconSize?: string
inTable?: boolean
}>(),
{
placement: 'bottom-end',
iconSize: '16px',
inTable: false
}
defineProps<{
menuList: Menu[]
placement?: Placement
iconName?: any
iconSize?: string
inTable?: boolean
}>(),
{
placement: 'bottom-end',
iconSize: '16px',
inTable: false,
}
)
const handleCommand = (command: string | number | object) => {
@@ -35,35 +35,29 @@ const emit = defineEmits(['handleCommand'])
<template>
<el-dropdown
popper-class="menu-more_popper"
:placement="placement"
:persistent="false"
trigger="click"
@command="handleCommand"
popper-class="menu-more_popper"
:placement="placement"
:persistent="false"
trigger="click"
@command="handleCommand"
>
<el-icon class="hover-icon" :class="inTable && 'hover-icon-in-table'" @click.stop>
<Icon>
<component class="svg-icon" :is="iconName || icon_more_outlined"></component>
<component :is="iconName || icon_more_outlined" class="svg-icon"></component>
</Icon>
</el-icon>
<template #dropdown>
<el-dropdown-menu :persistent="false">
<template v-for="ele in menuList"
>
<el-dropdown-item
:divided="ele.divided"
:command="ele.command"
:disabled="ele.disabled"
>
<el-icon class="handle-icon" :style="{ fontSize: iconSize }" v-if="ele.svgName">
<template v-for="ele in menuList">
<el-dropdown-item :divided="ele.divided" :command="ele.command" :disabled="ele.disabled">
<el-icon v-if="ele.svgName" class="handle-icon" :style="{ fontSize: iconSize }">
<Icon>
<component class="svg-icon" :is="ele.svgName"></component>
<component :is="ele.svgName" class="svg-icon"></component>
</Icon>
</el-icon>
{{ ele.label }}
</el-dropdown-item>
</template>
</el-dropdown-menu>
</template>
</el-dropdown>
@@ -1,10 +1,9 @@
<script lang="ts" setup>
import folder from '@/assets/svg/folder.svg'
import {type SQTreeNode} from '@/views/dashboard/utils/treeNode'
import {ref, reactive, computed} from 'vue'
import {saveDashboardResource} from "@/views/dashboard/utils/canvasUtils.ts";
import {dashboardApi} from "@/api/dashboard.ts";
import _ from "lodash";
import { type SQTreeNode } from '@/views/dashboard/utils/treeNode'
import { computed, reactive, ref } from 'vue'
import { saveDashboardResource } from '@/views/dashboard/utils/canvasUtils.ts'
import { dashboardApi } from '@/api/dashboard.ts'
const emits = defineEmits(['finish'])
const state = reactive({
@@ -19,7 +18,7 @@ const state = reactive({
tDataSource: [],
nameList: [],
targetInfo: null,
attachParams: null
attachParams: null,
})
const getTitle = (opt: string) => {
@@ -47,7 +46,7 @@ const getResourceNewName = (opt: string) => {
}
const getTree = async () => {
const params = {node_type: 'folder'}
const params = { node_type: 'folder' }
dashboardApi.list_resource(params).then((res) => {
state.tData = res || []
state.tDataSource = [...state.tData]
@@ -77,38 +76,37 @@ const resourceForm = reactive({
id: null,
pid: '',
pName: '',
name: 'New Dashboard'
name: 'New Dashboard',
})
const resourceFormRules = ref({
name: [
{
required: true,
message: state.placeholder,
trigger: 'change'
},
{
required: true,
message: state.placeholder,
trigger: 'blur'
},
{
min: 1,
max: 64,
message: 'Chart limit 1-64',
trigger: 'change'
},
{required: true, trigger: 'blur'}
],
pid: [
{
required: true,
message: 'Please select',
trigger: 'blur'
}
]
}
)
name: [
{
required: true,
message: state.placeholder,
trigger: 'change',
},
{
required: true,
message: state.placeholder,
trigger: 'blur',
},
{
min: 1,
max: 64,
message: 'Chart limit 1-64',
trigger: 'change',
},
{ required: true, trigger: 'blur' },
],
pid: [
{
required: true,
message: 'Please select',
trigger: 'blur',
},
],
})
const resetForm = () => {
state.dialogTitle = ''
@@ -122,7 +120,7 @@ const propsTree = {
label: 'name',
children: 'children',
// @ts-ignore
isLeaf: node => !node.children?.length
isLeaf: (node) => !node.children?.length,
}
const showPid = computed(() => {
@@ -145,7 +143,7 @@ const saveResource = () => {
type: 'success',
message: messageTips,
})
emits('finish', {opt: state.opt, resourceId: rsp.id})
emits('finish', { opt: state.opt, resourceId: rsp.id })
resetForm()
})
}
@@ -157,57 +155,56 @@ const nodeClick = (data: SQTreeNode) => {
const filterMethod = (value: any) => {
// @ts-ignore
state.tData = state.tDataSource.filter(item => item.name.includes(value))
state.tData = state.tDataSource.filter((item) => item.name.includes(value))
}
defineExpose({
optInit
optInit,
})
</script>
<template>
<el-dialog
class="create-dialog"
:title="state.dialogTitle"
v-model="resourceDialogShow"
width="420px"
:before-close="resetForm"
@submit.prevent
v-model="resourceDialogShow"
class="create-dialog"
:title="state.dialogTitle"
width="420px"
:before-close="resetForm"
@submit.prevent
>
<el-form
v-loading="loading"
label-position="top"
require-asterisk-position="right"
ref="resource"
:model="resourceForm"
:rules="resourceFormRules"
ref="resource"
v-loading="loading"
label-position="top"
require-asterisk-position="right"
:model="resourceForm"
:rules="resourceFormRules"
>
<el-form-item :label="state.resourceFormNameLabel" prop="name">
<el-input
@keydown.stop
@keyup.stop
:placeholder="state.placeholder"
v-model="resourceForm.name"
v-model="resourceForm.name"
:placeholder="state.placeholder"
@keydown.stop
@keyup.stop
/>
</el-form-item>
<el-form-item v-if="showPid" :label="'Folder'" prop="pid">
<el-tree-select
style="width: 100%"
@keydown.stop
@keyup.stop
v-model="resourceForm.pid"
:data="state.tData"
:props="propsTree"
@node-click="nodeClick"
:filter-method="filterMethod"
:render-after-expand="false"
filterable
v-model="resourceForm.pid"
style="width: 100%"
:data="state.tData"
:props="propsTree"
:filter-method="filterMethod"
:render-after-expand="false"
filterable
@keydown.stop
@keyup.stop
@node-click="nodeClick"
>
<template #default="{ data: { name } }">
<span class="custom-tree-node">
<el-icon>
<Icon name="dv-folder"><folder class="svg-icon custom-tree-folder"/></Icon>
<Icon name="dv-folder"><folder class="svg-icon custom-tree-folder" /></Icon>
</el-icon>
<span :title="name">{{ name }}</span>
</span>
@@ -268,6 +265,6 @@ defineExpose({
}
.custom-tree-folder {
color: rgb(255, 198, 10)
color: rgb(255, 198, 10);
}
</style>
@@ -1,6 +1,6 @@
<script setup lang="ts">
import icon_add_outlined from '@/assets/svg/icon_add_outlined.svg'
import {treeDraggableChart} from '@/views/dashboard/utils/treeDraggableChart'
import { treeDraggableChart } from '@/views/dashboard/utils/treeDraggableChart'
import newFolder from '@/assets/svg/new-folder.svg'
import icon_searchOutline_outlined from '@/assets/svg/icon_search-outline_outlined.svg'
import icon_folder from '@/assets/svg/icon_folder.svg'
@@ -10,41 +10,41 @@ import icon_edit_outlined from '@/assets/svg/icon_edit_outlined.svg'
import icon_rename from '@/assets/svg/icon_rename.svg'
import icon_delete from '@/assets/svg/icon_delete.svg'
import icon_more_outlined from '@/assets/svg/icon_more_outlined.svg'
import {onMounted, reactive, ref, watch, nextTick, computed} from 'vue'
import {ElIcon, ElScrollbar} from 'element-plus-secondary'
import {Icon} from '@/components/icon-custom'
import {type SQTreeNode} from '@/views/dashboard/utils/treeNode'
import { onMounted, reactive, ref, watch, nextTick, computed } from 'vue'
import { ElIcon, ElScrollbar } from 'element-plus-secondary'
import { Icon } from '@/components/icon-custom'
import { type SQTreeNode } from '@/views/dashboard/utils/treeNode'
import _ from 'lodash'
import router from '@/router'
import {dashboardStoreWithOut} from "@/stores/dashboard/dashboard.ts";
import ResourceGroupOpt from "@/views/dashboard/common/ResourceGroupOpt.vue";
import {dashboardApi} from "@/api/dashboard.ts";
import HandleMore from "@/views/dashboard/common/HandleMore.vue";
import {useI18n} from "vue-i18n";
import { dashboardStoreWithOut } from '@/stores/dashboard/dashboard.ts'
import ResourceGroupOpt from '@/views/dashboard/common/ResourceGroupOpt.vue'
import { dashboardApi } from '@/api/dashboard.ts'
import HandleMore from '@/views/dashboard/common/HandleMore.vue'
import { useI18n } from 'vue-i18n'
const {t} = useI18n()
const { t } = useI18n()
const dashboardStore = dashboardStoreWithOut()
const resourceGroupOptRef = ref(null)
defineProps({
curCanvasType: {
type: String,
required: true
required: true,
},
showPosition: {
required: false,
type: String,
default: 'preview'
default: 'preview',
},
resourceTable: {
required: false,
type: String,
default: 'core'
}
default: 'core',
},
})
const defaultProps = {
children: 'children',
label: 'name'
label: 'name',
}
const mounted = ref(false)
const selectedNodeKey: any = ref(null)
@@ -62,26 +62,24 @@ const state = reactive({
{
label: t('dashboard.rename'),
command: 'rename',
svgName: icon_rename
svgName: icon_rename,
},
{
label: t('dashboard.delete'),
command: 'delete',
svgName: icon_delete,
divided: true
}
divided: true,
},
],
})
// @ts-ignore
const {handleDrop, allowDrop, handleDragStart} = treeDraggableChart(
state,
'resourceTree',
'dashboard'
const { handleDrop, allowDrop, handleDragStart } = treeDraggableChart(
state,
'resourceTree',
'dashboard'
)
const routerDashboardId = router.currentRoute.value.query.dashboardId
if (routerDashboardId) {
selectedNodeKey.value = routerDashboardId
@@ -107,7 +105,7 @@ const filterNode = (value: string, data: SQTreeNode) => {
}
const nodeClick = (data: SQTreeNode, node: any) => {
dashboardStore.setCurComponent({component: null, index: null})
dashboardStore.setCurComponent({ component: null, index: null })
if (node.disabled) {
nextTick(() => {
const currentNode = resourceListTree.value.$el.querySelector('.is-current')
@@ -137,14 +135,14 @@ const getTree = async () => {
}
// @ts-ignore
const flattedTree = computed<SQTreeNode[]>(() => {
return _.filter(flatTree(state.resourceTree), node => node.leaf)
return _.filter(flatTree(state.resourceTree), (node) => node.leaf)
})
const hasData = computed<boolean>(() => flattedTree.value.length > 0)
function flatTree(tree: SQTreeNode[]) {
let result = _.cloneDeep(tree)
_.forEach(tree, node => {
_.forEach(tree, (node) => {
if (node.children && node.children.length > 0) {
result = _.union(result, flatTree(node.children))
}
@@ -172,12 +170,10 @@ const afterTreeInit = () => {
const copyLoading = ref(false)
const emit = defineEmits(['nodeClick'])
function createNewObject() {
}
function createNewObject() {}
// @ts-ignore
const resourceEdit = resourceId => {
const resourceEdit = (resourceId) => {
window.open(`#/canvas?resourceId=${resourceId}`, '_blank')
}
@@ -208,13 +204,11 @@ const getDefaultExpandedKeys = () => {
}
}
watch(filterText, val => {
watch(filterText, (val) => {
resourceListTree.value.filter(val)
})
const loadInit = () => {
}
const loadInit = () => {}
onMounted(() => {
loadInit()
getTree()
@@ -240,16 +234,16 @@ const operation = (opt: string, data: SQTreeNode) => {
type: 'warning',
tip: msg,
autofocus: false,
showClose: false
showClose: false,
}).then(() => {
dashboardApi.delete_resource({id: data.id}).then(() => {
dashboardApi.delete_resource({ id: data.id }).then(() => {
ElMessage.success(t('dashboard.delete_success'))
getTree()
})
})
} else if (opt === 'rename') {
//@ts-ignore
resourceGroupOptRef.value?.optInit({opt: 'rename', id: data.id, name: data.name})
resourceGroupOptRef.value?.optInit({ opt: 'rename', id: data.id, name: data.name })
}
}
@@ -257,11 +251,10 @@ const baseInfoChangeFinish = () => {
getTree()
}
defineExpose({
hasData,
createNewObject,
mounted
mounted,
})
</script>
@@ -273,97 +266,90 @@ defineExpose({
<div class="flex-align-center">
<el-tooltip :content="'New Folder'" placement="top" effect="dark">
<el-icon
class="custom-icon btn"
style="margin-right: 10px"
@click="addOperation({opt:'newFolder',type:'folder',pid:'root'} )"
class="custom-icon btn"
style="margin-right: 10px"
@click="addOperation({ opt: 'newFolder', type: 'folder', pid: 'root' })"
>
<Icon name="dv-new-folder">
<newFolder class="svg-icon"/>
<newFolder class="svg-icon" />
</Icon>
</el-icon>
</el-tooltip>
<el-tooltip :content="'Add Dashboard'" placement="top" effect="dark">
<el-icon
class="custom-icon btn"
style="margin-right: 10px"
@click="addOperation({opt:'newLeaf',type:'dashboard'} )"
class="custom-icon btn"
style="margin-right: 10px"
@click="addOperation({ opt: 'newLeaf', type: 'dashboard' })"
>
<Icon name="dv-new-folder">
<icon_fileAdd_outlined class="svg-icon"/>
<icon_fileAdd_outlined class="svg-icon" />
</Icon>
</el-icon>
</el-tooltip>
</div>
</div>
<el-input
:placeholder="'Search'"
v-model="filterText"
clearable
class="search-bar"
>
<el-input v-model="filterText" :placeholder="'Search'" clearable class="search-bar">
<template #prefix>
<el-icon>
<Icon name="icon_search-outline_outlined"
>
<icon_searchOutline_outlined class="svg-icon"
/>
<Icon name="icon_search-outline_outlined">
<icon_searchOutline_outlined class="svg-icon" />
</Icon>
</el-icon>
</template>
</el-input>
</div>
<el-scrollbar class="custom-tree" v-loading="copyLoading">
<el-scrollbar v-loading="copyLoading" class="custom-tree">
<el-tree
menu
ref="resourceListTree"
:default-expanded-keys="expandedArray"
:data="state.resourceTree"
:props="defaultProps"
node-key="id"
highlight-current
:expand-on-click-node="true"
:filter-node-method="filterNode"
@node-expand="nodeExpand"
@node-collapse="nodeCollapse"
@node-click="nodeClick"
@node-drag-start="handleDragStart"
@node-drop="handleDrop"
draggable
ref="resourceListTree"
menu
:default-expanded-keys="expandedArray"
:data="state.resourceTree"
:props="defaultProps"
node-key="id"
highlight-current
:expand-on-click-node="true"
:filter-node-method="filterNode"
draggable
@node-expand="nodeExpand"
@node-collapse="nodeCollapse"
@node-click="nodeClick"
@node-drag-start="handleDragStart"
@node-drop="handleDrop"
>
<template #default="{ node, data }">
<span class="custom-tree-node">
<el-icon style="font-size: 18px" v-if="data.node_type !== 'leaf'">
<Icon name="icon_folder"><icon_folder class="svg-icon"/></Icon>
<el-icon v-if="data.node_type !== 'leaf'" style="font-size: 18px">
<Icon name="icon_folder"><icon_folder class="svg-icon" /></Icon>
</el-icon>
<el-icon style="font-size: 18px" v-else>
<Icon name="icon_dashboard"><icon_dashboard class="svg-icon"/></Icon>
<el-icon v-else style="font-size: 18px">
<Icon name="icon_dashboard"><icon_dashboard class="svg-icon" /></Icon>
</el-icon>
<span :title="node.label" class="label-tooltip">
{{ node.label }}
</span>
<div class="icon-more">
<el-icon
v-on:click.stop
v-if="data.node_type !== 'leaf'"
class="hover-icon"
@click="addOperation({opt:'newLeaf',type:'dashboard',id:data.id} )"
v-if="data.node_type !== 'leaf'"
class="hover-icon"
@click.stop
@click="addOperation({ opt: 'newLeaf', type: 'dashboard', id: data.id })"
>
<Icon><icon_add_outlined class="svg-icon"/></Icon>
<Icon><icon_add_outlined class="svg-icon" /></Icon>
</el-icon>
<el-icon
v-on:click.stop
v-if="data.node_type === 'leaf'"
class="hover-icon"
@click="resourceEdit(data.id)"
v-if="data.node_type === 'leaf'"
class="hover-icon"
@click.stop
@click="resourceEdit(data.id)"
>
<Icon><icon_edit_outlined class="svg-icon"/></Icon>
<Icon><icon_edit_outlined class="svg-icon" /></Icon>
</el-icon>
<HandleMore
@handle-command=" (opt:string) => operation(opt,data)"
:menu-list="state.folderMenuList"
:icon-name="icon_more_outlined"
placement="bottom-start"
v-if="data.node_type !== 'leaf'"
v-if="data.node_type !== 'leaf'"
:menu-list="state.folderMenuList"
:icon-name="icon_more_outlined"
placement="bottom-start"
@handle-command="(opt: string) => operation(opt, data)"
></HandleMore>
</div>
</span>
@@ -1,15 +1,15 @@
<script setup lang="ts">
import {toRefs} from 'vue'
import { toRefs } from 'vue'
const props = defineProps({
title: {type: String, required: false},
tips: {type: String, required: false},
iconName: {type: String, required: false},
showSplitLine: {type: Boolean, required: false, default: false},
active: {type: Boolean, required: false, default: false},
title: { type: String, required: false },
tips: { type: String, required: false },
iconName: { type: String, required: false },
showSplitLine: { type: Boolean, required: false, default: false },
active: { type: Boolean, required: false, default: false },
})
const {title, tips, iconName, showSplitLine} = toRefs(props)
const { title, tips, iconName, showSplitLine } = toRefs(props)
const emits = defineEmits(['customClick'])
</script>
@@ -17,11 +17,11 @@ const emits = defineEmits(['customClick'])
<div class="flex-align-center">
<el-row class="group_icon" :title="tips" @click="emits('customClick')">
<el-col :span="24" class="group_inner" :class="{ 'inner-active': active }">
<component class="svg-icon toolbar-icon" :is="iconName"></component>
<component :is="iconName" class="svg-icon toolbar-icon"></component>
<span>{{ title }}</span>
</el-col>
</el-row>
<el-divider class="group-right-border" v-if="showSplitLine" direction="vertical"/>
<el-divider v-if="showSplitLine" class="group-right-border" direction="vertical" />
</div>
</template>
<style lang="less" scoped>
@@ -4,74 +4,74 @@ import SQView from '@/views/dashboard/components/sq-view/index.vue'
import SQEmpty from '@/views/dashboard/components/sq-empty/index.vue'
const COMPONENT_LIST = [
{
id: 1001,
component: 'SQView',
name: 'new-view',
propValue: '&nbsp;',
icon: 'icon_graphical',
innerType: 'bar',
locked: false,
editing: false,
x: 1,
y: 1,
sizeX: 36,
sizeY: 14,
style: {}
},
{
id: 1002,
component: 'SQTab',
name: 'new-tabs',
locked: false,
collisionActive: false,
moveInActive: false,
moveOutActive: false,
activeTabName: 'tab',
propValue: [
{
name: 'tab',
title: 'new_tab',
componentData: [],
closable: true
}
],
canvasActive: false,
editing: false,
x: 1,
y: 1,
sizeX: 36,
sizeY: 14,
style: {}
},
{
id: 1003,
component: 'SQText',
name: 'new text',
locked: false,
propValue: 'Double click to edit text',
editing: false,
x: 1,
y: 1,
sizeX: 36,
sizeY: 14,
style: {}
}
{
id: 1001,
component: 'SQView',
name: 'new-view',
propValue: '&nbsp;',
icon: 'icon_graphical',
innerType: 'bar',
locked: false,
editing: false,
x: 1,
y: 1,
sizeX: 36,
sizeY: 14,
style: {},
},
{
id: 1002,
component: 'SQTab',
name: 'new-tabs',
locked: false,
collisionActive: false,
moveInActive: false,
moveOutActive: false,
activeTabName: 'tab',
propValue: [
{
name: 'tab',
title: 'new_tab',
componentData: [],
closable: true,
},
],
canvasActive: false,
editing: false,
x: 1,
y: 1,
sizeX: 36,
sizeY: 14,
style: {},
},
{
id: 1003,
component: 'SQText',
name: 'new text',
locked: false,
propValue: 'Double click to edit text',
editing: false,
x: 1,
y: 1,
sizeX: 36,
sizeY: 14,
style: {},
},
]
export function findNewComponentFromList(component: string) {
return COMPONENT_LIST.find(item => item.component === component) || null;
return COMPONENT_LIST.find((item) => item.component === component) || null
}
export const componentsMap = {
SQTab: SQTab,
SQText: SQText,
SQView: SQView
SQTab: SQTab,
SQText: SQText,
SQView: SQView,
}
export function findComponent(key = 'SQEmpty') {
//@ts-ignore
return componentsMap[key] ? componentsMap[key] : SQEmpty
//@ts-ignore
return componentsMap[key] ? componentsMap[key] : SQEmpty
}
export default COMPONENT_LIST
@@ -1,11 +1,7 @@
<script setup lang="ts">
</script>
<script setup lang="ts"></script>
<template>
<div> no component can be find </div>
<div>no component can be find</div>
</template>
<style scoped lang="less">
</style>
<style scoped lang="less"></style>
@@ -5,7 +5,7 @@
</template>
<script setup lang="ts">
import {computed} from 'vue'
import { computed } from 'vue'
const props = defineProps({
hideTitle: Boolean,
@@ -24,26 +24,26 @@ const props = defineProps({
styleType: {
type: String,
default: '',
validator: (val: string) => ['', 'radioGroup'].includes(val)
}
validator: (val: string) => ['', 'radioGroup'].includes(val),
},
})
const tabStyle = computed(() => [
{'--de-font-color': props.fontColor},
{'--de-active-color': props.activeColor},
{'--de-border-color': props.borderColor},
{'--de-border-active-color': props.borderActiveColor}
{ '--de-font-color': props.fontColor },
{ '--de-active-color': props.activeColor },
{ '--de-border-color': props.borderColor },
{ '--de-border-active-color': props.borderActiveColor },
])
const tabClassName = computed(() =>
props.styleType
? [props.styleType, props.fontColor && 'fontColor', props.activeColor && 'activeColor']
: [
props.fontColor && 'fontColor',
props.activeColor && 'activeColor',
noBorder.value ? 'noBorder' : props.borderColor && 'borderColor',
props.borderActiveColor && 'borderActiveColor',
props.hideTitle && 'no-header'
]
props.styleType
? [props.styleType, props.fontColor && 'fontColor', props.activeColor && 'activeColor']
: [
props.fontColor && 'fontColor',
props.activeColor && 'activeColor',
noBorder.value ? 'noBorder' : props.borderColor && 'borderColor',
props.borderActiveColor && 'borderActiveColor',
props.hideTitle && 'no-header',
]
)
const noBorder = computed(() => props.borderColor === 'none')
@@ -1,10 +1,19 @@
<script setup lang="ts">
import {computed, type PropType, reactive, ref, toRefs, nextTick, getCurrentInstance, onMounted} from 'vue'
import CustomTab from "@/views/dashboard/components/sq-tab/CustomTab.vue";
import {guid, type CanvasItem,} from "@/utils/canvas.ts";
import DragHandle from "@/views/dashboard/canvas/DragHandle.vue";
import {ArrowDown} from "@element-plus/icons-vue";
import DashboardEditor from "@/views/dashboard/editor/DashboardEditor.vue";
import {
computed,
type PropType,
reactive,
ref,
toRefs,
nextTick,
getCurrentInstance,
onMounted,
} from 'vue'
import CustomTab from '@/views/dashboard/components/sq-tab/CustomTab.vue'
import { guid, type CanvasItem } from '@/utils/canvas.ts'
import DragHandle from '@/views/dashboard/canvas/DragHandle.vue'
import { ArrowDown } from '@element-plus/icons-vue'
import DashboardEditor from '@/views/dashboard/editor/DashboardEditor.vue'
const editableTabsValue = ref(null)
const showTabTitleFlag = ref(true)
@@ -12,34 +21,34 @@ const showTabTitleFlag = ref(true)
let currentInstance
import _ from 'lodash'
const emits = defineEmits(["parentAddItemBox"]);
const emits = defineEmits(['parentAddItemBox'])
const tabBaseMatrixCount = {
x: 36,
y: 12
y: 12,
}
const props = defineProps({
configItem: {
type: Object as PropType<CanvasItem>,
required: true
required: true,
},
canvasViewInfo: {
type: Object,
required: true
required: true,
},
showPosition: {
required: false,
type: String,
default: 'preview'
default: 'preview',
},
canvasId: {
type: String,
default: 'canvas-main'
}
default: 'canvas-main',
},
})
const {configItem} = toRefs(props)
const { configItem } = toRefs(props)
const state = reactive({
activeTabName: '',
@@ -51,7 +60,7 @@ const state = reactive({
headFontColor: '#OOOOOO',
headFontActiveColor: '#OOOOOO',
headBorderColor: '#OOOOOO',
headBorderActiveColor: '#OOOOOO'
headBorderActiveColor: '#OOOOOO',
})
function addTab() {
@@ -59,7 +68,7 @@ function addTab() {
name: guid('tab'),
title: 'New Tab',
componentData: [],
closable: true
closable: true,
}
configItem.value.propValue.push(newTab)
configItem.value.activeSubTabIndex = configItem.value.propValue.length - 1
@@ -74,7 +83,7 @@ function deleteCur(param: any) {
if (configItem.value.propValue[len].name === param.name) {
configItem.value.propValue.splice(len, 1)
const activeIndex =
(len - 1 + configItem.value.propValue.length) % configItem.value.propValue.length
(len - 1 + configItem.value.propValue.length) % configItem.value.propValue.length
editableTabsValue.value = configItem.value.propValue[activeIndex].name
configItem.value.activeSubTabIndex = configItem.value.propValue.length - 1
state.tabShow = false
@@ -106,7 +115,7 @@ function handleCommand(command: any) {
const beforeHandleCommand = (item: any, param: any) => {
return {
command: item,
param: param
param: param,
}
}
@@ -120,11 +129,12 @@ const titleStyle = (itemName: string) => {
const isEditMode = computed(() => true)
const addTabItem = (item: CanvasItem) => {
// do addTabItem
// @ts-ignore
const index = configItem.value.propValue.findIndex(tabItem => configItem.value.activeTabName === tabItem.name);
const index = configItem.value.propValue.findIndex(
(tabItem) => configItem.value.activeTabName === tabItem.name
)
// @ts-ignore
const refInstance = currentInstance.refs['tabEditorRef_' + index][0]
const newTabItem = _.cloneDeep(item)
@@ -140,43 +150,43 @@ onMounted(() => {
})
defineExpose({
addTabItem
addTabItem,
})
</script>
<template>
<div :class="{'tab-moveout':configItem.moveOutActive}">
<div :class="{ 'tab-moveout': configItem.moveOutActive }">
<drag-handle></drag-handle>
<custom-tab
v-model="editableTabsValue"
@tab-add="addTab"
:addable="isEditMode"
:font-color="state.headFontColor"
:active-color="state.headFontActiveColor"
:border-color="state.headBorderColor"
:border-active-color="state.headBorderActiveColor"
:hide-title="!showTabTitleFlag"
v-model="editableTabsValue"
:addable="isEditMode"
:font-color="state.headFontColor"
:active-color="state.headFontActiveColor"
:border-color="state.headBorderColor"
:border-active-color="state.headBorderActiveColor"
:hide-title="!showTabTitleFlag"
@tab-add="addTab"
>
<!-- {{ configItem.collisionActive }} & {{ configItem.moveInActive }}${{ configItem.moveOutActive }}-->
<template :key="tabItem.name" v-for="tabItem in configItem.propValue">
<template v-for="tabItem in configItem.propValue" :key="tabItem.name">
<el-tab-pane
class="el-tab-pane-custom"
:lazy="isEditMode"
:label="tabItem.title"
:name="tabItem.name"
class="el-tab-pane-custom"
:lazy="isEditMode"
:label="tabItem.title"
:name="tabItem.name"
>
<template #label>
<div class="custom-tab-title" @mousedown.stop>
<span class="title-inner" :style="titleStyle(tabItem.name)"
>{{ tabItem.title }}
>{{ tabItem.title }}
<span v-if="isEditMode">
<el-dropdown
popper-class="custom-de-tab-dropdown"
trigger="click"
@command="handleCommand"
popper-class="custom-de-tab-dropdown"
trigger="click"
@command="handleCommand"
>
<span class="el-dropdown-link">
<el-icon v-if="isEditMode"><ArrowDown/></el-icon>
<el-icon v-if="isEditMode"><ArrowDown /></el-icon>
</span>
<template #dropdown>
<el-dropdown-menu>
@@ -187,8 +197,8 @@ defineExpose({
copy
</el-dropdown-item>
<el-dropdown-item
v-if="configItem.propValue.length > 1"
:command="beforeHandleCommand('deleteCur', tabItem)"
v-if="configItem.propValue.length > 1"
:command="beforeHandleCommand('deleteCur', tabItem)"
>
delete
</el-dropdown-item>
@@ -202,26 +212,25 @@ defineExpose({
</el-tab-pane>
</template>
<div
class="tab-content-custom"
:key="tabItem.name + '-content'"
v-for="(tabItem, index) in configItem.propValue"
:class="{ 'switch-hidden': editableTabsValue !== tabItem.name }"
v-for="(tabItem, index) in configItem.propValue"
:key="tabItem.name + '-content'"
class="tab-content-custom"
:class="{ 'switch-hidden': editableTabsValue !== tabItem.name }"
>
<DashboardEditor
class="tab-dashboard-editor-main"
:ref="'tabEditorRef_'+index"
:canvas-component-data="tabItem.componentData"
:move-in-active="configItem.moveInActive"
:base-matrix-count="tabBaseMatrixCount"
:canvas-id="tabItem.name"
:parent-config-item="configItem"
@parentAddItemBox=" item => emits('parentAddItemBox',item)"
:ref="'tabEditorRef_' + index"
class="tab-dashboard-editor-main"
:canvas-component-data="tabItem.componentData"
:move-in-active="configItem.moveInActive"
:base-matrix-count="tabBaseMatrixCount"
:canvas-id="tabItem.name"
:parent-config-item="configItem"
@parent-add-item-box="(item) => emits('parentAddItemBox', item)"
>
</DashboardEditor>
</div>
</custom-tab>
</div>
</template>
<style scoped lang="less">
@@ -249,5 +258,4 @@ defineExpose({
overflow: visible !important;
}
}
</style>
</style>
@@ -1,15 +1,18 @@
<template>
<div
class="rich-main-class"
:class="{ 'edit-model': configItem.editing }"
@keydown.stop
@keyup.stop
@dblclick="setEdit"
class="rich-main-class"
:class="{ 'edit-model': configItem.editing }"
@keydown.stop
@keyup.stop
@dblclick="setEdit"
>
<editor :class="{ 'custom-text-content':true,'dragHandle':isDisabled}"
v-model="configItem.propValue"
:disabled="isDisabled" :init="init"
:id="tinymceId"></editor>
<editor
:id="tinymceId"
v-model="configItem.propValue"
:class="{ 'custom-text-content': true, dragHandle: isDisabled }"
:disabled="isDisabled"
:init="init"
></editor>
</div>
</template>
@@ -34,23 +37,22 @@ import 'tinymce/plugins/nonbreaking'
import 'tinymce/plugins/pagebreak'
import '@npkg/tinymce-plugins/letterspacing'
import {computed, nextTick, type PropType, reactive, toRefs} from 'vue'
import {onMounted} from 'vue'
import type {CanvasItem} from "@/utils/canvas.ts";
import { computed, nextTick, type PropType, reactive, toRefs } from 'vue'
import { onMounted } from 'vue'
import type { CanvasItem } from '@/utils/canvas.ts'
const props = defineProps({
configItem: {
type: Object as PropType<CanvasItem>,
required: true
required: true,
},
disabled: {
type: Boolean,
default: false
}
default: false,
},
})
const {configItem} = toRefs(props)
const { configItem } = toRefs(props)
const tinymceId = 'vue-tinymce-' + +new Date() + ((Math.random() * 1000).toFixed(0) + '')
const init = reactive({
selector: tinymceId,
@@ -59,25 +61,25 @@ const init = reactive({
skin_url: '/tinymce-sqlbot-private/skins/ui/oxide',
content_css: '/tinymce-sqlbot-private/skins/content/default/content.css',
plugins:
'advlist autolink link image lists charmap media wordcount contextmenu directionality pagebreak letterspacing', // 插件
'advlist autolink link image lists charmap media wordcount contextmenu directionality pagebreak letterspacing', // 插件
// 工具栏
toolbar:
'undo redo | fontselect fontsizeselect |forecolor backcolor bold italic letterspacing |underline strikethrough link lineheight| formatselect |' +
'top-align center-align bottom-align | alignleft aligncenter alignright | bullist numlist |' +
' blockquote subscript superscript removeformat | image ',
'undo redo | fontselect fontsizeselect |forecolor backcolor bold italic letterspacing |underline strikethrough link lineheight| formatselect |' +
'top-align center-align bottom-align | alignleft aligncenter alignright | bullist numlist |' +
' blockquote subscript superscript removeformat | image ',
toolbar_location: '/',
font_formats:
'微软雅黑=Microsoft YaHei;宋体=SimSun;黑体=SimHei;仿宋=FangSong;华文黑体=STHeiti;华文楷体=STKaiti;华文宋体=STSong;华文仿宋=STFangsong;Andale Mono=andale mono,times;Arial=arial,helvetica,sans-serif;Arial Black=arial black,avant garde;Book Antiqua=book antiqua,palatino;Comic Sans MS=comic sans ms,sans-serif;Courier New=courier new,courier;Georgia=georgia,palatino;Helvetica=helvetica;Impact=impact,chicago;Symbol=symbol;Tahoma=tahoma,arial,helvetica,sans-serif;Terminal=terminal,monaco;Times New Roman=times new roman,times;Trebuchet MS=trebuchet ms,geneva;Verdana=verdana,geneva;Webdings=webdings;Wingdings=wingdings',
'微软雅黑=Microsoft YaHei;宋体=SimSun;黑体=SimHei;仿宋=FangSong;华文黑体=STHeiti;华文楷体=STKaiti;华文宋体=STSong;华文仿宋=STFangsong;Andale Mono=andale mono,times;Arial=arial,helvetica,sans-serif;Arial Black=arial black,avant garde;Book Antiqua=book antiqua,palatino;Comic Sans MS=comic sans ms,sans-serif;Courier New=courier new,courier;Georgia=georgia,palatino;Helvetica=helvetica;Impact=impact,chicago;Symbol=symbol;Tahoma=tahoma,arial,helvetica,sans-serif;Terminal=terminal,monaco;Times New Roman=times new roman,times;Trebuchet MS=trebuchet ms,geneva;Verdana=verdana,geneva;Webdings=webdings;Wingdings=wingdings',
fontsize_formats:
'12px 14px 16px 18px 20px 22px 24px 28px 32px 36px 42px 48px 56px 72px 80px 90px 100px 110px 120px 140px 150px 170px 190px 210px', // 字体大小
'12px 14px 16px 18px 20px 22px 24px 28px 32px 36px 42px 48px 56px 72px 80px 90px 100px 110px 120px 140px 150px 170px 190px 210px', // 字体大小
menubar: false,
placeholder: '',
inline: true,
setup: function (editor : any) {
setup: function (editor: any) {
editor.on('blur', function () {
configItem.value.editing = false
});
}
})
},
})
const isDisabled = computed(() => props.disabled || !configItem.value.editing)
@@ -173,5 +175,4 @@ onMounted(() => {
list-style-type: decimal;
}
}
</style>
@@ -1,11 +1,5 @@
<script setup lang="ts">
<script setup lang="ts"></script>
</script>
<template></template>
<template>
</template>
<style scoped lang="less">
</style>
<style scoped lang="less"></style>
@@ -1,7 +1,7 @@
<script setup lang="ts">
import CanvasCore from '@/views/dashboard/canvas/CanvasCore.vue'
import {nextTick, onMounted, type PropType, ref} from 'vue'
import type {CanvasItem} from "@/utils/canvas.ts";
import { nextTick, onMounted, type PropType, ref } from 'vue'
import type { CanvasItem } from '@/utils/canvas.ts'
const canvasCoreRef = ref(null)
const dashboardEditorRef = ref(null)
@@ -14,35 +14,37 @@ const baseMarginTop = ref(0)
const props = defineProps({
canvasId: {
type: String,
default: 'canvas-main'
default: 'canvas-main',
},
parentConfigItem: {
type: Object as PropType<CanvasItem>,
required: false
required: false,
},
dashboardInfo: {
type: Object,
required: false
required: false,
},
canvasStyleData: {
type: Object,
required: false
required: false,
},
canvasComponentData: {
type: Array as PropType<CanvasItem[]>,
required: true
required: true,
},
baseMatrixCount: {
type: Object,
default: {
x: 72,
y: 36
}
default: () => {
return {
x: 72,
y: 36,
}
},
},
moveInActive: {
type: Boolean,
default: false
}
default: false,
},
})
const canvasSizeInit = () => {
@@ -61,8 +63,10 @@ const sizeInit = () => {
const screenHeight = dashboardEditorRef.value.offsetHeight
baseMarginLeft.value = 10
baseMarginTop.value = 10
baseWidth.value = (screenWidth - baseMarginLeft.value) / props.baseMatrixCount.x - baseMarginLeft.value
baseHeight.value = (screenHeight - baseMarginTop.value) / props.baseMatrixCount.y - baseMarginTop.value
baseWidth.value =
(screenWidth - baseMarginLeft.value) / props.baseMatrixCount.x - baseMarginLeft.value
baseHeight.value =
(screenHeight - baseMarginTop.value) / props.baseMatrixCount.y - baseMarginTop.value
}
}
@@ -73,7 +77,7 @@ const addItemToBox = (item: CanvasItem) => {
defineExpose({
canvasSizeInit,
addItemToBox
addItemToBox,
})
onMounted(() => {
@@ -91,29 +95,29 @@ onMounted(() => {
}
})
})
const emits = defineEmits(["parentAddItemBox"]);
const emits = defineEmits(['parentAddItemBox'])
</script>
<template>
<div
class='dashboard-editor-main'
:class="{'move-in-active': moveInActive}"
ref="dashboardEditorRef">
ref="dashboardEditorRef"
class="dashboard-editor-main"
:class="{ 'move-in-active': moveInActive }"
>
<CanvasCore
ref="canvasCoreRef"
:base-width="baseWidth"
:base-height="baseHeight"
:base-margin-left="baseMarginLeft"
:base-margin-top="baseMarginTop"
:canvas-component-data="canvasComponentData"
:canvas-style-data="canvasStyleData"
:dashboard-info="dashboardInfo"
:parent-config-item="parentConfigItem"
:canvas-id="canvasId"
@parentAddItemBox=" item => emits('parentAddItemBox',item)"
ref="canvasCoreRef"
:base-width="baseWidth"
:base-height="baseHeight"
:base-margin-left="baseMarginLeft"
:base-margin-top="baseMarginTop"
:canvas-component-data="canvasComponentData"
:canvas-style-data="canvasStyleData"
:dashboard-info="dashboardInfo"
:parent-config-item="parentConfigItem"
:canvas-id="canvasId"
@parent-add-item-box="(item) => emits('parentAddItemBox', item)"
></CanvasCore>
</div>
</template>
<style scoped lang="less">
+57 -64
View File
@@ -1,25 +1,25 @@
<script setup lang="ts">
import {dashboardStoreWithOut} from "@/stores/dashboard/dashboard.ts";
import {storeToRefs} from 'pinia'
import ComponentButtonLabel from "@/views/dashboard/components/button-label/ComponentButtonLabel.vue";
import { dashboardStoreWithOut } from '@/stores/dashboard/dashboard.ts'
import { storeToRefs } from 'pinia'
import ComponentButtonLabel from '@/views/dashboard/components/button-label/ComponentButtonLabel.vue'
import dvTab from '@/assets/svg/dv-tab.svg'
import dvText from '@/assets/svg/dv-text.svg'
import dvView from '@/assets/svg/dv-view.svg'
import ResourceGroupOpt from "@/views/dashboard/common/ResourceGroupOpt.vue";
import {ref, nextTick} from 'vue'
import {useI18n} from 'vue-i18n'
import {snapshotStoreWithOut} from "@/stores/dashboard/snapshot.ts";
import ResourceGroupOpt from '@/views/dashboard/common/ResourceGroupOpt.vue'
import { ref, nextTick } from 'vue'
import { useI18n } from 'vue-i18n'
import { snapshotStoreWithOut } from '@/stores/dashboard/snapshot.ts'
import icon_undo_outlined from '@/assets/svg/icon_undo_outlined.svg'
import icon_redo_outlined from '@/assets/svg/icon_redo_outlined.svg'
import icon_left_outlined from '@/assets/svg/icon_left_outlined.svg'
import {saveDashboardResource} from "@/views/dashboard/utils/canvasUtils.ts";
import { saveDashboardResource } from '@/views/dashboard/utils/canvasUtils.ts'
const {t} = useI18n()
const { t } = useI18n()
const dashboardStore = dashboardStoreWithOut()
const {dashboardInfo} = storeToRefs(dashboardStore)
const { dashboardInfo } = storeToRefs(dashboardStore)
const snapshotStore = snapshotStoreWithOut()
const {snapshotIndex} = storeToRefs(snapshotStore)
const { snapshotIndex } = storeToRefs(snapshotStore)
const emits = defineEmits(['addComponent'])
const resourceGroupOptRef = ref(null)
const openViewDialog = () => {
@@ -30,9 +30,7 @@ let nameEdit = ref(false)
let inputName = ref('')
let nameInput = ref(null)
const onDvNameChange = () => {
}
const onDvNameChange = () => {}
const saveCanvasWithCheck = () => {
if (dashboardInfo.value.dataState === 'prepare') {
@@ -41,7 +39,7 @@ const saveCanvasWithCheck = () => {
pid: props.baseParams?.pid,
opt: 'newLeaf',
nodeType: 'leaf',
parentSelect: true
parentSelect: true,
}
// @ts-ignore
resourceGroupOptRef.value?.optInit(createParams)
@@ -50,7 +48,7 @@ const saveCanvasWithCheck = () => {
opt: 'updateLeaf',
id: dashboardInfo.value.id,
name: dashboardInfo.value.name,
pid: dashboardInfo.value.pid
pid: dashboardInfo.value.pid,
}
saveDashboardResource(updateParams, function () {
ElMessage({
@@ -65,16 +63,15 @@ const props = defineProps({
baseParams: {
type: Object,
required: false,
}
},
})
const groupOptFinish = (result: any) => {
let url = window.location.href
url = url.replace(/(#\/[^?]*)(?:\?[^#]*)?/, `$1?resourceId=${result.resourceId}`)
window.history.replaceState({path: url}, '', url)
window.history.replaceState({ path: url }, '', url)
}
const editCanvasName = () => {
nameEdit.value = true
//@ts-ignore
@@ -102,7 +99,6 @@ const closeEditCanvasName = () => {
inputName.value = ''
}
const undo = () => {
if (snapshotIndex.value > 0) {
snapshotStore.undo()
@@ -115,56 +111,54 @@ const redo = () => {
}
}
const backToMain = () => {
let url = '#/dashboard/index'
if (dashboardInfo.value.id) {
url = url + '?resourceId=' + dashboardInfo.value.id
}
if (!!history.state.back) {
if (history.state.back) {
history.back()
} else {
window.open(url, '_self')
}
}
</script>
<template>
<div class="toolbar-main">
<el-icon class="custom-el-icon back-icon" @click="backToMain()">
<Icon name="icon_left_outlined">
<icon_left_outlined class="toolbar-hover-icon toolbar-icon"/>
<icon_left_outlined class="toolbar-hover-icon toolbar-icon" />
</Icon>
</el-icon>
<div class="left-area">
<span id="canvas-name" class="name-area" @dblclick="editCanvasName">
{{ dashboardInfo.name }}
</span>
<span id="canvas-name" class="name-area" @dblclick="editCanvasName">
{{ dashboardInfo.name }}
</span>
<div class="opt-area">
<el-tooltip effect="dark" :content="t('dashboard.undo')" placement="bottom">
<el-icon
class="toolbar-hover-icon"
:class="{ 'toolbar-icon-disabled': snapshotIndex < 1 }"
:disabled="snapshotIndex < 1"
@click="undo()"
class="toolbar-hover-icon"
:class="{ 'toolbar-icon-disabled': snapshotIndex < 1 }"
:disabled="snapshotIndex < 1"
@click="undo()"
>
<Icon name="icon_undo_outlined">
<icon_undo_outlined class="svg-icon"/>
<icon_undo_outlined class="svg-icon" />
</Icon>
</el-icon>
</el-tooltip>
<el-tooltip effect="dark" :content="t('dashboard.reduction')" placement="bottom">
<el-icon
class="toolbar-hover-icon opt-icon-redo"
:class="{
'toolbar-icon-disabled': snapshotIndex === snapshotStore.snapshotData.length - 1
}"
@click="redo()"
class="toolbar-hover-icon opt-icon-redo"
:class="{
'toolbar-icon-disabled': snapshotIndex === snapshotStore.snapshotData.length - 1,
}"
@click="redo()"
>
<Icon name="icon_redo_outlined">
<icon_redo_outlined class="svg-icon"/>
<icon_redo_outlined class="svg-icon" />
</Icon>
</el-icon>
</el-tooltip>
@@ -172,45 +166,46 @@ const backToMain = () => {
</div>
<div class="core-toolbar">
<component-button-label
:icon-name="dvView"
title="Add View"
themes="light"
is-label
@customClick="openViewDialog"
:icon-name="dvView"
title="Add View"
themes="light"
is-label
@custom-click="openViewDialog"
></component-button-label>
<component-button-label
:icon-name="dvText"
title="Text"
themes="light"
is-label
@customClick="() =>emits('addComponent', 'SQText')"
:icon-name="dvText"
title="Text"
themes="light"
is-label
@custom-click="() => emits('addComponent', 'SQText')"
></component-button-label>
<component-button-label
:icon-name="dvTab"
title="Tab Item"
themes="light"
is-label
@customClick="() =>emits('addComponent', 'SQTab')"
:icon-name="dvTab"
title="Tab Item"
themes="light"
is-label
@custom-click="() => emits('addComponent', 'SQTab')"
>
</component-button-label>
</div>
<div class="right-toolbar">
<el-button @click="saveCanvasWithCheck()"
style="float: right; margin-right: 12px"
type="primary"
<el-button
style="float: right; margin-right: 12px"
type="primary"
@click="saveCanvasWithCheck()"
>
Save
</el-button>
</div>
<Teleport v-if="nameEdit" :to="'#canvas-name'">
<input
@change="onDvNameChange"
ref="nameInput"
v-model="inputName"
@blur="closeEditCanvasName"
ref="nameInput"
v-model="inputName"
@change="onDvNameChange"
@blur="closeEditCanvasName"
/>
</Teleport>
<ResourceGroupOpt @finish="groupOptFinish " ref="resourceGroupOptRef"></ResourceGroupOpt>
<ResourceGroupOpt ref="resourceGroupOptRef" @finish="groupOptFinish"></ResourceGroupOpt>
</div>
</template>
@@ -285,7 +280,6 @@ const backToMain = () => {
color: #fff;
}
.toolbar-hover-icon {
cursor: pointer;
font-size: 18px !important;
@@ -302,5 +296,4 @@ const backToMain = () => {
background: transparent;
}
}
</style>
</style>
+21 -21
View File
@@ -1,26 +1,26 @@
<script setup lang="ts">
import {onMounted, ref, computed, reactive} from 'vue'
import Toolbar from "@/views/dashboard/editor/Toolbar.vue";
import DashboardEditor from "@/views/dashboard/editor/DashboardEditor.vue";
import {findNewComponentFromList} from "@/views/dashboard/components/component-list.ts";
import {guid} from "@/utils/canvas.ts";
import cloneDeep from 'lodash/cloneDeep';
import {storeToRefs} from "pinia";
import {dashboardStoreWithOut} from "@/stores/dashboard/dashboard.ts";
import { onMounted, ref, computed, reactive } from 'vue'
import Toolbar from '@/views/dashboard/editor/Toolbar.vue'
import DashboardEditor from '@/views/dashboard/editor/DashboardEditor.vue'
import { findNewComponentFromList } from '@/views/dashboard/components/component-list.ts'
import { guid } from '@/utils/canvas.ts'
import cloneDeep from 'lodash/cloneDeep'
import { storeToRefs } from 'pinia'
import { dashboardStoreWithOut } from '@/stores/dashboard/dashboard.ts'
import router from '@/router'
import {initCanvasData} from "@/views/dashboard/utils/canvasUtils.ts";
import { initCanvasData } from '@/views/dashboard/utils/canvasUtils.ts'
const dashboardStore = dashboardStoreWithOut()
const {componentData} = storeToRefs(dashboardStore)
const { componentData } = storeToRefs(dashboardStore)
const dataInitState = ref(false)
const state = reactive({
routerPid: null,
resourceId: null,
opt: null
opt: null,
})
const dashboardEditorRef = ref(null);
const dashboardEditorRef = ref(null)
const addComponent = (componentType: string) => {
const component = cloneDeep(findNewComponentFromList(componentType))
if (component && dashboardEditorRef.value) {
@@ -45,10 +45,14 @@ onMounted(() => {
//@ts-ignore
state.routerPid = router.currentRoute.value.query.pid
if (state.opt === 'create') {
dashboardStore.updateDashboardInfo({dataState: 'prepare', name: 'New Dashboard', pid: state.routerPid})
dashboardStore.updateDashboardInfo({
dataState: 'prepare',
name: 'New Dashboard',
pid: state.routerPid,
})
} else if (state.resourceId) {
dataInitState.value = false
initCanvasData({id: state.resourceId}, function () {
initCanvasData({ id: state.resourceId }, function () {
dataInitState.value = true
})
}
@@ -58,19 +62,15 @@ const baseParams = computed(() => {
return {
opt: state.opt,
resourceId: state.resourceId,
pid: state.routerPid
pid: state.routerPid,
}
})
</script>
<template>
<div class="editor-main">
<Toolbar :base-params="baseParams" @add-component="addComponent"></Toolbar>
<DashboardEditor
ref="dashboardEditorRef"
:canvas-component-data="componentData">
<DashboardEditor ref="dashboardEditorRef" :canvas-component-data="componentData">
</DashboardEditor>
</div>
</template>
@@ -82,4 +82,4 @@ const baseParams = computed(() => {
height: 100vh;
background: #f5f6f7;
}
</style>
</style>
+1 -1
View File
@@ -1,5 +1,5 @@
<script setup lang="ts">
import SQPreviewShow from "@/views/dashboard/preview/SQPreviewShow.vue";
import SQPreviewShow from '@/views/dashboard/preview/SQPreviewShow.vue'
</script>
<template>
@@ -1,52 +1,51 @@
<script setup lang="ts">
import {ref, toRefs, computed} from 'vue'
import {findComponent} from "@/views/dashboard/components/component-list.ts";
import { ref, toRefs, computed } from 'vue'
import { findComponent } from '@/views/dashboard/components/component-list.ts'
const componentWrapperInnerRef = ref(null)
const props = defineProps({
active: {
type: Boolean,
default: false
default: false,
},
configItem: {
type: Object,
required: true
required: true,
},
canvasViewInfo: {
type: Object,
required: true
required: true,
},
showPosition: {
required: false,
type: String,
default: 'preview'
default: 'preview',
},
canvasId: {
type: String,
default: 'canvas-main'
}
default: 'canvas-main',
},
})
const {configItem, showPosition} = toRefs(props)
const { configItem, showPosition } = toRefs(props)
const component = ref(null)
const wrapperId = 'wrapper-outer-id-' + configItem.value.id
const viewDemoInnerId = computed(() => 'enlarge-inner-content-' + configItem.value.id)
</script>
<template>
<div class="wrapper-outer" :id="wrapperId">
<div class="wrapper-inner" ref="componentWrapperInnerRef" :id="viewDemoInnerId">
<div :id="wrapperId" class="wrapper-outer">
<div :id="viewDemoInnerId" ref="componentWrapperInnerRef" class="wrapper-inner">
<div class="wrapper-inner-adaptor">
<component
ref="component"
class="component"
:is="findComponent(configItem['component'])"
:canvas-view-info="canvasViewInfo"
:view="canvasViewInfo[configItem.id]"
:config-item="configItem"
:show-position="showPosition"
:active="active"
:is="findComponent(configItem['component'])"
ref="component"
class="component"
:canvas-view-info="canvasViewInfo"
:view="canvasViewInfo[configItem.id]"
:config-item="configItem"
:show-position="showPosition"
:active="active"
/>
</div>
</div>
@@ -2,57 +2,59 @@
import elementResizeDetectorMaker from 'element-resize-detector'
const dashboardStore = dashboardStoreWithOut()
const {curComponent} = storeToRefs(dashboardStore)
const { curComponent } = storeToRefs(dashboardStore)
import {onMounted, toRefs, ref, computed, reactive} from 'vue'
import {dashboardStoreWithOut} from "@/stores/dashboard/dashboard.ts";
import {storeToRefs} from "pinia";
import SQComponentWrapper from "@/views/dashboard/preview/SQComponentWrapper.vue";
import type {CanvasItem} from "@/utils/canvas.ts";
import { onMounted, toRefs, ref, computed, reactive } from 'vue'
import { dashboardStoreWithOut } from '@/stores/dashboard/dashboard.ts'
import { storeToRefs } from 'pinia'
import SQComponentWrapper from '@/views/dashboard/preview/SQComponentWrapper.vue'
import type { CanvasItem } from '@/utils/canvas.ts'
const props = defineProps({
canvasStyleData: {
type: Object,
required: true
required: true,
},
componentData: {
type: Object,
required: true
required: true,
},
canvasViewInfo: {
type: Object,
required: true
required: true,
},
dashboardInfo: {
type: Object,
required: true
required: true,
},
baseMatrixCount: {
type: Object,
default: {
x: 72,
y: 36
}
default: () => {
return {
x: 72,
y: 36,
}
},
},
canvasId: {
type: String,
required: false,
default: 'canvas-main'
default: 'canvas-main',
},
showPosition: {
required: false,
type: String,
default: 'preview'
}
default: 'preview',
},
})
const { canvasStyleData, componentData, showPosition, canvasId} = toRefs(props)
const { canvasStyleData, componentData, showPosition, canvasId } = toRefs(props)
const domId = 'preview-' + canvasId.value
const previewCanvas = ref(null)
const renderReady = ref(true)
const state = reactive({
initState: true,
scrollMain: 0
scrollMain: 0,
})
const cellWidth = ref(0)
@@ -62,19 +64,17 @@ const baseHeight = ref(0)
const baseMarginLeft = ref(0)
const baseMarginTop = ref(0)
const canvasStyle = computed(() => {
return {background: '#f5f6f7'}
return { background: '#f5f6f7' }
})
const restore = () => {
}
const restore = () => {}
function nowItemStyle(item: CanvasItem) {
return {
width: cellWidth.value * item.sizeX - baseMarginLeft.value + 'px',
height: cellHeight.value * item.sizeY - baseMarginTop.value + 'px',
left: cellWidth.value * (item.x - 1) + baseMarginLeft.value + 'px',
top: cellHeight.value * (item.y - 1) + baseMarginTop.value + 'px'
top: cellHeight.value * (item.y - 1) + baseMarginTop.value + 'px',
}
}
@@ -86,14 +86,15 @@ const sizeInit = () => {
const screenHeight = previewCanvas.value.offsetHeight
baseMarginLeft.value = 10
baseMarginTop.value = 10
baseWidth.value = (screenWidth - baseMarginLeft.value) / props.baseMatrixCount.x - baseMarginLeft.value
baseHeight.value = (screenHeight - baseMarginTop.value) / props.baseMatrixCount.y - baseMarginTop.value
baseWidth.value =
(screenWidth - baseMarginLeft.value) / props.baseMatrixCount.x - baseMarginLeft.value
baseHeight.value =
(screenHeight - baseMarginTop.value) / props.baseMatrixCount.y - baseMarginTop.value
cellWidth.value = baseWidth.value + baseMarginLeft.value
cellHeight.value = baseHeight.value + baseMarginTop.value
}
}
onMounted(() => {
sizeInit()
//@ts-ignore
@@ -101,23 +102,29 @@ onMounted(() => {
})
defineExpose({
restore
restore,
})
</script>
<template>
<div :id="domId" class="canvas-container" :style="canvasStyle" ref="previewCanvas" v-if="state.initState">
<div
v-if="state.initState"
:id="domId"
ref="previewCanvas"
class="canvas-container"
:style="canvasStyle"
>
<template v-if="renderReady">
<SQComponentWrapper
v-for="(item, index) in componentData"
:active="!!curComponent && (item.id === curComponent['id'])"
:config-item="item"
:canvas-view-info="canvasStyleData"
:show-position="showPosition"
:canvas-id="canvasId"
:key="index"
:style="nowItemStyle(item)"
:index="index"
v-for="(item, index) in componentData"
:key="index"
:active="!!curComponent && item.id === curComponent['id']"
:config-item="item"
:canvas-view-info="canvasStyleData"
:show-position="showPosition"
:canvas-id="canvasId"
:style="nowItemStyle(item)"
:index="index"
/>
</template>
</div>
@@ -2,7 +2,7 @@
import dashboardInfoSvg from '@/assets/svg/dashboard-info.svg'
import icon_pc_outlined from '@/assets/svg/icon_pc_outlined.svg'
import icon_edit_outlined from '@/assets/svg/icon_edit_outlined.svg'
import DashboardDetailInfo from "@/views/dashboard/common/DashboardDetailInfo.vue";
import DashboardDetailInfo from '@/views/dashboard/common/DashboardDetailInfo.vue'
const emit = defineEmits(['reload', 'download', 'downloadAsAppTemplate'])
const preview = () => {
@@ -14,22 +14,23 @@ const edit = () => {
const props = defineProps({
dashboardInfo: {
type: Object,
required: true
}
required: true,
},
})
</script>
<template>
<div class="preview-head flex-align-center">
<div class="canvas-name ellipsis">{{ dashboardInfo.name }}</div>
<el-divider style="margin: 5px 10px 0 17px" direction="vertical"/>
<span class="create-area" style="line-height: 22px">Creator:{{ dashboardInfo.createName }}</span>
<el-divider style="margin: 5px 10px 0 17px" direction="vertical" />
<span class="create-area" style="line-height: 22px"
>Creator:{{ dashboardInfo.createName }}</span
>
<div style="padding-top: 4px" class="create-area flex-align-center">
<el-popover show-arrow :offset="8" placement="bottom" width="400" trigger="hover">
<template #reference>
<el-icon class="info-tips">
<dashboardInfoSvg class="svg-icon"/>
<dashboardInfoSvg class="svg-icon" />
</el-icon>
</template>
<DashboardDetailInfo :dashboard-info="dashboardInfo"></DashboardDetailInfo>
@@ -39,7 +40,7 @@ const props = defineProps({
<el-button secondary @click="preview">
<template #icon>
<icon name="icon_pc_outlined">
<icon_pc_outlined class="svg-icon"/>
<icon_pc_outlined class="svg-icon" />
</icon>
</template>
Preview
@@ -47,12 +48,11 @@ const props = defineProps({
<el-button class="custom-button" type="primary" @click="edit">
<template #icon>
<Icon name="icon_edit_outlined">
<icon_edit_outlined class="svg-icon"/>
<icon_edit_outlined class="svg-icon" />
</Icon>
</template>
Edit
</el-button
>
</el-button>
</div>
</div>
</template>
@@ -1,13 +1,13 @@
<script setup lang="ts">
import icon_add_outlined from '@/assets/svg/icon_add_outlined.svg'
import {reactive, ref, toRefs, onBeforeMount, computed} from 'vue'
import {load_resource_prepare} from '@/views/dashboard/utils/canvasUtils'
import {Icon} from '@/components/icon-custom'
import ResourceTree from "@/views/dashboard/common/ResourceTree.vue";
import SQPreview from "@/views/dashboard/preview/SQPreview.vue";
import SQPreviewHead from "@/views/dashboard/preview/SQPreviewHead.vue";
import EmptyBackground from "@/views/dashboard/common/EmptyBackground.vue";
import {dashboardStoreWithOut} from "@/stores/dashboard/dashboard.ts";
import { reactive, ref, toRefs, onBeforeMount, computed } from 'vue'
import { load_resource_prepare } from '@/views/dashboard/utils/canvasUtils'
import { Icon } from '@/components/icon-custom'
import ResourceTree from '@/views/dashboard/common/ResourceTree.vue'
import SQPreview from '@/views/dashboard/preview/SQPreview.vue'
import SQPreviewHead from '@/views/dashboard/preview/SQPreviewHead.vue'
import EmptyBackground from '@/views/dashboard/common/EmptyBackground.vue'
import { dashboardStoreWithOut } from '@/stores/dashboard/dashboard.ts'
const dashboardStore = dashboardStoreWithOut()
const previewCanvasContainer = ref(null)
@@ -18,23 +18,23 @@ const state = reactive({
canvasDataPreview: [],
canvasStylePreview: {},
canvasViewInfoPreview: {},
dashboardInfo: {}
dashboardInfo: {},
})
const props = defineProps({
showPosition: {
required: false,
type: String,
default: 'preview'
default: 'preview',
},
noClose: {
required: false,
type: Boolean,
default: false
}
default: false,
},
})
const {showPosition} = toRefs(props)
const { showPosition } = toRefs(props)
const resourceTreeRef = ref()
@@ -53,15 +53,16 @@ function createNew() {
}
const loadCanvasData = (params: any) => {
dataInitState.value = false
load_resource_prepare({id: params.id},
//@ts-ignore
function ({dashboardInfo, canvasDataResult, canvasStyleResult, canvasViewInfoPreview}) {
state.canvasDataPreview = canvasDataResult
state.canvasStylePreview = canvasStyleResult
state.canvasViewInfoPreview = canvasViewInfoPreview
state.dashboardInfo = dashboardInfo
dataInitState.value = true
}
load_resource_prepare(
{ id: params.id },
//@ts-ignore
function ({ dashboardInfo, canvasDataResult, canvasStyleResult, canvasViewInfoPreview }) {
state.canvasDataPreview = canvasDataResult
state.canvasStylePreview = canvasStyleResult
state.canvasViewInfoPreview = canvasViewInfoPreview
state.dashboardInfo = dashboardInfo
dataInitState.value = true
}
)
}
const getPreviewStateInfo = () => {
@@ -86,53 +87,53 @@ onBeforeMount(() => {
})
const sideTreeStatus = ref(true)
defineExpose({
getPreviewStateInfo
getPreviewStateInfo,
})
</script>
<template>
<div class="dv-preview dv-teleport-query">
<el-aside
class="resource-area"
:class="{ 'close-side': !slideShow, retract: !sideTreeStatus }"
ref="node"
ref="node"
class="resource-area"
:class="{ 'close-side': !slideShow, retract: !sideTreeStatus }"
>
<resource-tree
ref="resourceTreeRef"
v-show="slideShow"
:cur-canvas-type="'dashboard'"
:show-position="showPosition"
@node-click="resourceNodeClick"
v-show="slideShow"
ref="resourceTreeRef"
:cur-canvas-type="'dashboard'"
:show-position="showPosition"
@node-click="resourceNodeClick"
/>
</el-aside>
<el-container
class="preview-area"
:class="{ 'no-data': !state.dashboardInfo }"
v-loading="!dataInitState"
v-loading="!dataInitState"
class="preview-area"
:class="{ 'no-data': !state.dashboardInfo }"
>
<template v-if="previewShowFlag">
<SQPreviewHead :dashboard-info="state.dashboardInfo" @reload="reload"/>
<div ref="previewCanvasContainer" class="content" id="sq-preview-content">
<SQPreviewHead :dashboard-info="state.dashboardInfo" @reload="reload" />
<div id="sq-preview-content" ref="previewCanvasContainer" class="content">
<SQPreview
ref="dashboardPreview"
v-if="state.canvasStylePreview && dataInitState"
:dashboard-info="state.dashboardInfo"
:component-data="state.canvasDataPreview"
:canvas-style-data="state.canvasStylePreview"
:canvas-view-info="state.canvasViewInfoPreview"
:show-position="showPosition"
v-if="state.canvasStylePreview && dataInitState"
ref="dashboardPreview"
:dashboard-info="state.dashboardInfo"
:component-data="state.canvasDataPreview"
:canvas-style-data="state.canvasStylePreview"
:canvas-view-info="state.canvasViewInfoPreview"
:show-position="showPosition"
></SQPreview>
</div>
</template>
<template v-else-if="hasTreeData && mounted">
<EmptyBackground :description="'Please Select Resource'" img-type="select"/>
<EmptyBackground :description="'Please Select Resource'" img-type="select" />
</template>
<template v-else-if="mounted">
<EmptyBackground :description="'No Resource'" img-type="none">
<el-button v-if="rootManage" @click="createNew" type="primary">
<el-button v-if="rootManage" type="primary" @click="createNew">
<template #icon>
<Icon name="icon_add_outlined">
<icon_add_outlined class="svg-icon"/>
<icon_add_outlined class="svg-icon" />
</Icon>
</template>
Create Dashboard
@@ -1,9 +1,8 @@
<script setup lang="ts">
import SQPreview from "@/views/dashboard/preview/SQPreview.vue";
import {load_resource_prepare} from "@/views/dashboard/utils/canvasUtils.ts";
import {onMounted, reactive, ref} from "vue";
import router from "@/router";
import SQPreview from '@/views/dashboard/preview/SQPreview.vue'
import { load_resource_prepare } from '@/views/dashboard/utils/canvasUtils.ts'
import { onMounted, reactive, ref } from 'vue'
import router from '@/router'
const previewCanvasContainer = ref(null)
const dashboardPreview = ref(null)
@@ -14,58 +13,55 @@ const state = reactive({
canvasDataPreview: [],
canvasStylePreview: {},
canvasViewInfoPreview: {},
dashboardInfo: {}
dashboardInfo: {},
})
onMounted(() => {
//@ts-ignore
state.resourceId = router.currentRoute.value.query.resourceId
if (state.resourceId) {
loadCanvasData({id: state.resourceId})
}
}
)
//@ts-ignore
state.resourceId = router.currentRoute.value.query.resourceId
if (state.resourceId) {
loadCanvasData({ id: state.resourceId })
}
})
const loadCanvasData = (params: any) => {
dataInitState.value = false
load_resource_prepare({id: params.id},
//@ts-ignore
function ({dashboardInfo, canvasDataResult, canvasStyleResult, canvasViewInfoPreview}) {
state.canvasDataPreview = canvasDataResult
state.canvasStylePreview = canvasStyleResult
state.canvasViewInfoPreview = canvasViewInfoPreview
state.dashboardInfo = dashboardInfo
dataInitState.value = true
}
load_resource_prepare(
{ id: params.id },
//@ts-ignore
function ({ dashboardInfo, canvasDataResult, canvasStyleResult, canvasViewInfoPreview }) {
state.canvasDataPreview = canvasDataResult
state.canvasStylePreview = canvasStyleResult
state.canvasViewInfoPreview = canvasViewInfoPreview
state.dashboardInfo = dashboardInfo
dataInitState.value = true
}
)
}
</script>
<template>
<div ref="previewCanvasContainer" class="content" id="sq-preview-content">
<div id="sq-preview-content" ref="previewCanvasContainer" class="content">
<SQPreview
ref="dashboardPreview"
v-if="state.canvasStylePreview && dataInitState"
:dashboard-info="state.dashboardInfo"
:component-data="state.canvasDataPreview"
:canvas-style-data="state.canvasStylePreview"
:canvas-view-info="state.canvasViewInfoPreview"
:download-status="downloadStatus"
v-if="state.canvasStylePreview && dataInitState"
ref="dashboardPreview"
:dashboard-info="state.dashboardInfo"
:component-data="state.canvasDataPreview"
:canvas-style-data="state.canvasStylePreview"
:canvas-view-info="state.canvasViewInfoPreview"
:download-status="downloadStatus"
></SQPreview>
</div>
</template>
<style scoped lang="less">
.content {
position: relative;
display: flex;
width: 100%;
height: 100vh;
overflow-x: hidden;
overflow-y: auto;
align-items: center;
}
</style>
position: relative;
display: flex;
width: 100%;
height: 100vh;
overflow-x: hidden;
overflow-y: auto;
align-items: center;
}
</style>
@@ -1,11 +1,5 @@
<script setup lang="ts">
<script setup lang="ts"></script>
</script>
<template></template>
<template>
</template>
<style scoped lang="less">
</style>
<style scoped lang="less"></style>
@@ -1,80 +1,81 @@
import {dashboardApi} from "@/api/dashboard.ts";
import {dashboardStoreWithOut} from "@/stores/dashboard/dashboard.ts";
import {storeToRefs} from "pinia";
import { dashboardApi } from '@/api/dashboard.ts'
import { dashboardStoreWithOut } from '@/stores/dashboard/dashboard.ts'
import { storeToRefs } from 'pinia'
const dashboardStore = dashboardStoreWithOut()
const {componentData, canvasStyleData} = storeToRefs(dashboardStore)
const { componentData, canvasStyleData } = storeToRefs(dashboardStore)
const workspace_id = 'default' // temp
export const load_resource_prepare = (params: any, callBack: Function) => {
dashboardApi.load_resource(params).then((canvasInfo: any) => {
const dashboardInfo = {
id: canvasInfo.id,
name: canvasInfo.name,
pid: canvasInfo.pid,
workspaceId: canvasInfo.workspace_id,
status: canvasInfo.status,
type: canvasInfo.type,
createName: canvasInfo.create_name,
updateName: canvasInfo.update_name,
createTime: canvasInfo.create_time,
updateTime: canvasInfo.update_time,
contentId: canvasInfo.content_id
}
const canvasDataResult = JSON.parse(canvasInfo.component_data)
const canvasStyleResult = JSON.parse(canvasInfo.canvas_style_data)
const canvasViewInfoPreview = canvasInfo.canvas_view_Info
callBack({dashboardInfo, canvasDataResult, canvasStyleResult, canvasViewInfoPreview})
})
export const load_resource_prepare = (params: any, callBack: (obj: any) => void) => {
dashboardApi.load_resource(params).then((canvasInfo: any) => {
const dashboardInfo = {
id: canvasInfo.id,
name: canvasInfo.name,
pid: canvasInfo.pid,
workspaceId: canvasInfo.workspace_id,
status: canvasInfo.status,
type: canvasInfo.type,
createName: canvasInfo.create_name,
updateName: canvasInfo.update_name,
createTime: canvasInfo.create_time,
updateTime: canvasInfo.update_time,
contentId: canvasInfo.content_id,
}
const canvasDataResult = JSON.parse(canvasInfo.component_data)
const canvasStyleResult = JSON.parse(canvasInfo.canvas_style_data)
const canvasViewInfoPreview = canvasInfo.canvas_view_Info
callBack({ dashboardInfo, canvasDataResult, canvasStyleResult, canvasViewInfoPreview })
})
}
export const initCanvasData = (params: any, callBack: Function) => {
load_resource_prepare(params, function (result: any) {
dashboardStore.setDashboardInfo(result.dashboardInfo)
dashboardStore.setCanvasStyleData(result.canvasStyleResult)
dashboardStore.setComponentData(result.canvasDataResult)
dashboardStore.setCanvasViewInfo(result.canvasViewInfoPreview)
callBack()
})
export const initCanvasData = (params: any, callBack: () => void) => {
load_resource_prepare(params, function (result: any) {
dashboardStore.setDashboardInfo(result.dashboardInfo)
dashboardStore.setCanvasStyleData(result.canvasStyleResult)
dashboardStore.setComponentData(result.canvasDataResult)
dashboardStore.setCanvasViewInfo(result.canvasViewInfoPreview)
callBack()
})
}
export const saveDashboardResource = (params: any, callBack: Function) => {
params['workspace_id'] = workspace_id
dashboardApi.check_name(params).then((resCheck: any) => {
if (resCheck) {
if (params.opt === 'newLeaf') {// create canvas
const reqeustParams = {
...params,
component_data: JSON.stringify(componentData.value),
canvas_style_data: JSON.stringify(canvasStyleData.value)
}
dashboardApi.create_canvas(reqeustParams).then((res: any) => {
dashboardStore.updateDashboardInfo({id: res.id, dataState: 'ready'})
callBack(res)
})
} else if (params.opt === 'newFolder') {
dashboardApi.create_resource(params).then((res: any) => {
callBack(res)
})
} else if (params.opt === 'rename') {
dashboardApi.update_resource(params).then((res: any) => {
callBack(res)
})
}else if (params.opt === 'updateLeaf') {
const reqeustParams = {
...params,
component_data: JSON.stringify(componentData.value),
canvas_style_data: JSON.stringify(canvasStyleData.value)
}
dashboardApi.update_canvas(reqeustParams).then(() => {
callBack()
})
}
} else {
ElMessage({
type: 'warning',
message: 'Name Already In Use',
})
params['workspace_id'] = workspace_id
dashboardApi.check_name(params).then((resCheck: any) => {
if (resCheck) {
if (params.opt === 'newLeaf') {
// create canvas
const reqeustParams = {
...params,
component_data: JSON.stringify(componentData.value),
canvas_style_data: JSON.stringify(canvasStyleData.value),
}
})
}
dashboardApi.create_canvas(reqeustParams).then((res: any) => {
dashboardStore.updateDashboardInfo({ id: res.id, dataState: 'ready' })
callBack(res)
})
} else if (params.opt === 'newFolder') {
dashboardApi.create_resource(params).then((res: any) => {
callBack(res)
})
} else if (params.opt === 'rename') {
dashboardApi.update_resource(params).then((res: any) => {
callBack(res)
})
} else if (params.opt === 'updateLeaf') {
const reqeustParams = {
...params,
component_data: JSON.stringify(componentData.value),
canvas_style_data: JSON.stringify(canvasStyleData.value),
}
dashboardApi.update_canvas(reqeustParams).then(() => {
callBack()
})
}
} else {
ElMessage({
type: 'warning',
message: 'Name Already In Use',
})
}
})
}
@@ -1,14 +1,14 @@
import { cloneDeep } from 'lodash-es'
import {dashboardApi} from "@/api/dashboard.ts";
const treeDraggableChart = (state:any, key:any, type:any) => {
import { dashboardApi } from '@/api/dashboard.ts'
const treeDraggableChart = (state: any, key: any, type: any) => {
let dragNodeParentId = ''
let dragNodeId = ''
let dragNodeIndex = 0
let rawData:any[] = []
let rawData: any[] = []
const dfsTreeNode = (arr:any, parentId:any) => {
arr.forEach((element:any, index:any) => {
const dfsTreeNode = (arr: any, parentId: any) => {
arr.forEach((element: any, index: any) => {
if (element.id === dragNodeId) {
dragNodeIndex = index
dragNodeParentId = parentId
@@ -19,9 +19,9 @@ const treeDraggableChart = (state:any, key:any, type:any) => {
})
}
const dfsTreeNodeSaveLevel = (arr:any[], idArr:any) => {
arr.forEach((element:any) => {
const index = idArr.findIndex((ele:any) => {
const dfsTreeNodeSaveLevel = (arr: any[], idArr: any) => {
arr.forEach((element: any) => {
const index = idArr.findIndex((ele: any) => {
return ele === element.id
})
if (index !== -1) {
@@ -33,8 +33,8 @@ const treeDraggableChart = (state:any, key:any, type:any) => {
})
}
const dfsTreeNodeBack = (arr:any, parentId:any, params:any) => {
arr.forEach((element:any) => {
const dfsTreeNodeBack = (arr: any, parentId: any, params: any) => {
arr.forEach((element: any) => {
if (element.id === params.id) {
params.pid = parentId
}
@@ -44,8 +44,8 @@ const treeDraggableChart = (state:any, key:any, type:any) => {
})
}
const dfsTreeNodeReset = (arr:any, node:any) => {
arr.forEach((element:any) => {
const dfsTreeNodeReset = (arr: any, node: any) => {
arr.forEach((element: any) => {
if (element.id === dragNodeParentId) {
element.children.splice(dragNodeIndex, 0, node)
}
@@ -55,8 +55,8 @@ const treeDraggableChart = (state:any, key:any, type:any) => {
})
}
const dfsTreeNodeDel = (arr:any, node:any) => {
arr.forEach((element:any, index:any) => {
const dfsTreeNodeDel = (arr: any, node: any) => {
arr.forEach((element: any, index: any) => {
if (element.id === node.id) {
arr.splice(index, 1)
}
@@ -66,24 +66,24 @@ const treeDraggableChart = (state:any, key:any, type:any) => {
})
}
const handleDragStart = (node:any) => {
const handleDragStart = (node: any) => {
dragNodeId = node.data.id
rawData = cloneDeep(state[key])
dfsTreeNode(state[key], '0')
}
const allowDrop = (dropNode:any) => {
const allowDrop = (dropNode: any) => {
return !dropNode.data?.leaf
}
const handleDrop = async (draggingNode:any, dropNode:any, dropType:any) => {
const handleDrop = async (draggingNode: any, dropNode: any, dropType: any) => {
const params = {
id: draggingNode.data?.id,
name: draggingNode.data?.name,
nodeType: draggingNode.data?.leaf ? 'leaf' : 'folder',
pid: '0',
opt: 'move',
type
type,
}
if (dropType !== 'inner') {
const idArr = [params.id, dropNode.data?.id]
@@ -115,7 +115,8 @@ const treeDraggableChart = (state:any, key:any, type:any) => {
dfsTreeNodeBack(state[key], '0', params)
}
dashboardApi.move_resource(params)
dashboardApi
.move_resource(params)
.then(() => {
state.originResourceTree = cloneDeep(state[key])
})
@@ -132,7 +133,7 @@ const treeDraggableChart = (state:any, key:any, type:any) => {
return {
handleDrop,
allowDrop,
handleDragStart
handleDragStart,
}
}
@@ -1,14 +1,14 @@
import _ from 'lodash'
import type {SQTreeNode} from "@/views/dashboard/utils/treeNode";
import type { SQTreeNode } from '@/views/dashboard/utils/treeNode'
export function treeParentWeight(tree: SQTreeNode[], pWeight:any) {
export function treeParentWeight(tree: SQTreeNode[], pWeight: any) {
const pWeightResult = {}
weightCheckCircle(tree, pWeightResult, pWeight)
return pWeightResult
}
export function weightCheckCircle(tree: SQTreeNode[], pWeightResult:any, pWeight:any) {
_.forEach(tree, node => {
export function weightCheckCircle(tree: SQTreeNode[], pWeightResult: any, pWeight: any) {
_.forEach(tree, (node) => {
pWeightResult[node.id] = pWeight
if (node.children && node.children.length > 0) {
weightCheckCircle(node.children, pWeightResult, node.weight)
@@ -24,7 +24,7 @@ export default function treeSort(tree: SQTreeNode[], sortType: string) {
export function sortCircle(tree: SQTreeNode[], sortType: string) {
sortPer(tree, sortType)
_.forEach(tree, node => {
_.forEach(tree, (node) => {
if (node.children && node.children.length > 0) {
sortCircle(node.children, sortType)
}
+13 -17
View File
@@ -1,11 +1,10 @@
<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";
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
@@ -22,20 +21,17 @@ defineProps<{
// return 'needs-verification'
// }
// }
</script>
<template>
<div
class="connection-card"
>
<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'"/>
<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">
@@ -144,4 +140,4 @@ defineProps<{
background-color: #fef7e0;
color: #fbbc05;
}
</style>
</style>
+9 -4
View File
@@ -10,15 +10,20 @@
<div class="left-side">
<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" />
<el-button style="padding: 12px" text :icon="CreditCard" @click="editTables(ds)" />
</div>
<el-input
style="margin: 16px 0"
v-model="searchValue"
style="margin: 16px 0"
:placeholder="t('ds.Search Datasource')"
/>
<div>
<div v-for="item in tableList" class="list-item_primary" @click="clickTable(item)">
<div
v-for="(item, _index) in tableList"
:key="_index"
class="list-item_primary"
@click="clickTable(item)"
>
{{ item.table_name }}
</div>
</div>
@@ -139,7 +144,7 @@ const props = defineProps({
const { t } = useI18n()
const dsId = ref<Number>(0)
const dsId = ref<number>(0)
const searchValue = ref('')
const tableList = ref<any>([])
const currentTable = ref<any>({})
+9 -9
View File
@@ -5,8 +5,8 @@
width="600"
:destroy-on-close="true"
:close-on-click-modal="false"
@closed="close"
modal-class="add-datasource_dialog"
@closed="close"
>
<template #header>
<div style="display: flex">
@@ -25,7 +25,7 @@
</template>
<div v-show="active === 0" class="container">
<el-form :model="form" label-position="top" label-width="auto" ref="dsFormRef" :rules="rules">
<el-form ref="dsFormRef" :model="form" label-position="top" label-width="auto" :rules="rules">
<el-form-item :label="t('ds.form.name')" prop="name">
<el-input v-model="form.name" />
</el-form-item>
@@ -76,8 +76,8 @@
<el-input v-model="form.database" />
</el-form-item>
<el-form-item
:label="t('ds.form.connect_mode')"
v-if="form.type === 'oracle'"
:label="t('ds.form.connect_mode')"
prop="mode"
>
<el-radio-group v-model="form.mode">
@@ -89,12 +89,12 @@
<el-input v-model="form.extraJdbc" />
</el-form-item>
<el-form-item
:label="t('ds.form.schema')"
v-if="haveSchema.includes(form.type)"
:label="t('ds.form.schema')"
prop="dbSchema"
>
<el-input v-model="form.dbSchema" />
<el-button link type="primary" :icon="Plus" v-if="false">Get Schema</el-button>
<el-button v-if="false" link type="primary" :icon="Plus">Get Schema</el-button>
</el-form-item>
<span>
<span>{{ t('ds.form.support_version') }}:&nbsp;</span>
@@ -106,15 +106,15 @@
</div>
</el-form>
</div>
<div v-show="active === 1" class="container" v-loading="tableListLoading">
<div v-show="active === 1" v-loading="tableListLoading" class="container">
<el-checkbox-group v-model="checkList" style="position: relative">
<FixedSizeList
:itemSize="40"
:item-size="40"
:data="tableList"
:total="tableList.length"
:width="560"
:height="400"
:scrollbarAlwaysOn="true"
:scrollbar-always-on="true"
class-name="ed-select-dropdown__list"
layout="vertical"
>
@@ -141,8 +141,8 @@
{{ t('ds.preview') }}
</el-button>
<el-button
:loading="saveLoading"
v-show="active === 1 || !isCreate"
:loading="saveLoading"
type="primary"
@click="save(dsFormRef)"
>
+3 -3
View File
@@ -31,20 +31,20 @@
<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 :icon="List" @click="getTables(ds.id, ds.name)" />
<el-button
type="primary"
class="action-btn"
circle
@click="editDs(ds)"
:icon="IconOpeEdit"
@click="editDs(ds)"
/>
<el-button
type="danger"
class="action-btn"
circle
@click="deleteDs(ds)"
:icon="IconOpeDelete"
@click="deleteDs(ds)"
/>
</div>
</DatasourceItemCard>
+5 -5
View File
@@ -2,17 +2,17 @@ import CryptoJS from 'crypto-js'
const key = CryptoJS.enc.Utf8.parse('SQLBot1234567890')
export const encrypted = (str:string) => {
export const encrypted = (str: string) => {
return CryptoJS.AES.encrypt(str, key, {
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.Pkcs7
padding: CryptoJS.pad.Pkcs7,
}).toString()
}
export const decrypted = (str:string) => {
export const decrypted = (str: string) => {
const bytes = CryptoJS.AES.decrypt(str, key, {
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.Pkcs7
padding: CryptoJS.pad.Pkcs7,
})
return bytes.toString(CryptoJS.enc.Utf8)
}
}
+6 -10
View File
@@ -1,13 +1,9 @@
export const dsType = [
{label:"MySQL", value:"mysql"},
{label:"PostgreSQL", value:"pg"},
{label:"Microsoft SQL Server", value:"sqlServer"},
{label:"Oracle", value:"oracle"},
{label:"Excel/CSV", value:"excel"},
{ label: 'MySQL', value: 'mysql' },
{ label: 'PostgreSQL', value: 'pg' },
{ label: 'Microsoft SQL Server', value: 'sqlServer' },
{ label: 'Oracle', value: 'oracle' },
{ label: 'Excel/CSV', value: 'excel' },
]
export const haveSchema = [
'sqlServer',
'pg',
'oracle'
]
export const haveSchema = ['sqlServer', 'pg', 'oracle']
+14 -16
View File
@@ -2,32 +2,31 @@
<div class="login-container">
<div class="login-content">
<div class="login-left">
<div class="illustration">
</div>
<div class="illustration"></div>
</div>
<div class="login-right">
<div class="login-form">
<h2 class="title">Login</h2>
<el-form :model="loginForm" :rules="rules" ref="loginFormRef" @keyup.enter="submitForm">
<el-form ref="loginFormRef" :model="loginForm" :rules="rules" @keyup.enter="submitForm">
<el-form-item prop="username">
<el-input
v-model="loginForm.username"
placeholder="Account"
<el-input
v-model="loginForm.username"
placeholder="Account"
:prefix-icon="User"
size="large"
></el-input>
</el-form-item>
<el-form-item prop="password">
<el-input
v-model="loginForm.password"
placeholder="Password"
type="password"
<el-input
v-model="loginForm.password"
placeholder="Password"
type="password"
:prefix-icon="Lock"
size="large"
></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="submitForm" class="login-btn">Login</el-button>
<el-button type="primary" class="login-btn" @click="submitForm">Login</el-button>
</el-form-item>
</el-form>
</div>
@@ -47,12 +46,12 @@ const userStore = useUserStore()
const loginForm = ref({
username: '',
password: ''
password: '',
})
const rules = {
username: [{ required: true, message: 'Please input account', trigger: 'blur' }],
password: [{ required: true, message: 'Please input password', trigger: 'blur' }]
password: [{ required: true, message: 'Please input password', trigger: 'blur' }],
}
const loginFormRef = ref()
@@ -72,7 +71,7 @@ const submitForm = () => {
.login-container {
height: 100vh;
width: 100vw;
background-color: #F0F6F7;
background-color: #f0f6f7;
display: flex;
align-items: center;
justify-content: center;
@@ -94,7 +93,6 @@ const submitForm = () => {
flex-direction: column;
align-items: center;
.illustration {
flex: 1;
width: 100%;
@@ -143,4 +141,4 @@ const submitForm = () => {
:deep(.ed-input__wrapper) {
background-color: #f5f7fa;
}
</style>
</style>
+33 -33
View File
@@ -32,7 +32,7 @@
<div class="sqlbot-table">
<el-table :data="state.tableData" style="width: 100%">
<el-table-column prop="term" label="Term" width="280" />
<el-table-column prop="definition" label="Definition" />
<el-table-column prop="definition" label="Definition" />
<el-table-column prop="domain" label="Domain" width="180" />
<el-table-column prop="id" label="Actions" width="120" fixed="right">
<template #default="scope">
@@ -50,19 +50,27 @@
</div>
</div>
<el-dialog v-model="dialogFormVisible" title="Add Terminology" destroy-on-close width="500" @close="onFormClose" class="sqlbot-dialog">
<el-form :model="state.form"
<el-dialog
v-model="dialogFormVisible"
title="Add Terminology"
destroy-on-close
width="500"
class="sqlbot-dialog"
@close="onFormClose"
>
<el-form
ref="termFormRef"
:model="state.form"
label-width="180px"
label-position="top"
label-position="top"
class="sqlbot-form"
size="large"
ref="termFormRef"
>
<el-form-item label="Term name" >
<el-form-item label="Term name">
<el-input v-model="state.form.term" autocomplete="off" />
</el-form-item>
<el-form-item label="Definition" >
<el-input type="textarea" v-model="state.form.definition" />
<el-form-item label="Definition">
<el-input v-model="state.form.definition" type="textarea" />
</el-form-item>
<el-form-item label="Domain">
<el-select v-model="state.form.domain" placeholder="Please select a domain">
@@ -75,38 +83,35 @@
<template #footer>
<div class="dialog-footer">
<el-button @click="closeForm">Cancel</el-button>
<el-button type="primary" @click="saveHandler">
save
</el-button>
<el-button type="primary" @click="saveHandler"> save </el-button>
</div>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted } from 'vue';
import IconOpeUpload from '@/assets/svg/operate/ope-upload.svg';
import IconOpeDownload from '@/assets/svg/operate/ope-download.svg';
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 { ref, reactive, onMounted } from 'vue'
import IconOpeUpload from '@/assets/svg/operate/ope-upload.svg'
import IconOpeDownload from '@/assets/svg/operate/ope-download.svg'
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 } from '@element-plus/icons-vue'
import { termApi } from '@/api/setting'
const keyword = ref('')
const dialogFormVisible = ref(false)
const termFormRef = ref()
const state = reactive({
tableData: [
],
tableData: [],
form: {
id: '',
term: '',
definition: '',
domain: ''
domain: '',
},
})
const handleSearch = (e: any) => {
console.log('search', e);
console.log('search', e)
}
const editHandler = (id: any) => {
termApi.query(id).then((res: any) => {
@@ -116,15 +121,11 @@ const editHandler = (id: any) => {
})
}
const deleteHandler = (id: any) => {
ElMessageBox.confirm(
'Are you sure to delete?',
'Warning',
{
confirmButtonText: 'OK',
cancelButtonText: 'Cancel',
type: 'warning',
}
)
ElMessageBox.confirm('Are you sure to delete?', 'Warning', {
confirmButtonText: 'OK',
cancelButtonText: 'Cancel',
type: 'warning',
})
.then(() => {
termApi.delete(id).then(() => {
ElMessage({
@@ -133,7 +134,6 @@ const deleteHandler = (id: any) => {
})
search()
})
})
.catch(() => {
ElMessage({
@@ -147,7 +147,7 @@ const openDialog = () => {
dialogFormVisible.value = true
}
const resetForm = () => {
if (!termFormRef) return
if (!termFormRef.value) return
Object.keys(state.form).forEach((key) => {
state.form[key as keyof typeof state.form] = ''
})
@@ -217,7 +217,7 @@ onMounted(() => {
--el-input-inner-height: 40px !important;
--el-input-border-radius: 24px;
--el-input-bg-color: #f5f7fa;
--el-input-border-color: #e5e8ed;
--el-input-border-color: #e5e8ed;
}
}
.tool-right {
+8 -8
View File
@@ -24,15 +24,15 @@
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import IconProfessional from '@/assets/svg/professional.svg';
import IconTrain from '@/assets/svg/train.svg';
import Professional from './Professional.vue';
const activeName = ref('0');
import { ref } from 'vue'
import IconProfessional from '@/assets/svg/professional.svg'
import IconTrain from '@/assets/svg/train.svg'
import Professional from './Professional.vue'
const activeName = ref('0')
const handleClick = (tab: any, event: MouseEvent) => {
console.log(tab, event);
};
console.log(tab, event)
}
</script>
<style lang="less" scoped>
@@ -72,4 +72,4 @@ const handleClick = (tab: any, event: MouseEvent) => {
}
}
}
</style>
</style>
+69 -63
View File
@@ -29,26 +29,31 @@
</div>
</div>
<div class="sqlbot-table">
<el-table :data="state.tableData" style="width: 100%" row-key="id" @selection-change="handleSelectionChange">
<el-table
:data="state.tableData"
style="width: 100%"
row-key="id"
@selection-change="handleSelectionChange"
>
<el-table-column type="selection" width="55" />
<el-table-column prop="name" label="Model Name" width="280" />
<el-table-column prop="type" label="Model Type" width="280">
<el-table-column prop="type" label="Model Type" width="280">
<template #default="scope">
<span>{{ getModelTypeName(scope.row.type) }}</span>
</template>
</el-table-column>
<el-table-column prop="api_key" label="API KEY" width="280">
<template #default="scope">
<div class="user-status-container" :class="[ scope.row.api_key ? 'active' : 'disabled' ]">
<div class="user-status-container" :class="[scope.row.api_key ? 'active' : 'disabled']">
<span>{{ scope.row.api_key ? 'Configured' : 'Not Configured' }}</span>
</div>
</template>
</el-table-column>
<el-table-column prop="status" label="Status" width="180">
<template #default="scope">
<div class="user-status-container" :class="[ scope.row.status ? 'active' : 'disabled' ]">
<div class="user-status-container" :class="[scope.row.status ? 'active' : 'disabled']">
<el-icon>
<SuccessFilled v-if="scope.row.status"/>
<SuccessFilled v-if="scope.row.status" />
<CircleCloseFilled v-else />
</el-icon>
<span>{{ scope.row.status ? 'Enabled' : 'Disabled' }}</span>
@@ -87,40 +92,57 @@
@current-change="handleCurrentChange"
/>
</div>
</div>
</div>
<el-dialog v-model="dialogFormVisible" title="Add Ai Model" destroy-on-close width="500" @close="onFormClose" class="sqlbot-dialog">
<el-form :model="state.form"
<el-dialog
v-model="dialogFormVisible"
title="Add Ai Model"
destroy-on-close
width="500"
class="sqlbot-dialog"
@close="onFormClose"
>
<el-form
ref="termFormRef"
:model="state.form"
label-width="180px"
label-position="top"
label-position="top"
class="sqlbot-form"
size="large"
ref="termFormRef"
>
<el-form-item label="Model Name" >
<el-form-item label="Model Name">
<el-input v-model="state.form.name" autocomplete="off" />
</el-form-item>
<el-form-item label="Model Type">
<el-select v-model="state.form.type" placeholder="Please select a type">
<el-option v-for="item in modelTypeOptions" :key="item.value" :label="item.label" :value="item.value" />
<el-option
v-for="item in modelTypeOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
<!-- <el-option label="domain1" value="0" />
<el-option label="domain2" value="1" />
<el-option label="domain3" value="2" /> -->
</el-select>
</el-form-item>
<el-form-item label="API KEY" >
<el-input type="password" v-model="state.form.api_key" show-password placeholder="Please input password" />
<el-form-item label="API KEY">
<el-input
v-model="state.form.api_key"
type="password"
show-password
placeholder="Please input password"
/>
</el-form-item>
<el-form-item label="API Endpoint URL" >
<el-form-item label="API Endpoint URL">
<el-input v-model="state.form.endpoint" placeholder="Please input endpoint" />
</el-form-item>
<!-- <el-form-item label="Maximum Context Window" >
<!-- <el-form-item label="Maximum Context Window" >
<el-input v-model="state.form.max_context_window" type="number" placeholder="Please input max_context_window" />
</el-form-item>
@@ -128,34 +150,32 @@
<el-slider v-model="state.form.temperature" :format-tooltip="formatTooltip" />
</el-form-item>-->
<el-form-item label="Model Status" >
<el-form-item label="Model Status">
<el-checkbox v-model="state.form.status" label="" size="large" />
</el-form-item>
<el-form-item label="Description" >
<el-input type="textarea" v-model="state.form.description" />
<el-form-item label="Description">
<el-input v-model="state.form.description" type="textarea" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="closeForm">Cancel</el-button>
<el-button type="primary" @click="saveHandler">
save
</el-button>
<el-button type="primary" @click="saveHandler"> save </el-button>
</div>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted } from 'vue';
import IconEnable from '@/assets/svg/operate/ope-enable.svg';
import IconDisable from '@/assets/svg/operate/ope-disable.svg';
import IconOpeAdd from '@/assets/svg/operate/ope-add.svg';
import { ref, reactive, onMounted } from 'vue'
import IconEnable from '@/assets/svg/operate/ope-enable.svg'
import IconDisable from '@/assets/svg/operate/ope-disable.svg'
import IconOpeAdd from '@/assets/svg/operate/ope-add.svg'
import { SuccessFilled, CircleCloseFilled, Search as IconSearch } from '@element-plus/icons-vue'
import IconOpeEdit from '@/assets/svg/operate/ope-edit.svg';
import IconOpeDelete from '@/assets/svg/operate/ope-delete.svg';
import IconOpeEdit from '@/assets/svg/operate/ope-edit.svg'
import IconOpeDelete from '@/assets/svg/operate/ope-delete.svg'
import { modelApi } from '@/api/system'
import { formatTimestamp } from '@/utils/date'
import { modelTypeOptions, getModelTypeName } from '@/entity/CommonEntity.ts'
@@ -163,8 +183,7 @@ const keyword = ref('')
const dialogFormVisible = ref(false)
const termFormRef = ref()
const state = reactive({
tableData: [
],
tableData: [],
form: {
id: '',
name: '',
@@ -174,20 +193,20 @@ const state = reactive({
max_context_window: 0,
temperature: 0,
status: false,
description: ''
description: '',
},
pageInfo: {
currentPage: 1,
pageSize: 20,
total: 0
total: 0,
},
selectedIds: []
selectedIds: [],
})
onMounted(() => {
search()
})
const handleSearch = (e: any) => {
console.log('search', e);
console.log('search', e)
}
const editHandler = (id: any) => {
modelApi.query(id).then((res: any) => {
@@ -197,15 +216,11 @@ const editHandler = (id: any) => {
})
}
const deleteHandler = (id: any) => {
ElMessageBox.confirm(
'Are you sure to delete?',
'Warning',
{
confirmButtonText: 'OK',
cancelButtonText: 'Cancel',
type: 'warning',
}
)
ElMessageBox.confirm('Are you sure to delete?', 'Warning', {
confirmButtonText: 'OK',
cancelButtonText: 'Cancel',
type: 'warning',
})
.then(() => {
modelApi.delete(id).then(() => {
ElMessage({
@@ -214,7 +229,6 @@ const deleteHandler = (id: any) => {
})
search()
})
})
.catch(() => {
ElMessage({
@@ -228,7 +242,7 @@ const openDialog = () => {
dialogFormVisible.value = true
}
const resetForm = () => {
if (!termFormRef) return
if (!termFormRef.value) return
state.form = {
id: '',
name: '',
@@ -238,7 +252,7 @@ const resetForm = () => {
max_context_window: 0,
temperature: 0,
status: false,
description: ''
description: '',
}
}
@@ -314,18 +328,14 @@ const batchDisableHandler = () => {
}
const param = {
ids: state.selectedIds,
status: false
status: false,
}
ElMessageBox.confirm(
'Are you sure to execute this operation?',
'Warning',
{
confirmButtonText: 'OK',
cancelButtonText: 'Cancel',
type: 'warning',
}
)
ElMessageBox.confirm('Are you sure to execute this operation?', 'Warning', {
confirmButtonText: 'OK',
cancelButtonText: 'Cancel',
type: 'warning',
})
.then(() => {
modelApi.status(param).then(() => {
search()
@@ -341,9 +351,6 @@ const batchDisableHandler = () => {
message: 'Execute canceled',
})
})
}
const batchEnableHandler = () => {
if (state.selectedIds.length === 0) {
@@ -355,7 +362,7 @@ const batchEnableHandler = () => {
}
const param = {
ids: state.selectedIds,
status: true
status: true,
}
modelApi.status(param).then(() => {
search()
@@ -365,7 +372,6 @@ const batchEnableHandler = () => {
})
})
}
</script>
<style lang="less" scoped>
@@ -390,7 +396,7 @@ const batchEnableHandler = () => {
--el-input-inner-height: 40px !important;
--el-input-border-radius: 24px;
--el-input-bg-color: #f5f7fa;
--el-input-border-color: #e5e8ed;
--el-input-border-color: #e5e8ed;
}
}
.tool-right {
+34 -34
View File
@@ -32,13 +32,13 @@
<div class="sqlbot-table">
<el-table :data="state.tableData" style="width: 100%">
<el-table-column prop="account" label="Account" width="280" />
<el-table-column prop="name" label="Name" width="280" />
<el-table-column prop="name" label="Name" width="280" />
<el-table-column prop="email" label="Email" width="280" />
<el-table-column prop="status" label="Status" width="180">
<template #default="scope">
<div class="user-status-container" :class="[ scope.row.status ? 'active' : 'disabled' ]">
<div class="user-status-container" :class="[scope.row.status ? 'active' : 'disabled']">
<el-icon>
<SuccessFilled v-if="scope.row.status"/>
<SuccessFilled v-if="scope.row.status" />
<CircleCloseFilled v-else />
</el-icon>
<span>{{ scope.row.status ? 'Activated' : 'Disabled' }}</span>
@@ -79,19 +79,27 @@
</div>
</div>
<el-dialog v-model="dialogFormVisible" title="Add Terminology" destroy-on-close width="500" @close="onFormClose" class="sqlbot-dialog">
<el-form :model="state.form"
<el-dialog
v-model="dialogFormVisible"
title="Add Terminology"
destroy-on-close
width="500"
class="sqlbot-dialog"
@close="onFormClose"
>
<el-form
ref="termFormRef"
:model="state.form"
label-width="180px"
label-position="top"
label-position="top"
class="sqlbot-form"
size="large"
ref="termFormRef"
>
<el-form-item label="Account" >
<el-form-item label="Account">
<el-input v-model="state.form.term" autocomplete="off" />
</el-form-item>
<el-form-item label="Definition" >
<el-input type="textarea" v-model="state.form.definition" />
<el-form-item label="Definition">
<el-input v-model="state.form.definition" type="textarea" />
</el-form-item>
<el-form-item label="Domain">
<el-select v-model="state.form.domain" placeholder="Please select a domain">
@@ -104,23 +112,21 @@
<template #footer>
<div class="dialog-footer">
<el-button @click="closeForm">Cancel</el-button>
<el-button type="primary" @click="saveHandler">
save
</el-button>
<el-button type="primary" @click="saveHandler"> save </el-button>
</div>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted } from 'vue';
import { ref, reactive, onMounted } from 'vue'
/* import IconOpeUpload from '@/assets/svg/operate/ope-upload.svg';
import IconOpeDownload from '@/assets/svg/operate/ope-download.svg';
import IconOpeAdd from '@/assets/svg/operate/ope-add.svg'; */
import { SuccessFilled, CircleCloseFilled } from '@element-plus/icons-vue'
import IconOpeEdit from '@/assets/svg/operate/ope-edit.svg';
import IconOpeDelete from '@/assets/svg/operate/ope-delete.svg';
import IconOpeEdit from '@/assets/svg/operate/ope-edit.svg'
import IconOpeDelete from '@/assets/svg/operate/ope-delete.svg'
import { Search } from '@element-plus/icons-vue'
import { userApi } from '@/api/auth'
import { formatTimestamp } from '@/utils/date'
@@ -129,22 +135,21 @@ const keyword = ref('')
const dialogFormVisible = ref(false)
const termFormRef = ref()
const state = reactive({
tableData: [
],
tableData: [],
form: {
id: '',
term: '',
definition: '',
domain: ''
domain: '',
},
pageInfo: {
currentPage: 1,
pageSize: 20,
total: 0
}
total: 0,
},
})
const handleSearch = (e: any) => {
console.log('search', e);
console.log('search', e)
}
const editHandler = (id: any) => {
console.log('editHandler', id)
@@ -156,15 +161,11 @@ const editHandler = (id: any) => {
}
const deleteHandler = (id: any) => {
console.log('deleteHandler', id)
ElMessageBox.confirm(
'Are you sure to delete?',
'Warning',
{
confirmButtonText: 'OK',
cancelButtonText: 'Cancel',
type: 'warning',
}
)
ElMessageBox.confirm('Are you sure to delete?', 'Warning', {
confirmButtonText: 'OK',
cancelButtonText: 'Cancel',
type: 'warning',
})
.then(() => {
/* userApi.delete(id).then(() => {
ElMessage({
@@ -173,7 +174,6 @@ const deleteHandler = (id: any) => {
})
search()
}) */
})
.catch(() => {
ElMessage({
@@ -187,7 +187,7 @@ const deleteHandler = (id: any) => {
dialogFormVisible.value = true
} */
const resetForm = () => {
if (!termFormRef) return
if (!termFormRef.value) return
Object.keys(state.form).forEach((key) => {
state.form[key as keyof typeof state.form] = ''
})
@@ -270,7 +270,7 @@ onMounted(() => {
--el-input-inner-height: 40px !important;
--el-input-border-radius: 24px;
--el-input-bg-color: #f5f7fa;
--el-input-border-color: #e5e8ed;
--el-input-border-color: #e5e8ed;
}
}
.tool-right {
+8 -8
View File
@@ -1,17 +1,17 @@
<script lang="ts" setup>
import { Document } from "@element-plus/icons-vue";
import { Document } from '@element-plus/icons-vue'
withDefaults(
defineProps<{
name: string;
description: string;
creator: string;
name: string
description: string
creator: string
}>(),
{
name: "",
description: "",
creator: "",
name: '',
description: '',
creator: '',
}
);
)
</script>
<template>
+20 -26
View File
@@ -1,49 +1,49 @@
<script lang="ts" setup>
import { ref, computed } from "vue";
import DatasourceCard from "./DatasourceCard.vue";
import { ref, computed } from 'vue'
import DatasourceCard from './DatasourceCard.vue'
interface Datasource {
id?: string;
name: string;
description: string;
creator: string;
id?: string
name: string
description: string
creator: string
}
const props = withDefaults(
defineProps<{
datasourceList: Array<Datasource>;
datasourceList: Array<Datasource>
}>(),
{
datasourceList: () => [],
}
);
)
const datasourceName = ref("");
const datasourceName = ref('')
const datasourceListComputed = computed(() =>
props.datasourceList.filter((val) =>
val.name.toLowerCase().includes(datasourceName.value.toLowerCase())
)
);
)
const dialogVisible = ref(false);
const dialogVisible = ref(false)
</script>
<template>
<div class="chat-init_tip">
<div class="hello-sqlbot">Hello, I'm SQLBot, happy to serve you!</div>
<div class="function-sqlbot">
I can help you query data, generate charts, detect data anomalies, predict
data, etc. Please select a data source and start intelligent data query~
I can help you query data, generate charts, detect data anomalies, predict data, etc. Please
select a data source and start intelligent data query~
</div>
<div class="select-datasource">
<span class="title">Select data source</span>
<el-button @click="dialogVisible = true" text>View more</el-button>
<el-button text @click="dialogVisible = true">View more</el-button>
</div>
<div class="datasource-content">
<DatasourceCard
v-for="ele in datasourceList"
:key="ele.id"
:name="ele.name"
:description="ele.description"
:creator="ele.creator"
v-for="ele in datasourceList"
:key="ele.id"
></DatasourceCard>
</div>
<el-button type="primary">Create a new data source</el-button>
@@ -55,27 +55,21 @@ const dialogVisible = ref(false);
modal-class="select-datasource_dialog"
>
<div class="search-datasource">
<el-input
v-model="datasourceName"
style="width: 240px"
placeholder="Please input"
/>
<el-input v-model="datasourceName" style="width: 240px" placeholder="Please input" />
</div>
<div class="datasource-content">
<DatasourceCard
v-for="ele in datasourceListComputed"
:key="ele.id"
:name="ele.name"
:description="ele.description"
:creator="ele.creator"
v-for="ele in datasourceListComputed"
:key="ele.id"
></DatasourceCard>
</div>
<template #footer>
<div class="dialog-footer">
<el-button @click="dialogVisible = false">Cancel</el-button>
<el-button type="primary" @click="dialogVisible = false">
Confirm
</el-button>
<el-button type="primary" @click="dialogVisible = false"> Confirm </el-button>
</div>
</template>
</el-dialog>
+4 -4
View File
@@ -19,12 +19,12 @@ export default defineConfig(({ mode }) => {
Components({
resolvers: [ElementPlusResolver()],
}),
svgLoader()
svgLoader(),
],
resolve: {
alias: {
'@': path.resolve(__dirname, './src')
}
'@': path.resolve(__dirname, './src'),
},
},
css: {
preprocessorOptions: {
@@ -46,6 +46,6 @@ export default defineConfig(({ mode }) => {
esbuild: {
jsxFactory: 'h',
jsxFragment: 'Fragment',
}
},
}
})