feat: ai问题预载

main
许宏杰 1 month ago
parent e98176d07d
commit e78a248c69

@ -12,4 +12,132 @@
body {
background-color: #18181c;
}
}
.chat-container {
box-sizing: border-box;
padding: 20px;
height: 100%;
gap: 15px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
.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: 50px;
width: 50px;
}
.user-message {
flex-direction: row-reverse;
.message-content {
display: flex;
flex-direction: column;
align-items: flex-end;
}
}
.text-ros {
display: flex;
align-items: center;
gap: 6px;
}
.ai-loading {
display: flex;
align-items: center;
font-size: 14px;
gap: 10px;
color: #ffffff;
}
.ai-message {
flex-direction: row;
.message-content {
display: flex;
flex-direction: column;
align-items: flex-start;
}
.ai-hint {
font-size: 12px;
}
}
.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: 10px 18px;
background: #282a31;
border-radius: 10px 10px 10px 10px;
}
}
}
.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%;
}
}
.echarts-box {
height: 220px;
width: 400px;
}
.echatrs-theme {
margin-top: 10px;
display: flex;
align-items: center;
gap: 6px;
.theme-title {
font-size: 14px;
color: #cfd5e5;
font-weight: 400;
}
.theme-list {
display: flex;
align-items: center;
gap: 10px;
.theme-item {
cursor: pointer;
background: #31363e;
border-radius: 2px;
font-size: 14px;
color: #cfd5e5;
font-weight: 400;
box-sizing: border-box;
padding: 2px 10px;
}
}
}
}

@ -1,150 +0,0 @@
import * as echarts from 'echarts';
interface ChartData {
xData: string[]; // X轴分类数据如['一月', '二月', '三月']
yData: number[]; // 单一数值数组对应X轴每个分类的值
seriesName?: string; // 系列名称(默认'数据系列'
color?: string; // 柱状图颜色(默认主题蓝色)
}
export class BarChart {
private chart: echarts.ECharts | null = null;
private dom: HTMLElement;
private xData: string[];
private yData: number[];
private seriesName: string = '数据系列';
private currentColor: string = '#1890ff'; // 默认颜色
constructor(containerId: string, chartData: ChartData) {
this.dom = document.getElementById(containerId)!;
this.xData = chartData.xData;
this.yData = chartData.yData;
this.seriesName = chartData.seriesName || this.seriesName;
this.currentColor = chartData.color || this.currentColor;
this.initChart();
}
private initChart(): void {
this.chart = echarts.init(this.dom);
this.setChartOptions();
window.addEventListener('resize', () => this.chart?.resize());
}
private setChartOptions(): void {
const option= {
// tooltip: {
// trigger: 'axis',
// formatter: (params) => `
// <div style="font-size: 14px; font-weight: 500">${params[0].name}</div>
// <div style="display: flex; align-items: center; gap: 4px; margin: 4px 0">
// <span style="width: 8px; height: 8px; border-radius: 2px; background: ${this.currentColor}"></span>
// <span>${this.seriesName}: ${params[0].value}</span>
// </div>
// `
// },
// legend: {
// data: [this.seriesName],
// top: '16px',
// left: 'center',
// textStyle: { color: '#666', fontSize: 14 }
// },
grid: {
left: '0%',
right: '0%',
bottom: '5%',
containLabel: true
},
xAxis: {
type: 'category',
data: this.xData,
axisTick: { alignWithLabel: true, length: 8, color: '#e0e0e0' },
axisLine: { lineStyle: { color: '#C3CAD9', width: 1 } },
textStyle: { color: '#C3CAD9', fontSize: 12 }
},
yAxis: {
type: 'value',
name: '',
// nameTextStyle: { color: 'red', fontSize: 14, margin: 20 },
splitLine: { lineStyle: { type: 'dashed', color: '#f0f0f0' } },
// textStyle: { color: '#666', fontSize: 14 }
axisLabel:{
color: '#C3CAD9',
fontSize: 12,
}
},
series: [{
name: this.seriesName,
type: 'bar',
data: this.yData,
barWidth: '25%', // 柱体宽度
barRadius: 4, // 柱体圆角
itemStyle: {
color: this.currentColor,
emphasis: {
shadowBlur: 8,
shadowColor: 'rgba(0, 0, 0, 0.2)'
}
},
label: {
show: true,
position: 'top',
color: '#fff',
fontSize: 12,
formatter: '{c}' // 显示数值
}
}]
};
this.chart?.setOption(option);
}
/**
*
* @param index /RGB/HSL
*/
changeColorStyle(index: number): void {
if (!this.chart) {
console.warn('未读取到元素!')
return
}
if (index === 0) {
this.currentColor = '#3071EC'
} else if (index === 1) {
this.currentColor = '#6790EA'
} else if (index === 2) {
this.currentColor = '#0A50FF'
}
this.chart.setOption({
series: [
{
itemStyle: { color: this.currentColor }
}
]
});
}
/**
*
* @param newData
*/
updateData(newData: ChartData): void {
if (!this.chart) return;
this.xData = newData.xData;
this.yData = newData.yData;
this.seriesName = newData.seriesName || this.seriesName;
this.currentColor = newData.color || this.currentColor;
this.setChartOptions();
}
dispose(): void {
if (this.chart) {
this.chart.dispose();
this.chart = null;
}
window.removeEventListener('resize', () => this.chart?.resize());
}
}

