feat: 仪表板支持问数 (#17216)

This commit is contained in:
王嘉豪
2025-10-17 16:06:05 +08:00
committed by GitHub
parent 6ce2bf144b
commit 7b27114fcf
16 changed files with 356 additions and 5 deletions

View File

@@ -1,6 +1,7 @@
package io.dataease.chart.dao.ext.mapper;
import io.dataease.api.chart.vo.ViewSelectorVO;
import io.dataease.api.dataset.vo.DataSQLBotDatasetVO;
import io.dataease.chart.dao.auto.entity.CoreChartView;
import io.dataease.chart.dao.ext.entity.ChartBasePO;
import io.dataease.extensions.view.dto.ChartViewDTO;
@@ -35,4 +36,30 @@ public interface ExtChartViewMapper {
LIMIT 1
""")
ChartViewDTO findChartViewAround(@Param("viewId") String viewId);
@Select("""
select DISTINCT table_id from core_chart_view_snapshot where scene_id=#{dvId}
""")
List<Long> findDatasetGroupIdByDvId(@Param("dvId") String dvId);
@Select("""
SELECT
DISTINCT
sdg.id AS table_id,
sdg.NAME AS table_name,
cd.id AS ds_id,
cd.NAME AS ds_name\s
FROM
core_dataset_table sdt
INNER JOIN core_datasource cd ON sdt.datasource_id = cd.id
INNER JOIN core_dataset_group sdg ON sdt.dataset_group_id = sdg.id
INNER JOIN snapshot_core_chart_view sccv on sccv.table_id = sdt.dataset_group_id\s
WHERE
sccv.scene_id = #{dvId}
""")
List<DataSQLBotDatasetVO> findDataSQLBotDatasetDvId(@Param("dvId") String dvId);
}

View File

@@ -6,12 +6,15 @@ import io.dataease.api.dataset.union.DatasetGroupInfoDTO;
import io.dataease.api.dataset.union.DatasetTableInfoDTO;
import io.dataease.api.dataset.union.UnionDTO;
import io.dataease.api.dataset.vo.DataSQLBotAssistantVO;
import io.dataease.api.dataset.vo.DataSQLBotDatasetVO;
import io.dataease.api.dataset.vo.SQLBotAssistanTable;
import io.dataease.api.dataset.vo.SQLBotAssistantField;
import io.dataease.api.permissions.dataset.api.ColumnPermissionsApi;
import io.dataease.api.permissions.dataset.dto.DataSetColumnPermissionsDTO;
import io.dataease.api.permissions.dataset.dto.DataSetRowPermissionsTreeDTO;
import io.dataease.auth.bo.TokenUserBO;
import io.dataease.chart.dao.auto.mapper.CoreChartViewMapper;
import io.dataease.chart.dao.ext.mapper.ExtChartViewMapper;
import io.dataease.commons.utils.EncryptUtils;
import io.dataease.constant.ColumnPermissionConstants;
import io.dataease.dataset.dao.auto.entity.CoreDatasetGroup;
@@ -68,6 +71,9 @@ public class DatasetSQLBotManage {
@Resource
private DataSetAssistantMapper dataSetAssistantMapper;
@Resource
private ExtChartViewMapper extChartViewMapper;
@Resource
private EngineManage engineManage;
@@ -147,8 +153,12 @@ public class DatasetSQLBotManage {
return datasetRowPermissions.stream().collect(Collectors.groupingBy(DataSetRowPermissionsTreeDTO::getDatasetId));
}
public List<DataSQLBotDatasetVO> getDatasetList(String dvInfo){
return extChartViewMapper.findDataSQLBotDatasetDvId(dvInfo);
}
public List<DataSQLBotAssistantVO> getDatasourceList(Long dsId, Long datasetId) {
public List<DataSQLBotAssistantVO> getDatasourceList(Long dsId, Long datasetId, String dvInfo) {
TokenUserBO user = Objects.requireNonNull(AuthUtils.getUser());
Long oid = user.getDefaultOid();
Long uid = user.getUserId();
@@ -165,6 +175,12 @@ public class DatasetSQLBotManage {
if (ObjectUtils.isNotEmpty(dsId)) {
queryWrapper.eq("cd.id", dsId);
}
if(ObjectUtils.isNotEmpty(dvInfo)){
List<Long> targetDsGroupIds = extChartViewMapper.findDatasetGroupIdByDvId(dvInfo);
if(CollectionUtils.isNotEmpty(targetDsGroupIds)){
queryWrapper.in("cdg.id", targetDsGroupIds);
}
}
if (ObjectUtils.isEmpty(model)) {
if (!isAdmin) {
return null;

View File

@@ -2,6 +2,7 @@ package io.dataease.dataset.server;
import io.dataease.api.dataset.DataAssistantApi;
import io.dataease.api.dataset.vo.DataSQLBotAssistantVO;
import io.dataease.api.dataset.vo.DataSQLBotDatasetVO;
import io.dataease.dataset.manage.DatasetSQLBotManage;
import jakarta.annotation.Resource;
import org.springframework.web.bind.annotation.RequestMapping;
@@ -16,7 +17,12 @@ public class DatasetSQLBotServer implements DataAssistantApi {
@Resource
private DatasetSQLBotManage datasetSQLBotManage;
@Override
public List<DataSQLBotAssistantVO> getDatasourceList(Long dsId, Long datasetId) {
return datasetSQLBotManage.getDatasourceList(dsId, datasetId);
public List<DataSQLBotAssistantVO> getDatasourceList(Long dsId, Long datasetId, String dvInfo) {
return datasetSQLBotManage.getDatasourceList(dsId, datasetId,dvInfo);
}
@Override
public List<DataSQLBotDatasetVO> getDatasetList(String dvInfo) {
return datasetSQLBotManage.getDatasetList(dvInfo);
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

View File

@@ -0,0 +1,3 @@
import request from '@/config/axios'
export const findDvSqlBotDataset = dvInfo => request.get({ url: '/sqlbot/dataset/' + dvInfo })

View File

@@ -2929,6 +2929,7 @@ export default {
column_name: 'Field name'
},
visualization: {
cur_sq_dataset: 'Currently selected dataset:',
data_match_type: 'Data match type',
select_resource: 'Select {0}',
change_screen_page: 'Change {0}',

View File

@@ -2848,6 +2848,7 @@ export default {
column_name: '欄位名稱'
},
visualization: {
cur_sq_dataset: '當前選擇的數據集:',
data_match_type: '資料匹配方式',
select_resource: '請選擇{0}',
change_screen_page: '更換{0}',

View File

@@ -2857,6 +2857,7 @@ export default {
column_name: '字段名称'
},
visualization: {
cur_sq_dataset: '当前选择的数据集:',
data_match_type: '数据匹配方式',
select_resource: '请选择{0}',
change_screen_page: '更换{0}',

View File

@@ -1,7 +1,6 @@
const suffix = `${import.meta.env.VITE_VERSION}-dataease`
const dom = document.querySelector('head')
const cb = dom.appendChild.bind(dom)
const formatterUrl = <T extends Node>(node: T, prefix: string) => {
if (['SCRIPT', 'LINK'].includes(node.nodeName)) {

View File

@@ -34,6 +34,7 @@ import eventBus from '@/utils/eventBus'
import { useI18n } from '@/hooks/web/useI18n'
import DashboardHiddenComponent from '@/components/dashboard/DashboardHiddenComponent.vue'
import { recoverToPublished } from '@/api/visualization/dataVisualization'
import SqlAssistant from '@/views/sqlbot/assistant.vue'
const embeddedStore = useEmbedded()
const { wsCache } = useCache()
const canvasCacheOutRef = ref(null)
@@ -338,6 +339,7 @@ onUnmounted(() => {
:class="{ 'preview-content': editMode === 'preview' }"
element-loading-background="rgba(0, 0, 0, 0)"
>
<SqlAssistant></SqlAssistant>
<!-- 中间画布 -->
<main class="center" :class="{ 'de-screen-full': fullscreenFlag }">
<de-canvas

View File

@@ -0,0 +1,63 @@
<template>
<div class="inactivate-div">
<el-dropdown @command="handleCommand">
<div class="inactivate-button el-dropdown-link" :style="{ color: fontColor }">
API 功能
<img class="api-img" src="/api.png" alt="inactivate" />
<el-icon class="el-icon--right">
<arrow-down />
</el-icon>
</div>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item command="showHistory">历史记录(/)</el-dropdown-item>
<el-dropdown-item command="newChat" divided>新建对话</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</template>
<script setup lang="ts">
import { ElDropdown, ElDropdownMenu, ElDropdownItem, ElIcon } from 'element-plus-secondary'
import { ArrowDown } from '@element-plus/icons-vue'
defineProps({
fontColor: {
type: String,
default: '#000000'
}
})
const emit = defineEmits(['showHistory', 'newChat'])
const handleCommand = (command: string) => {
if (command === 'showHistory') {
emit('showHistory')
} else if (command === 'newChat') {
emit('newChat')
}
}
</script>
<style scoped>
.inactivate-div {
position: absolute;
right: 32px;
top: 92px;
height: auto;
padding: 8px 16px;
display: flex;
border-radius: 4px;
}
.inactivate-button {
display: flex;
align-items: center;
cursor: pointer;
font-size: 14px;
}
.api-img {
width: 24px;
height: 24px;
margin: 0 4px;
}
</style>

View File

@@ -0,0 +1,77 @@
<script setup lang="ts">
import { findDvSqlBotDataset } from '@/api/aiSqlBot'
import { onMounted, reactive } from 'vue'
import { storeToRefs } from 'pinia'
import { dvMainStoreWithOut } from '@/store/modules/data-visualization/dvMain'
import { useI18n } from '@/hooks/web/useI18n'
const { t } = useI18n()
const dvMainStore = dvMainStoreWithOut()
const { dvInfo } = storeToRefs(dvMainStore)
const init = () => {
if (dvInfo.value.id) {
findDvSqlBotDataset(dvInfo.value.id).then(res => {
state.baseDatasetInfo = res.data
state.curDatasetInfo = state.baseDatasetInfo[0]
datasetSelect()
})
}
}
onMounted(() => {
init()
})
const state = reactive({
baseDatasetInfo: [],
curDatasetInfo: null,
curDatasetId: null
})
const datasetSelect = () => {
localStorage.setItem('dsId', state.curDatasetInfo.dsId)
localStorage.setItem('tableId', state.curDatasetInfo.tableId)
}
</script>
<template>
<el-row class="de-sq-assistant">
<span class="de-sq-tips">{{ t('visualization.cur_sq_dataset') }}</span>
<el-select
v-model="state.curDatasetInfo"
class="de-sq-select"
:teleported="false"
size="small"
@change="datasetSelect"
>
<el-option
v-for="option in state.baseDatasetInfo"
size="mini"
:key="option.tableId"
:value="option"
:label="option.tableName"
></el-option>
</el-select>
</el-row>
</template>
<style scoped lang="less">
.de-sq-assistant {
position: absolute;
display: flex;
bottom: 140px;
left: 20px;
width: 100%;
z-index: 10;
color: #646a73;
font-size: 14px;
}
.de-sq-tips {
font-size: 14px;
line-height: 28px;
}
.de-sq-select {
width: 150px;
}
</style>

View File

@@ -0,0 +1,131 @@
<template>
<div
id="dataease-v2-embedded-assistant-sqlbot"
class="dataease-v2-embedded-assistant-sqlbot"
></div>
</template>
<script lang="ts" setup>
import { createApp, onMounted, onUnmounted, reactive, ref } from 'vue'
import request from '@/config/axios'
import { useUserStoreWithOut } from '@/store/modules/user'
import SQDatasetSelect from '@/views/sqlbot/SQDatasetSelect.vue'
const userStore = useUserStoreWithOut()
const loading = ref(true)
const state = reactive({
domain: '',
id: '',
enabled: false,
valid: false,
historyShow: false
})
const sqlbotExist = ref(false)
const timer = ref()
const loadSqlbotInfo = () => {
const url = '/sysParameter/sqlbot'
request.get({ url }).then(res => {
if (res && res.data) {
const { domain, id, enabled, valid } = res.data
if (!enabled) {
console.error('sqlbot embedded disabled')
}
if (!valid) {
console.error('sqlbot embedded invalid')
}
state.domain = domain
state.id = id
state.enabled = enabled
state.valid = valid
loadSqlbotPage()
}
})
}
const loadSqlbotPage = () => {
const scriptId = `sqlbot-assistant-float-script-${state.assistantId}`
const exitsScript = document.getElementById(scriptId)
if (exitsScript && window['sqlbot_assistant_handler']) {
mountedEmbeddedPage()
return
}
console.log('==test==0=')
const script = document.createElement('script')
script.defer = true
script.async = true
script.id = scriptId
let sqlbotDomain = state.domain
if (sqlbotDomain.endsWith('/')) {
sqlbotDomain = sqlbotDomain.slice(0, -1)
}
script.src = `${sqlbotDomain}/assistant.js?id=${state.id}&online=true&userFlag=${
userStore.getUid
}&t=${new Date().getTime()}`
script.onload = () => {
console.log('==test==00=')
mountedEmbeddedPage()
}
document.head.appendChild(script)
}
const mountedEmbeddedPage = () => {
if (sqlbotExist.value) {
return
}
const tempTimer = setTimeout(() => {
console.log('==test==1=' + window['sqlbot_assistant_handler'])
if (window['sqlbot_assistant_handler']) {
console.log('==test==2=')
// window['sqlbot_assistant_handler'].mounted('#dataease-v2-embedded-assistant-sqlbot', {
// embeddedId: state.id,
// online: true,
// userFlag: userStore.getUid
// })
const container = document.getElementById('sqlbot-assistant-chat-container')
if (container) {
const mountPoint = document.createElement('div')
mountPoint.id = 'chat-component-mount-point'
container.appendChild(mountPoint)
const chatApp = createApp(SQDatasetSelect)
chatApp.mount(mountPoint)
}
loading.value = false
sqlbotExist.value = true
if (tempTimer) {
clearTimeout(tempTimer)
}
}
}, 2000)
}
onMounted(() => {
loadSqlbotInfo()
timer.value = setInterval(() => {
loadSqlbotInfo()
}, 30000)
})
onUnmounted(() => {
if (timer.value) {
clearInterval(timer.value)
timer.value = null
}
})
</script>
<style lang="less">
#sqlbot-assistant-chat-container {
z-index: 200;
}
</style>
<style lang="less" scoped>
.dataease-v2-embedded-assistant-sqlbot {
width: 20px;
height: 20px;
overflow: hidden;
position: absolute;
display: flex;
}
</style>

View File

@@ -70,6 +70,7 @@ const mountedEmbeddedPage = () => {
}
const tempTimer = setTimeout(() => {
if (window['sqlbot_embedded_handler']) {
console.log('===test===111')
window['sqlbot_embedded_handler'].mounted('#dataease-v2-embedded-sqlbot', {
embeddedId: state.id,
online: true,

View File

@@ -1,12 +1,17 @@
package io.dataease.api.dataset;
import io.dataease.api.dataset.vo.DataSQLBotAssistantVO;
import io.dataease.api.dataset.vo.DataSQLBotDatasetVO;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.List;
public interface DataAssistantApi {
@GetMapping("/datasource")
List<DataSQLBotAssistantVO> getDatasourceList(@RequestParam(required = false) Long dsId, @RequestParam(required = false) Long datasetId);
List<DataSQLBotAssistantVO> getDatasourceList(@RequestParam(required = false) Long dsId, @RequestParam(required = false) Long datasetId, @RequestParam(required = false) String dvInfo);
@GetMapping("/dataset/{dvInfo}")
List<DataSQLBotDatasetVO> getDatasetList(@PathVariable String dvInfo);
}

View File

@@ -0,0 +1,18 @@
package io.dataease.api.dataset.vo;
import lombok.Data;
import java.io.Serializable;
@Data
public class DataSQLBotDatasetVO implements Serializable {
private Long tableId;
private String tableName;
private String dsId;
private String dsName;
}