mirror of
https://gh-proxy.org/https://github.com/klinsmaya/SQLBot.git
synced 2026-11-10 10:16:55 +08:00
Merge remote-tracking branch 'origin/main'
This commit is contained in:
@@ -180,6 +180,7 @@ export class ChatInfo extends Chat {
|
||||
chat_type: string,
|
||||
datasource: number,
|
||||
engine_type: string,
|
||||
ds_type: string,
|
||||
datasource_name: string,
|
||||
datasource_exists: boolean,
|
||||
records: Array<ChatRecord>
|
||||
@@ -192,6 +193,7 @@ export class ChatInfo extends Chat {
|
||||
chat_type?: string,
|
||||
datasource?: number,
|
||||
engine_type?: string,
|
||||
ds_type?: string,
|
||||
datasource_name?: string,
|
||||
datasource_exists: boolean = true,
|
||||
records: Array<ChatRecord> = []
|
||||
@@ -206,6 +208,7 @@ export class ChatInfo extends Chat {
|
||||
this.chat_type = param1.chat_type
|
||||
this.datasource = param1.datasource
|
||||
this.engine_type = param1.engine_type
|
||||
this.ds_type = param1.ds_type
|
||||
} else {
|
||||
this.id = param1
|
||||
this.create_time = getDate(create_time)
|
||||
@@ -214,6 +217,7 @@ export class ChatInfo extends Chat {
|
||||
this.chat_type = chat_type
|
||||
this.datasource = datasource
|
||||
this.engine_type = engine_type
|
||||
this.ds_type = ds_type
|
||||
}
|
||||
}
|
||||
this.datasource_name = datasource_name
|
||||
@@ -275,6 +279,7 @@ export const chatApi = {
|
||||
data.chat_type,
|
||||
data.datasource,
|
||||
data.engine_type,
|
||||
data.ds_type,
|
||||
data.datasource_name,
|
||||
data.datasource_exists,
|
||||
toChatRecordList(data.records)
|
||||
|
||||
@@ -5,3 +5,4 @@ export const updateAssistant = (data: any) => request.put('/system/assistant', d
|
||||
export const saveAssistant = (data: any) => request.post('/system/assistant', data)
|
||||
export const getOne = (id: any) => request.get(`/system/assistant/${id}`)
|
||||
export const delOne = (id: any) => request.delete(`/system/assistant/${id}`)
|
||||
export const dsApi = (id: any) => request.get(`/datasource/ws/${id}`)
|
||||
|
||||
@@ -8,4 +8,6 @@ export const modelApi = {
|
||||
delete: (id: number) => request.delete(`/system/aimodel/${id}`),
|
||||
query: (id: number) => request.get(`/system/aimodel/${id}`),
|
||||
setDefault: (id: number) => request.put(`/system/aimodel/default/${id}`),
|
||||
check: (data: any) =>
|
||||
request.post('/system/aimodel/status', data, { requestOptions: { silent: true } }),
|
||||
}
|
||||
|
||||
@@ -25,4 +25,5 @@ export const userApi = {
|
||||
deleteBatch: (data: any) => request.delete(`/user`, { data }),
|
||||
get: (key: string) => request.get(`/user/${key}`),
|
||||
pwd: (id: any) => request.patch(`/user/pwd/${id}`),
|
||||
status: (data: any) => request.patch('/user/status', data),
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M3.33333 5.3335V13.3335H10V5.3335H3.33333ZM11.3333 4.66683V14.0742C11.3333 14.4015 11.0548 14.6668 10.7111 14.6668H2.62222C2.27858 14.6668 2 14.4015 2 14.0742V4.59276C2 4.26548 2.27858 4.00016 2.62222 4.00016H10.6667C11.0349 4.00016 11.3333 4.29864 11.3333 4.66683ZM13.8047 1.52876C13.9254 1.6494 14 1.81607 14 2.00016V10.3335C14 10.5176 13.8508 10.6668 13.6667 10.6668H13C12.8159 10.6668 12.6667 10.5176 12.6667 10.3335V2.66683H6.33333C6.14924 2.66683 6 2.51759 6 2.3335V1.66683C6 1.48273 6.14924 1.3335 6.33333 1.3335H13.3333C13.5174 1.3335 13.6841 1.40812 13.8047 1.52876Z" fill="#1F2329"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 706 B |
@@ -0,0 +1,5 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12 23C18.0751 23 23 18.0751 23 12C23 5.92487 18.0751 1 12 1C5.92487 1 1 5.92487 1 12C1 18.0751 5.92487 23 12 23Z"/>
|
||||
<path d="M11.5 7C11.2239 7 11 7.22386 11 7.5V13.5C11 13.7761 11.2239 14 11.5 14H12.5C12.7761 14 13 13.7761 13 13.5V7.5C13 7.22386 12.7761 7 12.5 7H11.5Z" fill="white"/>
|
||||
<path d="M11.5 15C11.2239 15 11 15.2239 11 15.5V16.5C11 16.7761 11.2239 17 11.5 17H12.5C12.7761 17 13 16.7761 13 16.5V15.5C13 15.2239 12.7761 15 12.5 15H11.5Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 561 B |
@@ -73,9 +73,9 @@ const logout = () => {
|
||||
<template>
|
||||
<el-popover trigger="click" popper-class="system-person" :placement="collapse ? 'right' : 'top'">
|
||||
<template #reference>
|
||||
<button class="person" :class="collapse && 'collapse'">
|
||||
<button class="person" :title="name" :class="collapse && 'collapse'">
|
||||
<img class="default-avatar" :src="Default_avatar" width="32px" height="32px" />
|
||||
<span v-if="!collapse" class="name ellipsis" :title="name">{{ name }}</span>
|
||||
<span v-if="!collapse" class="name ellipsis">{{ name }}</span>
|
||||
</button></template
|
||||
>
|
||||
<div class="popover">
|
||||
|
||||
@@ -10,6 +10,30 @@ const pwdForm = reactive({
|
||||
confirm_pwd: '',
|
||||
})
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
const validatePass = (rule: any, value: any, callback: any) => {
|
||||
if (value === '') {
|
||||
callback(new Error(t('common.please_input', { msg: t('user.upgrade_pwd.new_pwd') })))
|
||||
} else {
|
||||
if (pwdForm.confirm_pwd !== '') {
|
||||
if (!pwdRef.value) return
|
||||
pwdRef.value.validateField('confirm_pwd')
|
||||
}
|
||||
callback()
|
||||
}
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
const validatePass2 = (rule: any, value: any, callback: any) => {
|
||||
if (value === '') {
|
||||
callback(new Error(t('common.please_input', { msg: t('user.upgrade_pwd.confirm_pwd') })))
|
||||
} else if (value !== pwdForm.new_pwd) {
|
||||
callback(new Error(t('user.upgrade_pwd.two_pwd_not_match')))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
const rules = {
|
||||
pwd: [
|
||||
{
|
||||
@@ -18,27 +42,8 @@ const rules = {
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
new_pwd: [
|
||||
{
|
||||
required: true,
|
||||
message: t('common.please_input', { msg: t('user.upgrade_pwd.new_pwd') }),
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
confirm_pwd: [
|
||||
{
|
||||
required: true,
|
||||
message: t('common.please_input', { msg: t('user.upgrade_pwd.confirm_pwd') }),
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const initForm = (item: any) => {
|
||||
if (item) {
|
||||
Object.assign(pwdForm, { ...item })
|
||||
}
|
||||
pwdRef.value.clearValidate()
|
||||
new_pwd: [{ validator: validatePass, trigger: 'blur' }],
|
||||
confirm_pwd: [{ validator: validatePass2, trigger: 'blur' }],
|
||||
}
|
||||
|
||||
const emits = defineEmits(['pwdSaved'])
|
||||
@@ -61,7 +66,6 @@ const submit = () => {
|
||||
})
|
||||
}
|
||||
defineExpose({
|
||||
initForm,
|
||||
submit,
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -103,25 +103,28 @@ onMounted(async () => {
|
||||
</template>
|
||||
</el-input>
|
||||
<div class="popover-content">
|
||||
<div
|
||||
v-for="ele in defaultWorkspaceListWithSearch"
|
||||
:key="ele.name"
|
||||
class="popover-item"
|
||||
:class="currentWorkspace.id === ele.id && 'isActive'"
|
||||
@click="handleDefaultWorkspaceChange(ele)"
|
||||
>
|
||||
<el-icon size="16">
|
||||
<icon_moments_categories_outlined></icon_moments_categories_outlined>
|
||||
</el-icon>
|
||||
<el-scrollbar max-height="400px">
|
||||
<div
|
||||
:title="ele.name"
|
||||
class="datasource-name ellipsis"
|
||||
v-html="formatKeywords(ele.name)"
|
||||
></div>
|
||||
<el-icon size="16" class="done">
|
||||
<icon_done_outlined></icon_done_outlined>
|
||||
</el-icon>
|
||||
</div>
|
||||
v-for="ele in defaultWorkspaceListWithSearch"
|
||||
:key="ele.name"
|
||||
class="popover-item"
|
||||
:class="currentWorkspace.id === ele.id && 'isActive'"
|
||||
@click="handleDefaultWorkspaceChange(ele)"
|
||||
>
|
||||
<el-icon size="16">
|
||||
<icon_moments_categories_outlined></icon_moments_categories_outlined>
|
||||
</el-icon>
|
||||
<div
|
||||
:title="ele.name"
|
||||
class="datasource-name ellipsis"
|
||||
v-html="formatKeywords(ele.name)"
|
||||
></div>
|
||||
<el-icon size="16" class="done">
|
||||
<icon_done_outlined></icon_done_outlined>
|
||||
</el-icon>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
|
||||
<div v-if="!defaultWorkspaceListWithSearch.length" class="popover-item empty">
|
||||
{{ $t('model.relevant_results_found') }}
|
||||
</div>
|
||||
|
||||
@@ -6,6 +6,10 @@
|
||||
"AI Model Configuration": "AI Model Configuration"
|
||||
},
|
||||
"common": {
|
||||
"limited_to_30": "The number of data tables is limited to 30",
|
||||
"enter_your_password": "Please enter your password",
|
||||
"your_account_email_address": "Please enter your account/email address",
|
||||
"the_correct_password": "Please enter the correct password",
|
||||
"input_content": "Please Input Content",
|
||||
"empty": " ",
|
||||
"may_not_exist": "No results yet, the user may not exist",
|
||||
@@ -118,7 +122,8 @@
|
||||
"earlier": "Earlier",
|
||||
"no_time": "No Time",
|
||||
"rename_conversation_title": "Rename conversation title",
|
||||
"conversation_title": "Conversation title"
|
||||
"conversation_title": "Conversation title",
|
||||
"copied": "Copied"
|
||||
},
|
||||
"ds": {
|
||||
"title": "Data Source",
|
||||
@@ -224,6 +229,9 @@
|
||||
"custom_notes": "Custom notes",
|
||||
"field_type": "Field type",
|
||||
"field_name": "Field name",
|
||||
"copy": "Copy",
|
||||
"incorrect_email_format": "Incorrect email format",
|
||||
"the_original_one": "Are you sure you want to restore the password to the original one?",
|
||||
"relevant_content_found": "No relevant content found",
|
||||
"please_enter": "Please enter",
|
||||
"Please_select": "Please select",
|
||||
@@ -458,6 +466,10 @@
|
||||
"add_interface_credentials": "Add interface credentials",
|
||||
"edit_interface_credentials": "Edit interface credentials",
|
||||
"duplicate_name": "Duplicate name",
|
||||
"duplicate_name_": "Duplicate name",
|
||||
"duplicate_account": "Duplicate account",
|
||||
"repeating_parameters": "Repeating Parameters",
|
||||
"duplicate_email": "Duplicate email",
|
||||
"interface_credentials": "Interface credentials",
|
||||
"no_credentials_yet": "No credentials yet",
|
||||
"intelligent_customer_service": "SQLBot Intelligent Customer Service"
|
||||
@@ -506,4 +518,4 @@
|
||||
"back_community": "Restore to Community Edition",
|
||||
"confirm_tips": "Are you sure to restore to Community Edition?"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,10 @@
|
||||
"AI Model Configuration": "模型配置"
|
||||
},
|
||||
"common": {
|
||||
"limited_to_30": "数据表数量限制30",
|
||||
"enter_your_password": "请输入密码",
|
||||
"your_account_email_address": "请输入账号/邮箱",
|
||||
"the_correct_password": "请输入正确的密码",
|
||||
"input_content": "请输入内容",
|
||||
"may_not_exist": "暂无结果,用户可能不存在",
|
||||
"empty": "",
|
||||
@@ -31,7 +35,8 @@
|
||||
"re_upload": "重新上传",
|
||||
"not_exceed_50mb": "支持 XLS、XLSX、CSV 格式,文件大小不超过 50MB",
|
||||
"reset_password": "重置密码",
|
||||
"password_reset_successful": "重置密码成功"
|
||||
"password_reset_successful": "重置密码成功",
|
||||
"or": "或者"
|
||||
},
|
||||
"dashboard": {
|
||||
"add_success": "添加成功",
|
||||
@@ -121,7 +126,8 @@
|
||||
"earlier": "更早以前",
|
||||
"no_time": "没有时间",
|
||||
"rename_conversation_title": "重命名对话标题",
|
||||
"conversation_title": "对话标题"
|
||||
"conversation_title": "对话标题",
|
||||
"copied": "已复制"
|
||||
},
|
||||
"ds": {
|
||||
"title": "数据源",
|
||||
@@ -223,6 +229,9 @@
|
||||
"edit": "编辑",
|
||||
"source_connection_failed": "数据源连接失败",
|
||||
"confirm": "确定",
|
||||
"copy": "复制",
|
||||
"the_original_one": "确认恢复为初始密码吗?",
|
||||
"incorrect_email_format": "邮箱格式不对",
|
||||
"enabled_status": "启用状态",
|
||||
"custom_notes": "自定义备注",
|
||||
"field_type": "字段类型",
|
||||
@@ -275,7 +284,8 @@
|
||||
"verification_successful": "校验成功",
|
||||
"del_default_tip": "无法删除模型: {msg}",
|
||||
"del_default_warn": "该模型为系统默认模型,请先设置其他模型为系统默认模型,再删除此模型。",
|
||||
"del_warn_tip": "是否删除模型: {msg}?"
|
||||
"del_warn_tip": "是否删除模型: {msg}?",
|
||||
"check_failed": "模型无效【{msg}】"
|
||||
},
|
||||
"user": {
|
||||
"workspace": "工作空间",
|
||||
@@ -332,7 +342,8 @@
|
||||
"title": "修改密码",
|
||||
"old_pwd": "旧密码",
|
||||
"new_pwd": "新密码",
|
||||
"confirm_pwd": "确认密码"
|
||||
"confirm_pwd": "确认密码",
|
||||
"two_pwd_not_match": "两次输入的密码不一致"
|
||||
}
|
||||
},
|
||||
"workspace": {
|
||||
@@ -463,6 +474,10 @@
|
||||
"add_interface_credentials": "添加接口凭证",
|
||||
"edit_interface_credentials": "编辑接口凭证",
|
||||
"duplicate_name": "重复名称",
|
||||
"duplicate_name_": "重复姓名",
|
||||
"duplicate_account": "重复账号",
|
||||
"duplicate_email": "重复邮箱",
|
||||
"repeating_parameters": "重复参数",
|
||||
"interface_credentials": "接口凭证",
|
||||
"no_credentials_yet": "暂无凭证",
|
||||
"intelligent_customer_service": "SQLBot 智能客服",
|
||||
@@ -518,4 +533,4 @@
|
||||
"back_community": "还原至社区版",
|
||||
"confirm_tips": "确定还原至社区版?"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@ import DashboardPreview from '@//views/dashboard/preview/SQPreviewSingle.vue'
|
||||
import Dashboard from '@/views/dashboard/index.vue'
|
||||
import Model from '@/views/system/model/Model.vue'
|
||||
import Embedded from '@/views/system/embedded/index.vue'
|
||||
import assistant from '@/views/embedded/assistant.vue'
|
||||
import assistant from '@/views/embedded/index.vue'
|
||||
import Member from '@/views/system/member/index.vue'
|
||||
import Permission from '@/views/system/permission/index.vue'
|
||||
import User from '@/views/system/user/User.vue'
|
||||
|
||||
@@ -19,7 +19,7 @@ export const watchRouter = (router: any) => {
|
||||
return
|
||||
}
|
||||
if (!token) {
|
||||
ElMessage.error('Please login first')
|
||||
// ElMessage.error('Please login first')
|
||||
next('/login')
|
||||
return
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ interface AssistantState {
|
||||
flag: number
|
||||
type: number
|
||||
certificate: string
|
||||
online: boolean
|
||||
}
|
||||
|
||||
export const AssistantStore = defineStore('assistant', {
|
||||
@@ -21,6 +22,7 @@ export const AssistantStore = defineStore('assistant', {
|
||||
flag: 0,
|
||||
type: 0,
|
||||
certificate: '',
|
||||
online: false,
|
||||
}
|
||||
},
|
||||
getters: {
|
||||
@@ -39,6 +41,9 @@ export const AssistantStore = defineStore('assistant', {
|
||||
getType(): number {
|
||||
return this.type
|
||||
},
|
||||
getOnline(): boolean {
|
||||
return this.online
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
setCertificate(certificate: string) {
|
||||
@@ -61,6 +66,9 @@ export const AssistantStore = defineStore('assistant', {
|
||||
wsCache.set(flagKey, flag)
|
||||
}
|
||||
},
|
||||
setOnline(online: boolean) {
|
||||
this.online = !!online
|
||||
},
|
||||
async setChat() {
|
||||
if (!this.assistant) {
|
||||
return null
|
||||
|
||||
@@ -80,6 +80,9 @@ class HttpService {
|
||||
encodeURIComponent(assistantStore.getCertificate)
|
||||
)
|
||||
}
|
||||
if (!assistantStore.getType) {
|
||||
config.headers['X-SQLBOT-ASSISTANT-ONLINE'] = assistantStore.getOnline
|
||||
}
|
||||
}
|
||||
const locale = getLocale()
|
||||
if (locale) {
|
||||
@@ -168,9 +171,21 @@ class HttpService {
|
||||
errorMessage = 'Invalid request parameters'
|
||||
break
|
||||
case 401:
|
||||
errorMessage = 'Unauthorized, please login again'
|
||||
errorMessage = error.response?.data
|
||||
? error.response.data.toString()
|
||||
: 'Unauthorized, please login again'
|
||||
// Redirect to login page if needed
|
||||
break
|
||||
ElMessage({
|
||||
message: errorMessage,
|
||||
type: 'error',
|
||||
showClose: true,
|
||||
})
|
||||
setTimeout(() => {
|
||||
wsCache.delete('user.token')
|
||||
window.location.reload()
|
||||
}, 1000)
|
||||
return
|
||||
// break
|
||||
case 403:
|
||||
errorMessage = 'Access denied'
|
||||
break
|
||||
@@ -184,10 +199,7 @@ class HttpService {
|
||||
errorMessage = `Server responded with error: ${error.response.status}`
|
||||
}
|
||||
if (error?.response?.data) {
|
||||
const msgData: any = error.response.data
|
||||
if (msgData?.msg) {
|
||||
errorMessage = msgData.msg
|
||||
}
|
||||
errorMessage = error.response.data.toString()
|
||||
}
|
||||
} else if (error.request) {
|
||||
errorMessage = 'No response from server'
|
||||
@@ -249,6 +261,9 @@ class HttpService {
|
||||
encodeURIComponent(assistantStore.getCertificate)
|
||||
)
|
||||
}
|
||||
if (!assistantStore.getType) {
|
||||
heads['X-SQLBOT-ASSISTANT-ONLINE'] = assistantStore.getOnline
|
||||
}
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
|
||||
@@ -117,14 +117,9 @@ const createNewChat = async () => {
|
||||
|
||||
async function doCreateNewChat() {
|
||||
if (isAssistant.value) {
|
||||
const assistantChat = await assistantStore.setChat()
|
||||
if (assistantChat) {
|
||||
onChatCreated(assistantChat)
|
||||
}
|
||||
return
|
||||
} else {
|
||||
chatCreatorRef.value?.showDs()
|
||||
}
|
||||
chatCreatorRef.value?.showDs()
|
||||
}
|
||||
|
||||
function onClickHistory(chat: Chat) {
|
||||
|
||||
@@ -15,6 +15,7 @@ import icon_export_outlined from '@/assets/svg/icon_export_outlined.svg'
|
||||
import icon_into_item_outlined from '@/assets/svg/icon_into-item_outlined.svg'
|
||||
import icon_window_max_outlined from '@/assets/svg/icon_window-max_outlined.svg'
|
||||
import icon_window_mini_outlined from '@/assets/svg/icon_window-mini_outlined.svg'
|
||||
import icon_copy_outlined from '@/assets/svg/icon_copy_outlined.svg'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import SQLComponent from '@/views/chat/component/SQLComponent.vue'
|
||||
|
||||
@@ -189,6 +190,13 @@ function showSql() {
|
||||
function addToDashboard() {
|
||||
console.log('todo')
|
||||
}
|
||||
|
||||
function copy() {
|
||||
if (props.message?.record?.sql) {
|
||||
navigator.clipboard.writeText(props.message.record.sql)
|
||||
ElMessage.info(t('qa.copied'))
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -209,11 +217,11 @@ function addToDashboard() {
|
||||
<div class="chart-select-container">
|
||||
<el-tooltip effect="dark" :content="t('chat.type')" placement="top">
|
||||
<ChartPopover
|
||||
:chartTypeList="chartTypeList"
|
||||
:chartType="chartType"
|
||||
:title="t('chat.type')"
|
||||
@typeChange="onTypeChange"
|
||||
v-if="chartTypeList.length > 0"
|
||||
:chart-type-list="chartTypeList"
|
||||
:chart-type="chartType"
|
||||
:title="t('chat.type')"
|
||||
@type-change="onTypeChange"
|
||||
></ChartPopover>
|
||||
</el-tooltip>
|
||||
|
||||
@@ -318,12 +326,17 @@ function addToDashboard() {
|
||||
direction="rtl"
|
||||
body-class="chart-sql-drawer-body"
|
||||
>
|
||||
<div>
|
||||
<div class="sql-block">
|
||||
<SQLComponent
|
||||
v-if="message.record?.sql"
|
||||
:sql="message.record?.sql"
|
||||
style="margin-top: 12px"
|
||||
/>
|
||||
<el-button v-if="message.record?.sql" circle class="input-icon" @click="copy">
|
||||
<el-icon size="16">
|
||||
<icon_copy_outlined />
|
||||
</el-icon>
|
||||
</el-button>
|
||||
</div>
|
||||
</el-drawer>
|
||||
</div>
|
||||
@@ -490,4 +503,18 @@ function addToDashboard() {
|
||||
margin-top: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.sql-block {
|
||||
position: relative;
|
||||
|
||||
.input-icon {
|
||||
min-width: unset;
|
||||
position: absolute;
|
||||
top: 12px;
|
||||
right: 12px;
|
||||
|
||||
border-color: #dee0e3;
|
||||
box-shadow: 0px 4px 8px 0px #1f23291a;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -17,9 +17,22 @@ const props = defineProps({
|
||||
},
|
||||
})
|
||||
const currentIcon = computed(() => {
|
||||
return props.chartTypeList.find((ele) => ele.value === props.chartType || ele.value === 'table')
|
||||
.icon
|
||||
if (props.chartType === 'table') {
|
||||
const [ele] = props.chartTypeList || []
|
||||
if (ele.icon) {
|
||||
return ele.icon
|
||||
}
|
||||
return null
|
||||
}
|
||||
return props.chartTypeList.find((ele) => ele.value === props.chartType).icon
|
||||
})
|
||||
|
||||
const firstItem = () => {
|
||||
if (props.chartType === 'table') {
|
||||
const [ele] = props.chartTypeList || []
|
||||
handleDefaultChatChange(ele || {})
|
||||
}
|
||||
}
|
||||
const emits = defineEmits(['typeChange'])
|
||||
const handleDefaultChatChange = (val: any) => {
|
||||
emits('typeChange', val.value)
|
||||
@@ -29,7 +42,11 @@ const handleDefaultChatChange = (val: any) => {
|
||||
<template>
|
||||
<el-popover trigger="click" popper-class="chat-type_select" placement="bottom">
|
||||
<template #reference>
|
||||
<div class="chat-select_type" :class="chartType && chartType !== 'table' && 'active'">
|
||||
<div
|
||||
class="chat-select_type"
|
||||
:class="chartType && chartType !== 'table' && 'active'"
|
||||
@click="firstItem"
|
||||
>
|
||||
<component :is="currentIcon" />
|
||||
<el-icon style="transform: scale(0.75)" class="expand" size="16">
|
||||
<icon_expand_down_filled></icon_expand_down_filled>
|
||||
|
||||
@@ -106,7 +106,13 @@ export class Bar extends BaseG2Chart {
|
||||
},
|
||||
labels: [
|
||||
{
|
||||
text: (data: any) => `${data[y[0].value]}${_data.isPercent ? '%' : ''}`,
|
||||
text: (data: any) => {
|
||||
const value = data[y[0].value]
|
||||
if (value === undefined || value === null) {
|
||||
return ''
|
||||
}
|
||||
return `${value}${_data.isPercent ? '%' : ''}`
|
||||
},
|
||||
transform: [
|
||||
{ type: 'contrastReverse' },
|
||||
{ type: 'exceedAdjust' },
|
||||
|
||||
@@ -95,7 +95,13 @@ export class Column extends BaseG2Chart {
|
||||
},
|
||||
labels: [
|
||||
{
|
||||
text: (data: any) => `${data[y[0].value]}${_data.isPercent ? '%' : ''}`,
|
||||
text: (data: any) => {
|
||||
const value = data[y[0].value]
|
||||
if (value === undefined || value === null) {
|
||||
return ''
|
||||
}
|
||||
return `${value}${_data.isPercent ? '%' : ''}`
|
||||
},
|
||||
position: 'top',
|
||||
transform: [
|
||||
{ type: 'contrastReverse' },
|
||||
|
||||
@@ -61,7 +61,13 @@ export class Line extends BaseG2Chart {
|
||||
},
|
||||
labels: [
|
||||
{
|
||||
text: (data: any) => `${data[y[0].value]}${_data.isPercent ? '%' : ''}`,
|
||||
text: (data: any) => {
|
||||
const value = data[y[0].value]
|
||||
if (value === undefined || value === null) {
|
||||
return ''
|
||||
}
|
||||
return `${value}${_data.isPercent ? '%' : ''}`
|
||||
},
|
||||
style: {
|
||||
dx: -10,
|
||||
dy: -12,
|
||||
|
||||
@@ -15,15 +15,16 @@
|
||||
@on-click-side-bar-btn="hideSideBar"
|
||||
/>
|
||||
</el-aside>
|
||||
<div v-if="!isAssistant && !chatListSideBarShow" class="hidden-sidebar-btn">
|
||||
<div
|
||||
v-if="isAssistant || !chatListSideBarShow"
|
||||
class="hidden-sidebar-btn"
|
||||
:class="{ 'assistant-popover-sidebar': isAssistant }"
|
||||
>
|
||||
<el-popover
|
||||
:visible="isAssistant ? floatPopoverVisible : null"
|
||||
:width="280"
|
||||
placement="bottom-start"
|
||||
popper-style="padding: 0;
|
||||
height: 654px;
|
||||
border: 1px solid rgba(222, 224, 227, 1);
|
||||
border-radius: 6px;
|
||||
"
|
||||
:popper-style="{ ...defaultFloatPopoverStyle }"
|
||||
>
|
||||
<template #reference>
|
||||
<el-button link type="primary" class="icon-btn" @click="showSideBar">
|
||||
@@ -33,6 +34,7 @@
|
||||
</el-button>
|
||||
</template>
|
||||
<ChatListContainer
|
||||
ref="floatPopoverRef"
|
||||
v-model:chat-list="chatList"
|
||||
v-model:current-chat-id="currentChatId"
|
||||
v-model:current-chat="currentChat"
|
||||
@@ -57,7 +59,10 @@
|
||||
<el-container :loading="loading">
|
||||
<el-main
|
||||
class="chat-record-list"
|
||||
:class="{ 'hide-sidebar': !isAssistant && !chatListSideBarShow }"
|
||||
:class="{
|
||||
'hide-sidebar': !isAssistant && !chatListSideBarShow,
|
||||
'assistant-chat-main': isAssistant,
|
||||
}"
|
||||
>
|
||||
<div v-if="computedMessages.length == 0 && !loading" class="welcome-content-block">
|
||||
<div class="welcome-content">
|
||||
@@ -65,11 +70,16 @@
|
||||
<el-icon size="32">
|
||||
<logo_fold />
|
||||
</el-icon>
|
||||
{{ t('qa.greeting') }}
|
||||
{{ isAssistant ? t('embedded.i_am_sqlbot') : t('qa.greeting') }}
|
||||
</div>
|
||||
<div class="sub">
|
||||
{{ isAssistant ? t('embedded.predict_data_etc') : t('qa.hint_description') }}
|
||||
</div>
|
||||
<div v-if="isAssistant" class="sub assistant-sub">
|
||||
{{ t('embedded.intelligent_data_query') }}
|
||||
</div>
|
||||
<div class="sub">{{ t('qa.hint_description') }}</div>
|
||||
<el-button
|
||||
v-if="currentChatId === undefined"
|
||||
v-if="!isAssistant && currentChatId === undefined"
|
||||
size="large"
|
||||
type="primary"
|
||||
class="greeting-btn"
|
||||
@@ -255,15 +265,13 @@
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
</el-main>
|
||||
<el-footer
|
||||
v-if="computedMessages.length > 0 || (isAssistant && currentChatId)"
|
||||
class="chat-footer"
|
||||
>
|
||||
<el-footer v-if="computedMessages.length > 0 || isAssistant" class="chat-footer">
|
||||
<div class="input-wrapper" @click="clickInput">
|
||||
<div class="datasource">
|
||||
<div v-if="!isAssistant" class="datasource">
|
||||
<template v-if="currentChat.datasource && currentChat.datasource_name">
|
||||
{{ t('qa.selected_datasource') }}:
|
||||
<img
|
||||
v-if="currentChatEngineType"
|
||||
style="margin-left: 4px; margin-right: 4px"
|
||||
:src="currentChatEngineType"
|
||||
width="16px"
|
||||
@@ -332,14 +340,21 @@ import icon_start_outlined from '@/assets/svg/icon_start_outlined.svg'
|
||||
import logo_fold from '@/assets/LOGO-fold.svg'
|
||||
import logo from '@/assets/LOGO.svg'
|
||||
import icon_send_filled from '@/assets/svg/icon_send_filled.svg'
|
||||
|
||||
import { useAssistantStore } from '@/stores/assistant'
|
||||
import { onClickOutside } from '@vueuse/core'
|
||||
|
||||
const props = defineProps<{
|
||||
startChatDsId?: number
|
||||
}>()
|
||||
|
||||
const floatPopoverRef = ref()
|
||||
const floatPopoverVisible = ref(false)
|
||||
const assistantStore = useAssistantStore()
|
||||
const defaultFloatPopoverStyle = ref({
|
||||
padding: '0',
|
||||
height: '654px',
|
||||
border: '1px solid rgba(222, 224, 227, 1)',
|
||||
borderRadius: '6px',
|
||||
})
|
||||
|
||||
const isAssistant = computed(() => assistantStore.getAssistant)
|
||||
|
||||
@@ -410,10 +425,8 @@ const createNewChatSimple = async () => {
|
||||
const createNewChat = async () => {
|
||||
goEmpty()
|
||||
if (isAssistant.value) {
|
||||
const assistantChat = await assistantStore.setChat()
|
||||
if (assistantChat) {
|
||||
onChatCreatedQuick(assistantChat as any)
|
||||
}
|
||||
currentChat.value = new ChatInfo()
|
||||
currentChatId.value = undefined
|
||||
return
|
||||
}
|
||||
chatCreatorRef.value?.showDs()
|
||||
@@ -440,32 +453,9 @@ function onClickHistory(chat: Chat) {
|
||||
}
|
||||
|
||||
const currentChatEngineType = computed(() => {
|
||||
return (
|
||||
dsTypeWithImg.find((ele) => currentChat.value.ds_type === ele.type || ele.type === 'excel') ||
|
||||
{}
|
||||
).img
|
||||
return (dsTypeWithImg.find((ele) => currentChat.value.ds_type === ele.type) || {}).img
|
||||
})
|
||||
|
||||
function toAssistantHistory(chat: Chat) {
|
||||
currentChat.value = new ChatInfo(chat)
|
||||
if (chat !== undefined && chat.id !== undefined && !loading.value) {
|
||||
currentChatId.value = chat.id
|
||||
loading.value = true
|
||||
chatApi
|
||||
.get(chat.id)
|
||||
.then((res) => {
|
||||
const info = chatApi.toChatInfo(res)
|
||||
if (info) {
|
||||
currentChat.value = info
|
||||
onClickHistory(info)
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function onChatDeleted(id: number) {
|
||||
console.log('deleted', id)
|
||||
}
|
||||
@@ -476,6 +466,10 @@ function onChatRenamed(chat: Chat) {
|
||||
|
||||
const chatListSideBarShow = ref<boolean>(true)
|
||||
function hideSideBar() {
|
||||
if (isAssistant.value) {
|
||||
floatPopoverVisible.value = false
|
||||
return
|
||||
}
|
||||
chatListSideBarShow.value = false
|
||||
}
|
||||
|
||||
@@ -542,13 +536,23 @@ function onChatStop() {
|
||||
isTyping.value = false
|
||||
console.debug('onChatStop')
|
||||
}
|
||||
|
||||
const assistantPrepareSend = async () => {
|
||||
if (
|
||||
isAssistant.value &&
|
||||
(currentChatId.value == null || typeof currentChatId.value == 'undefined')
|
||||
) {
|
||||
const assistantChat = await assistantStore.setChat()
|
||||
if (assistantChat) {
|
||||
onChatCreatedQuick(assistantChat as any)
|
||||
}
|
||||
}
|
||||
}
|
||||
const sendMessage = async () => {
|
||||
if (!inputMessage.value.trim()) return
|
||||
|
||||
loading.value = true
|
||||
isTyping.value = true
|
||||
|
||||
await assistantPrepareSend()
|
||||
const currentRecord = new ChatRecord()
|
||||
currentRecord.create_time = new Date()
|
||||
currentRecord.chat_id = currentChatId.value
|
||||
@@ -716,13 +720,6 @@ const handleCtrlEnter = (e: KeyboardEvent) => {
|
||||
})
|
||||
}
|
||||
|
||||
const getHistoryList = () => {
|
||||
return chatList.value
|
||||
}
|
||||
const getCurrentChatId = () => {
|
||||
return currentChatId.value
|
||||
}
|
||||
|
||||
const inputRef = ref()
|
||||
|
||||
function clickInput() {
|
||||
@@ -770,12 +767,29 @@ function stop(func?: (...p: any[]) => void, ...param: any[]) {
|
||||
func(...param)
|
||||
}
|
||||
}
|
||||
|
||||
const showFloatPopover = () => {
|
||||
if (isAssistant.value && !floatPopoverVisible.value) {
|
||||
floatPopoverVisible.value = true
|
||||
}
|
||||
}
|
||||
const assistantPrepareInit = () => {
|
||||
if (!isAssistant.value) {
|
||||
return
|
||||
}
|
||||
Object.assign(defaultFloatPopoverStyle.value, {
|
||||
height: '100% !important',
|
||||
inset: '0px auto auto 0px',
|
||||
})
|
||||
goEmpty()
|
||||
onClickOutside(floatPopoverRef, () => {
|
||||
if (floatPopoverVisible.value) {
|
||||
floatPopoverVisible.value = false
|
||||
}
|
||||
})
|
||||
}
|
||||
defineExpose({
|
||||
getHistoryList,
|
||||
toAssistantHistory,
|
||||
getCurrentChatId,
|
||||
createNewChat,
|
||||
showFloatPopover,
|
||||
})
|
||||
|
||||
const hiddenChatCreatorRef = ref()
|
||||
@@ -793,6 +807,7 @@ function jumpCreatChat() {
|
||||
|
||||
onMounted(() => {
|
||||
getChatList(jumpCreatChat)
|
||||
assistantPrepareInit()
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -802,7 +817,11 @@ onMounted(() => {
|
||||
position: relative;
|
||||
|
||||
border-radius: 12px;
|
||||
|
||||
.assistant-popover-sidebar {
|
||||
button {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
.hidden-sidebar-btn {
|
||||
z-index: 1;
|
||||
position: absolute;
|
||||
@@ -841,6 +860,9 @@ onMounted(() => {
|
||||
border-radius: 12px;
|
||||
}
|
||||
}
|
||||
.assistant-chat-main {
|
||||
padding: 0 0 20px 0;
|
||||
}
|
||||
|
||||
.chat-scroll {
|
||||
width: 100%;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { ref, computed, onMounted, reactive } from 'vue'
|
||||
import { datasourceApi } from '@/api/datasource'
|
||||
import icon_right_outlined from '@/assets/svg/icon_right_outlined.svg'
|
||||
import icon_form_outlined from '@/assets/svg/icon_form_outlined.svg'
|
||||
@@ -67,11 +67,32 @@ const currentTable = ref<any>({})
|
||||
const ds = ref<any>({})
|
||||
const btnSelect = ref('d')
|
||||
|
||||
const pageInfo = reactive({
|
||||
currentPage: 1,
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
})
|
||||
|
||||
const handleSizeChange = (val: number) => {
|
||||
pageInfo.currentPage = 1
|
||||
pageInfo.pageSize = val
|
||||
}
|
||||
const handleCurrentChange = (val: number) => {
|
||||
pageInfo.currentPage = val
|
||||
}
|
||||
|
||||
const fieldListComputed = computed(() => {
|
||||
const { currentPage, pageSize } = pageInfo
|
||||
return fieldList.value.slice((currentPage - 1) * pageSize, currentPage * pageSize)
|
||||
})
|
||||
|
||||
const init = () => {
|
||||
initLoading.value = true
|
||||
datasourceApi.getDs(props.info.id).then((res) => {
|
||||
ds.value = res
|
||||
fieldList.value = []
|
||||
pageInfo.total = 0
|
||||
pageInfo.currentPage = 1
|
||||
datasourceApi.tableList(props.info.id).then((res) => {
|
||||
tableList.value = res
|
||||
initLoading.value = false
|
||||
@@ -100,10 +121,15 @@ const handleSelectTableList = () => {
|
||||
const clickTable = (table: any) => {
|
||||
loading.value = true
|
||||
currentTable.value = table
|
||||
fieldList.value = []
|
||||
pageInfo.total = 0
|
||||
previewData.value = []
|
||||
datasourceApi
|
||||
.fieldList(table.id)
|
||||
.then((res) => {
|
||||
fieldList.value = res
|
||||
pageInfo.total = res.length
|
||||
pageInfo.currentPage = 1
|
||||
datasourceApi.previewData(props.info.id, buildData()).then((res) => {
|
||||
previewData.value = res
|
||||
})
|
||||
@@ -191,6 +217,8 @@ const btnSelectClick = (val: any) => {
|
||||
.fieldList(currentTable.value.id)
|
||||
.then((res) => {
|
||||
fieldList.value = res
|
||||
pageInfo.total = res.length
|
||||
pageInfo.currentPage = 1
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false
|
||||
@@ -307,43 +335,72 @@ const btnSelectClick = (val: any) => {
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<div class="preview-or-schema">
|
||||
<el-table v-if="btnSelect === 'd'" :data="fieldList" style="width: 100%">
|
||||
<el-table-column prop="field_name" :label="t('datasource.field_name')" width="180" />
|
||||
<el-table-column prop="field_type" :label="t('datasource.field_type')" width="180" />
|
||||
<el-table-column prop="field_comment" :label="t('datasource.field_original_notes')" />
|
||||
<el-table-column :label="t('datasource.field_notes_1')">
|
||||
<template #default="scope">
|
||||
<div class="field-comment">
|
||||
<span :title="scope.row.custom_comment" class="notes-in_table">{{
|
||||
scope.row.custom_comment
|
||||
}}</span>
|
||||
<el-tooltip
|
||||
:offset="14"
|
||||
effect="dark"
|
||||
:content="$t('datasource.edit')"
|
||||
placement="top"
|
||||
>
|
||||
<el-icon class="action-btn" size="16" @click="editField(scope.row)">
|
||||
<edit></edit>
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="t('datasource.enabled_status')" width="180">
|
||||
<template #default="scope">
|
||||
<div style="display: flex; align-items: center">
|
||||
<el-switch
|
||||
v-model="scope.row.checked"
|
||||
size="small"
|
||||
@change="changeStatus(scope.row)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<template v-else>
|
||||
<div
|
||||
v-if="!loading"
|
||||
class="preview-or-schema"
|
||||
:class="btnSelect === 'q' && 'overflow-preview'"
|
||||
>
|
||||
<div v-if="btnSelect === 'd'" class="table-content_preview">
|
||||
<el-table :data="fieldListComputed" style="width: 100%">
|
||||
<el-table-column
|
||||
prop="field_name"
|
||||
:label="t('datasource.field_name')"
|
||||
width="180"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="field_type"
|
||||
:label="t('datasource.field_type')"
|
||||
width="180"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="field_comment"
|
||||
:label="t('datasource.field_original_notes')"
|
||||
/>
|
||||
<el-table-column :label="t('datasource.field_notes_1')">
|
||||
<template #default="scope">
|
||||
<div class="field-comment">
|
||||
<span :title="scope.row.custom_comment" class="notes-in_table">{{
|
||||
scope.row.custom_comment
|
||||
}}</span>
|
||||
<el-tooltip
|
||||
:offset="14"
|
||||
effect="dark"
|
||||
:content="$t('datasource.edit')"
|
||||
placement="top"
|
||||
>
|
||||
<el-icon class="action-btn" size="16" @click="editField(scope.row)">
|
||||
<edit></edit>
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="t('datasource.enabled_status')" width="180">
|
||||
<template #default="scope">
|
||||
<div style="display: flex; align-items: center">
|
||||
<el-switch
|
||||
v-model="scope.row.checked"
|
||||
size="small"
|
||||
@change="changeStatus(scope.row)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
<div v-if="fieldList.length && btnSelect === 'd'" class="pagination-container">
|
||||
<el-pagination
|
||||
v-model:current-page="pageInfo.currentPage"
|
||||
v-model:page-size="pageInfo.pageSize"
|
||||
:page-sizes="[10, 20, 30]"
|
||||
:background="true"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
:total="pageInfo.total"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
/>
|
||||
</div>
|
||||
<template v-if="btnSelect === 'q'">
|
||||
<div class="preview-num">
|
||||
{{ t('ds.pieces_in_total', { msg: total, ms: showNum }) }}
|
||||
</div>
|
||||
@@ -588,7 +645,21 @@ const btnSelectClick = (val: any) => {
|
||||
.preview-or-schema {
|
||||
margin-top: 16px;
|
||||
height: calc(100% - 50px);
|
||||
overflow-y: auto;
|
||||
|
||||
&.overflow-preview {
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.table-content_preview {
|
||||
max-height: calc(100% - 50px);
|
||||
overflow-y: auto;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.pagination-container {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.field-comment {
|
||||
display: flex;
|
||||
|
||||
@@ -233,7 +233,6 @@ const save = async (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return
|
||||
await formEl.validate((valid) => {
|
||||
if (valid) {
|
||||
saveLoading.value = true
|
||||
const list = tableList.value
|
||||
.filter((ele: any) => {
|
||||
return checkList.value.includes(ele.tableName)
|
||||
@@ -242,6 +241,12 @@ const save = async (formEl: FormInstance | undefined) => {
|
||||
return { table_name: ele.tableName, table_comment: ele.tableComment }
|
||||
})
|
||||
|
||||
if (checkList.value.length > 30) {
|
||||
ElMessage.error(t('common.limited_to_30'))
|
||||
return
|
||||
}
|
||||
saveLoading.value = true
|
||||
|
||||
const requestObj = buildConf()
|
||||
if (form.value.id) {
|
||||
if (!isEditTable.value) {
|
||||
@@ -278,8 +283,6 @@ const save = async (formEl: FormInstance | undefined) => {
|
||||
.finally(() => {
|
||||
saveLoading.value = false
|
||||
})
|
||||
|
||||
console.log(a, 'datasourceApi')
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -510,12 +513,12 @@ defineExpose({
|
||||
<div class="name">{{ form.filename }}</div>
|
||||
<div class="size">{{ form.filename.split('.')[1] }} - {{ fileSize }}</div>
|
||||
</div>
|
||||
<el-icon class="action-btn" size="16" @click="clearFile">
|
||||
<el-icon v-if="!form.id" class="action-btn" size="16" @click="clearFile">
|
||||
<IconOpeDelete></IconOpeDelete>
|
||||
</el-icon>
|
||||
</div>
|
||||
<el-upload
|
||||
v-if="form.filename"
|
||||
v-if="form.filename && !form.id"
|
||||
class="upload-user"
|
||||
accept=".xlsx,.xls,.csv"
|
||||
:headers="headers"
|
||||
@@ -531,7 +534,7 @@ defineExpose({
|
||||
</el-button>
|
||||
</el-upload>
|
||||
<el-upload
|
||||
v-else
|
||||
v-else-if="!form.id"
|
||||
class="upload-user"
|
||||
accept=".xlsx,.xls,.csv"
|
||||
:headers="headers"
|
||||
|
||||
@@ -1,69 +1,30 @@
|
||||
<template>
|
||||
<div class="sqlbot-assistant-container">
|
||||
<div class="head">
|
||||
<div class="flex align-center">
|
||||
<div class="head-img-div">
|
||||
<img :src="AssistantGif" />
|
||||
</div>
|
||||
<h4>SQLBot 小助手</h4>
|
||||
</div>
|
||||
<div class="header">
|
||||
<el-icon size="20" @click="openHistory">
|
||||
<icon_sidebar_outlined></icon_sidebar_outlined>
|
||||
</el-icon>
|
||||
<img :src="LOGO" class="logo" width="30px" height="30px" alt="" />
|
||||
<span class="tite">{{ $t('embedded.intelligent_customer_service') }}</span>
|
||||
|
||||
<el-tooltip effect="dark" :content="$t('embedded.new_conversation')" placement="top">
|
||||
<el-icon class="new-chat" size="20" @click="createChat">
|
||||
<icon_new_chat_outlined></icon_new_chat_outlined>
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<chat-component v-if="!loading" ref="chatRef" class="sqlbot-chat-container" />
|
||||
</div>
|
||||
<div class="sqlbot-top-btn">
|
||||
<el-icon style="cursor: pointer" @click="createChat">
|
||||
<icon_new_chat_outlined />
|
||||
</el-icon>
|
||||
<el-icon style="cursor: pointer" @click="openHistory">
|
||||
<history></history>
|
||||
</el-icon>
|
||||
</div>
|
||||
<div class="sqlbot-history-container">
|
||||
<el-drawer
|
||||
v-model="drawer"
|
||||
:append-to-body="false"
|
||||
class="sqlbot-history-drawer"
|
||||
modal-class="sqlbot-history-modal"
|
||||
title=""
|
||||
direction="ttb"
|
||||
:with-header="false"
|
||||
>
|
||||
<div class="sqlbot-history-title">
|
||||
<span>历史记录</span>
|
||||
</div>
|
||||
<div class="sqlbot-history-content">
|
||||
<div v-if="!chatList.length" class="history-empty">
|
||||
<span>暂无历史记录</span>
|
||||
</div>
|
||||
<div v-else class="sqlbot-chat-list-inner">
|
||||
<el-scrollbar max-height="350px" class="sqlbot-chat-scroller">
|
||||
<template v-for="chat in chatList" :key="chat.id">
|
||||
<div
|
||||
class="chat-list-item"
|
||||
:class="{ 'is-active-chat': currentId === chat.id }"
|
||||
@click="onClickHistory(chat)"
|
||||
>
|
||||
<span class="title">{{ chat.brief ?? 'Untitled' }}</span>
|
||||
<!-- <div class="history-operate">
|
||||
<el-icon @click="EditPen(chat)"><IconOpeEdit /></el-icon>
|
||||
<el-icon @click="Delete(chat)"><IconOpeDelete /></el-icon>
|
||||
</div> -->
|
||||
</div>
|
||||
</template>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
</div>
|
||||
</el-drawer>
|
||||
<div class="sqlbot-chat-container">
|
||||
<chat-component v-if="!loading" ref="chatRef" />
|
||||
</div>
|
||||
<div class="drawer-assistant" @click="openHistory"></div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { onBeforeMount, onBeforeUnmount, ref } from 'vue'
|
||||
import ChatComponent from '@/views/chat/index.vue'
|
||||
import AssistantGif from '@/assets/img/assistant.gif'
|
||||
import history from '@/assets/svg/chart/history.svg'
|
||||
import LOGO from '@/assets/embedded/LOGO.png'
|
||||
import icon_sidebar_outlined from '@/assets/embedded/icon_sidebar_outlined.svg'
|
||||
import icon_new_chat_outlined from '@/assets/svg/icon_new_chat_outlined.svg'
|
||||
/* import IconOpeEdit from '@/assets/svg/operate/ope-edit.svg'
|
||||
import IconOpeDelete from '@/assets/svg/operate/ope-delete.svg' */
|
||||
import { useRoute } from 'vue-router'
|
||||
import { assistantApi } from '@/api/assistant'
|
||||
import { useAssistantStore } from '@/stores/assistant'
|
||||
@@ -72,28 +33,14 @@ const assistantStore = useAssistantStore()
|
||||
const route = useRoute()
|
||||
|
||||
const chatRef = ref()
|
||||
const chatList = ref<Array<any>>([])
|
||||
const drawer = ref(false)
|
||||
const currentId = ref()
|
||||
|
||||
const createChat = () => {
|
||||
chatRef.value?.createNewChat()
|
||||
}
|
||||
const openHistory = () => {
|
||||
chatList.value = chatRef.value?.getHistoryList()
|
||||
currentId.value = chatRef.value?.getCurrentChatId()
|
||||
drawer.value = true
|
||||
}
|
||||
const onClickHistory = (chat: any) => {
|
||||
chatRef.value?.toAssistantHistory(chat)
|
||||
chatRef.value?.showFloatPopover()
|
||||
}
|
||||
|
||||
/* const EditPen = (chat: any) => {
|
||||
chatRef.value?.onChatRenamed(chat)
|
||||
}
|
||||
const Delete = (chat: any) => {
|
||||
chatRef.value?.onChatDeleted(chat.id)
|
||||
} */
|
||||
const validator = ref({
|
||||
id: '',
|
||||
valid: false,
|
||||
@@ -111,14 +58,31 @@ const communicationCb = async (event: any) => {
|
||||
const certificate = event.data['certificate']
|
||||
assistantStore.setType(1)
|
||||
assistantStore.setCertificate(certificate)
|
||||
// store certificate to pinia
|
||||
}
|
||||
if (event.data?.busi == 'setOnline') {
|
||||
setFormatOnline(event.data.online)
|
||||
}
|
||||
}
|
||||
}
|
||||
const setFormatOnline = (text?: any) => {
|
||||
if (text === null || typeof text === 'undefined') {
|
||||
assistantStore.setOnline(false)
|
||||
return
|
||||
}
|
||||
if (typeof text === 'boolean') {
|
||||
assistantStore.setOnline(text)
|
||||
return
|
||||
}
|
||||
if (typeof text === 'string') {
|
||||
assistantStore.setOnline(text.toLowerCase() === 'true')
|
||||
return
|
||||
}
|
||||
assistantStore.setOnline(false)
|
||||
}
|
||||
onBeforeMount(async () => {
|
||||
const assistantId = route.query.id
|
||||
const online = route.query.online
|
||||
console.log(online)
|
||||
setFormatOnline(online)
|
||||
const now = Date.now()
|
||||
assistantStore.setFlag(now)
|
||||
const param = {
|
||||
@@ -151,31 +115,58 @@ onBeforeUnmount(() => {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
color: rgb(31, 35, 41);
|
||||
.flex {
|
||||
display: flex;
|
||||
}
|
||||
.head {
|
||||
background: linear-gradient(90deg, #ebf1ff 24.34%, #e5fbf8 56.18%, #f2ebfe 90.18%);
|
||||
position: fixed;
|
||||
.header {
|
||||
width: 100%;
|
||||
left: 0;
|
||||
top: 0;
|
||||
z-index: 100;
|
||||
height: 56px;
|
||||
line-height: 56px;
|
||||
box-sizing: border-box;
|
||||
border-bottom: 1px solid #dee0e3;
|
||||
.align-center {
|
||||
align-items: center;
|
||||
.head-img-div {
|
||||
display: flex;
|
||||
margin-left: 24px;
|
||||
img {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
background: #1cba9014;
|
||||
height: 56px;
|
||||
padding: 0 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: fixed;
|
||||
.logo {
|
||||
margin: 0 8px 0 16px;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-weight: 500;
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
.ed-icon {
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
background-color: #1f23291a;
|
||||
position: absolute;
|
||||
border-radius: 6px;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
transform: translate(-50%, -50%);
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
display: none;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
&::after {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.new-chat {
|
||||
position: absolute;
|
||||
right: 88px;
|
||||
top: 17px;
|
||||
}
|
||||
}
|
||||
.sqlbot-chat-container {
|
||||
padding-top: 56px;
|
||||
@@ -183,82 +174,17 @@ onBeforeUnmount(() => {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
.sqlbot-top-btn {
|
||||
right: 85px;
|
||||
z-index: 2009;
|
||||
position: fixed;
|
||||
top: 16px;
|
||||
display: flex;
|
||||
column-gap: 16px;
|
||||
// right: 16px;
|
||||
font-size: 22px;
|
||||
i:first-child {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
}
|
||||
.sqlbot-history-container {
|
||||
width: 100%;
|
||||
:deep(.sqlbot-history-modal) {
|
||||
top: 56px !important;
|
||||
}
|
||||
:deep(.sqlbot-history-title) {
|
||||
border-bottom: 1px solid #dee0e3;
|
||||
padding: 16px 24px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #1f2329;
|
||||
}
|
||||
:deep(.ed-drawer__body) {
|
||||
padding: 0 !important;
|
||||
}
|
||||
:deep(.sqlbot-history-drawer) {
|
||||
height: fit-content !important;
|
||||
.sqlbot-history-content {
|
||||
padding: 16px;
|
||||
.history-empty {
|
||||
height: 60px;
|
||||
color: #8f959e !important;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 14px;
|
||||
}
|
||||
.is-active-chat {
|
||||
background-color: #ebf1ff;
|
||||
color: #3370ff;
|
||||
font-weight: 500;
|
||||
}
|
||||
.chat-list-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 10px 8px;
|
||||
font-size: 14px;
|
||||
color: #1f2329;
|
||||
justify-content: space-between;
|
||||
.history-operate {
|
||||
display: none;
|
||||
}
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
border-radius: 4px;
|
||||
background: rgba(31, 35, 41, 0.1);
|
||||
.history-operate {
|
||||
padding-right: 16px;
|
||||
display: flex;
|
||||
column-gap: 16px;
|
||||
align-items: center;
|
||||
font-size: 18px;
|
||||
i {
|
||||
color: #8f959e;
|
||||
&:hover {
|
||||
color: #8f959e99;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.drawer-assistant {
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 50%;
|
||||
background: #f5f6f7;
|
||||
box-shadow: 0px 6px 24px 0px #1f232914;
|
||||
padding: 16px;
|
||||
border-right: 1px solid #dee0e3;
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -7,21 +7,26 @@
|
||||
<div class="login-right">
|
||||
<div class="login-form">
|
||||
<h2 class="title">Login</h2>
|
||||
<el-form ref="loginFormRef" :model="loginForm" :rules="rules" @keyup.enter="submitForm">
|
||||
<el-form
|
||||
ref="loginFormRef"
|
||||
class="form-content_error"
|
||||
:model="loginForm"
|
||||
:rules="rules"
|
||||
@keyup.enter="submitForm"
|
||||
>
|
||||
<el-form-item prop="username">
|
||||
<el-input
|
||||
v-model="loginForm.username"
|
||||
placeholder="Account"
|
||||
:prefix-icon="User"
|
||||
:placeholder="$t('common.your_account_email_address')"
|
||||
size="large"
|
||||
></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="password">
|
||||
<el-input
|
||||
v-model="loginForm.password"
|
||||
placeholder="Password"
|
||||
:placeholder="$t('common.enter_your_password')"
|
||||
type="password"
|
||||
:prefix-icon="Lock"
|
||||
show-password
|
||||
size="large"
|
||||
></el-input>
|
||||
</el-form-item>
|
||||
@@ -39,10 +44,11 @@
|
||||
import { ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
import { User, Lock } from '@element-plus/icons-vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const router = useRouter()
|
||||
const userStore = useUserStore()
|
||||
const { t } = useI18n()
|
||||
|
||||
const loginForm = ref({
|
||||
username: '',
|
||||
@@ -50,8 +56,8 @@ const loginForm = ref({
|
||||
})
|
||||
|
||||
const rules = {
|
||||
username: [{ required: true, message: 'Please input account', trigger: 'blur' }],
|
||||
password: [{ required: true, message: 'Please input password', trigger: 'blur' }],
|
||||
username: [{ required: true, message: t('common.your_account_email_address'), trigger: 'blur' }],
|
||||
password: [{ required: true, message: t('common.the_correct_password'), trigger: 'blur' }],
|
||||
}
|
||||
|
||||
const loginFormRef = ref()
|
||||
@@ -108,10 +114,24 @@ const submitForm = () => {
|
||||
padding: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
height: 361px;
|
||||
|
||||
.login-form {
|
||||
width: 100%;
|
||||
padding: 0 40px;
|
||||
position: absolute;
|
||||
top: 40px;
|
||||
left: 0;
|
||||
|
||||
.form-content_error {
|
||||
.ed-form-item--default {
|
||||
margin-bottom: 24px;
|
||||
&.is-error {
|
||||
margin-bottom: 48px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 28px;
|
||||
@@ -125,7 +145,6 @@ const submitForm = () => {
|
||||
height: 45px;
|
||||
font-size: 16px;
|
||||
border-radius: 4px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.agreement {
|
||||
|
||||
@@ -38,7 +38,7 @@ const handleEmbedded = () => {
|
||||
<div class="card">
|
||||
<div class="name-icon">
|
||||
<img :src="Lock" width="32px" height="32px" />
|
||||
<span class="name">{{ name }}</span>
|
||||
<span class="name ellipsis" :title="name">{{ name }}</span>
|
||||
<span class="default" :class="isBase && 'is-base'">{{
|
||||
isBase ? $t('embedded.basic_application') : $t('embedded.advanced_application')
|
||||
}}</span>
|
||||
@@ -88,6 +88,7 @@ const handleEmbedded = () => {
|
||||
font-weight: 500;
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
max-width: 200px;
|
||||
}
|
||||
|
||||
.default {
|
||||
|
||||
@@ -14,8 +14,7 @@ import icon_copy_outlined from '@/assets/embedded/icon_copy_outlined.svg'
|
||||
import Card from './Card.vue'
|
||||
import { workspaceList } from '@/api/workspace'
|
||||
import DsCard from './DsCard.vue'
|
||||
import { datasourceApi } from '@/api/datasource'
|
||||
import { getList, updateAssistant, saveAssistant, delOne } from '@/api/embedded'
|
||||
import { getList, updateAssistant, saveAssistant, delOne, dsApi } from '@/api/embedded'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
|
||||
@@ -49,7 +48,7 @@ const currentEmbedded = reactive<any>(cloneDeep(defaultEmbedded))
|
||||
|
||||
const isCreate = ref(false)
|
||||
const defaultForm = {
|
||||
oid: '',
|
||||
oid: 1,
|
||||
private_list: [] as any,
|
||||
}
|
||||
const dsForm = reactive(cloneDeep(defaultForm))
|
||||
@@ -107,9 +106,13 @@ const handleAddEmbedded = (val: any) => {
|
||||
handleAdvancedEmbedded(null)
|
||||
}
|
||||
}
|
||||
|
||||
const wsChanged = (val: any) => {
|
||||
dsForm.private_list = []
|
||||
dsForm.oid = val
|
||||
getDsList()
|
||||
}
|
||||
const getDsList = () => {
|
||||
datasourceApi.list().then((res: any) => {
|
||||
dsApi(dsForm.oid).then((res: any) => {
|
||||
dsListOptions.value = res || []
|
||||
if (!currentEmbedded.id) {
|
||||
dsForm.private_list = dsListOptions.value.map((ele) => ele.id)
|
||||
@@ -119,10 +122,10 @@ const getDsList = () => {
|
||||
const handleBaseEmbedded = (row: any) => {
|
||||
advancedApplication.value = false
|
||||
initWorkspace()
|
||||
getDsList()
|
||||
if (row) {
|
||||
Object.assign(dsForm, JSON.parse(row.configuration))
|
||||
}
|
||||
getDsList()
|
||||
ruleConfigvVisible.value = true
|
||||
dialogTitle.value = row?.id
|
||||
? t('embedded.edit_basic_applications')
|
||||
@@ -350,16 +353,36 @@ const btnSelect = ref('d')
|
||||
|
||||
const dialogVisible = ref(false)
|
||||
const scriptElement = ref('')
|
||||
const jsCodeElement = ref('')
|
||||
const handleEmbedded = (row: any) => {
|
||||
dialogVisible.value = true
|
||||
const { origin, pathname } = window.location
|
||||
scriptElement.value = `g-#script
|
||||
async
|
||||
defer
|
||||
id="${row.id}"
|
||||
id="sqlbot-assistant-float-script-${row.id}"
|
||||
src="${origin + pathname}assistant.js?id=${row.id}"k-*g-#/scriptk-*`
|
||||
.replaceAll('g-#', '<')
|
||||
.replaceAll('k-*', '>')
|
||||
|
||||
jsCodeElement.value = `(function(){
|
||||
const script = document.createElement('script');
|
||||
script.defer = true;
|
||||
script.async = true;
|
||||
script.src = "${origin + pathname}assistant.js?id=${row.id}";
|
||||
script.id = "sqlbot-assistant-float-script-${row.id}";
|
||||
document.head.appendChild(script);
|
||||
})()`
|
||||
}
|
||||
const copyJsCode = () => {
|
||||
navigator.clipboard
|
||||
.writeText(jsCodeElement.value)
|
||||
.then(function () {
|
||||
ElMessage.success(t('embedded.copy_successful'))
|
||||
})
|
||||
.catch(function () {
|
||||
ElMessage.error(t('embedded.copy_successful'))
|
||||
})
|
||||
}
|
||||
const copyCode = () => {
|
||||
navigator.clipboard
|
||||
@@ -417,15 +440,15 @@ const saveHandler = () => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-loading="searchLoading" class="permission">
|
||||
<div v-loading="searchLoading" class="embedded">
|
||||
<div class="tool-left">
|
||||
<div class="btn-select">
|
||||
<el-button :class="[btnSelect === 'd' && 'is-active']" text @click="btnSelect = 'd'">
|
||||
{{ t('embedded.embedded_assistant') }}
|
||||
</el-button>
|
||||
<el-button :class="[btnSelect === 'q' && 'is-active']" text @click="btnSelect = 'q'">
|
||||
<!-- <el-button :class="[btnSelect === 'q' && 'is-active']" text @click="btnSelect = 'q'">
|
||||
{{ t('embedded.embedded_page') }}
|
||||
</el-button>
|
||||
</el-button> -->
|
||||
</div>
|
||||
<div>
|
||||
<el-input
|
||||
@@ -697,6 +720,7 @@ const saveHandler = () => {
|
||||
:placeholder="
|
||||
$t('datasource.please_enter') + $t('common.empty') + $t('user.workspace')
|
||||
"
|
||||
@change="wsChanged"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in workspaces"
|
||||
@@ -770,6 +794,19 @@ const saveHandler = () => {
|
||||
{{ scriptElement }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="code">
|
||||
<div class="copy">
|
||||
{{ $t('common.or') }}
|
||||
<el-icon size="16" @click="copyJsCode">
|
||||
<icon_copy_outlined></icon_copy_outlined>
|
||||
</el-icon>
|
||||
</div>
|
||||
|
||||
<div class="script">
|
||||
{{ jsCodeElement }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-dialog>
|
||||
<el-drawer
|
||||
@@ -857,7 +894,8 @@ const saveHandler = () => {
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.permission {
|
||||
.embedded {
|
||||
height: 100%;
|
||||
.ed-empty {
|
||||
padding-top: 200px;
|
||||
padding-bottom: 0;
|
||||
|
||||
@@ -69,6 +69,48 @@ const defaultModelListWithSearch = computed(() => {
|
||||
})
|
||||
})
|
||||
|
||||
const modelCheckHandler = (item: any) => {
|
||||
setTimeout(() => {
|
||||
modelApi.check(item).catch((err: any) => {
|
||||
if (err.response?.data?.msg) {
|
||||
ElMessage.error(t('model.check_failed', { msg: err.response.data.msg || '' }))
|
||||
} else {
|
||||
ElMessage.error(t('model.check_failed', { msg: err.message || '' }))
|
||||
}
|
||||
})
|
||||
}, 1000)
|
||||
}
|
||||
const duplicateName = async (item: any) => {
|
||||
const res = await modelApi.queryAll()
|
||||
const names = res.filter((ele: any) => ele.id !== item.id).map((ele: any) => ele.name)
|
||||
if (names.includes(item.name)) {
|
||||
ElMessage.error(t('embedded.duplicate_name'))
|
||||
return
|
||||
}
|
||||
|
||||
if (!item.id) {
|
||||
modelApi.add(item).then(() => {
|
||||
beforeClose()
|
||||
search()
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: t('workspace.add_successfully'),
|
||||
})
|
||||
modelCheckHandler(item)
|
||||
})
|
||||
return
|
||||
}
|
||||
modelApi.edit(item).then(() => {
|
||||
beforeClose()
|
||||
search()
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: t('common.save_success'),
|
||||
})
|
||||
modelCheckHandler(item)
|
||||
})
|
||||
}
|
||||
|
||||
const handleDefaultModelChange = (item: any) => {
|
||||
const current_default_node = modelList.value.find((ele: Model) => ele.default_model)
|
||||
if (current_default_node?.id === item.id) {
|
||||
@@ -185,25 +227,7 @@ const search = () => {
|
||||
search()
|
||||
|
||||
const submit = (item: any) => {
|
||||
if (!item.id) {
|
||||
modelApi.add(item).then(() => {
|
||||
beforeClose()
|
||||
search()
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: t('workspace.add_successfully'),
|
||||
})
|
||||
})
|
||||
return
|
||||
}
|
||||
modelApi.edit(item).then(() => {
|
||||
beforeClose()
|
||||
search()
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: t('common.save_success'),
|
||||
})
|
||||
})
|
||||
duplicateName(item)
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -76,7 +76,7 @@ const advancedSettingPagination = computed(() => {
|
||||
return advancedSetting.value.slice(currentPage.value * 5 - 5, currentPage.value * 5)
|
||||
})
|
||||
|
||||
const handelCurrentChange = (val: any) => {
|
||||
const handleCurrentChange = (val: any) => {
|
||||
currentPage.value = val
|
||||
}
|
||||
|
||||
@@ -116,9 +116,22 @@ const addParams = () => {
|
||||
paramsFormRef.value.submit()
|
||||
}
|
||||
|
||||
const submit = (item: any) => {
|
||||
const duplicateName = async (item: any) => {
|
||||
const arr = advancedSetting.value.filter((ele: any) => ele.id !== item.id)
|
||||
const names = arr.map((ele: any) => ele.name)
|
||||
const keys = arr.map((ele: any) => ele.key)
|
||||
if (names.includes(item.name)) {
|
||||
ElMessage.error(t('embedded.duplicate_name'))
|
||||
return
|
||||
}
|
||||
|
||||
if (keys.includes(item.key)) {
|
||||
ElMessage.error(t('embedded.repeating_parameters'))
|
||||
return
|
||||
}
|
||||
|
||||
if (isCreate.value) {
|
||||
advancedSetting.value.push({ ...item })
|
||||
advancedSetting.value.push({ ...item, id: +new Date() })
|
||||
beforeClose()
|
||||
return
|
||||
}
|
||||
@@ -132,6 +145,10 @@ const submit = (item: any) => {
|
||||
beforeClose()
|
||||
}
|
||||
|
||||
const submit = (item: any) => {
|
||||
duplicateName(item)
|
||||
}
|
||||
|
||||
const beforeClose = () => {
|
||||
paramsFormRef.value.close()
|
||||
paramsFormDrawer.value = false
|
||||
@@ -142,6 +159,7 @@ const supplierChang = (supplier: any) => {
|
||||
modelForm.api_domain = config.api_domain
|
||||
modelForm.base_model = ''
|
||||
}
|
||||
let curId = +new Date()
|
||||
const initForm = (item?: any) => {
|
||||
modelForm.id = ''
|
||||
modelRef.value.clearValidate()
|
||||
@@ -149,6 +167,12 @@ const initForm = (item?: any) => {
|
||||
Object.assign(modelForm, { ...item })
|
||||
if (item?.config_list?.length) {
|
||||
advancedSetting.value = item.config_list
|
||||
advancedSetting.value.forEach((ele: any) => {
|
||||
if (!ele.id) {
|
||||
ele.id = curId
|
||||
curId += 1
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -294,7 +318,7 @@ defineExpose({
|
||||
:default-page-size="5"
|
||||
layout="prev, pager, next"
|
||||
:total="advancedSetting.length"
|
||||
@current-change="handelCurrentChange"
|
||||
@current-change="handleCurrentChange"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -153,7 +153,7 @@ const emits = defineEmits(['update:item', 'del'])
|
||||
</el-option>
|
||||
</el-select>
|
||||
<el-select
|
||||
v-if="['null', 'empty'].includes(item.term)"
|
||||
v-if="['null', 'not_null', 'empty', 'not_empty'].includes(item.term)"
|
||||
v-model="item.term"
|
||||
style="max-width: 280px; margin-left: 8px"
|
||||
:placeholder="t('datasource.Please_select')"
|
||||
|
||||
@@ -219,7 +219,9 @@ const handleInitDsIdChange = (val: any) => {
|
||||
activeTable.value = null
|
||||
fieldListOptions.value = []
|
||||
columnForm.permissions = []
|
||||
authTreeRef.value.init({})
|
||||
if (authTreeRef.value) {
|
||||
authTreeRef.value.init({})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -375,6 +377,13 @@ const closeForm = () => {
|
||||
const authTreeRef = ref()
|
||||
const saveHandler = () => {
|
||||
columnFormRef.value.validate((res: any) => {
|
||||
const names = currentPermission.permissions
|
||||
.filter((ele: any) => ele.id !== columnForm.id)
|
||||
.map((ele: any) => ele.name)
|
||||
if (names.includes(columnForm.name)) {
|
||||
ElMessage.error(t('embedded.duplicate_name'))
|
||||
return
|
||||
}
|
||||
if (res) {
|
||||
if (columnForm.type === 'row') {
|
||||
authTreeRef.value.submit()
|
||||
|
||||
@@ -23,12 +23,12 @@
|
||||
</template>
|
||||
{{ $t('user.filter') }}
|
||||
</el-button>
|
||||
<el-button secondary @click="handleUserImport">
|
||||
<!-- <el-button secondary @click="handleUserImport">
|
||||
<template #icon>
|
||||
<ccmUpload></ccmUpload>
|
||||
</template>
|
||||
{{ $t('user.batch_import') }}
|
||||
</el-button>
|
||||
</el-button> -->
|
||||
<el-button type="primary" @click="editHandler(null)">
|
||||
<template #icon>
|
||||
<icon_add_outlined></icon_add_outlined>
|
||||
@@ -110,10 +110,57 @@
|
||||
:content="$t('common.reset_password')"
|
||||
placement="top"
|
||||
>
|
||||
<el-icon class="action-btn" size="16" @click="handleEditPassword(scope.row.id)">
|
||||
<el-icon
|
||||
:ref="
|
||||
(el: any) => {
|
||||
setButtonRef(el, scope.row)
|
||||
}
|
||||
"
|
||||
v-click-outside="() => onClickOutside(scope.row)"
|
||||
class="action-btn"
|
||||
size="16"
|
||||
>
|
||||
<IconLock></IconLock>
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
<el-popover
|
||||
:ref="
|
||||
(el: any) => {
|
||||
setPopoverRef(el, scope.row)
|
||||
}
|
||||
"
|
||||
placement="right"
|
||||
virtual-triggering
|
||||
:width="300"
|
||||
:virtual-ref="scope.row.buttonRef"
|
||||
trigger="click"
|
||||
show-arrow
|
||||
>
|
||||
<div class="reset-pwd-confirm">
|
||||
<div class="confirm-header">
|
||||
<span class="icon-span">
|
||||
<el-icon size="24">
|
||||
<icon_warning_filled class="svg-icon" />
|
||||
</el-icon>
|
||||
</span>
|
||||
<span class="header-span">{{ t('datasource.the_original_one') }}</span>
|
||||
</div>
|
||||
<div class="confirm-content">
|
||||
<span>SQLBot@123456</span>
|
||||
<el-button style="margin-left: 4px" text @click="copyText">{{
|
||||
t('datasource.copy')
|
||||
}}</el-button>
|
||||
</div>
|
||||
<div class="confirm-foot">
|
||||
<el-button secondary @click="closeResetInfo(scope.row)">{{
|
||||
t('common.cancel')
|
||||
}}</el-button>
|
||||
<el-button type="primary" @click="handleEditPassword(scope.row.id)">
|
||||
{{ t('datasource.confirm') }}
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</el-popover>
|
||||
|
||||
<el-tooltip
|
||||
:offset="14"
|
||||
@@ -227,7 +274,11 @@
|
||||
multiple
|
||||
:placeholder="$t('datasource.Please_select') + $t('common.empty') + $t('user.workspace')"
|
||||
>
|
||||
<el-option v-for="item in options" :key="item.id" :label="item.name" :value="item.id" />
|
||||
<el-option v-for="item in options" :key="item.id" :label="item.name" :value="item.id">
|
||||
<div class="ellipsis" :title="item.name" style="max-width: 500px; padding-right: 30px">
|
||||
{{ item.name }}
|
||||
</div>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('user.user_status')">
|
||||
@@ -295,7 +346,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted, nextTick } from 'vue'
|
||||
import { ref, unref, reactive, onMounted, nextTick } from 'vue'
|
||||
import UserImport from './UserImport.vue'
|
||||
import SuccessFilled from '@/assets/svg/gou_icon.svg'
|
||||
import CircleCloseFilled from '@/assets/svg/icon_ban_filled.svg'
|
||||
@@ -308,11 +359,13 @@ import IconLock from '@/assets/svg/icon-key_outlined.svg'
|
||||
import IconOpeEdit from '@/assets/svg/icon_edit_outlined.svg'
|
||||
import IconOpeDelete from '@/assets/svg/icon_delete.svg'
|
||||
import iconFilter from '@/assets/svg/icon-filter_outlined.svg'
|
||||
import ccmUpload from '@/assets/svg/icon_ccm-upload_outlined.svg'
|
||||
// import ccmUpload from '@/assets/svg/icon_ccm-upload_outlined.svg'
|
||||
import icon_add_outlined from '@/assets/svg/icon_add_outlined.svg'
|
||||
import { userApi } from '@/api/user'
|
||||
import { workspaceList } from '@/api/workspace'
|
||||
import { formatTimestamp } from '@/utils/date'
|
||||
import { ClickOutside as vClickOutside } from 'element-plus-secondary'
|
||||
import icon_warning_filled from '@/assets/svg/icon_warning_filled.svg'
|
||||
|
||||
const { t } = useI18n()
|
||||
const keyword = ref('')
|
||||
@@ -404,6 +457,12 @@ const rules = {
|
||||
message: t('datasource.please_enter') + t('common.empty') + t('user.email'),
|
||||
trigger: 'blur',
|
||||
},
|
||||
{
|
||||
required: true,
|
||||
pattern: /^[a-zA-Z0-9_._-]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$/,
|
||||
message: t('datasource.incorrect_email_format'),
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
@@ -424,6 +483,34 @@ const passwordRules = {
|
||||
],
|
||||
}
|
||||
|
||||
const closeResetInfo = (row: any) => {
|
||||
row.popoverRef?.hide()
|
||||
row.resetInfoShow = false
|
||||
}
|
||||
const setPopoverRef = (el: any, row: any) => {
|
||||
row.popoverRef = el
|
||||
}
|
||||
|
||||
const copyText = () => {
|
||||
navigator.clipboard
|
||||
.writeText('SQLBot@123456')
|
||||
.then(function () {
|
||||
ElMessage.success(t('embedded.copy_successful'))
|
||||
})
|
||||
.catch(function () {
|
||||
ElMessage.error(t('embedded.copy_successful'))
|
||||
})
|
||||
}
|
||||
|
||||
const setButtonRef = (el: any, row: any) => {
|
||||
row.buttonRef = el
|
||||
}
|
||||
const onClickOutside = (row: any) => {
|
||||
if (row.popoverRef) {
|
||||
unref(row.popoverRef).popperRef?.delayHide?.()
|
||||
}
|
||||
}
|
||||
|
||||
const multipleTableRef = ref()
|
||||
const multipleSelectionAll = ref<any[]>([])
|
||||
const dialogTitle = ref('')
|
||||
@@ -447,9 +534,9 @@ const handleEditPassword = (id: any) => {
|
||||
})
|
||||
}
|
||||
|
||||
const handleUserImport = () => {
|
||||
userImportRef.value.showDialog()
|
||||
}
|
||||
// const handleUserImport = () => {
|
||||
// userImportRef.value.showDialog()
|
||||
// }
|
||||
|
||||
const handleConfirmPassword = () => {
|
||||
passwordRef.value.validate((val: any) => {
|
||||
@@ -533,8 +620,13 @@ const editHandler = (row: any) => {
|
||||
}
|
||||
|
||||
const statusHandler = (row: any) => {
|
||||
state.form = { ...row }
|
||||
editTerm()
|
||||
/* state.form = { ...row }
|
||||
editTerm() */
|
||||
const param = {
|
||||
id: row.id,
|
||||
status: row.status,
|
||||
}
|
||||
userApi.status(param)
|
||||
}
|
||||
|
||||
const cancelDelete = () => {
|
||||
@@ -569,30 +661,14 @@ const deleteHandler = (row: any) => {
|
||||
customClass: 'confirm-no_icon',
|
||||
autofocus: false,
|
||||
}).then(() => {
|
||||
multipleSelectionAll.value = multipleSelectionAll.value.filter((ele) => ele.id !== row.id)
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: t('dashboard.delete_success'),
|
||||
userApi.delete(row.id).then(() => {
|
||||
multipleSelectionAll.value = multipleSelectionAll.value.filter((ele) => ele.id !== row.id)
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: t('dashboard.delete_success'),
|
||||
})
|
||||
search()
|
||||
})
|
||||
search()
|
||||
// userApi.delete(row.id).then(() => {
|
||||
// multipleSelectionAll.value = multipleSelectionAll.value.filter((ele) => ele.id !== row.id)
|
||||
// ElMessage({
|
||||
// type: 'success',
|
||||
// message: t('dashboard.delete_success'),
|
||||
// })
|
||||
// search()
|
||||
// })
|
||||
})
|
||||
}
|
||||
|
||||
/* const openDialog = () => {
|
||||
dialogFormVisible.value = true
|
||||
} */
|
||||
const resetForm = () => {
|
||||
if (!termFormRef.value) return
|
||||
Object.keys(state.form).forEach((key) => {
|
||||
state.form[key as keyof typeof state.form] = ''
|
||||
})
|
||||
}
|
||||
|
||||
@@ -600,7 +676,7 @@ const closeForm = () => {
|
||||
dialogFormVisible.value = false
|
||||
}
|
||||
const onFormClose = () => {
|
||||
resetForm()
|
||||
state.form = { ...defaultForm }
|
||||
dialogFormVisible.value = false
|
||||
}
|
||||
|
||||
@@ -639,22 +715,8 @@ const search = () => {
|
||||
})
|
||||
}
|
||||
const addTerm = () => {
|
||||
userApi
|
||||
.add(state.form)
|
||||
.then(() => {
|
||||
dialogFormVisible.value = false
|
||||
search()
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: t('common.save_success'),
|
||||
})
|
||||
})
|
||||
.finally(() => {
|
||||
state.form = { ...defaultForm }
|
||||
})
|
||||
}
|
||||
const editTerm = () => {
|
||||
userApi.edit(state.form).then(() => {
|
||||
const { account, email, name, oid, status } = state.form
|
||||
userApi.add({ account, email, name, oid, status }).then(() => {
|
||||
dialogFormVisible.value = false
|
||||
search()
|
||||
ElMessage({
|
||||
@@ -663,14 +725,33 @@ const editTerm = () => {
|
||||
})
|
||||
})
|
||||
}
|
||||
const editTerm = () => {
|
||||
const { account, id, create_time, email, language, name, oid, oid_list, origin, status } =
|
||||
state.form
|
||||
userApi
|
||||
.edit({ account, id, create_time, email, language, name, oid, oid_list, origin, status })
|
||||
.then(() => {
|
||||
dialogFormVisible.value = false
|
||||
search()
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: t('common.save_success'),
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const duplicateName = () => {
|
||||
if (state.form.id) {
|
||||
editTerm()
|
||||
} else {
|
||||
addTerm()
|
||||
}
|
||||
}
|
||||
|
||||
const saveHandler = () => {
|
||||
termFormRef.value.validate((res: any) => {
|
||||
if (res) {
|
||||
if (state.form.id) {
|
||||
editTerm()
|
||||
} else {
|
||||
addTerm()
|
||||
}
|
||||
duplicateName()
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -839,3 +920,54 @@ onMounted(() => {
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="less">
|
||||
.reset-pwd-confirm {
|
||||
padding: 5px 15px;
|
||||
.confirm-header {
|
||||
width: 100%;
|
||||
min-height: 40px;
|
||||
line-height: 40px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
.icon-span {
|
||||
color: var(--ed-color-warning);
|
||||
font-size: 22px;
|
||||
i {
|
||||
top: 3px;
|
||||
}
|
||||
}
|
||||
.header-span {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
margin-left: 10px;
|
||||
white-space: pre-wrap;
|
||||
word-break: keep-all;
|
||||
}
|
||||
}
|
||||
.confirm-foot {
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
margin-top: 15px;
|
||||
.ed-button {
|
||||
min-width: 48px;
|
||||
height: 28px;
|
||||
line-height: 28px;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
.confirm-warning {
|
||||
font-size: 12px;
|
||||
color: var(--ed-color-danger);
|
||||
margin-left: 33px;
|
||||
}
|
||||
.confirm-content {
|
||||
margin-left: 33px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -97,6 +97,26 @@ const init = () => {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const duplicateName = async () => {
|
||||
const res = await workspaceList()
|
||||
const names = res.filter((ele: any) => ele.id !== workspaceForm.id).map((ele: any) => ele.name)
|
||||
if (names.includes(workspaceForm.name)) {
|
||||
ElMessage.error(t('embedded.duplicate_name'))
|
||||
return
|
||||
}
|
||||
|
||||
const req = workspaceForm.id ? workspaceUpdate : workspaceCreate
|
||||
req(workspaceForm).then(() => {
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: t('common.save_success'),
|
||||
})
|
||||
init()
|
||||
fieldDialog.value = false
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
init()
|
||||
})
|
||||
@@ -224,15 +244,7 @@ const closeField = () => {
|
||||
const saveField = () => {
|
||||
workspaceFormRef.value.validate((res: any) => {
|
||||
if (res) {
|
||||
const req = workspaceForm.id ? workspaceUpdate : workspaceCreate
|
||||
req(workspaceForm).then(() => {
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: t('common.save_success'),
|
||||
})
|
||||
init()
|
||||
fieldDialog.value = false
|
||||
})
|
||||
duplicateName()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user