You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
506 lines
17 KiB
506 lines
17 KiB
<template>
|
|
<div class="chat-embed layout-bg">
|
|
<div class="chat-embed__header">
|
|
<div class="chat-width flex align-center" style="height: 56px;align-items: center;line-height: 56px">
|
|
<div class="mr-12 ml-24 flex">
|
|
<el-avatar
|
|
shape="square"
|
|
:size="32"
|
|
style="background: none"
|
|
>
|
|
<img src="@/assets/logo/deepseek.png" alt="" />
|
|
</el-avatar>
|
|
</div>
|
|
<h4 style="color: #1f2329;font-size: 16px; font-style: normal; font-weight: bold;margin: 0; -webkit-font-smoothing: antialiased">果知小助手</h4>
|
|
</div>
|
|
</div>
|
|
<div class="chat-embed__main">
|
|
<dataquerychat
|
|
ref="dataQueryRef"
|
|
:record="chatDataList"
|
|
class="AiChat-embed"
|
|
@scroll="handleScroll"
|
|
@showEdit="showEdit"
|
|
@download ="download"
|
|
></dataquerychat>
|
|
</div>
|
|
<el-drawer
|
|
v-model="showDrawer"
|
|
size="65%"
|
|
title="数据问答工作台"
|
|
>
|
|
<el-row :gutter="20">
|
|
<el-col :span="8">
|
|
<div class="head-container">
|
|
<el-input
|
|
v-model="chooseData"
|
|
placeholder="请输入搜索可选数据"
|
|
clearable
|
|
prefix-icon="Search"
|
|
style="margin-bottom: 20px"
|
|
/>
|
|
</div>
|
|
<div class="head-container" style="height: calc(100vh - 170px);overflow: auto">
|
|
<el-tree
|
|
:data="treeData"
|
|
:expand-on-click-node="false"
|
|
:filter-node-method="filterNode"
|
|
ref="dataQueryTreeRef"
|
|
node-key="id"
|
|
highlight-current
|
|
default-expand-all
|
|
@node-click="handleNodeClick"
|
|
>
|
|
<template #default="{ node, data }">
|
|
<div class="custom-tree-node">
|
|
<span>{{ node.label }}</span>
|
|
<div v-if="data.showStar">
|
|
<el-link v-if="!data.checked" :underline="false" type="warning">
|
|
<el-icon><Star /></el-icon>
|
|
</el-link>
|
|
<el-link v-if="data.checked" :underline="false" type="warning">
|
|
<el-icon><StarFilled /></el-icon>
|
|
</el-link>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</el-tree>
|
|
</div>
|
|
</el-col>
|
|
<el-col :span="16">
|
|
<el-divider content-position="left">条件设置</el-divider>
|
|
<el-table :data="tableData">
|
|
<el-table-column label="字段名称" prop="name" width="120"></el-table-column>
|
|
<el-table-column label="条件类型" prop="cd_type" width="130">
|
|
<template #default="scope">
|
|
<el-select v-model="scope.row.cd_type">
|
|
<el-option key="时间筛选" :value="'时间筛选'" label="时间筛选"></el-option>
|
|
<el-option key="维度筛选" :value="'维度筛选'" label="维度筛选"></el-option>
|
|
<el-option key="分组条件" :value="'分组条件'" label="分组条件"></el-option>
|
|
<el-option key="查询其它条件" :value="'查询其它条件'" label="查询其它条件"></el-option>
|
|
<el-option key="输出结果" :value="'输出结果'" label="输出结果"></el-option>
|
|
<el-option key="输出其它条件" :value="'输出其它条件'" label="输出其它条件"></el-option>
|
|
</el-select>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column label="控件类型" prop="ct_type" width="130">
|
|
<template #default="scope">
|
|
<el-select v-model="scope.row.ct_type">
|
|
<el-option key="datePicker" :value="'datePicker'" label="单日筛选"></el-option>
|
|
<el-option key="dateRangePicker" :value="'dateRangePicker'" label="周期筛选"></el-option>
|
|
<el-option key="input" :value="'input'" label="输入框"></el-option>
|
|
<el-option key="select" :value="'select'" label="单项筛选"></el-option>
|
|
<el-option key="mselect" :value="'mselect'" label="多项筛选"></el-option>
|
|
<el-option key="radioGroup" :value="'radioGroup'" label="是否筛选"></el-option>
|
|
</el-select>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column label="条件取值">
|
|
<template #default="scope">
|
|
<template v-if="scope.row.ct_type === 'mselect'">
|
|
<el-select v-model="scope.row.default_value" multiple style="width: 100%;" clearable>
|
|
<el-option v-for="opt in scope.row.options" :key="opt" :value="opt" :label="opt"></el-option>
|
|
</el-select>
|
|
</template>
|
|
<template v-else-if="scope.row.ct_type === 'datePicker'">
|
|
<el-date-picker
|
|
v-model="scope.row.default_value"
|
|
type="date"
|
|
format="YYYY-MM-DD"
|
|
value-format="YYYY-MM-DD"
|
|
placeholder="Pick a day"
|
|
/>
|
|
</template>
|
|
<template v-else-if="scope.row.ct_type === 'dateRangePicker'">
|
|
<el-date-picker
|
|
v-model="scope.row.dateRangeValue"
|
|
type="daterange"
|
|
style="width: 100%;"
|
|
range-separator="-"
|
|
format="YYYY-MM-DD"
|
|
value-format="YYYY-MM-DD"
|
|
start-placeholder="Start date"
|
|
end-placeholder="End date"
|
|
/>
|
|
</template>
|
|
<template v-else-if="scope.row.ct_type === 'radioGroup'">
|
|
<el-radio-group v-model="scope.row.default_value">
|
|
<el-radio :key="'radio'+opt" v-for="opt in scope.row.options" :value="opt">{{ opt }}</el-radio>
|
|
</el-radio-group>
|
|
</template>
|
|
<template v-else>
|
|
<el-input v-model="scope.row.default_value"></el-input>
|
|
</template>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column label="操作" width="100">
|
|
<template #default="scope">
|
|
<el-tooltip
|
|
style="margin-right: 10px"
|
|
class="box-item"
|
|
effect="dark"
|
|
content="上移"
|
|
placement="top-start"
|
|
v-if="scope.$index > 0"
|
|
>
|
|
<el-link :underline="false" v-if="scope.$index > 0" @click="changeTableIndex('top',scope.$index)" style="margin-right: 10px" type="primary"><el-icon><Top /></el-icon></el-link>
|
|
</el-tooltip>
|
|
<el-tooltip
|
|
style="margin-right: 10px"
|
|
class="box-item"
|
|
effect="dark"
|
|
content="下移"
|
|
placement="top-start"
|
|
v-if="scope.$index < tableData.length-1"
|
|
>
|
|
<el-link :underline="false" v-if="scope.$index < tableData.length-1" @click="changeTableIndex('bottom',scope.$index)" style="margin-right: 10px" type="primary"><el-icon><Bottom /></el-icon></el-link>
|
|
</el-tooltip>
|
|
<el-tooltip
|
|
style="margin-right: 10px"
|
|
class="box-item"
|
|
effect="dark"
|
|
content="删除"
|
|
placement="top-start"
|
|
>
|
|
<el-link :underline="false" @click="remove(scope.$index)" type="danger"><el-icon><Delete /></el-icon></el-link>
|
|
</el-tooltip>
|
|
</template>
|
|
</el-table-column>
|
|
</el-table>
|
|
<div class="operate-textarea flex chat-width" style="margin-top: 20px">
|
|
<el-input
|
|
ref="quickInputRef"
|
|
v-model="inputValue"
|
|
:placeholder="'Ctrl+Enter 换行,Enter发送'"
|
|
:autosize="{ minRows: 1, maxRows: 4 }"
|
|
type="textarea"
|
|
:maxlength="100000"
|
|
/>
|
|
<div class="operate flex align-center">
|
|
<el-button
|
|
text
|
|
class="sent-button"
|
|
:disabled="inputValue === ''"
|
|
@click="sendChatHandle"
|
|
>
|
|
<img v-show="inputValue === ''" src="@/assets/aichat/icon_send.svg" alt="" />
|
|
<img v-show="inputValue !== ''" src="@/assets/aichat/icon_send_colorful.svg" alt="" />
|
|
</el-button>
|
|
</div>
|
|
</div>
|
|
</el-col>
|
|
</el-row>
|
|
</el-drawer>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { ref, onMounted, reactive, nextTick, computed } from 'vue'
|
|
import { v4 as uuidv4 } from 'uuid';
|
|
import Cookies from 'js-cookie'
|
|
import { useRoute } from 'vue-router'
|
|
import Dataquerychat from "./dataquerychat.vue";
|
|
const route = useRoute()
|
|
const { proxy } = getCurrentInstance();
|
|
const chatDataList = ref([])
|
|
const dataQueryRef = ref()
|
|
const chatLogeData = ref([])
|
|
const show = ref(false)
|
|
const showDrawer = ref(false)
|
|
const currentRecordList = ref([])
|
|
const chooseData = ref("")
|
|
const treeData = ref([])
|
|
const tableData = ref([])
|
|
const inputValue = ref("")
|
|
const sessionId = ref(Cookies.get("chatSessionId")) // 当前历史记录Id 默认为'new'
|
|
|
|
function handleScroll(event) {
|
|
if (event.scrollTop === 0 && currentRecordList.value.length>0) {
|
|
const history_height = event.dialogScrollbar.offsetHeight
|
|
event.scrollDiv.setScrollTop(event.dialogScrollbar.offsetHeight - history_height)
|
|
}
|
|
}
|
|
function sendChatHandle(){
|
|
let queryObj = {
|
|
"type": "question",
|
|
"content": inputValue.value.trim(),
|
|
"time": formatDate(new Date()),
|
|
}
|
|
let inputVal = inputValue.value
|
|
dataQueryRef.value.handleSend(queryObj,inputVal);
|
|
showDrawer.value = false
|
|
}
|
|
function clickListHandle(item){
|
|
|
|
}
|
|
function padZero(num) {
|
|
return num < 10 ? '0' + num : num;
|
|
}
|
|
function formatDate(date) {
|
|
const year = date.getFullYear();
|
|
const month = padZero(date.getMonth() + 1); // 月份从0开始,所以需要+1
|
|
const day = padZero(date.getDate());
|
|
const hours = padZero(date.getHours());
|
|
const minutes = padZero(date.getMinutes());
|
|
const seconds = padZero(date.getSeconds());
|
|
|
|
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
|
|
}
|
|
function remove(index){
|
|
uncheckTree(tableData.value[index].name)
|
|
tableData.value.splice(index,1)
|
|
}
|
|
function uncheckTree(name){
|
|
if (treeData.value.length>0){
|
|
for (let i = 0; i < treeData.value.length; i++) {
|
|
if (treeData.value[i].children && treeData.value[i].children.length>0){
|
|
for (let j = 0; j < treeData.value[i].children.length; j++) {
|
|
if (treeData.value[i].children[j].value === name){
|
|
treeData.value[i].children[j].checked = false
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
function changeTableIndex(type,index){
|
|
if(type === 'top'){
|
|
let temp = tableData.value[index]
|
|
tableData.value[index] = tableData.value[index - 1]
|
|
tableData.value[index - 1] = temp
|
|
}
|
|
if (type === 'bottom'){
|
|
let temp = tableData.value[index]
|
|
tableData.value[index] = tableData.value[index + 1]
|
|
tableData.value[index + 1] = temp
|
|
}
|
|
}
|
|
function showEdit(data){
|
|
let array = JSON.parse(JSON.stringify(data.content.datas))
|
|
let compareArr = JSON.parse(JSON.stringify(data.content.query))
|
|
compareArr.push(...JSON.parse(JSON.stringify(data.content.result)))
|
|
treeData.value = []
|
|
if (array.length >0){
|
|
for (let i = 0; i < array.length; i++) {
|
|
let tab_cn_name = array[i].tab_cn_name;
|
|
let fld_cn_name = array[i].fld_cn_name;
|
|
let fld_type = array[i].fld_type;
|
|
if (treeData.value.length>0){
|
|
if (treeData.value.some(item=>item.label === tab_cn_name)){
|
|
for (let j = 0; j < treeData.value.length; j++) {
|
|
if (treeData.value[j].label === tab_cn_name){
|
|
treeData.value[j].children.push({
|
|
label:fld_cn_name +" | "+ fld_type,
|
|
value: fld_cn_name,
|
|
checked: compareArr.length>0?compareArr.some(item=>item.name === fld_cn_name):false,
|
|
showStar: true
|
|
})
|
|
}
|
|
}
|
|
}else {
|
|
treeData.value.push({
|
|
label: tab_cn_name,
|
|
value: tab_cn_name,
|
|
checked: false,
|
|
showStar: false,
|
|
children:[{
|
|
label:fld_cn_name +" | "+ fld_type,
|
|
value: fld_cn_name,
|
|
checked: false,
|
|
showStar: true
|
|
}]
|
|
})
|
|
}
|
|
}else {
|
|
treeData.value.push({
|
|
label: tab_cn_name,
|
|
value: tab_cn_name,
|
|
checked: false,
|
|
showStar: false,
|
|
children:[{
|
|
label:fld_cn_name +" | "+ fld_type,
|
|
value: fld_cn_name,
|
|
checked: false,
|
|
showStar: true,
|
|
}]
|
|
})
|
|
}
|
|
}
|
|
}
|
|
tableData.value = []
|
|
tableData.value.push(...data.content.query)
|
|
tableData.value.push(...data.content.result)
|
|
showDrawer.value = true
|
|
}
|
|
watch(chooseData, val => {
|
|
proxy.$refs["dataQueryTreeRef"].filter(val);
|
|
});
|
|
function download(data){
|
|
|
|
}
|
|
function handleNodeClick(data){
|
|
if (tableData.value.length>0){
|
|
if (tableData.value.some(item=>item.name===data.value)){
|
|
let index = -1;
|
|
for (let i = 0; i < tableData.value.length; i++) {
|
|
if (tableData.value[i].name === data.value){
|
|
index = i;
|
|
}
|
|
}
|
|
if (index >=0){
|
|
tableData.value.splice(index, 1);
|
|
}
|
|
}else {
|
|
tableData.value.push({
|
|
name:data.value,
|
|
cd_type:'',
|
|
ct_type:'',
|
|
default_value:''
|
|
})
|
|
}
|
|
}
|
|
data.checked = !data.checked
|
|
}
|
|
const filterNode = (value, data) => {
|
|
if (!value) return true;
|
|
return data.label.indexOf(value) !== -1;
|
|
};
|
|
function formatDefaultValue(item){
|
|
let value = item.default_value
|
|
if (item.cd_type === '输出结果'){
|
|
return item.name
|
|
}
|
|
if (item.ct_type === 'dateRangePicker'){
|
|
if (item.dateRangeValue){
|
|
return "开始日期为"+item.dateRangeValue[0]+"截止日期为"+item.dateRangeValue[1]
|
|
}else {
|
|
return ""
|
|
}
|
|
}
|
|
return value
|
|
}
|
|
watch(tableData,(newValue,oldValueValue) =>{
|
|
if (tableData.value.length>0){
|
|
let resultList = []
|
|
let queryList = []
|
|
for (let i = 0; i < tableData.value.length; i++) {
|
|
if (tableData.value[i].cd_type === '输出结果'){
|
|
resultList.push(formatDefaultValue(tableData.value[i]))
|
|
}else{
|
|
queryList.push(tableData.value[i].name+"为"+formatDefaultValue(tableData.value[i]))
|
|
}
|
|
}
|
|
if (resultList.length > 0){
|
|
queryList.push("输出结果为"+resultList.join("和"))
|
|
}
|
|
let result = "查询"+queryList.join(",")
|
|
inputValue.value = result
|
|
}
|
|
},{ deep: true})
|
|
|
|
|
|
</script>
|
|
|
|
<style lang="scss">
|
|
.chat-embed {
|
|
overflow: hidden;
|
|
height: 100%;
|
|
&__header {
|
|
background: linear-gradient(90deg, #ebf1ff 24.34%, #e5fbf8 56.18%, #f2ebfe 90.18%);;
|
|
position: absolute;
|
|
width: 100%;
|
|
left: 0;
|
|
top: 0;
|
|
z-index: 100;
|
|
height: 56px;
|
|
line-height: 56px;
|
|
box-sizing: border-box;
|
|
border-bottom: 1px solid #dee0e3;
|
|
}
|
|
&__main {
|
|
padding-top: 80px;
|
|
height: 100%;
|
|
overflow: hidden;
|
|
}
|
|
.new-chat-button {
|
|
z-index: 11;
|
|
}
|
|
// 历史对话弹出层
|
|
.chat-popover {
|
|
position: absolute;
|
|
top: 56px;
|
|
background: #ffffff;
|
|
padding-bottom: 24px;
|
|
z-index: 2009;
|
|
}
|
|
.chat-popover-button {
|
|
z-index: 100;
|
|
position: absolute;
|
|
top: 18px;
|
|
right: 85px;
|
|
font-size: 20px;
|
|
}
|
|
.chat-popover-mask {
|
|
background-color: var(--el-overlay-color-lighter);
|
|
bottom: 0;
|
|
height: 100%;
|
|
left: 0;
|
|
overflow: auto;
|
|
position: absolute;
|
|
right: 0;
|
|
top: 56px;
|
|
z-index: 2008;
|
|
}
|
|
.gradient-divider {
|
|
position: relative;
|
|
text-align: center;
|
|
color: var(--el-color-info);
|
|
::before {
|
|
content: '';
|
|
width: 17%;
|
|
height: 1px;
|
|
background: linear-gradient(90deg, rgba(222, 224, 227, 0) 0%, #dee0e3 100%);
|
|
position: absolute;
|
|
left: 16px;
|
|
top: 50%;
|
|
}
|
|
::after {
|
|
content: '';
|
|
width: 17%;
|
|
height: 1px;
|
|
background: linear-gradient(90deg, #dee0e3 0%, rgba(222, 224, 227, 0) 100%);
|
|
position: absolute;
|
|
right: 16px;
|
|
top: 50%;
|
|
}
|
|
}
|
|
.AiChat-embed {
|
|
.ai-chat__operate {
|
|
padding-top: 12px;
|
|
}
|
|
}
|
|
.chat-width {
|
|
max-width: 860px;
|
|
margin: 0 auto;
|
|
}
|
|
}
|
|
.flex {
|
|
display: flex;
|
|
}
|
|
|
|
.mr-12 {
|
|
margin-right: 12px;
|
|
|
|
}
|
|
.ml-24 {
|
|
margin-left: 24px;
|
|
}
|
|
.custom-tree-node {
|
|
flex: 1;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
font-size: 14px;
|
|
padding-right: 8px;
|
|
}
|
|
</style>
|
|
|