@ -1,149 +0,0 @@
import * as echarts from 'echarts';
interface ChartData {
xData: string[]; // X轴分类数据如['周一', '周二', '周三']
yData: number[]; // 单一折线数据(数值数组)
seriesName?: string; // 系列名称(默认'数据趋势'
color?: string; // 折线颜色(默认主题蓝色)
}
export class LineChart {
private chart: echarts.ECharts | null = null;
private dom: HTMLElement;
private xData: string[];
private yData: number[];
private seriesName: string = '数据趋势';
private currentColor: string = '#1890ff'; // 默认颜色ECharts主题蓝
constructor(containerId: string, chartData: ChartData) {
this.dom = document.getElementById(containerId)!;
this.xData = chartData.xData;
this.yData = chartData.yData;
this.seriesName = chartData.seriesName || this.seriesName;
this.currentColor = chartData.color || this.currentColor;
this.initChart();
}
private initChart(): void {
this.chart = echarts.init(this.dom);
this.setChartOptions();
window.addEventListener('resize', () => this.chart?.resize());
}
private setChartOptions(): void {
const option= {
// tooltip: {
// trigger: 'axis',
// formatter: (params) => `
// <div style="font-size: 14px; font-weight: 500">${params[0].name}</div>
// <div style="display: flex; align-items: center; gap: 4px; margin: 4px 0">
// <span style="width: 8px; height: 8px; border-radius: 50%; background: ${this.currentColor}"></span>
// <span>${this.seriesName}: ${params[0].value}</span>
// </div>
// `
// },
// legend: {
// data: [this.seriesName],
// top: '16px',
// left: 'center',
// textStyle: { color: '#666', fontSize: 14 }
// },
grid: {
top:'5%',
left: '0%',
right: '0%',
bottom: '5%',
containLabel: true
},
xAxis: {
type: 'category',
data: this.xData,
axisTick: { alignWithLabel: true, color: '#e0e0e0' },
axisLine: { lineStyle: { color: '#C3CAD9', width: 1 } },
axisLabel:{
color: '#C3CAD9',
fontSize: 12,
}
},
yAxis: {
type: 'value',
name: '',
splitLine: { lineStyle: { type: 'dashed', color: '#f0f0f0' } },
axisLabel:{
color: '#C3CAD9',
fontSize: 12,
}
},
series: [{
name: this.seriesName,
type: 'line',
data: this.yData,
color: this.currentColor,
smooth: true, // 曲线平滑
symbol: 'circle', // 标记样式
symbolSize: 8, // 标记大小
itemStyle: {
emphasis: { scale: 1.2 } // 悬停时标记放大
},
lineStyle: { width: 2 }, // 线宽
label: {
show: true,
position: 'top',
color: '#fff',
fontSize: 12,
formatter: '{c}' // 显示数值
}
}]
};
this.chart?.setOption(option);
}
/**
* 线
* @param newColor /RGB/HSL
*/
changeColorStyle(index: number): void {
if (!this.chart) {
console.warn('未读取到元素!')
return
}
if (index === 0) {
this.currentColor = '#3071EC'
} else if (index === 1) {
this.currentColor = '#6790EA'
} else if (index === 2) {
this.currentColor = '#0A50FF'
}
this.chart.setOption({
series: [{
color: this.currentColor,
itemStyle: { color: this.currentColor },
lineStyle: { color: this.currentColor }
}]
});
}
/**
*
* @param newData
*/
updateData(newData: ChartData): void {
if (!this.chart) return;
this.xData = newData.xData;
this.yData = newData.yData;
this.seriesName = newData.seriesName || this.seriesName;
this.currentColor = newData.color || this.currentColor;
this.setChartOptions();
}
dispose(): void {
if (this.chart) {
this.chart.dispose();
this.chart = null;
}
window.removeEventListener('resize', () => this.chart?.resize());
}
}

