Browse Source

AI小财神框架

dong
hongxilin 4 months ago
commit
523ef9ce7b
  1. 201
      src/views/AIchat.vue
  2. 84
      src/views/AIfind.vue
  3. 468
      src/views/homePage.vue

201
src/views/AIchat.vue

@ -0,0 +1,201 @@
<script setup>
import { ref, onMounted, watch, nextTick } from 'vue'
import { ElDialog } from 'element-plus'
import { getReplyStreamAPI } from '../api/AIxiaocaishen'
// GIF
const currentGif = ref('')
//
const newsList = ref(Array(10).fill().map((_, i) => ({
title: `引导提出问题 ${i + 1}`,
content: `新闻 ${i + 1} 的详细内容...`
})))
//
const dialogVisible = ref(false)
const currentNews = ref('')
const showNews = (news) => {
currentNews.value = news
dialogVisible.value = true
}
//
const props = defineProps({
messages: Array,
})
watch(() => props.messages, async (newVal, oldVal) => {
console.log('消息列表已更新,最新消息:', newVal[newVal.length - 1])
//
const response = await getReplyStreamAPI({
"workflow_id": "7480464341100494863",
"parameters": {
"input": newVal[newVal.length - 1].content
}
});
const reader = response.body.getReader();
console.log(response, 'response')
console.log(reader, 'reader')
}, { deep: true, immediate: true })
// GIF
onMounted(() => {
const random = Math.floor(Math.random() * 4) + 1
console.log(random, 'random')
currentGif.value = `src/assets/img/AIchat/AIgif${random}.gif`
})
</script>
<template>
<div class="chat-container">
<!-- GIF区域 -->
<div class="gif-area">
<img :src="currentGif" alt="AI动画">
<div class="marquee-container">
<div class="marquee-row top">
<div v-for="(news, index) in newsList.slice(0, 5)" :key="'top' + index" class="marquee-item"
@click="showNews(news)">
{{ news.title }}
</div>
</div>
<div class="marquee-row bottom">
<div v-for="(news, index) in newsList.slice(5, 10)" :key="'bottom' + index" class="marquee-item"
@click="showNews(news)">
{{ news.title }}
</div>
</div>
</div>
</div>
<!-- 消息区域 -->
<div class="message-area">
<div v-for="(msg, index) in messages" :key="index" :class="['message-bubble', msg.sender]">
{{ msg.content }}
</div>
</div>
<!-- 新闻弹窗 -->
<el-dialog v-model="dialogVisible" title="新闻详情" width="60%">
<p>{{ currentNews.content }}</p>
</el-dialog>
</div>
</template>
<style scoped>
.chat-container {
display: flex;
flex-direction: column;
}
.gif-area {
/* position: relative; */
height: 30vh;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.gif-area img {
width: 30%;
/* 改为百分比单位 */
min-width: 200px;
/* 最小尺寸 */
max-width: 400px;
/* 最大尺寸 */
height: auto;
left: 50%;
transition: all 0.3s;
/* 添加过渡效果 */
}
.marquee-container {
/* position: absolute; */
bottom: 0;
width: 100%;
}
.marquee-row {
white-space: nowrap;
overflow: visible;
padding: 8px 0;
width: 100%;
}
.marquee-item {
display: inline-block;
margin: 0 15px;
padding: 8px 20px;
background: rgba(255, 255, 255, 0.9);
/* 白色背景 */
border-radius: 10px;
/* 圆角矩形 */
color: #333;
/* 文字颜色改为深色 */
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
/* 添加阴影 */
transition: all 0.3s;
transition: color 0.3s;
}
.top {
animation: marquee 15s linear infinite;
}
.bottom {
animation: marquee 15s linear infinite reverse;
}
@keyframes marquee {
0% {
transform: translateX(100%);
}
100% {
transform: translateX(-150%);
}
}
.message-area {
flex: 1;
overflow: auto;
padding: 20px;
}
.message-bubble {
max-width: 70%;
margin: 10px 20px;
padding: 15px 25px;
border-radius: 10px;
position: relative;
}
.message-bubble.user {
background: #8263f0;
color: white;
margin-left: auto;
/* border-bottom-right-radius: 5px; */
}
.message-bubble.ai {
background: #f0f0f0;
color: #333;
margin-right: auto;
border-bottom-left-radius: 5px;
}
.message-area {
flex: 1;
overflow-y: auto;
padding: 20px;
display: flex;
flex-direction: column;
gap: 15px;
}
</style>

84
src/views/AIfind.vue

@ -0,0 +1,84 @@
<script setup>
import { ref } from 'vue'
//
const newsList = ref(Array(10).fill().map((_, i) => ({
title: `每日资讯 ${i + 1}`,
content: `资讯 ${i + 1} 的详细内容...`
})))
//
const dialogVisible = ref(false)
const currentNews = ref('')
const showNews = (news) => {
currentNews.value = news
dialogVisible.value = true
}
</script>
<template>
<div class="news-container">
<el-text v-for="(news, index) in newsList" :key="index" class="news-item" @click="showNews(news)">
{{ news.title }}
</el-text>
<el-text class="daily-item">
<span></span>
<span class="purple-text">每日复盘</span>
<span> 即将上线</span>
<br>
<span>敬请期待</span>
</el-text>
</div>
<!-- 新闻弹窗 -->
<el-dialog v-model="dialogVisible" title="每日资讯详情" width="60%" >
<p>{{ currentNews.content }}</p>
</el-dialog>
</template>
<style scoped>
.news-container {
height: auto;
display: flex;
flex-direction: column;
align-items: center;
/* 水平居中 */
gap: 10px;
}
.news-item {
width: 60%;
/* 拉长宽度 */
padding: 10px;
border: 2px solid #af84e0 !important;
/* 紫色边框 */
border-radius: 4px;
text-align: center;
/* 文字居中 */
font-size: 18px;
font-weight: bold;
color: #000;
}
/* 响应式调整 */
@media (max-width: 768px) {
.news-item {
width: 60%;
font-size: 16px;
}
}
.purple-text {
color: #7315df !important;
}
.daily-item {
font-size: 20px;
font-weight: bold;
line-height: 1.8;
white-space: nowrap;
text-align: center;
/* 保持首行不换行 */
}
</style>

468
src/views/homePage.vue

@ -0,0 +1,468 @@
<script setup>
//
import { ref, computed, onMounted, watch, nextTick } from 'vue'
import { setHeight } from '../utils/setHeight'
import { getReplyAPI } from '../api/AIxiaocaishen'
import AIchat from '../views/AIchat.vue'
import AIfind from '../views/AIfind.vue'
//
// sessionStorage 使 'aifindCow'tab
const activeTab = ref(sessionStorage.getItem('activeTabAI') || 'AIchat')
const activeIndex = ref(parseInt(sessionStorage.getItem('activeIndexAI') || '0'))
const tabs = computed(() => [
{
name: 'AIchat',
label: 'AI对话'
},
{
name: 'AIfind',
label: '发现'
}
])
const setActiveTab = (tab, index) => {
activeTab.value = tab
activeIndex.value = index //
// tab index sessionStorage
sessionStorage.setItem('activeTabAI', tab)
sessionStorage.setItem('activeIndexAI', index.toString())
setHeight(document.getElementById('testId')) //
}
//
const activeComponent = computed(() => {
return activeTab.value === 'AIchat' ? AIchat : AIfind
})
//
const getCount = () => {
console.log('获取次数')
}
//
const isThinking = ref(false)
const toggleThink = () => {
isThinking.value = !isThinking.value
}
//
const message = ref('');
//
const messages = ref([])
//
const isLoading = ref(false)
// sendMessage
const triggerFetch = ref(false)
const sendMessage = async () => {
if (!message.value) return
if (isLoading.value) return
//
messages.value.push({
sender: 'user',
content: message.value,
timestamp: new Date().toISOString()
})
//
message.value = ''
}
//
const showAnnouncement = () => {
console.log('打开公告')
}
//
const showCount = () => {
console.log('显示剩余次数')
}
//
const tabContent = ref(null)
watch(messages, async () => {
await nextTick()
if (tabContent.value) {
tabContent.value.scrollTop = tabContent.value.scrollHeight
}
}, { deep: true, immediate: true })
onMounted(() => {
setHeight(document.getElementById('testId')) //
})
</script>
<template>
<div class="homepage" id="testId">
<el-container>
<!-- AI小财神头部 logo 次数 公告 -->
<el-header class="homepage-head">
<!-- logo -->
<div class="homepage-logo">
<img src="src\assets\img\homePage\logo.png" alt="图片加载失败" class="logo1">
<img src="src\assets\img\homePage\madeInHL.png" alt="图片加载失败" class="logo2">
</div>
<div class="homepage-right-group">
<div class="count-badge" @click="showCount">
<img src="src\assets\img\homePage\get-count-all.png" class="action-btn">
<div class="count-number">1000</div>
</div>
<img src="src\assets\img\homePage\announcement.png" class="announcement-btn action-btn"
@click="showAnnouncement">
</div>
</el-header>
<!-- 主体部分小人 问题轮询图 对话内容 -->
<el-main class="homepage-body">
<div class="main-wrapper">
<section class="tab-section">
<div class="tab-container">
<div v-for="(tab, index) in tabs" :key="tab.name" @click="setActiveTab(tab.name, index)"
:class="['tab-item', { active: activeIndex === index }]">
<span>{{ tab.label }}</span>
</div>
</div>
</section>
<div class="tab-content" ref="tabContent">
<component :is="activeComponent" :messages="messages" />
</div>
</div>
</el-main>
<!-- 尾部 问题输入框 深度思考 多语言 语音播报 -->
<el-footer class="homepage-footer">
<!-- 第一行按钮 -->
<div class="footer-first-line">
<div class="left-group">
<img v-if="isThinking" src="src\assets\img\homePage\tail\think-active.png" @click="toggleThink"
class="action-btn">
<img v-else src="src\assets\img\homePage\tail\think-no-active.png" @click="toggleThink"
class="action-btn">
<img src="src\assets\img\homePage\tail\language.png" @click="changeLanguage" class="action-btn">
<img src="src\assets\img\homePage\tail\voice.png" @click="toggleVoice" class="action-btn">
</div>
<img src="src\assets\img\homePage\tail\send.png" @click="sendMessage" class="action-btn send-btn">
</div>
<!-- 第二行输入框 -->
<div class="footer-second-line">
<img src="src\assets\img\homePage\tail\msg.png" class="msg-icon">
<el-input type="textarea" v-model="message" :autosize="{ minRows: 1, maxRows: 4 }"
placeholder="给AI小财神发消息..." class="msg-input"
@keydown.enter.exact.prevent="isLoading ? null : sendMessage()" resize="none">
</el-input>
</div>
</el-footer>
</el-container>
</div>
</template>
<style scoped>
/* 标签栏 */
.tab-container {
display: flex;
gap: 30px;
margin-bottom: 10px;
padding: 0 20px;
justify-content: flex-end;
/* 新增右对齐 */
}
.tab-item {
cursor: pointer;
padding: 8px 12px;
font-size: clamp(18px, 3vw, 20px);
color: #999;
transition: all 0.3s;
border-bottom: 2px solid transparent;
font-weight: bold;
}
.tab-item.active {
color: #000;
border-color: #000;
}
.tab-item:not(.active):hover {
color: #666;
}
.tab-content {
overflow-y: auto;
overflow-x: hidden;
scroll-behavior: smooth; /* 添加平滑滚动效果 */
}
@media (max-width: 768px) {
.tab-container {
gap: 15px;
padding: 0 10px;
}
.tab-item {
font-size: clamp(14px, 3vw, 16px);
padding: 6px 10px;
}
}
</style>
<style scoped>
.homepage {
height: 100vh;
margin: 0 auto;
background-image: url(src/assets/img/homePage/bk.png);
background-size: 100% 100%;
background-repeat: no-repeat;
background-position: center;
display: flex;
}
.homepage .el-container {
height: 100%;
flex-direction: column;
/* 明确纵向排列 */
display: flex;
overflow: hidden;
}
.el-container .el-header {
height: 10%;
/* 设置头部高度 */
margin-top: 5px;
}
.el-container .el-main {
overflow-y: hidden;
overflow-x: hidden;
/* 新增滚动条 */
flex: 1 1 0%;
min-height: 0;
scrollbar-width: thin;
scrollbar-color: #888 transparent;
}
.el-container .el-footer {
/* height: 11%; */
height: auto;
min-height: 70px;
gap: 5px;
margin-top: 0;
}
.homepage-head {
padding: 0px;
/* 启用 flex 布局 */
display: flex;
position: relative;
/* 左右分开布局 */
justify-content: space-between;
}
.homepage-right-group {
display: flex;
gap: 8px;
align-items: center;
margin-left: auto;
margin-right: 20px;
}
.homepage-right-group .action-btn {
height: 40px;
}
.homepage-right-group .count-badge {
position: relative;
cursor: pointer;
}
.homepage-right-group .count-badge .count-number {
position: absolute;
top: 6px;
right: 29px;
color: #573dfc;
font-size: 12px;
font-weight: bold;
}
.homepage-right-group .announcement-btn {
cursor: pointer;
transition: transform 0.2s;
}
.homepage-right-group .announcement-btn:hover {
transform: scale(1.05);
}
.homepage-body {
padding: 0px;
height: calc(100% - 70px);
/* 根据底部高度调整 */
}
.main-wrapper {
height: 100%;
display: flex;
flex-direction: column;
}
.tab-section {
flex-shrink: 0;
/* 禁止伸缩 */
}
.tab-content {
flex: 1;
overflow-y: auto;
min-height: 0;
/* 关键:允许内容收缩 */
}
.homepage-logo {
height: 100%;
/* 改为根据内容自适应 */
width: fit-content;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
/* 固定左间距 */
margin-left: 20px;
margin-right: auto;
/* 新增相对定位 */
position: relative;
}
/* 新增媒体查询适配小屏幕 */
@media (max-width: 768px) {
.homepage-logo {
margin-left: 10px;
left: 0;
}
}
.logo1 {
width: 120px;
/* 固定 logo1 尺寸 */
height: auto;
margin-bottom: 8px;
/* 添加间距 */
}
.logo2 {
width: 80px;
/* 缩小 logo2 尺寸 */
height: auto;
}
/* 尾部 */
.homepage-footer {
display: flex;
flex-direction: column;
gap: 5px;
/* margin-top: auto; */
margin-bottom: 20px;
flex-shrink: 0;
height: auto;
}
.footer-first-line {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: auto;
flex-shrink: 0;
}
.left-group {
display: flex;
gap: 15px;
}
.action-btn {
cursor: pointer;
transition: transform 0.2s;
height: 28px;
}
.action-btn:hover {
transform: scale(1.05);
}
.send-btn {
margin-left: auto;
margin-right: 5px;
}
.footer-second-line {
position: relative;
display: flex;
height: auto;
align-items: flex-end;
flex: 1;
margin-top: auto;
min-height: 34px;
}
.msg-icon {
position: absolute;
left: 12px;
top: 50%;
transform: translateY(-50%);
width: 24px;
z-index: 1;
}
.msg-input:deep(.el-textarea__inner) {
border: none !important;
box-shadow: none !important;
overflow-y: auto !important;
/* 强制显示滚动条 */
transition: all 0.2s ease-out;
/* 添加过渡效果 */
}
/* .msg-input:deep(.el-textarea__inner:focus) {
border: none !important;
box-shadow: 0 4px 12px rgba(89, 24, 241, 0.3) !important;
} */
.msg-input {
min-height: 34px;
max-height: 120px;
width: calc(100% - 65px);
border-radius: 20px;
padding: 0px 20px 0 45px;
font-size: 16px;
transition: height 0.2s ease-out;
/* 添加高度过渡效果 */
overflow-y: hidden;
/* 隐藏垂直滚动条 */
box-shadow: 0 4px 12px rgba(89, 24, 241, 0.3);
background: #fff;
}
.msg-input:focus {
outline: none;
}
@media (max-width: 768px) {
.action-btn {
height: 28px;
}
.footer-second-line {
height: 50px;
}
.msg-input {
font-size: 16px;
}
}
</style>
Loading…
Cancel
Save