|
|
@ -32,13 +32,16 @@ |
|
|
<!-- 顶部固定区域占位符 --> |
|
|
<!-- 顶部固定区域占位符 --> |
|
|
<view class="banner-placeholder"></view> |
|
|
<view class="banner-placeholder"></view> |
|
|
|
|
|
|
|
|
<view class="banner-panel" |
|
|
|
|
|
|
|
|
<view |
|
|
|
|
|
class="banner-panel" |
|
|
:class="messages.length === 0 ? '' : 'panelShow'" |
|
|
:class="messages.length === 0 ? '' : 'panelShow'" |
|
|
|
|
|
:style="{ paddingTop: safeAreaInsets?.top + 'px' }" |
|
|
> |
|
|
> |
|
|
<image |
|
|
<image |
|
|
src="https://d31zlh4on95l9h.cloudfront.net/images/42e18bd7fe97d4f4f37aa70439a0990b.svg" |
|
|
src="https://d31zlh4on95l9h.cloudfront.net/images/42e18bd7fe97d4f4f37aa70439a0990b.svg" |
|
|
class="pray-banner" |
|
|
class="pray-banner" |
|
|
:class="messages.length === 0 ? '' : 'show'" |
|
|
:class="messages.length === 0 ? '' : 'show'" |
|
|
|
|
|
:style="{ paddingTop: safeAreaInsets?.top + 'px' }" |
|
|
mode="aspectFill" |
|
|
mode="aspectFill" |
|
|
></image> |
|
|
></image> |
|
|
<view class="contain"> |
|
|
<view class="contain"> |
|
|
@ -106,7 +109,12 @@ |
|
|
<!-- 聊天区域 --> |
|
|
<!-- 聊天区域 --> |
|
|
<view class="chat-container" v-if="messages.length > 0"> |
|
|
<view class="chat-container" v-if="messages.length > 0"> |
|
|
<!-- 给聊天容器添加滚动引用 --> |
|
|
<!-- 给聊天容器添加滚动引用 --> |
|
|
<scroll-view class="chat-scroll-view" scroll-y="true" :scroll-top="scrollTop"> |
|
|
|
|
|
|
|
|
<scroll-view |
|
|
|
|
|
class="chat-scroll-view" |
|
|
|
|
|
scroll-y="true" |
|
|
|
|
|
:scroll-top="scrollTop" |
|
|
|
|
|
:style="{ paddingTop: safeAreaInsets?.top + 'px' }" |
|
|
|
|
|
> |
|
|
<view class="message-list" id="messageList"> |
|
|
<view class="message-list" id="messageList"> |
|
|
<view |
|
|
<view |
|
|
v-for="(message, index) in messages" |
|
|
v-for="(message, index) in messages" |
|
|
@ -125,16 +133,40 @@ |
|
|
></text> |
|
|
></text> |
|
|
<!-- 会话内容 --> |
|
|
<!-- 会话内容 --> |
|
|
<view class="message-content"> |
|
|
<view class="message-content"> |
|
|
<text class="message-text">{{ message.content }}</text> |
|
|
|
|
|
<!-- loading --> |
|
|
<!-- loading --> |
|
|
<view |
|
|
<view |
|
|
class="loading-dots" |
|
|
class="loading-dots" |
|
|
v-if="message.isThinking " |
|
|
|
|
|
|
|
|
v-if="message.isThinking || !message.isUser" |
|
|
> |
|
|
> |
|
|
<text class="dot"></text> |
|
|
|
|
|
<text class="dot"></text> |
|
|
|
|
|
<text class="dot"></text> |
|
|
|
|
|
|
|
|
<div class="thinking-process"> |
|
|
|
|
|
<div class="thinking-header"> |
|
|
|
|
|
<div class="thinking-icon">∞</div> |
|
|
|
|
|
<div class="thinking-title">深度思考 正在思考</div> |
|
|
|
|
|
<div class="thinking-count">25 个结果</div> |
|
|
|
|
|
<div class="thinking-toggle" @click="toggleThinking"> |
|
|
|
|
|
<span v-if="showThinking">↑</span> |
|
|
|
|
|
<span v-else>↓</span> |
|
|
|
|
|
</div> |
|
|
|
|
|
</div> |
|
|
|
|
|
<div v-show="showThinking" class="thinking-content"> |
|
|
|
|
|
<div class="thinking-item"> |
|
|
|
|
|
<div class="item-status"> |
|
|
|
|
|
<span class="checkmark">✓</span> |
|
|
|
|
|
</div> |
|
|
|
|
|
<div class="item-text">问题分析完成</div> |
|
|
|
|
|
</div> |
|
|
|
|
|
<div class="thinking-item"> |
|
|
|
|
|
<div class="item-status"> |
|
|
|
|
|
<span class="checkmark">✓</span> |
|
|
|
|
|
</div> |
|
|
|
|
|
<div class="item-text">收集相关信息</div> |
|
|
|
|
|
</div> |
|
|
|
|
|
</div> |
|
|
|
|
|
</div> |
|
|
</view> |
|
|
</view> |
|
|
|
|
|
<!-- 使用 rich-text 渲染 Markdown 内容 --> |
|
|
|
|
|
<rich-text v-if="!message.isUser" class="message-text" :nodes="renderMarkdown(message.content)"></rich-text> |
|
|
|
|
|
<text v-else class="message-text">{{ message.content }}</text> |
|
|
</view> |
|
|
</view> |
|
|
</view> |
|
|
</view> |
|
|
</view> |
|
|
</view> |
|
|
@ -179,14 +211,36 @@ const { safeAreaInsets } = uni.getSystemInfoSync(); |
|
|
|
|
|
|
|
|
import { ref, onMounted, nextTick, watch } from "vue"; |
|
|
import { ref, onMounted, nextTick, watch } from "vue"; |
|
|
import footerBar from "../../components/footerBar-cn.vue"; |
|
|
import footerBar from "../../components/footerBar-cn.vue"; |
|
|
|
|
|
import marked from "marked"; // 引入 marked 库 |
|
|
|
|
|
// 设置 marked 选项 |
|
|
|
|
|
marked.setOptions({ |
|
|
|
|
|
renderer: new marked.Renderer(), |
|
|
|
|
|
highlight: null, // 如果需要代码高亮,可以设置适当的函数 |
|
|
|
|
|
langPrefix: 'language-', |
|
|
|
|
|
pedantic: false, |
|
|
|
|
|
gfm: true, |
|
|
|
|
|
breaks: false, |
|
|
|
|
|
sanitize: false, |
|
|
|
|
|
smartLists: true, |
|
|
|
|
|
smartypants: false, |
|
|
|
|
|
xhtml: false |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
// 创建一个用于渲染 Markdown 的函数 |
|
|
|
|
|
const renderMarkdown = (content) => { |
|
|
|
|
|
if (!content) return ''; |
|
|
|
|
|
return marked.parse(content); |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
const type = ref('member') |
|
|
|
|
|
|
|
|
const type = ref("member"); |
|
|
const inputMessage = ref(""); |
|
|
const inputMessage = ref(""); |
|
|
|
|
|
const showThinking = ref(true); |
|
|
const isSending = ref(false); |
|
|
const isSending = ref(false); |
|
|
const uuid = ref(""); |
|
|
const uuid = ref(""); |
|
|
const messages = ref([]); |
|
|
const messages = ref([]); |
|
|
const scrollTop = ref(0); // 用于控制scroll-view的滚动位置 |
|
|
const scrollTop = ref(0); // 用于控制scroll-view的滚动位置 |
|
|
|
|
|
const dataInfo = ref("") |
|
|
|
|
|
|
|
|
const hotTopics = ref([ |
|
|
const hotTopics = ref([ |
|
|
{ |
|
|
{ |
|
|
id: 1, |
|
|
id: 1, |
|
|
@ -222,12 +276,16 @@ onMounted(() => { |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
// 监听消息变化,当有新消息时自动滚动到最新消息 |
|
|
// 监听消息变化,当有新消息时自动滚动到最新消息 |
|
|
watch(messages, (newMessages) => { |
|
|
|
|
|
|
|
|
watch( |
|
|
|
|
|
messages, |
|
|
|
|
|
(newMessages) => { |
|
|
// 延迟执行滚动,确保DOM更新完成 |
|
|
// 延迟执行滚动,确保DOM更新完成 |
|
|
setTimeout(() => { |
|
|
setTimeout(() => { |
|
|
scrollToBottom(); |
|
|
scrollToBottom(); |
|
|
}, 100); |
|
|
}, 100); |
|
|
}, { deep: true }); |
|
|
|
|
|
|
|
|
}, |
|
|
|
|
|
{ deep: true } |
|
|
|
|
|
); |
|
|
|
|
|
|
|
|
// 初始化 UUID |
|
|
// 初始化 UUID |
|
|
const initUUID = () => { |
|
|
const initUUID = () => { |
|
|
@ -263,9 +321,13 @@ const goBlank = () => { |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
// 发送消息 |
|
|
// 发送消息 |
|
|
const sendMessage = () => { |
|
|
|
|
|
|
|
|
const sendMessage = async () => { |
|
|
if (inputMessage.value.trim() === "" || isSending.value) return; |
|
|
if (inputMessage.value.trim() === "" || isSending.value) return; |
|
|
|
|
|
|
|
|
|
|
|
const res = await getDataInfo(); |
|
|
|
|
|
console.log("数据格式为"+dataInfo.value); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const userMessage = { |
|
|
const userMessage = { |
|
|
content: inputMessage.value, |
|
|
content: inputMessage.value, |
|
|
isUser: true, |
|
|
isUser: true, |
|
|
@ -328,13 +390,50 @@ const simulateBotResponse = (userMessage) => { |
|
|
}, 100); |
|
|
}, 100); |
|
|
|
|
|
|
|
|
// 模拟流式响应 |
|
|
// 模拟流式响应 |
|
|
let responseText = `我已经收到您的消息: "${userMessage}"。作为您的股市顾问,我可以为您提供专业的投资建议。请问您想了解哪方面的信息?`; |
|
|
|
|
|
|
|
|
let responseText = `我已经收到您的消息: "${userMessage}"。 |
|
|
|
|
|
## 股票分析报告 |
|
|
|
|
|
|
|
|
|
|
|
### 股票名称: Tesla Inc. (TSLA) |
|
|
|
|
|
|
|
|
|
|
|
- **当前价格**: 448.980 |
|
|
|
|
|
- **更新时间**: 23/10/2025 |
|
|
|
|
|
- **今日无变盘点** |
|
|
|
|
|
|
|
|
|
|
|
### 技术分析 |
|
|
|
|
|
- **CFTL**: 当前牵牛绳为红色,处于龙线区域,最近出现“牛刀小试”,度牛线目前处于青绿色区域。 |
|
|
|
|
|
- **空间预测**: |
|
|
|
|
|
- 预测低一值: 413.364 |
|
|
|
|
|
- 预测高一值: 426.636 |
|
|
|
|
|
- 预测低二值: 421.670 |
|
|
|
|
|
- 预测高二值: 448.314 |
|
|
|
|
|
- **能量分析**: AI智能均线多头排列,当前卖盘小于买盘 |
|
|
|
|
|
|
|
|
|
|
|
### 资金与主力 |
|
|
|
|
|
- **主力分析**: |
|
|
|
|
|
1. 该股庄家中长期筹码成本价格为 356.036,短期资金成本价格为 406.429。该股筹码分散,当日筹码成本价格为 439.322。 |
|
|
|
|
|
2. 近日没有出现主力集中吸筹。 |
|
|
|
|
|
3. 近期主力持仓比例大于散户持仓比例。当日主力持仓增加。当日散户持仓减少。 |
|
|
|
|
|
|
|
|
|
|
|
### 综合评价 |
|
|
|
|
|
- **个股走势评价**: |
|
|
|
|
|
- 该股整体趋势向好,出现暴涨的可能性较大,当前如果已经持有该股票,可以继续持股观察,如果尚未持有该股票,可持续进行观察,目前处于机会的初期,处于反弹阶段,可以分步建仓! |
|
|
|
|
|
|
|
|
|
|
|
- **核心证据链**: |
|
|
|
|
|
- 资金共识:当日多方资金流入。 |
|
|
|
|
|
- 趋势动能:该股中长期处于上升趋势,短期处于弱势状态。 |
|
|
|
|
|
|
|
|
|
|
|
- **牛股评级**: ★★★☆☆ |
|
|
|
|
|
- **暴涨概率**: 60% |
|
|
|
|
|
- **风险评估**: 非常安全 |
|
|
|
|
|
- **安全边际**: 432.671~458.057 |
|
|
|
|
|
- **黄金价域**: 427.995~440.835`; |
|
|
let index = 0; |
|
|
let index = 0; |
|
|
|
|
|
|
|
|
const typeWriter = () => { |
|
|
const typeWriter = () => { |
|
|
if (index < responseText.length) { |
|
|
if (index < responseText.length) { |
|
|
// 使用 Vue 的响应式更新机制 |
|
|
// 使用 Vue 的响应式更新机制 |
|
|
messages.value[messages.value.length - 1].content = responseText.substring(0, index + 1); |
|
|
|
|
|
|
|
|
messages.value[messages.value.length - 1].content = |
|
|
|
|
|
responseText.substring(0, index + 1); |
|
|
index++; |
|
|
index++; |
|
|
|
|
|
|
|
|
// 滚动到底部,更频繁地触发滚动以适应文本增长 |
|
|
// 滚动到底部,更频繁地触发滚动以适应文本增长 |
|
|
@ -374,6 +473,35 @@ const scrollToTop = () => { |
|
|
// 滚动到顶部 |
|
|
// 滚动到顶部 |
|
|
scrollTop.value = 0; |
|
|
scrollTop.value = 0; |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
const toggleThinking = () => { |
|
|
|
|
|
showThinking.value = !showThinking.value; |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// function getDataInfo() { |
|
|
|
|
|
// return uni.request({ |
|
|
|
|
|
// url: 'http://localhost:8888/ka', |
|
|
|
|
|
// data: {}, |
|
|
|
|
|
// header: { |
|
|
|
|
|
// Accept: 'application/json', |
|
|
|
|
|
// 'Content-Type': 'application/json', |
|
|
|
|
|
// 'X-Requested-With': 'XMLHttpRequest' |
|
|
|
|
|
// }, |
|
|
|
|
|
// method: 'GET', |
|
|
|
|
|
// sslVerify: true, |
|
|
|
|
|
// success: (res) => { |
|
|
|
|
|
// console.log(res.data); |
|
|
|
|
|
// res.data.forEach(item => { |
|
|
|
|
|
// console.log("item是"+item); |
|
|
|
|
|
// dataInfo.value = item.name; |
|
|
|
|
|
// }); |
|
|
|
|
|
|
|
|
|
|
|
// }, |
|
|
|
|
|
// fail: (error) => {} |
|
|
|
|
|
// }) |
|
|
|
|
|
// } |
|
|
</script> |
|
|
</script> |
|
|
|
|
|
|
|
|
<style scoped> |
|
|
<style scoped> |
|
|
@ -826,7 +954,6 @@ const scrollToTop = () => { |
|
|
z-index: 1; /* 在灰底之上、内容之下 */ |
|
|
z-index: 1; /* 在灰底之上、内容之下 */ |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.contain { |
|
|
.contain { |
|
|
margin: 0 20rpx; |
|
|
margin: 0 20rpx; |
|
|
gap: 5rpx; |
|
|
gap: 5rpx; |
|
|
@ -857,4 +984,76 @@ const scrollToTop = () => { |
|
|
.banner-placeholder { |
|
|
.banner-placeholder { |
|
|
height: 120rpx; /* 与header高度一致,防止内容被固定头部遮挡 */ |
|
|
height: 120rpx; /* 与header高度一致,防止内容被固定头部遮挡 */ |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
.thinking-process { |
|
|
|
|
|
margin: 10px 0; |
|
|
|
|
|
border: 1px solid #e5e5e5; |
|
|
|
|
|
border-radius: 8px; |
|
|
|
|
|
background-color: #f9f9f9; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
.thinking-header { |
|
|
|
|
|
display: flex; |
|
|
|
|
|
align-items: center; |
|
|
|
|
|
padding: 10px 15px; |
|
|
|
|
|
cursor: pointer; |
|
|
|
|
|
background-color: #fff; |
|
|
|
|
|
border-bottom: 1px solid #e5e5e5; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
.thinking-icon { |
|
|
|
|
|
font-size: 16px; |
|
|
|
|
|
margin-right: 8px; |
|
|
|
|
|
color: #d47c45; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
.thinking-title { |
|
|
|
|
|
font-size: 14px; |
|
|
|
|
|
font-weight: 500; |
|
|
|
|
|
color: #d47c45; |
|
|
|
|
|
margin-right: 8px; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
.thinking-count { |
|
|
|
|
|
font-size: 12px; |
|
|
|
|
|
color: #666; |
|
|
|
|
|
margin-right: 8px; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
.thinking-toggle { |
|
|
|
|
|
font-size: 12px; |
|
|
|
|
|
color: #999; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
.thinking-content { |
|
|
|
|
|
padding: 10px 15px; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
.thinking-item { |
|
|
|
|
|
display: flex; |
|
|
|
|
|
align-items: center; |
|
|
|
|
|
margin-bottom: 8px; |
|
|
|
|
|
padding: 4px 0; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
.item-status { |
|
|
|
|
|
width: 16px; |
|
|
|
|
|
height: 16px; |
|
|
|
|
|
border-radius: 50%; |
|
|
|
|
|
background-color: #f0f0f0; |
|
|
|
|
|
display: flex; |
|
|
|
|
|
justify-content: center; |
|
|
|
|
|
align-items: center; |
|
|
|
|
|
margin-right: 8px; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
.checkmark { |
|
|
|
|
|
font-size: 10px; |
|
|
|
|
|
color: #ff0000; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
.item-text { |
|
|
|
|
|
font-size: 12px; |
|
|
|
|
|
color: #333; |
|
|
|
|
|
} |
|
|
</style> |
|
|
</style> |