@ -1,115 +0,0 @@
import * as echarts from 'echarts'
interface ChartData {
xData: string[]
yData: number[]
}
export class PieChart {
private chart: echarts.ECharts | null = null
private dom: HTMLElement
private xData: string[]
private yData: number[]
private currentColors: string[] = ['#0A50FF', '#23CAF4', '#E96F69', '#F2F7F8', '#35C75D', '#35C75D', '#E6FA72']
private scienceColors: string[] = ['#3071EC', '#41B7FD', '#54D360', '#DAE8F7', '#F6A450', '#EA7261', '#CA68F5'] // 科技风
private businessColors: string[] = ['#6790EA', '#63C8EA', '#88DE95', '#CFEE5F', '#F3D088', '#F99774', '#F17E7E'] // 商务风
private simpleColors: string[] = ['#0A50FF', '#23CAF4', '#E96F69', '#F2F7F8', '#35C75D', '#35C75D', '#E6FA72'] // 简约风
constructor(containerId: string, { xData, yData }: ChartData) {
this.dom = document.getElementById(containerId)!
this.xData = xData
this.yData = yData
this.initChart()
}
private initChart(): void {
this.chart = echarts.init(this.dom)
this.setChartOptions()
window.addEventListener('resize', () => this.chart?.resize())
}
private setChartOptions(): void {
const option = {
tooltip: {
trigger: 'item',
formatter: '{a} <br/>{b}: {c} ({d}%)'
},
legend: {
bottom: '10%',
left: 'center',
icon: 'rect',
itemWidth: 10,
itemHeight: 5,
textStyle: {
color: '#C3CAD9',
fontSize: 12
},
data: this.xData
},
series: [
{
name: '访问来源',
type: 'pie',
radius: ['30%', '50%'], // 设置内外半径,形成环形
center: ['50%', '50%'],
color: this.currentColors, // 应用颜色配置
data: this.xData.map((name, index) => ({
name,
value: this.yData[index]
})),
label: {
show: false,
position: 'center'
}
// labelLine: {
// show: false
// },
// emphasis: {
// itemStyle: {
// shadowBlur: 10,
// shadowOffsetX: 0,
// shadowColor: 'rgba(0, 0, 0, 0.5)'
// }
// }
}
]
}
this.chart?.setOption(option)
}
/**
*
* @param newColors
*/
changeColorStyle(index: number): void {
if (!this.chart) {
console.warn('未读取到元素!')
return
}
if (index === 0) {
this.currentColors = this.simpleColors
} else if (index === 1) {
this.currentColors = this.businessColors
} else if (index === 2) {
this.currentColors = this.scienceColors
}
// 仅更新颜色配置,避免重新初始化图表
this.chart.setOption({
series: [
{
color: this.currentColors
}
]
})
}
dispose(): void {
if (this.chart) {
this.chart.dispose()
this.chart = null
}
window.removeEventListener('resize', () => this.chart?.resize())
}
}

