feat: 表格

main
许宏杰 1 month ago
parent 61ae51d1ee
commit 2e4379651e

@ -1,375 +0,0 @@
<template>
<div class="chat-container">
<div class="chat-list" ref="scrollContainer">
<!-- <div class="echarts-box" id="test"></div>
<div class="echarts-style-row">
<div class="style-row-name">风格切换</div>
<div class="echarts-style-list">
<div class="style-item" v-for="(echartItem, echartIndex) in styleColor" :key="echartIndex">
{{ echartItem }}
</div>
</div>
</div> -->
<div
v-for="(item, index) in messages"
:key="index"
:class="['chat-item', item.from == 'user' ? 'user-message' : 'ai-message']"
>
<!-- 头像 -->
<img :src="item.from == 'user' ? userIcon : aiIcon" class="user-icon" alt="" />
<!-- 内容 -->
<div class="message-content">
<div class="message-time">{{ item.time }}</div>
<div class="message-text">
<span>{{ item.text }}</span>
<div v-if="item.from === 'ai' && item.chartType">
<div :id="item.chartType + index" class="echarts-box"></div>
<div class="echarts-style-row">
<div class="style-row-name">风格切换</div>
<div class="echarts-style-list">
<div
class="style-item"
@click="hanlderStyleColor(item.chartType, index, echartIndex)"
:class="echartIndex == item.chartInstanceActiveIndex ? 'activeItem' : ''"
v-for="(echartItem, echartIndex) in styleColor"
:key="echartIndex"
>
{{ echartItem }}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<n-input
class="chat-input"
round
v-model:value="keyWord"
type="textarea"
placeholder="请输入需要统计的数据"
:autosize="{ minRows: 5 }"
>
<template #suffix>
<div class="chat-suffix">
<span>常用问题</span>
<!-- <img src="~@/assets/images/ai/chat-send.png" class="chat-send" alt="" /> -->
<n-button :loading="loading" size="small" :class="['chat-send']" :bordered="false" @click="handleSend">
</n-button>
</div>
</template>
</n-input>
</div>
</template>
<script setup lang="ts">
import moment from 'moment'
// @ts-ignore
import { getAiMsg } from '@/api/ai.js'
// @ts-ignore
import { AiChatroomType } from './types'
// @ts-ignore
import { PieChart } from './class/PieChart'
// @ts-ignore
import { LineChart } from './class/LineChart'
// @ts-ignore
import { BarChart } from './class/BarChart'
import { ref, reactive, onMounted, onUnmounted, watch, nextTick } from 'vue'
import { historyMessageRoomById } from '@/api/path'
import userIcon from '@/assets/images/ai/user-icon.png'
import aiIcon from '@/assets/images/ai/ai-icon.png'
import { useMessage } from 'naive-ui'
import { useRoute } from 'vue-router'
import useMessageRoomStore from '@/store/modules/messageRoom'
const useMessageRoom = useMessageRoomStore()
let infoData = ref({})
const route = useRoute()
const messageDialog = useMessage()
const scrollContainer = ref<HTMLDivElement | null>(null)
// const chartContainer = ref<HTMLDivElement | null>(null)
let loading = ref(false)
let keyWord = ref('')
let messages= reactive([])
let chartInstance: LineChart | null = null
//
const chartData = {
xData: ['Q1', 'Q2', 'Q3', 'Q4'],
yData: [120, 150, 130, 180],
seriesName: '年度营收', //
color: '#ff6b6b' //
}
const styleColor: string[] = ['简约风', '商务风', '科技风']
const handleSend = async () => {
if (!keyWord.value.trim()) {
messageDialog.warning('请先输入需要统计的数据!')
return
}
/**
*保存用户提问信息
*/
setMessageStore({
from: 'user',
text: keyWord.value,
time: moment().format('YYYY/MM/DD HH:mm:ss')
})
loading.value = true
try {
const res = await getAiMsg({ prompt: keyWord.value })
console.log('回答', res)
//AI
if (res.type) {
setMessageStore({
from: 'ai',
text: '以图表形式展示',
chartType: res.chartType,
time: moment().format('YYYY/MM/DD HH:mm:ss'),
chartInstanceItem: null,
chartInstanceActiveIndex: 0,
xData: res.xData,
yData: res.yData
})
nextTick(() => {
const lastIndex = messages.length - 1
const itemData = messages[lastIndex]
const xData = res.xData
const yData = res.yData.map((str: string) => parseInt(str, 10))
if (itemData.chartType === 'pie')
itemData.chartInstanceItem = new PieChart(`${itemData.chartType}${lastIndex}`, { xData, yData })
if (itemData.chartType === 'line') {
itemData.chartInstanceItem = new LineChart(`${itemData.chartType}${lastIndex}`, { xData, yData })
}
if (itemData.chartType === 'bar') {
itemData.chartInstanceItem = new BarChart(`${itemData.chartType}${lastIndex}`, { xData, yData })
}
})
} else {
//
setMessageStore({
from: 'ai',
text: res,
time: moment().format('YYYY/MM/DD HH:mm:ss')
})
}
keyWord.value = '' //
loading.value = false
} catch (error) {
//
setMessageStore({
from: 'ai',
text: '服务器繁忙,请稍后再试。',
time: moment().format('YYYY/MM/DD HH:mm:ss')
})
keyWord.value = '' //
loading.value = false
}
}
const setMessageStore = (messageItem: any) => {
messages.push(messageItem)
useMessageRoom.setMessage(messages[0].text, {
from: messageItem.from,
text: messageItem.text,
chartType: messageItem.chartType,
time: messageItem.time,
chartInstanceItem: null,
chartInstanceActiveIndex: 0,
xData: messageItem.xData,
yData: messageItem.yData
})
}
const hanlderStyleColor = (type: string, fatherIndex: number, childIndex: number) => {
const messagesItem = messages[fatherIndex]
if (messagesItem.chartInstanceActiveIndex == childIndex) return
messagesItem.chartInstanceActiveIndex = childIndex
messagesItem.chartInstanceItem?.changeColorStyle(childIndex)
}
onMounted(() => {
getInfo()
})
const getInfo = async () => {
const id = checkLastPartIsTimestamp(route.path)
const res = await historyMessageRoomById(id)
if (res?.data) {
res.data.content = JSON.parse(res.data.content)
infoData.value = res.data
// messages = infoData.value.content.list
infoData.value.content.list.forEach(item => {
messages.push(item);
});
}
// console.log(infoData.value.content.list)
}
const checkLastPartIsTimestamp = (str: string) => {
// /
const lastSlashIndex = str.lastIndexOf('/')
if (lastSlashIndex === -1) {
return false
}
// /
const lastPart = str.slice(lastSlashIndex + 1)
return lastPart
}
// messages
watch(
messages,
() => {
nextTick(() => {
if (scrollContainer.value) {
scrollContainer.value.scrollTop = scrollContainer.value.scrollHeight
}
})
},
{ deep: true }
)
onUnmounted(() => {
//
messages.forEach(instance => {
if (instance.chartType) {
instance.chartInstanceItem?.dispose()
}
})
})
</script>
<style lang="scss" scoped>
.chat-container {
box-sizing: border-box;
padding: 20px;
height: 100%;
gap: 20px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
}
.chat-input {
width: 50%;
}
.chat-suffix {
font-size: 14px;
height: 100%;
display: flex;
align-items: center;
flex-direction: column;
justify-content: space-between;
}
.chat-send {
cursor: pointer;
width: 46px;
height: 30px;
margin-bottom: 10px;
background: url('@/assets/images/ai/chat-send.png');
background-size: 100% 100%;
}
.chat-list {
overflow-y: auto;
display: flex;
flex-direction: column;
gap: 25px;
width: 50%;
height: 100%;
// border: 1px solid red;
}
.chat-item {
display: flex;
gap: 6px;
}
.user-icon {
height: 40px;
width: 40px;
}
.user-message {
flex-direction: row-reverse;
.message-content {
display: flex;
flex-direction: column;
align-items: flex-end;
}
}
.ai-message {
flex-direction: row;
.message-content {
display: flex;
flex-direction: column;
align-items: flex-start;
}
}
.message-content {
font-size: 14px;
font-weight: 400;
.message-time {
color: #c3cad9;
}
width: calc(100% - 110px);
padding-top: 15px;
.message-text {
width: auto;
color: #ffffff;
font-size: 15px;
margin-top: 10px;
padding: 18px;
background: #282a31;
border-radius: 10px 10px 10px 10px;
}
}
::v-deep .n-input .n-input__suffix .n-base-loading {
color: #283e81 !important;
}
/* 设置滚动条整体样式 */
::-webkit-scrollbar {
width: 0px;
}
.echarts-box {
height: 300px;
width: 500px;
// border: 1px solid red;
}
.echarts-style-row {
display: flex;
align-items: center;
.style-row-name {
font-size: 14px;
color: #cfd5e5;
font-weight: 400;
}
.echarts-style-list {
display: flex;
align-items: center;
gap: 10px;
}
.style-item {
cursor: pointer;
font-size: 14px;
color: #cfd5e5;
font-weight: 400;
background: #31363e;
border-radius: 2px 2px 2px 2px;
padding: 3px 10px;
}
.activeItem {
background: rgba(63, 123, 248, 0.1);
color: #3f7bf8;
}
}
</style>

