ArvinLovegood ec7534ff2c perf(stock):优化盘前盘后标签显示逻辑
- 修改了盘前盘后标签的显示条件,仅在盘前盘后值大于 0 时显示
- 这个改动可以避免在盘前盘后值为 0时不必要的标签显示,提高界面的可读性和性能
2025-03-05 22:52:31 +08:00

943 lines
30 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<script setup>
import {computed, h, onBeforeMount, onBeforeUnmount, onMounted, reactive, ref} from 'vue'
import {
Follow,
GetAIResponseResult,
GetConfig,
GetFollowList,
GetStockList,
GetVersionInfo,
Greet,
NewChatStream,
SaveAIResponseResult,
SendDingDingMessageByType,
SetAlarmChangePercent,
SetCostPriceAndVolume,
SetStockSort,
UnFollow
} from '../../wailsjs/go/main/App'
import {
NAvatar,
NButton,
NFlex,
NForm,
NFormItem,
NInputNumber,
NText,
useDialog,
useMessage,
useModal,
useNotification
} from 'naive-ui'
import {EventsEmit, EventsOn, WindowFullscreen, WindowReload, WindowUnfullscreen} from '../../wailsjs/runtime'
import {
Add,
ChatboxOutline,
} from '@vicons/ionicons5'
import {MdPreview,MdEditor } from 'md-editor-v3';
// preview.css相比style.css少了编辑器那部分样式
//import 'md-editor-v3/lib/preview.css';
import 'md-editor-v3/lib/style.css';
import { ExportPDF } from '@vavt/v3-extension';
import '@vavt/v3-extension/lib/asset/ExportPDF.css';
import html2canvas from "html2canvas";
import {asBlob} from 'html-docx-js-typescript';
import vueDanmaku from 'vue3-danmaku'
const danmus = ref([])
const ws = ref(null)
const toolbars = [0];
const handleProgress = (progress) => {
console.log(`Export progress: ${progress.ratio * 100}%`);
};
const enableEditor= ref(false)
const mdPreviewRef = ref(null)
const mdEditorRef = ref(null)
const tipsRef = ref(null)
const message = useMessage()
const modal = useModal()
const notify = useNotification()
const dialog = useDialog()
const stocks=ref([])
const results=ref({})
const ticker=ref({})
const stockList=ref([])
const followList=ref([])
const options=ref([])
const modalShow = ref(false)
const modalShow2 = ref(false)
const modalShow3 = ref(false)
const modalShow4 = ref(false)
const addBTN = ref(true)
const formModel = ref({
name: "",
code: "",
costPrice: 0.000,
volume: 0,
alarm: 0,
alarmPrice:0,
sort:999,
})
const data = reactive({
modelName:"",
chatId: "",
question:"",
name: "",
code: "",
fenshiURL:"",
kURL:"",
resultText: "Please enter your name below 👇",
fullscreen: false,
airesult: "",
openAiEnable: false,
loading: true,
enableDanmu: false,
})
const icon = ref('https://raw.githubusercontent.com/ArvinLovegood/go-stock/master/build/appicon.png');
const sortedResults = computed(() => {
//console.log("computed",sortedResults.value)
const sortedKeys =Object.keys(results.value).sort();
const sortedObject = {};
sortedKeys.forEach(key => {
sortedObject[key] = results.value[key];
});
return sortedObject
});
onBeforeMount(()=>{
GetStockList("").then(result => {
stockList.value = result
options.value=result.map(item => {
return {
label: item.name+" - "+item.ts_code,
value: item.ts_code
}
})
})
GetFollowList().then(result => {
followList.value = result
for (const followedStock of result) {
if(followedStock.StockCode.startsWith("us")){
followedStock.StockCode="gb_"+ followedStock.StockCode.replace("us", "").toLowerCase()
}
if (!stocks.value.includes(followedStock.StockCode)) {
console.log("followList",followedStock.StockCode)
stocks.value.push(followedStock.StockCode)
}
}
monitor()
message.destroyAll()
})
GetConfig().then(result => {
if (result.openAiEnable) {
data.openAiEnable = true
}
if (result.enableDanmu) {
data.enableDanmu = true
}
})
})
onMounted(() => {
message.loading("Loading...")
// console.log(`the component is now mounted.`)
ticker.value=setInterval(() => {
if(isTradingTime()){
//monitor()
data.fenshiURL='http://image.sinajs.cn/newchart/min/n/'+data.code+'.gif'+"?t="+Date.now()
}
}, 3500)
GetVersionInfo().then((res) => {
icon.value = res.icon;
});
// 创建 WebSocket 连接
ws.value = new WebSocket('ws://8.134.249.145:16688/ws'); // 替换为你的 WebSocket 服务器地址
//ws.value = new WebSocket('ws://localhost:16688/ws'); // 替换为你的 WebSocket 服务器地址
ws.value.onopen = () => {
console.log('WebSocket 连接已打开');
};
ws.value.onmessage = (event) => {
if(data.enableDanmu){
danmus.value.push(event.data);
}
};
ws.value.onerror = (error) => {
console.error('WebSocket 错误:', error);
};
ws.value.onclose = () => {
console.log('WebSocket 连接已关闭');
};
})
onBeforeUnmount(() => {
// console.log(`the component is now unmounted.`)
clearInterval(ticker.value)
ws.value.close()
})
EventsOn("refresh",(data)=>{
message.success(data)
})
EventsOn("showSearch",(data)=>{
addBTN.value = data === 1;
})
EventsOn("stock_price",(data)=>{
updateData(data)
})
EventsOn("refreshFollowList",(data)=>{
WindowReload()
// message.loading("refresh...")
// GetFollowList().then(result => {
// followList.value = result
// for (const followedStock of result) {
// if (!stocks.value.includes(followedStock.StockCode)) {
// stocks.value.push(followedStock.StockCode)
// }
// }
// monitor()
// message.destroyAll
// })
})
EventsOn("newChatStream",async (msg) => {
//console.log("newChatStream:->",data.airesult)
data.loading = false
//console.log(msg)
if (msg === "DONE") {
SaveAIResponseResult(data.code, data.name, data.airesult, data.chatId,data.question)
message.info("AI分析完成")
message.destroyAll()
} else {
if(msg.chatId){
data.chatId = msg.chatId
}
if(msg.question){
data.question = msg.question
}
if(msg.content){
data.airesult = data.airesult + msg.content
}
if(msg.extraContent){
data.airesult = data.airesult + msg.extraContent
}
}
})
EventsOn("updateVersion",async (msg) => {
const githubTimeStr = msg.published_at;
// 创建一个 Date 对象
const utcDate = new Date(githubTimeStr);
// 获取本地时间
const date = new Date(utcDate.getTime());
const year = date.getFullYear();
// getMonth 返回值是 0 - 11所以要加 1
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
const seconds = String(date.getSeconds()).padStart(2, '0');
const formattedDate = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
console.log("GitHub UTC 时间:", utcDate);
console.log("转换后的本地时间:", formattedDate);
notify.info({
avatar: () =>
h(NAvatar, {
size: 'small',
round: false,
src: icon.value
}),
title: '发现新版本: ' + msg.tag_name,
content: () => {
//return h(MdPreview, {theme:'dark',modelValue:msg.commit?.message}, null)
return h('div', {
style: {
'text-align': 'left',
'font-size': '14px',
}
}, { default: () => msg.commit?.message })
},
duration: 5000,
meta: "发布时间:"+formattedDate,
action: () => {
return h(NButton, {
type: 'primary',
size: 'small',
onClick: () => {
window.open(msg.html_url)
}
}, { default: () => '查看' })
}
})
})
EventsOn("warnMsg",async (msg) => {
notify.error({
avatar: () =>
h(NAvatar, {
size: 'small',
round: false,
src: icon.value
}),
title: '警告',
duration: 5000,
content: () => {
return h('div', {
style: {
'text-align': 'left',
'font-size': '14px',
}
}, { default: () => msg })
},
})
})
//判断是否是A股交易时间
function isTradingTime() {
const now = new Date();
const day = now.getDay(); // 获取星期几0表示周日1-6表示周一至周六
if (day >= 1 && day <= 5) { // 周一至周五
const hours = now.getHours();
const minutes = now.getMinutes();
const totalMinutes = hours * 60 + minutes;
const startMorning = 9 * 60 + 15; // 上午9点15分换算成分钟数
const endMorning = 11 * 60 + 30; // 上午11点30分换算成分钟数
const startAfternoon = 13 * 60; // 下午13点换算成分钟数
const endAfternoon = 15 * 60; // 下午15点换算成分钟数
if ((totalMinutes >= startMorning && totalMinutes < endMorning) ||
(totalMinutes >= startAfternoon && totalMinutes < endAfternoon)) {
return true;
}
}
return false;
}
function AddStock(){
if (!data?.code) {
message.error("请输入有效股票代码");
return;
}
if (!stocks.value.includes(data.code)) {
Follow(data.code).then(result => {
if(result==="关注成功"){
stocks.value.push(data.code)
message.success(result)
monitor();
}else{
message.error(result)
}
})
}else{
message.error("已经关注了")
}
}
function removeMonitor(code,name,key) {
console.log("removeMonitor",name,code,key)
stocks.value.splice(stocks.value.indexOf(code),1)
console.log("removeMonitor-key",key)
console.log("removeMonitor-v",results.value[key])
delete results.value[key]
console.log("removeMonitor-v",results.value[key])
UnFollow(code).then(result => {
message.success(result)
})
}
function SendDanmu(){
//danmus.value.push(data.name)
console.log("SendDanmu",data.name)
console.log("SendDanmu-readyState", ws.value.readyState)
ws.value.send(data.name)
}
function getStockList(value){
// console.log("getStockList",value)
let result;
result=stockList.value.filter(item => item.name.includes(value)||item.ts_code.includes(value))
options.value=result.map(item => {
return {
label: item.name+" - "+item.ts_code,
value: item.ts_code
}
})
if(value&&value.indexOf("-")<=0){
data.code=value
}
}
async function updateData(result) {
console.log("stock_price",result['日期'],result['时间'],result['股票代码'],result['股票名称'],result['当前价格'],result['盘前盘后'])
if(result["当前价格"]<=0){
result["当前价格"]=result["卖一报价"]
}
if (result.changePercent>0) {
result.type="error"
result.color="#E88080"
}else if (result.changePercent<0) {
result.type="success"
result.color="#63E2B7"
}else {
result.type="default"
result.color="#FFFFFF"
}
if(result.profitAmount>0){
result.profitType="error"
}else if(result.profitAmount<0){
result.profitType="success"
}
if(result["当前价格"]){
if(result.alarmChangePercent>0&&Math.abs(result.changePercent)>=result.alarmChangePercent){
SendMessage(result,1)
}
if(result.alarmPrice>0&&result["当前价格"]>=result.alarmPrice){
SendMessage(result,2)
}
if(result.costPrice>0&&result["当前价格"]>=result.costPrice){
SendMessage(result,3)
}
}
result.key=GetSortKey(result.sort,result["股票代码"])
results.value[GetSortKey(result.sort,result["股票代码"])]=result
}
async function monitor() {
for (let code of stocks.value) {
// console.log(code)
Greet(code).then(result => {
updateData(result)
})
}
}
//数字长度不够前面补0
function padZero(num, length) {
return (Array(length).join('0') + num).slice(-length);
}
function GetSortKey(sort,code){
return padZero(sort,6)+"_"+code
}
function onSelect(item) {
//console.log("onSelect",item)
if(item.indexOf("-")>0){
item=item.split("-")[1].toLowerCase()
}
if(item.indexOf(".")>0){
data.code=item.split(".")[1].toLowerCase()+item.split(".")[0]
}
}
function search(code,name){
setTimeout(() => {
//window.open("https://xueqiu.com/S/"+code)
//window.open("https://www.cls.cn/stock?code="+code)
//window.open("https://quote.eastmoney.com/"+code+".html")
//window.open("https://finance.sina.com.cn/realstock/company/"+code+"/nc.shtml")
window.open("https://www.iwencai.com/unifiedwap/result?w="+name)
//window.open("https://www.iwencai.com/chat/?question="+code)
}, 500)
}
function setStock(code,name){
let res=followList.value.filter(item => item.StockCode===code)
//console.log("res:",res)
formModel.value.name=name
formModel.value.code=code
formModel.value.volume=res[0].Volume?res[0].Volume:0
formModel.value.costPrice=res[0].CostPrice
formModel.value.alarm=res[0].AlarmChangePercent
formModel.value.alarmPrice=res[0].AlarmPrice
formModel.value.sort=res[0].Sort
modalShow.value=true
}
function showFenshi(code,name){
data.code=code
data.name=name
data.fenshiURL='http://image.sinajs.cn/newchart/min/n/'+data.code+'.gif'+"?t="+Date.now()
if(code.startsWith('hk')){
data.fenshiURL='http://image.sinajs.cn/newchart/hk_stock/min/'+data.code.replace("hk","")+'.gif'+"?t="+Date.now()
}
if(code.startsWith('gb_')){
data.fenshiURL='http://image.sinajs.cn/newchart/usstock/min/'+data.code.replace("gb_","")+'.gif'+"?t="+Date.now()
}
modalShow2.value=true
}
function showK(code,name){
data.code=code
data.name=name
data.kURL='http://image.sinajs.cn/newchart/daily/n/'+data.code+'.gif'+"?t="+Date.now()
if(code.startsWith('hk')){
data.kURL='http://image.sinajs.cn/newchart/hk_stock/daily/'+data.code.replace("hk","")+'.gif'+"?t="+Date.now()
}
if(code.startsWith('gb_')){
data.kURL='http://image.sinajs.cn/newchart/usstock/daily/'+data.code.replace("gb_","")+'.gif'+"?t="+Date.now()
}
//https://image.sinajs.cn/newchart/usstock/daily/dji.gif
//https://image.sinajs.cn/newchart/hk_stock/daily/06030.gif?1740729404273
modalShow3.value=true
}
function updateCostPriceAndVolumeNew(code,price,volume,alarm,formModel){
if(formModel.sort){
SetStockSort(formModel.sort,code).then(result => {
//message.success(result)
})
}
if(alarm||formModel.alarmPrice){
SetAlarmChangePercent(alarm,formModel.alarmPrice,code).then(result => {
//message.success(result)
})
}
SetCostPriceAndVolume(code,price,volume).then(result => {
modalShow.value=false
message.success(result)
GetFollowList().then(result => {
followList.value = result
for (const followedStock of result) {
if (!stocks.value.includes(followedStock.StockCode)) {
stocks.value.push(followedStock.StockCode)
}
}
monitor()
message.destroyAll()
})
})
}
function fullscreen(){
if(data.fullscreen){
WindowUnfullscreen()
}else{
WindowFullscreen()
}
data.fullscreen=!data.fullscreen
}
//type 报警类型: 1 涨跌报警;2 股价报警 3 成本价报警
function SendMessage(result,type){
let typeName=getTypeName(type)
let img='http://image.sinajs.cn/newchart/min/n/'+result["股票代码"]+'.gif'+"?t="+Date.now()
let markdown="### go-stock ["+typeName+"]\n\n"+
"### "+result["股票名称"]+"("+result["股票代码"]+")\n" +
"- 当前价格: "+result["当前价格"]+" "+result.changePercent+"%\n" +
"- 最高价: "+result["今日最高价"]+" "+result.highRate+"\n" +
"- 最低价: "+result["今日最低价"]+" "+result.lowRate+"\n" +
"- 昨收价: "+result["昨日收盘价"]+"\n" +
"- 今开价: "+result["今日开盘价"]+"\n" +
"- 成本价: "+result.costPrice+" "+result.profit+"% "+result.profitAmount+" ¥\n" +
"- 成本数量: "+result.costVolume+"股\n" +
"- 日期: "+result["日期"]+" "+result["时间"]+"\n\n"+
"![image]("+img+")\n"
let title=result["股票名称"]+"("+result["股票代码"]+") "+result["当前价格"]+" "+result.changePercent
let msg='{' +
' "msgtype": "markdown",' +
' "markdown": {' +
' "title":"['+typeName+"]"+title+'",' +
' "text": "'+markdown+'"' +
' },' +
' "at": {' +
' "isAtAll": true' +
' }' +
' }'
// SendDingDingMessage(msg,result["股票代码"])
SendDingDingMessageByType(msg,result["股票代码"],type)
}
function aiReCheckStock(stock,stockCode) {
data.modelName=""
data.airesult=""
data.time=""
data.name=stock
data.code=stockCode
data.loading=true
modalShow4.value=true
message.loading("ai检测中...",{
duration: 0,
})
//
NewChatStream(stock,stockCode,data.question)
}
function aiCheckStock(stock,stockCode){
GetAIResponseResult(stockCode).then(result => {
if(result.content){
data.modelName=result.modelName
data.chatId=result.chatId
data.question=result.question
data.name=stock
data.code=stockCode
data.loading=false
modalShow4.value=true
data.airesult=result.content
const date = new Date(result.CreatedAt);
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
const seconds = String(date.getSeconds()).padStart(2, '0');
data.time=`${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
}else{
data.modelName=""
data.question=""
data.airesult=""
data.time=""
data.name=stock
data.code=stockCode
data.loading=true
modalShow4.value=true
message.loading("ai检测中...",{
duration: 0,
})
NewChatStream(stock,stockCode)
}
})
}
function getTypeName(type){
switch (type)
{
case 1:
return "涨跌报警"
case 2:
return "股价报警"
case 3:
return "成本价报警"
default:
return ""
}
}
//获取高度
function getHeight() {
return document.documentElement.clientHeight
}
window.onerror = function (msg, source, lineno, colno, error) {
// 将错误信息发送给后端
EventsEmit("frontendError", {
page: "stock.vue",
message: msg,
source: source,
lineno: lineno,
colno: colno,
error: error ? error.stack : null,
data:data,
results:results,
followList:followList,
stockList:stockList,
stocks:stocks,
formModel:formModel,
});
message.error("发生错误:"+msg)
return true;
};
function saveAsImage(name,code) {
const element = document.querySelector('.md-editor-preview');
if (element) {
html2canvas(element,{
useCORS: true, // 解决跨域图片问题
scale: 2, // 提高截图质量
allowTaint: true, // 允许跨域图片
}).then(canvas => {
const link = document.createElement('a');
link.href = canvas.toDataURL('image/png');
link.download = name+"["+code+']-ai-analysis-result.png';
link.click();
});
} else {
message.error('无法找到分析结果元素');
}
}
async function copyToClipboard() {
try {
await navigator.clipboard.writeText(data.airesult);
message.success('分析结果已复制到剪切板');
} catch (err) {
message.error('复制失败: ' + err);
}
}
function saveAsMarkdown() {
const blob = new Blob([data.airesult], { type: 'text/markdown;charset=utf-8' });
const link = document.createElement('a');
link.href = URL.createObjectURL(blob);
link.download = `${data.name}[${data.code}]-ai-analysis-result.md`;
link.click();
URL.revokeObjectURL(link.href);
link.remove()
}
function getHtml(ref) {
if (ref.value) {
// 获取 MdPreview 组件的根元素
const rootElement = ref.value.$el;
// 获取 HTML 内容
return rootElement.innerHTML;
} else {
console.error('mdPreviewRef is not yet available');
return "";
}
}
// 导出文档
async function saveAsWord() {
// 将富文本内容拼接为一个完整的html
const html = getHtml(mdPreviewRef)
const tipsHtml = getHtml(tipsRef)
const value = `
${html}
<hr>
<div style="font-size: 12px;color: red">
${tipsHtml}
</div>
<br>
本报告由go-stock项目生成
<p>
<a href="https://github.com/ArvinLovegood/go-stock">
AI赋能股票分析自选股行情获取成本盈亏展示涨跌报警推送市场整体/个股情绪分析K线技术指标分析等。数据全部保留在本地。支持DeepSeekOpenAI OllamaLMStudioAnythingLLM硅基流动火山方舟阿里云百炼等平台或模型。
</a></p>
`
// landscape就是横着的portrait是竖着的默认是竖屏portrait。
const blob = await asBlob(value, { orientation: 'portrait' })
const a = document.createElement('a')
a.href = URL.createObjectURL(blob)
a.download = `${data.name}[${data.code}]-ai-analysis-result.docx`;
a.click()
// 下载后将标签移除
URL.revokeObjectURL(a.href);
a.remove()
}
</script>
<template>
<vue-danmaku v-model:danmus="danmus" style="height:100px; width:100%;z-index: 9;position:absolute; top: 400px; pointer-events: none;" ></vue-danmaku>
<n-grid :x-gap="8" :cols="3" :y-gap="8" >
<n-gi v-for="result in sortedResults" style="margin-left: 2px" onmouseover="this.style.border='1px solid #3498db' " onmouseout="this.style.border='0px'">
<n-card :data-code="result['股票代码']" :bordered="false" :title="result['股票名称']" :closable="false" @close="removeMonitor(result['股票代码'],result['股票名称'],result.key)">
<n-grid :cols="1" :y-gap="6">
<n-gi>
<n-text :type="result.type" >
<n-number-animation :duration="1000" :precision="2" :from="result['上次当前价格']" :to="Number(result['当前价格'])" />
<n-tag size="small" :type="result.type" :bordered="false" v-if="result['盘前盘后']>0">({{result['盘前盘后']}} {{result['盘前盘后涨跌幅']}}%)</n-tag>
</n-text>
<n-text style="padding-left: 10px;" :type="result.type">
<n-number-animation :duration="1000" :precision="3" :from="0" :to="result.changePercent" />%
</n-text>&nbsp;
<n-text size="small" v-if="result.costVolume>0" :type="result.type">
<n-number-animation :duration="1000" :precision="2" :from="0" :to="result.profitAmountToday" />
</n-text>
</n-gi>
</n-grid>
<n-grid :cols="2" :y-gap="4" :x-gap="4" >
<n-gi>
<n-text :type="'info'">{{"最高 "+result["今日最高价"]+" "+result.highRate }}%</n-text>
</n-gi>
<n-gi>
<n-text :type="'info'">{{"最低 "+result["今日最低价"]+" "+result.lowRate }}%</n-text>
</n-gi>
<n-gi>
<n-text :type="'info'">{{"昨收 "+result["昨日收盘价"]}}</n-text>
</n-gi>
<n-gi>
<n-text :type="'info'">{{"今开 "+result["今日开盘价"]}}</n-text>
</n-gi>
</n-grid>
<template #header-extra>
<n-tag size="small" :bordered="false">{{result['股票代码']}}</n-tag>&nbsp;
<n-button size="tiny" secondary type="primary" @click="removeMonitor(result['股票代码'],result['股票名称'],result.key)">
取消关注
</n-button>&nbsp;
<n-button size="tiny" v-if="data.openAiEnable" secondary type="warning" @click="aiCheckStock(result['股票名称'],result['股票代码'])">
AI分析
</n-button>
</template>
<template #footer>
<n-flex justify="center">
<n-tag size="small" v-if="result.volume>0" :type="result.profitType">{{result.volume+"股"}}</n-tag>
<n-tag size="small" v-if="result.costPrice>0" :type="result.profitType">{{"成本:"+result.costPrice+"*"+result.costVolume+" "+result.profit+"%"+" ( "+result.profitAmount+" ¥ )"}}</n-tag>
</n-flex>
</template>
<template #action>
<n-flex justify="space-between">
<n-text :type="'info'">{{result["日期"]+" "+result["时间"]}}</n-text>
<n-button size="tiny" type="info" @click="setStock(result['股票代码'],result['股票名称'])"> 成本 </n-button>
<n-button size="tiny" type="success" @click="showFenshi(result['股票代码'],result['股票名称'])"> 分时 </n-button>
<n-button size="tiny" type="error" @click="showK(result['股票代码'],result['股票名称'])"> 日K </n-button>
<n-button size="tiny" type="warning" @click="search(result['股票代码'],result['股票名称'])"> 详情 </n-button>
</n-flex>
</template>
</n-card >
</n-gi>
</n-grid>
<div style="position: fixed;bottom: 18px;right:0;z-index: 10;width: 420px">
<!-- <n-card :bordered="false">-->
<n-input-group >
<!-- <n-button type="error" @click="addBTN=!addBTN" > <n-icon :component="Search"/>&nbsp;<n-text v-if="addBTN">隐藏</n-text></n-button>-->
<n-auto-complete v-model:value="data.name" v-if="addBTN"
:input-props="{
autocomplete: 'disabled',
}"
:options="options"
placeholder="股票指数名称/代码/弹幕"
clearable @update-value="getStockList" :on-select="onSelect"/>
<n-button type="primary" @click="AddStock" v-if="addBTN">
<n-icon :component="Add"/> &nbsp;关注该股票
</n-button>
<n-button type="error" @click="SendDanmu" v-if="data.enableDanmu">
<n-icon :component="ChatboxOutline"/> &nbsp;发送弹幕
</n-button>
</n-input-group>
<!-- </n-card>-->
</div>
<n-modal transform-origin="center" size="small" v-model:show="modalShow" :title="formModel.name" style="width: 400px" :preset="'card'">
<n-form :model="formModel" :rules="{
costPrice: { required: true, message: '请输入成本'},
volume: { required: true, message: '请输入数量'},
alarm:{required: true, message: '涨跌报警值'} ,
alarmPrice: { required: true, message: '请输入报警价格'},
sort: { required: true, message: '请输入排序值'},
}" label-placement="left" label-width="80px">
<n-form-item label="股票成本" path="costPrice">
<n-input-number v-model:value="formModel.costPrice" min="0" placeholder="请输入股票成本" >
<template #suffix>
{{formModel.code.indexOf("hk")>=0?"HK$":"¥"}}
</template>
</n-input-number>
</n-form-item>
<n-form-item label="股票数量" path="volume">
<n-input-number v-model:value="formModel.volume" min="0" step="100" placeholder="请输入股票数量" >
<template #suffix>
</template>
</n-input-number>
</n-form-item>
<n-form-item label="涨跌提醒" path="alarm">
<n-input-number v-model:value="formModel.alarm" min="0" placeholder="请输入涨跌报警值(%)" >
<template #suffix>
%
</template>
</n-input-number>
</n-form-item>
<n-form-item label="股价提醒" path="alarmPrice">
<n-input-number v-model:value="formModel.alarmPrice" min="0" placeholder="请输入股价报警值(¥)" >
<template #suffix>
{{formModel.code.indexOf("hk")>=0?"HK$":"¥"}}
</template>
</n-input-number>
</n-form-item>
<n-form-item label="股票排序" path="sort">
<n-input-number v-model:value="formModel.sort" min="0" placeholder="请输入股价排序值" >
</n-input-number>
</n-form-item>
</n-form>
<template #footer>
<n-button type="primary" @click="updateCostPriceAndVolumeNew(formModel.code,formModel.costPrice,formModel.volume,formModel.alarm,formModel)">保存</n-button>
</template>
</n-modal>
<n-modal v-model:show="modalShow2" :title="data.name" style="width: 600px" :preset="'card'">
<n-image :src="data.fenshiURL" />
</n-modal>
<n-modal v-model:show="modalShow3" :title="data.name" style="width: 600px" :preset="'card'">
<n-image :src="data.kURL" />
</n-modal>
<n-modal transform-origin="center" v-model:show="modalShow4" preset="card" style="width: 800px;" :title="'['+data.name+']AI分析结果'" >
<n-spin size="small" :show="data.loading">
<MdEditor v-if="enableEditor" :toolbars="toolbars" ref="mdEditorRef" style="height: 440px;text-align: left" :modelValue="data.airesult" :theme="'dark'">
<template #defToolbars>
<ExportPDF :file-name="data.name+'['+data.code+']AI分析报告'" style="text-align: left" :modelValue="data.airesult" @onProgress="handleProgress" />
</template>
</MdEditor >
<MdPreview v-if="!enableEditor" ref="mdPreviewRef" style="height: 440px;text-align: left" :modelValue="data.airesult" :theme="'dark'"/>
</n-spin>
<template #footer>
<n-flex justify="space-between" ref="tipsRef">
<n-text type="info" v-if="data.time" >
<n-tag v-if="data.modelName" type="warning" round :title="data.chatId" :bordered="false">{{data.modelName}}</n-tag>
{{data.time}}
</n-text>
<n-text type="error" >*AI分析结果仅供参考请以实际行情为准投资需谨慎风险自担</n-text>
</n-flex>
</template>
<template #action>
<n-flex justify="right">
<n-input v-model:value="data.question" style="text-align: left" clearable
type="textarea"
:show-count="true"
placeholder="请输入您的问题:例如{{stockName}}[{{stockCode}}]分析和总结"
:autosize="{
minRows: 2,
maxRows: 5
}"
/>
<!-- <n-button size="tiny" type="error" @click="enableEditor=!enableEditor">编辑/预览</n-button>-->
<n-button size="tiny" type="warning" @click="aiReCheckStock(data.name,data.code)">再次分析</n-button>
<n-button size="tiny" type="info" @click="saveAsImage(data.name,data.code)">保存为图片</n-button>
<n-button size="tiny" type="success" @click="copyToClipboard">复制到剪切板</n-button>
<n-button size="tiny" type="primary" @click="saveAsMarkdown">保存为Markdown文件</n-button>
<n-button size="tiny" type="primary" @click="saveAsWord">保存为Word文件</n-button>
</n-flex>
</template>
</n-modal>
</template>
<style scoped>
.md-editor-preview h3{
text-align: center !important;
}
.md-editor-preview p{
text-align: left !important;
}
</style>