@ -1,7 +1,7 @@
<template>
<div class="chat-container">
<div class="chat-list" ref="scrollContainer">
<div>
<!-- <div>
<div class="echarts-box" id="barEcharts"></div>
<div class="echatrs-theme">
<div class="theme-title">风格切换</div>
@ -11,7 +11,9 @@
</div>
</div>
</div>
</div>
</div> -->
<AiHint></AiHint>
<div
v-for="(item, index) in messageList"
@ -24,10 +26,35 @@
<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.loading" class="ai-loading">
<div class="text-ros">
<span>{{ item.text }}</span>
<div v-show="item.from === 'user'">
<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 v-show="item.from === 'ai' && item.loading" class="ai-loading">
<span>思考中</span> <n-spin size="small" />
</div>
<div class="ai-hint" v-show="item.from === 'ai'">AI</div>
</div>
</div>
</div>
@ -56,7 +83,8 @@
import { ref, reactive, onMounted, onUnmounted, watch, nextTick } from 'vue'
import { getAiMsg } from '@/api/ai.js'
import moment from 'moment'
import {LineChart, BarChart ,PieChart} from './class/index'
import { LineChart, BarChart, PieChart } from './class/index'
import { AiHint } from '../components'
import userIcon from '@/assets/images/ai/user-icon.png'
import aiIcon from '@/assets/images/ai/ai-icon.png'
//
@ -70,26 +98,25 @@ let lastIndex = ref(0)
//
const echartsTheme = ['简约风', '商务风', '科技风']
let bar = null
// let bar = null
/**
* 页面初始完成
*/
onMounted(() => {
bar = new PieChart('barEcharts', {
title:'图表标题',
unit:'元',
data: [
{ value: 1048, name: 'Search Engine' },
{ value: 735, name: 'Direct' },
{ value: 580, name: 'Email' },
{ value: 484, name: 'Union Ads' },
{ value: 300, name: 'Video Ads' },
{ value: 200, name: 'qq Ads' },
{ value: 100, name: 'vx Ads' }
]
})
// bar = new PieChart('barEcharts', {
// title:'',
// unit:'',
// data: [
// { value: 1048, name: 'Search Engine' },
// { value: 735, name: 'Direct' },
// { value: 580, name: 'Email' },
// { value: 484, name: 'Union Ads' },
// { value: 300, name: 'Video Ads' },
// { value: 200, name: 'qq Ads' },
// { value: 100, name: 'vx Ads' }
// ]
// })
})
/**
@ -103,34 +130,34 @@ const handleSend = async () => {
text: keyWord.value,
time: moment().format('YYYY/MM/DD HH:mm:ss')
})
//AI
setMessageItem({
from: 'ai',
text: '',
time: moment().format('YYYY/MM/DD HH:mm:ss'),
loading: true //ai
})
try {
const res = await getAiMsg({ prompt: keyWord.value })
if (res.type) {
console.log('图表形式')
} else {
//
updataMessageItem({
from: 'ai',
text: res,
time: moment().format('YYYY/MM/DD HH:mm:ss'),
loading: false
})
}
} catch {
updataMessageItem({
from: 'ai',
text: '服务器繁忙,请稍后再试。',
time: moment().format('YYYY/MM/DD HH:mm:ss'),
loading: false
})
}
// //AI
// setMessageItem({
// from: 'ai',
// text: '',
// time: moment().format('YYYY/MM/DD HH:mm:ss'),
// loading: true //ai
// })
// try {
// const res = await getAiMsg({ prompt: keyWord.value })
// if (res.type) {
// console.log('')
// } else {
// //
// updataMessageItem({
// from: 'ai',
// text: res,
// time: moment().format('YYYY/MM/DD HH:mm:ss'),
// loading: false
// })
// }
// } catch {
// updataMessageItem({
// from: 'ai',
// text: '',
// time: moment().format('YYYY/MM/DD HH:mm:ss'),
// loading: false
// })
// }
}
/**
* 存储聊天室数据
@ -161,125 +188,4 @@ const handlerTheme = index => {
}
</script>
<style lang="scss" scoped>
.chat-container {
box-sizing: border-box;
padding: 20px;
height: 100%;
gap: 15px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
.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: 50px;
width: 50px;
}
.user-message {
flex-direction: row-reverse;
.message-content {
display: flex;
flex-direction: column;
align-items: flex-end;
}
}
.ai-loading {
display: flex;
align-items: center;
font-size: 14px;
gap: 10px;
color: #ffffff;
}
.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: 10px 18px;
background: #282a31;
border-radius: 10px 10px 10px 10px;
}
}
}
.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%;
}
}
}
.echarts-box {
height: 220px;
width: 400px;
}
.echatrs-theme {
margin-top: 10px;
display: flex;
align-items: center;
gap: 6px;
.theme-title {
font-size: 14px;
color: #cfd5e5;
font-weight: 400;
}
.theme-list {
display: flex;
align-items: center;
gap: 10px;
.theme-item {
cursor: pointer;
background: #31363e;
border-radius: 2px;
font-size: 14px;
color: #cfd5e5;
font-weight: 400;
box-sizing: border-box;
padding: 2px 10px;
}
}
}
</style>
<style lang="scss" scoped></style>

@ -0,0 +1,102 @@
<template>
<div :class="['chat-item', 'ai-message']">
<!-- 头像 -->
<img :src="aiIcon" class="user-icon" alt="" />
<!-- 内容 -->
<div class="message-content">
<div class="message-time">{{ moment().format('YYYY/MM/DD HH:mm:ss') }}</div>
<div class="message-text">
<span>今天我能为您做些什么呢?</span>
<p>作为你的智慧伙伴我不仅可以写文案出点子还可以和你聊天回答问题您也可以点击</p>
<div class="issue-list">
<div class="issue-top">
<div class="issue-title">下面的问题常用问题快速向我提问</div>
<div class="issue-btn">
<n-icon size="14">
<svg
version="1.1"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
x="0px"
y="0px"
viewBox="0 0 512 512"
enable-background="new 0 0 512 512"
xml:space="preserve"
>
<path
d="M433,288.8c-7.7,0-14.3,5.9-14.9,13.6c-6.9,83.1-76.8,147.9-161.8,147.9c-89.5,0-162.4-72.4-162.4-161.4
c0-87.6,70.6-159.2,158.2-161.4c2.3-0.1,4.1,1.7,4.1,4v50.3c0,12.6,13.9,20.2,24.6,13.5L377,128c10-6.3,10-20.8,0-27.1l-96.1-66.4
c-10.7-6.7-24.6,0.9-24.6,13.5v45.7c0,2.2-1.7,4-3.9,4C148,99.8,64,184.6,64,288.9C64,394.5,150.1,480,256.3,480
c100.8,0,183.4-76.7,191.6-175.1C448.7,296.2,441.7,288.8,433,288.8L433,288.8z"
></path>
</svg>
</n-icon>
<span class="refresh">换一批</span>
</div>
</div>
<div class="issue-list">
<div class="issue-item" v-for="(item,index) in list" :key="index">{{ item.text }}</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import moment from 'moment'
import aiIcon from '@/assets/images/ai/ai-icon.png'
import { ref, reactive } from 'vue'
let list = reactive([
{ text: '帮我统计一下这个月的人事变动情况' },
{ text: ' 帮我统计一下上月度销售总金额是多少' },
{ text: ' 帮我统计一下上个季度整体客户的变动情况' },
{ text: '帮我统计一下上月度销售情况,分为销售人员、销售产品、销售金额三个维度' }
])
</script>
<style scoped>
.message-text {
span {
font-weight: bold;
}
p {
margin: 10px 0;
font-size: 14px;
}
.issue-top {
display: flex;
align-items: center;
justify-content: space-between;
.issue-title {
font-size: 14px;
}
.issue-btn {
display: flex;
align-items: center;
gap: 3px;
}
.refresh {
cursor: pointer;
font-size: 12px;
font-weight: 400;
}
}
.issue-list{
margin-top: 15px;
display: flex;
flex-direction: column;
gap: 10px;
.issue-item{
font-size: 13px;
cursor: pointer;
box-sizing: border-box;
padding: 5px 10px;
background-color: #3D424D;
border-radius: 4px;
}
}
}
</style>

@ -0,0 +1,3 @@
export { default as AiHint } from './aiHint/index.vue'
// export { default as history } from './history'
// export { default as logo } from './logo'

@ -20,6 +20,6 @@
// "strictNullChecks": true, //使null
"noImplicitThis": true //this
},
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue", "types/**/*", "src/api/mock/vchart/bar.js"],
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue", "types/**/*", "src/api/mock/vchart/bar.js", "src/views/AI/components/index.js"],
"exclude": ["node_modules", "dist", "**/*.js"]
}

Loading…
Cancel
Save