@ -1,17 +1,6 @@
<template> <template>
<div class="chat-container"> <div class="chat-container">
<div class="chat-list" ref="scrollContainer"> <div class="chat-list" ref="scrollContainer">
<!-- <div>
<div class="echarts-box" id="barEcharts"></div>
<div class="echatrs-theme">
<div class="theme-title">风格切换</div>
<div class="theme-list">
<div class="theme-item" @click="handlerTheme(index)" v-for="(item, index) in echartsTheme" :key="index">
{{ item }}
</div>
</div>
</div>
</div> -->
<AiHint></AiHint> <AiHint></AiHint>
@ -28,28 +17,9 @@
<div class="message-text" :id="`box${index}`"> <div class="message-text" :id="`box${index}`">
<!-- 纯文本 --> <!-- 纯文本 -->
<div class="text-ros"> <div class="text-ros">
<span>{{ item.text }}</span> <span v-show="item.type == 'text'">{{ item.text }}</span>
<div v-show="item.add"> <AddIssue v-show="item.add"></AddIssue>
<n-tooltip placement="bottom" trigger="hover">
<template #trigger>
<n-icon size="14" style="cursor: pointer">
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
viewBox="0 0 12 12"
>
<g fill="none">
<path
d="M6.5 1.75a.75.75 0 0 0-1.5 0V5H1.75a.75.75 0 0 0 0 1.5H5v3.25a.75.75 0 0 0 1.5 0V6.5h3.25a.75.75 0 0 0 0-1.5H6.5V1.75z"
fill="currentColor"
></path>
</g>
</svg>
</n-icon>
</template>
<span> 添加为常用问题 </span>
</n-tooltip>
</div>
</div> </div>
<!-- 图表 --> <!-- 图表 -->
<section v-if="ehcatrsType.includes(item.type)"> <section v-if="ehcatrsType.includes(item.type)">
@ -69,13 +39,14 @@
</div> </div>
</div> </div>
</section> </section>
<!-- 表格 -->
<AiTable v-if="!item.type" :obj="item"></AiTable>
<!-- 思考 --> <!-- 思考 -->
<div v-show="item.from === 'ai' && item.loading" class="ai-loading"> <div v-show="item.from === 'ai' && item.loading" class="ai-loading">
<span>思考中</span> <n-spin size="small" content-class="spin-class" /> <span>思考中</span> <n-spin size="small" content-class="spin-class" />
</div> </div>
<!-- 免责 --> <!-- 免责 -->
<div class="ai-hint" v-show="item.from === 'ai' && (item.text || item.type)"> <div class="ai-hint" v-show="item.from === 'ai' && !item.loading">
<span>本回答由AI生成内容仅供参考</span> <span>本回答由AI生成内容仅供参考</span>
<div class="upload-text" @click="uploadImage(index)"> <div class="upload-text" @click="uploadImage(index)">
<n-icon size="14"> <n-icon size="14">
@ -124,7 +95,7 @@ import { ref, reactive, onMounted, onUnmounted, watch, nextTick } from 'vue'
import { getAiMsg } from '@/api/ai.js' import { getAiMsg } from '@/api/ai.js'
import moment from 'moment' import moment from 'moment'
import { LineChart, BarChart, PieChart } from './class/index' import { LineChart, BarChart, PieChart } from './class/index'
import { AiHint } from '../components' import { AiHint, AiTable,AddIssue } from '../components'
import userIcon from '@/assets/images/ai/user-icon.png' import userIcon from '@/assets/images/ai/user-icon.png'
import aiIcon from '@/assets/images/ai/ai-icon.png' import aiIcon from '@/assets/images/ai/ai-icon.png'
import { useMessage } from 'naive-ui' import { useMessage } from 'naive-ui'
@ -133,7 +104,6 @@ import { canvasCut } from '@/utils'
import { historyMessageRoomById } from '@/api/path' import { historyMessageRoomById } from '@/api/path'
import { useRoute } from 'vue-router' import { useRoute } from 'vue-router'
const route = useRoute() const route = useRoute()
const useMessageRoom = useMessageRoomStore() const useMessageRoom = useMessageRoomStore()
const messageDialog = useMessage() const messageDialog = useMessage()
@ -170,8 +140,10 @@ const getMessageInfo = async () => {
const res = await historyMessageRoomById(id) const res = await historyMessageRoomById(id)
if (res.code == 200 && res.data) { if (res.code == 200 && res.data) {
res.data.content = JSON.parse(res.data.content) res.data.content = JSON.parse(res.data.content)
console.log(res.data)
useMessageRoom.resetList(res.data) useMessageRoom.resetList(res.data)
res.data.content.map((item, index) => { res.data.content.map((item, index) => {
item.chartInstanceActiveIndex = 0
messageList.push(item) messageList.push(item)
initEcharts(item, index) initEcharts(item, index)
}) })
@ -209,9 +181,11 @@ const handleSend = async () => {
setMessageItem({ setMessageItem({
from: 'user', from: 'user',
text: keyWord.value, text: keyWord.value,
type:'text',
time: moment().format('YYYY/MM/DD HH:mm:ss'), time: moment().format('YYYY/MM/DD HH:mm:ss'),
add: true add: true
}) })
return
//AI //AI
setMessageItem( setMessageItem(
{ {
@ -221,7 +195,7 @@ const handleSend = async () => {
loading: true, //ai loading: true, //ai
chartInstanceItem: null, chartInstanceItem: null,
chartInstanceActiveIndex: 0, chartInstanceActiveIndex: 0,
type: null, type:'text',
xData: [], xData: [],
yData: [] yData: []
}, },
@ -268,10 +242,11 @@ const handleSend = async () => {
xData: res.xData, xData: res.xData,
data: res.data data: res.data
}) })
} else if (res.type === 'text') { } else {
// //
updataMessageItem({ updataMessageItem({
from: 'ai', from: 'ai',
title: res.title,
type: res.type, type: res.type,
text: res.data, text: res.data,
time: moment().format('YYYY/MM/DD HH:mm:ss'), time: moment().format('YYYY/MM/DD HH:mm:ss'),
@ -299,9 +274,8 @@ const initEcharts = (item, index) => {
if (item.type === 'pie') { if (item.type === 'pie') {
item.chartInstanceItem = new PieChart(`${item.type}${index}`, { item.chartInstanceItem = new PieChart(`${item.type}${index}`, {
title: item.title, title: item.title,
unit: item.unit, unit: item.unit,
xData: item.xData, data: item.data
data: item.yData
}) })
} else if (item.type === 'bar') { } else if (item.type === 'bar') {
item.chartInstanceItem = new BarChart(`${item.type}${index}`, { item.chartInstanceItem = new BarChart(`${item.type}${index}`, {
@ -329,12 +303,7 @@ const setMessageItem = (obj, set = true) => {
messageList.push(obj) messageList.push(obj)
lastIndex.value = messageList.length - 1 lastIndex.value = messageList.length - 1
if (set) { if (set) {
useMessageRoom.setMessage({ useMessageRoom.setMessage(obj)
from: obj.from,
text: obj.text,
time: obj.time,
add: true
})
} }
} }
@ -346,19 +315,20 @@ const updataMessageItem = data => {
messageList[lastIndex.value] = data messageList[lastIndex.value] = data
keyWord.value = '' keyWord.value = ''
loading.value = false loading.value = false
useMessageRoom.setMessage({ useMessageRoom.setMessage(data)
from: data.from, // useMessageRoom.setMessage({
text: data.text, // from: data.from,
type: data.type, // text: data.text,
loading: false, //ai // type: data.type,
time: data.time, // loading: false, //ai
chartInstanceItem: data.chartInstanceItem, // time: data.time,
chartInstanceActiveIndex: 0, // chartInstanceItem: data.chartInstanceItem,
xData: data.xData, // chartInstanceActiveIndex: 0,
yData: data.yData, // xData: data.xData,
title: data.title, // yData: data.yData,
unit: data.unit // title: data.title,
}) // unit: data.unit
// })
} }
/** /**
@ -374,12 +344,10 @@ const handlerTheme = (item, index) => {
item.chartInstanceActiveIndex = index item.chartInstanceActiveIndex = index
item.chartInstanceItem.changeColorStyle(index) item.chartInstanceItem.changeColorStyle(index)
setTimeout(() => {
setTimeout(() => { startWatching()
startWatching() console.log('监听')
console.log('监听') }, 5000)
}, 5000)
} }
/** /**
@ -407,7 +375,7 @@ const startWatching = () => {
scrollContainer.value.scrollTop = scrollContainer.value.scrollHeight scrollContainer.value.scrollTop = scrollContainer.value.scrollHeight
} }
}) })
}) },{deep:true})
isWatching.value = true isWatching.value = true
} }
@ -420,7 +388,7 @@ const pauseWatching = () => {
onUnmounted(() => { onUnmounted(() => {
// //
messageList.forEach(instance => { messageList.forEach(instance => {
if (instance.type) { if (ehcatrsType.includes( instance.type) && instance.type) {
instance.chartInstanceItem.dispose() instance.chartInstanceItem.dispose()
} }
}) })

@ -0,0 +1,65 @@
<template>
<div>
<n-popconfirm v-model:show="showModal">
<template #trigger>
<n-button>引用</n-button>
</template>
譬如我或许可以就大象本身写一点什么但对象的驯化却不知从何写起
<template #action>
<n-button size="small" @click="showModal = false">
或许吧
</n-button>
</template>
</n-popconfirm>
<!-- <n-tooltip placement="bottom" trigger="hover">
<template #trigger>
<n-icon size="14" style="cursor: pointer" @click="handleAdd()">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 12 12">
<g fill="none">
<path
d="M6.5 1.75a.75.75 0 0 0-1.5 0V5H1.75a.75.75 0 0 0 0 1.5H5v3.25a.75.75 0 0 0 1.5 0V6.5h3.25a.75.75 0 0 0 0-1.5H6.5V1.75z"
fill="currentColor"
></path>
</g>
</svg>
</n-icon>
</template>
<span> 添加为常用问题 </span>
</n-tooltip> -->
<!-- 弹窗信息 -->
<!-- <n-modal v-model:show="showModal">
<n-card style="width: 600px" title="添加常用问题" :bordered="false" size="huge" role="dialog" aria-modal="true">
<div class="issue-list">
<div class="issue-item"></div>
<div class="issue-item"></div>
<div class="issue-item"></div>
</div>
<template #footer> 尾部 </template>
</n-card>
</n-modal> -->
</div>
</template>
<script setup>
import { ref } from 'vue'
let showModal = ref(true)
const handleAdd = () => {
showModal.value = true
}
</script>
<style lang="scss" scoped>
.issue-list {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 20px;
.issue-item{
height: 100px;
background-color: rgba(242, 242, 242, 1);
}
}
</style>

@ -0,0 +1,68 @@
<template>
<div class="ai-table">
<div class="table-title">{{ obj.title }}</div>
<div class="table-list">
<div class="list-item" v-for="(item, index) in obj.text" :key="index">
<div class="item-header">
<div class="header-item">{{ item.column }}</div>
</div>
<div class="item-boby" v-for="(valueItem, valueIndex) in item.list" :key="valueIndex">
{{ valueItem }}
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, reactive } from 'vue'
const props = defineProps({
obj: {
type: Object,
default: () => {}
},
})
</script>
<style lang="scss" scoped>
.ai-table {
width: fit-content;
}
.table-title{
font-size: 14px;
font-weight: bold;
margin-bottom: 8px;
color: #fff;
}
.table-list {
display: flex;
align-items: center;
background-color: rgba(195, 202, 217, 0.5);
gap: 1px;
.list-item{
flex: 1;
min-width: 150px;
max-width: 300px;
}
.item-header {
background-color: #507afc;
& > div {
padding: 3px 10px;
text-align: center;
font-size: 15px;
font-weight: bold;
}
}
.item-boby{
margin-top: 1px;
background-color: #3D424D;
text-align: center;
font-size: 14px;
font-weight: 400;
}
}
</style>

@ -1,3 +1,6 @@
export { default as AiHint } from './aiHint/index.vue' export { default as AiHint } from './aiHint/index.vue'
export { default as AiHistory } from './history/index.vue' export { default as AiHistory } from './history/index.vue'
export { default as AiLogo } from './logo/index.vue' export { default as AiLogo } from './logo/index.vue'
export { default as AiTable } from './aiTable/index.vue'
export { default as AddIssue } from './addIssue/index.vue'

@ -49,7 +49,7 @@ const menuList = [
{ name: '聊天', path: '/chat' }, { name: '聊天', path: '/chat' },
{ name: '数据看板', path: '/board' } { name: '数据看板', path: '/board' }
] ]
const ehcatrsType = ['pie', 'line', 'bar']
const getCurrentRoute = path => { const getCurrentRoute = path => {
return route.path.includes(path) return route.path.includes(path)
} }
@ -58,7 +58,7 @@ const saveMessage = async path => {
// //
if (useMessageRoom.messageRoom.content.length > 0) { if (useMessageRoom.messageRoom.content.length > 0) {
useMessageRoom.messageRoom.content.forEach(instance => { useMessageRoom.messageRoom.content.forEach(instance => {
if (instance.chartType && instance.chartInstanceItem) { if (ehcatrsType.includes( instance.type) && instance.chartInstanceItem) {
instance.chartInstanceItem.dispose() instance.chartInstanceItem.dispose()
} }
}) })

Loading…
Cancel
Save