feat(系统参数): 支持 SQLBot 嵌入设置

This commit is contained in:
dataeaseShu
2025-08-28 13:44:55 +08:00
committed by dataeaseShu
parent f366000546
commit 2e9fd0e50d
7 changed files with 522 additions and 1 deletions

View File

@@ -0,0 +1,30 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M22.6662 10.9352H22.4815C22.5918 11.5417 22.647 12.1569 22.6466 12.7733C22.6505 13.5304 22.5604 14.2851 22.3783 15.02H22.8989C23.1216 15.02 23.3353 14.9315 23.4928 14.774C23.6504 14.6164 23.7389 14.4028 23.7389 14.18V12.008C23.7389 11.8671 23.7111 11.7276 23.6572 11.5975C23.6033 11.4673 23.5243 11.349 23.4247 11.2494C23.3251 11.1498 23.2068 11.0708 23.0767 11.0169C22.9465 10.9629 22.8071 10.9352 22.6662 10.9352Z" fill="#149CC5"/>
<path d="M1.35329 12.7733C1.35289 12.1569 1.40815 11.5417 1.51841 10.9352H1.33379C1.04929 10.9352 0.77645 11.0482 0.575285 11.2494C0.374121 11.4506 0.261108 11.7234 0.261108 12.0079V14.1799C0.261108 14.4027 0.349608 14.6164 0.507139 14.7739C0.664669 14.9314 0.878326 15.0199 1.10111 15.0199H1.62167C1.43954 14.2851 1.34939 13.5304 1.35329 12.7733Z" fill="#69CAA4"/>
<path d="M12 3.036C6.48872 3.036 2.021 7.26186 2.021 12.7732C2.021 18.2845 6.48878 20.964 12 20.964C17.5113 20.964 21.979 18.2846 21.979 12.7734C21.979 7.26216 17.5113 3.036 12 3.036ZM14.4212 16.9003H9.57884C8.89344 16.9012 8.2158 16.7552 7.59164 16.472C7.14074 16.2673 5.82542 16.6973 5.46164 16.3705C5.02082 15.9745 5.55524 14.7954 5.28464 14.2616C4.94336 13.5882 4.76607 12.8437 4.7672 12.0887C4.7672 10.8127 5.27412 9.58884 6.17644 8.68652C7.07876 7.7842 8.30258 7.27728 9.57866 7.27728H14.4211C15.6972 7.27728 16.921 7.7842 17.8233 8.68652C18.7256 9.58884 19.2325 10.8127 19.2325 12.0887C19.2326 12.7206 19.1081 13.3463 18.8664 13.93C18.6246 14.5138 18.2702 15.0443 17.8234 15.4911C17.3767 15.9379 16.8463 16.2923 16.2625 16.5341C15.6788 16.776 15.0531 16.9005 14.4213 16.9005L14.4212 16.9003Z" fill="url(#paint0_linear_25461_245)"/>
<path d="M12 3.036C6.48872 3.036 2.021 7.26186 2.021 12.7732C2.021 18.2845 6.48878 20.964 12 20.964C17.5113 20.964 21.979 18.2846 21.979 12.7734C21.979 7.26216 17.5113 3.036 12 3.036ZM14.4212 16.9003H9.57884C8.89344 16.9012 8.2158 16.7552 7.59164 16.472C7.14074 16.2673 5.82542 16.6973 5.46164 16.3705C5.02082 15.9745 5.55524 14.7954 5.28464 14.2616C4.94336 13.5882 4.76607 12.8437 4.7672 12.0887C4.7672 10.8127 5.27412 9.58884 6.17644 8.68652C7.07876 7.7842 8.30258 7.27728 9.57866 7.27728H14.4211C15.6972 7.27728 16.921 7.7842 17.8233 8.68652C18.7256 9.58884 19.2325 10.8127 19.2325 12.0887C19.2326 12.7206 19.1081 13.3463 18.8664 13.93C18.6246 14.5138 18.2702 15.0443 17.8234 15.4911C17.3767 15.9379 16.8463 16.2923 16.2625 16.5341C15.6788 16.776 15.0531 16.9005 14.4213 16.9005L14.4212 16.9003Z" fill="url(#paint1_linear_25461_245)"/>
<path d="M8.4365 11.932H7.01324V15.0357H8.4365V11.932Z" fill="#75CCCC"/>
<path d="M11.2958 10.5546H9.8725V15.0357H11.2958V10.5546Z" fill="#75CCCC"/>
<path d="M14.1551 12.3505H12.7318V15.0357H14.1551V12.3505Z" fill="#75CCCC"/>
<path d="M16.9868 9.96175H15.5635V15.0357H16.9868V9.96175Z" fill="#75CCCC"/>
<defs>
<linearGradient id="paint0_linear_25461_245" x1="5.32016" y1="3.55278" x2="20.0794" y2="23.3993" gradientUnits="userSpaceOnUse">
<stop stop-color="#B2F288"/>
<stop offset="0.181" stop-color="#80D79C"/>
<stop offset="0.371" stop-color="#52BEAD"/>
<stop offset="0.554" stop-color="#2FAABB"/>
<stop offset="0.724" stop-color="#159CC5"/>
<stop offset="0.878" stop-color="#0594CB"/>
<stop offset="1" stop-color="#0091CD"/>
</linearGradient>
<linearGradient id="paint1_linear_25461_245" x1="5.32016" y1="3.55278" x2="20.0794" y2="23.3993" gradientUnits="userSpaceOnUse">
<stop stop-color="#B2F288"/>
<stop offset="0.181" stop-color="#80D79C"/>
<stop offset="0.371" stop-color="#52BEAD"/>
<stop offset="0.554" stop-color="#2FAABB"/>
<stop offset="0.724" stop-color="#159CC5"/>
<stop offset="0.878" stop-color="#0594CB"/>
<stop offset="1" stop-color="#0091CD"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

@@ -1,5 +1,16 @@
export default {
common: {
next_week: 'Next week',
next_month: 'Next month',
next_quarter: 'Next quarter',
next_year: 'Next year',
sqlbot_settings: 'SQLBot Settings',
third_party_embed: 'Third-party embed',
application_id: 'Application ID',
sqlbot_server_url: 'SQLBot server URL',
enter_the_url: 'Please enter the URL',
the_application_id: 'Please enter the application ID',
embed: 'Embed',
empty: ' ',
first_item: 'First Item',
cross_source: 'Cross-source',

View File

@@ -1,5 +1,16 @@
export default {
common: {
next_week: '下週',
next_month: '下個月',
next_quarter: '下季',
next_year: '明年',
sqlbot_settings: 'SQLBot 設定',
third_party_embed: '第三方嵌入',
application_id: '應用程式 ID',
sqlbot_server_url: 'SQLBot 伺服器 URL',
enter_the_url: '請輸入 URL',
the_application_id: '請輸入應用程式 ID',
embed: '嵌入',
empty: '',
first_item: '首項',
cross_source: '跨源',

View File

@@ -1,5 +1,16 @@
export default {
common: {
next_week: '下周',
next_month: '下月',
next_quarter: '下季',
next_year: '明年',
sqlbot_settings: 'SQLBot 设置',
third_party_embed: '第三方嵌入',
application_id: '应用 ID',
sqlbot_server_url: 'SQLBot 服务器 URL',
enter_the_url: '请输入 URL',
the_application_id: '请输入应用 ID',
embed: '嵌入',
empty: '',
first_item: '首项',
cross_source: '跨源',

View File

@@ -15,6 +15,7 @@
jsname="L21lbnUvc2V0dGluZy9lbWFpbC9pbmRleA=="
v-if="activeName === 'email'"
/>
<third-party v-if="activeName === 'third_party'" />
</div>
</div>
<xpack-component jsname="L2NvbXBvbmVudC9tZW51LWhhbmRsZXIvRW1haWxIYW5kbGVy" @loaded="addTable" />
@@ -25,6 +26,7 @@ import { ref } from 'vue'
import { useI18n } from '@/hooks/web/useI18n'
import MapSetting from './map/MapSetting.vue'
import BasicInfo from './basic/BasicInfo.vue'
import ThirdParty from './third-party/index.vue'
import EngineInfo from '@/views/system/parameter/engine/EngineInfo.vue'
import { XpackComponent } from '@/components/plugin'
/* import EmailInfo from './email/EmailInfo.vue' */
@@ -33,7 +35,11 @@ const { t } = useI18n()
const tabArray = ref([
{ label: t('system.basic_settings'), name: 'basic' },
{ label: t('system.map_settings'), name: 'map' },
{ label: t('system.engine_settings'), name: 'engine' }
{ label: t('system.engine_settings'), name: 'engine' },
{
label: t('common.third_party_embed'),
name: 'third_party'
}
])
const activeName = ref('basic')

View File

@@ -0,0 +1,207 @@
<script lang="ts" setup>
import { ref, reactive } from 'vue'
import { ElMessage, ElLoading } from 'element-plus-secondary'
import { useI18n } from '@/hooks/web/useI18n'
import type { FormInstance, FormRules } from 'element-plus-secondary'
import request from '@/config/axios'
const { t } = useI18n()
const dialogVisible = ref(false)
const loadingInstance = ref(null)
const dingtalkForm = ref<FormInstance>()
interface DingtalkForm {
id?: string
domain?: string
}
const state = reactive({
form: reactive<DingtalkForm>({
id: null,
domain: null
})
})
const validateUrl = (rule, value, callback) => {
const reg = new RegExp(/(http|https):\/\/([\w.]+\/?)\S*/)
if (!reg.test(value)) {
callback(new Error(t('system.incorrect_please_re_enter')))
} else {
callback()
}
}
const rule = reactive<FormRules>({
id: [
{
required: true,
message: t('common.the_application_id'),
trigger: 'blur'
}
],
domain: [
{
required: true,
message: t('common.enter_the_url'),
trigger: 'blur'
},
{ required: true, validator: validateUrl, trigger: 'blur' }
]
})
const edit = row => {
state.form = {
id: row.id,
domain: row.domain
}
dialogVisible.value = true
}
const save = () => {
const param = { ...state.form }
const method = request.post({ url: '/sysParameter/sqlbot', data: param })
showLoading()
method
.then(res => {
if (!res.msg) {
ElMessage.success(t('common.save_success'))
emits('saved')
reset()
}
closeLoading()
})
.catch(() => {
closeLoading()
})
}
const emits = defineEmits(['saved'])
const submitForm = async (formEl: FormInstance | undefined) => {
if (!formEl) return
await formEl.validate(valid => {
if (valid) {
let url = `${
state.form.domain.endsWith('/') ? state.form.domain : state.form.domain + '/'
}api/v1/system/assistant/info/${state.form.id}`
fetch(url)
.then(response => response.json())
.finally(() => {
save()
})
}
})
}
const resetForm = (formEl: FormInstance | undefined) => {
if (!formEl) return
formEl.resetFields()
dialogVisible.value = false
}
const reset = () => {
resetForm(dingtalkForm.value)
}
const showLoading = () => {
loadingInstance.value = ElLoading.service({
target: '.platform-info-drawer'
})
}
const closeLoading = () => {
loadingInstance.value?.close()
}
const validateHandler = () => {
let url = `${
state.form.domain.endsWith('/') ? state.form.domain : state.form.domain + '/'
}api/v1/system/assistant/info/${state.form.id}`
fetch(url)
.then(response => response.json())
.then(() => {
state.form.valid = true
ElMessage.success(t('datasource.validate_success'))
})
.catch(() => {
state.form.enable = false
state.form.valid = false
save()
})
}
defineExpose({
edit
})
</script>
<template>
<el-drawer
:title="t('common.sqlbot_settings')"
v-model="dialogVisible"
modal-class="platform-info-drawer"
size="600px"
direction="rtl"
>
<el-form
ref="dingtalkForm"
require-asterisk-position="right"
:model="state.form"
:rules="rule"
label-width="80px"
label-position="top"
>
<el-form-item :label="$t('common.sqlbot_server_url')" prop="domain">
<el-input v-model="state.form.domain" :placeholder="t('common.enter_the_url')" />
</el-form-item>
<el-form-item :label="$t('common.application_id')" prop="id">
<el-input v-model="state.form.id" :placeholder="t('common.the_application_id')" />
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="resetForm(dingtalkForm)">{{ t('common.cancel') }}</el-button>
<el-button :disabled="!state.form.id || !state.form.domain" @click="validateHandler">
{{ t('commons.validate') }}
</el-button>
<el-button type="primary" @click="submitForm(dingtalkForm)">
{{ t('commons.save') }}
</el-button>
</span>
</template>
</el-drawer>
</template>
<style lang="less">
.platform-info-drawer {
.ed-drawer__footer {
height: 64px !important;
padding: 16px 24px !important;
.dialog-footer {
height: 32px;
line-height: 32px;
}
}
.ed-form-item__label {
line-height: 22px !important;
height: 22px !important;
}
}
</style>
<style lang="less" scoped>
.platform-info-drawer {
.ed-form-item {
margin-bottom: 16px;
}
.is-error {
margin-bottom: 40px !important;
}
.input-with-select {
.ed-input-group__prepend {
width: 72px;
background-color: #fff;
padding: 0 20px;
color: #1f2329;
text-align: center;
font-family: var(--de-custom_font, 'PingFang');
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 22px;
}
}
}
</style>

View File

@@ -0,0 +1,245 @@
<script lang="ts" setup>
import logo_dingtalk from '@/assets/svg/icon_sqlbot_colorful.svg'
import { ref, reactive } from 'vue'
import InfoTemplate from '@/views/system/common/InfoTemplate.vue'
import thirdEdit from './ThirdEdit.vue'
import request from '@/config/axios'
import { useI18n } from '@/hooks/web/useI18n'
import { ElMessage } from 'element-plus-secondary'
const { t } = useI18n()
const editor = ref()
const existInfo = ref(true)
const copyList = []
const settingList = reactive([
{
pkey: t('common.sqlbot_server_url'),
pval: '',
type: 'text',
sort: 2
},
{
pkey: t('common.application_id'),
pval: '',
type: 'text',
sort: 3
}
])
const info = ref({})
const mappingArray = ['domain', 'id']
const search = () => {
const url = '/sysParameter/sqlbot'
request.get({ url }).then(res => {
if (res.data) {
info.value = res.data
for (let index = 0; index < settingList.length; index++) {
const element = settingList[index]
const key = mappingArray[index]
element['pval'] = res.data[key] || '-'
}
}
})
}
const switchEnableApi = enable => {
const param = { ...info.value }
request.post({ url: '/sysParameter/sqlbot', data: param })
}
const edit = () => {
editor?.value.edit(info.value)
}
const validate = () => {
if (info.value?.id && info.value?.domain) {
validateHandler()
}
}
const save = () => {
const param = { ...info.value }
const method = request.post({ url: '/sysParameter/sqlbot', data: param })
method
.then(res => {
console.log(res)
})
.catch(() => {
console.log(res)
})
}
const validateHandler = () => {
let url = `${
info.value.domain.endsWith('/') ? info.value.domain : info.value.domain + '/'
}api/v1/system/assistant/info/${info.value.id}`
fetch(url)
.then(response => response.json())
.then(() => {
info.value.valid = true
ElMessage.success(t('datasource.validate_success'))
})
.catch(() => {
info.value.enable = false
info.value.valid = false
save()
})
}
search()
</script>
<template>
<div v-if="info.id" class="container-sys-platform">
<div class="platform-head-container just-head">
<div class="platform-setting-head">
<div class="platform-setting-head-left">
<div class="lead-left-icon">
<el-icon size="24px">
<Icon name="logo_dingtalk"><logo_dingtalk class="svg-icon" /></Icon>
</el-icon>
<span>SQLBot</span>
</div>
<div class="lead-left-status" :class="{ invalid: !info.valid }">
<span>{{ info.valid ? t('datasource.valid') : t('datasource.invalid') }}</span>
</div>
</div>
<div v-if="existInfo" class="platform-setting-head-right">
<el-switch class="status-switch" v-model="info.valid" @change="switchEnableApi" />
</div>
<div v-else class="platform-setting-head-right-btn">
<el-button type="primary" @click="edit">{{ t('system.access') }}</el-button>
</div>
</div>
</div>
<InfoTemplate
v-if="existInfo"
class="platform-setting-main"
:copy-list="copyList"
setting-key="dingtalk"
setting-title=""
:hide-head="true"
:setting-data="settingList"
@edit="edit"
/>
<div v-if="existInfo" class="platform-foot-container">
<el-button type="primary" @click="edit">
{{ t('commons.edit') }}
</el-button>
<el-button secondary :disabled="!info.id || !info.domain" @click="validate">{{
t('commons.validate')
}}</el-button>
</div>
</div>
<div v-else class="no-params">
<el-icon size="24px">
<Icon name="logo_dingtalk"><logo_dingtalk class="svg-icon" /></Icon>
</el-icon>
<span style="margin-left: 8px">SQLBot</span>
<el-button type="primary" @click="edit">
{{ t('common.embed') }}
</el-button>
</div>
<third-edit ref="editor" @saved="search" />
</template>
<style lang="less" scoped>
.no-params {
height: 72px;
border-radius: 4px;
display: flex;
padding: 0 24px;
align-items: center;
.ed-button {
margin-left: auto;
}
}
.container-sys-platform {
padding: 24px;
overflow: hidden;
border-radius: 4px;
background: var(--ContentBG, #ffffff);
}
.platform-head-container {
height: 41px;
border-bottom: 1px solid #1f232926;
}
.just-head {
height: auto !important;
border: none !important;
}
.platform-setting-head {
height: 24px;
display: flex;
align-items: center;
justify-content: space-between;
.platform-setting-head-left {
display: flex;
.lead-left-icon {
display: flex;
line-height: 24px;
align-items: center;
i {
width: 24px;
height: 24px;
font-size: 20px;
}
span {
margin-left: 4px;
font-family: var(--de-custom_font, 'PingFang');
font-size: 16px;
font-style: normal;
font-weight: 500;
line-height: 24px;
}
}
.lead-left-status {
margin-left: 4px;
width: 40px;
height: 24px;
background: #34c72433;
padding: 0 6px;
font-size: 14px;
border-radius: 2px;
overflow: hidden;
span {
line-height: 24px;
color: #2ca91f;
}
}
.invalid {
background: #f54a4533 !important;
span {
color: #d03f3b !important;
}
}
}
.platform-setting-head-right-btn {
height: 32px;
line-height: 32px;
}
.platform-setting-head-right {
height: 22px;
line-height: 24px;
display: flex;
span {
margin-right: 8px;
font-size: 14px;
height: 22px;
line-height: 22px;
}
.status-switch {
line-height: 22px !important;
height: 22px !important;
}
}
}
.platform-setting-main {
display: inline-block;
width: 100%;
padding: 16px 0 0 0 !important;
::v-deep(.info-template-content) {
display: contents !important;
}
}
.platform-foot-container {
height: 32px;
margin-top: -7px;
}
</style>