diff --git a/backend/data/stock_data_api.go b/backend/data/stock_data_api.go index 8b178e3..334f938 100644 --- a/backend/data/stock_data_api.go +++ b/backend/data/stock_data_api.go @@ -400,7 +400,13 @@ func (receiver StockDataApi) Follow(stockCode string) string { logger.SugaredLogger.Error(err) return "关注失败" } - + if strings.HasPrefix(stockCode, "us") { + stockCode = strings.Replace(stockCode, "us", "gb_", 1) + } + if strings.HasPrefix(stockCode, "US") { + stockCode = strings.Replace(stockCode, "US", "gb_", 1) + } + stockCode = strings.ToLower(stockCode) maxSort := int64(0) db.Dao.Model(&FollowedStock{}).Raw("select max(sort) as sort from followed_stock").Scan(&maxSort) @@ -463,15 +469,64 @@ func (receiver StockDataApi) SetAlarmChangePercent(val, alarmPrice float64, stoc return "设置成功" } -func (receiver StockDataApi) SetStockSort(sort int64, stockCode string) { - if strutil.HasPrefixAny(stockCode, []string{"gb_"}) { - stockCode = strings.ToLower(stockCode) - stockCode = strings.Replace(stockCode, "gb_", "us", 1) +func (receiver StockDataApi) SetStockSort(newSort int64, stockCode string) { + //if strutil.HasPrefixAny(stockCode, []string{"gb_"}) { + // stockCode = strings.ToLower(stockCode) + // stockCode = strings.Replace(stockCode, "gb_", "us", 1) + //} + + // 获取当前排序值 + var currentStock FollowedStock + if err := db.Dao.Model(&FollowedStock{}).Where("stock_code = ?", strings.ToLower(stockCode)).First(¤tStock).Error; err != nil { + logger.SugaredLogger.Error("找不到当前股票: ", err.Error()) + return } - err := db.Dao.Model(&FollowedStock{}).Where("stock_code = ?", strings.ToLower(stockCode)).Update("sort", sort).Error - if err != nil { - logger.SugaredLogger.Error(err.Error()) + + oldSort := currentStock.Sort + + // 如果排序值没有变化,直接返回 + if oldSort == newSort { + return } + // 检查新排序位置是否被占用 + var count int64 + if err := db.Dao.Model(&FollowedStock{}).Where("sort = ?", newSort).Count(&count).Error; err != nil { + logger.SugaredLogger.Error("检查新排序位置被占用失败: ", err.Error()) + return + } + if count == 0 { + // 新位置未被占用,直接更新当前记录 + if err := db.Dao.Model(&FollowedStock{}). + Where("stock_code = ?", strings.ToLower(stockCode)). + Update("sort", newSort).Error; err != nil { + logger.SugaredLogger.Error("更新排序位置失败: ", err.Error()) + } + } else { + // 新位置已被占用,需要移动其他记录 + if newSort < oldSort { + // 向前移动:将中间记录向后移动 + if err := db.Dao.Model(&FollowedStock{}). + Where("sort >= ? AND sort < ?", newSort, oldSort). + Update("sort", gorm.Expr("sort + 1")).Error; err != nil { + logger.SugaredLogger.Error("向前排序更新失败: ", err.Error()) + } + } else { + // 向后移动:将中间记录向前移动 + if err := db.Dao.Model(&FollowedStock{}). + Where("sort > ? AND sort <= ?", oldSort, newSort). + Update("sort", gorm.Expr("sort - 1")).Error; err != nil { + logger.SugaredLogger.Error("向后排序更新失败: ", err.Error()) + } + } + + // 更新目标记录的排序 + if err := db.Dao.Model(&FollowedStock{}). + Where("stock_code = ?", strings.ToLower(stockCode)). + Update("sort", newSort).Error; err != nil { + logger.SugaredLogger.Error("更新股票排序失败: ", err.Error()) + } + } + } func (receiver StockDataApi) SetStockAICron(cron string, stockCode string) { if strutil.HasPrefixAny(stockCode, []string{"gb_"}) { diff --git a/frontend/src/components/stock.vue b/frontend/src/components/stock.vue index 379b9cc..424d655 100644 --- a/frontend/src/components/stock.vue +++ b/frontend/src/components/stock.vue @@ -2,28 +2,31 @@ import {computed, h, onBeforeMount, onBeforeUnmount, onMounted, reactive, ref} from 'vue' import * as echarts from 'echarts'; import { + AddGroup, + AddStockGroup, Follow, GetAIResponseResult, GetConfig, GetFollowList, + GetGroupList, + GetPromptTemplates, + GetStockKLine, GetStockList, + GetStockMinutePriceLineData, GetVersionInfo, Greet, NewChatStream, + RemoveGroup, + RemoveStockGroup, SaveAIResponseResult, + SaveAsMarkdown, SendDingDingMessageByType, SetAlarmChangePercent, SetCostPriceAndVolume, - SetStockSort, - UnFollow, - ShareAnalysis, - SaveAsMarkdown, - GetPromptTemplates, SetStockAICron, - AddGroup, - GetGroupList, - AddStockGroup, - RemoveStockGroup, RemoveGroup, GetStockKLine, GetStockMinutePriceLineData + SetStockSort, + ShareAnalysis, + UnFollow } from '../../wailsjs/go/main/App' import { NAvatar, @@ -35,7 +38,6 @@ import { NText, useDialog, useMessage, - useModal, useNotification } from 'naive-ui' import { @@ -46,24 +48,22 @@ import { WindowReload, WindowUnfullscreen } from '../../wailsjs/runtime' -import { - Add, - ChatboxOutline, -} from '@vicons/ionicons5' -import {MdPreview,MdEditor } from 'md-editor-v3'; +import {Add, ChatboxOutline,} from '@vicons/ionicons5' +import {MdEditor, MdPreview} 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 {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' -import {keys, pad, padStart} from "lodash"; +import {keys, padStart} from "lodash"; import {useRoute, useRouter} from 'vue-router' import MoneyTrend from "./moneyTrend.vue"; + const route = useRoute() const router = useRouter() @@ -83,18 +83,18 @@ const kLineChartRef2 = ref(null); const handleProgress = (progress) => { //console.log(`Export progress: ${progress.ratio * 100}%`); }; -const enableEditor= ref(false) +const enableEditor = ref(false) const mdPreviewRef = ref(null) -const mdEditorRef = ref(null) +const mdEditorRef = ref(null) const tipsRef = ref(null) const message = useMessage() const notify = useNotification() -const stocks=ref([]) -const results=ref({}) -const stockList=ref([]) -const followList=ref([]) -const groupList=ref([]) -const options=ref([]) +const stocks = ref([]) +const results = ref({}) +const stockList = ref([]) +const followList = ref([]) +const groupList = ref([]) +const options = ref([]) const modalShow = ref(false) const modalShow2 = ref(false) const modalShow3 = ref(false) @@ -107,85 +107,82 @@ const formModel = ref({ costPrice: 0.000, volume: 0, alarm: 0, - alarmPrice:0, - sort:999, - cron:"", + alarmPrice: 0, + sort: 999, + cron: "", }) -const promptTemplates=ref([]) -const sysPromptOptions=ref([]) -const userPromptOptions=ref([]) +const promptTemplates = ref([]) +const sysPromptOptions = ref([]) +const userPromptOptions = ref([]) const data = reactive({ - modelName:"", + modelName: "", chatId: "", - question:"", - sysPromptId:null, + question: "", + sysPromptId: null, name: "", code: "", - fenshiURL:"", - kURL:"", + fenshiURL: "", + kURL: "", resultText: "Please enter your name below 👇", fullscreen: false, airesult: "", openAiEnable: false, loading: true, enableDanmu: false, - darkTheme:false, - changePercent:0 + darkTheme: false, + changePercent: 0 }) -const feishiInterval= ref(null) +const feishiInterval = ref(null) -const currentGroupId=ref(0) +const currentGroupId = ref(0) - -const theme=computed(() => { +const theme = computed(() => { return data.darkTheme ? 'dark' : 'light' }) -const danmakuColor = computed(()=> { +const danmakuColor = computed(() => { return data.darkTheme ? 'color:#fff' : 'color:#000' }) const icon = ref('https://raw.githubusercontent.com/ArvinLovegood/go-stock/master/build/appicon.png'); const sortedResults = computed(() => { - ////console.log("computed",sortedResults.value) - const sortedKeys =keys(results.value).sort(); - ////console.log("sortedKeys",sortedKeys) + const sortedKeys = keys(results.value).sort(); const sortedObject = {}; sortedKeys.forEach(key => { - sortedObject[key] = results.value[key]; + sortedObject[key] = results.value[key]; }); return sortedObject }); -const groupResults=computed(() => { - const group={} - for (const key in sortedResults.value) { - if(stocks.value.includes(sortedResults.value[key]['股票代码'])){ - group[key]=sortedResults.value[key] +const groupResults = computed(() => { + const group = {} + for (const key in sortedResults.value) { + if (stocks.value.includes(sortedResults.value[key]['股票代码'])) { + group[key] = sortedResults.value[key] } } return group }) -const showPopover=ref(false) +const showPopover = ref(false) -onBeforeMount(()=>{ +onBeforeMount(() => { GetGroupList().then(result => { - groupList.value=result - if(route.query.groupId){ - message.success("切换分组:"+route.query.groupName) - currentGroupId.value=Number(route.query.groupId) + groupList.value = result + if (route.query.groupId) { + message.success("切换分组:" + route.query.groupName) + currentGroupId.value = Number(route.query.groupId) //console.log("route.params",route.query) } }) GetStockList("").then(result => { stockList.value = result - options.value=result.map(item => { + options.value = result.map(item => { return { - label: item.name+" - "+item.ts_code, + label: item.name + " - " + item.ts_code, value: item.ts_code } }) @@ -201,11 +198,11 @@ onBeforeMount(()=>{ data.darkTheme = true } }) - GetPromptTemplates("","").then(res=>{ - promptTemplates.value=res + GetPromptTemplates("", "").then(res => { + promptTemplates.value = res - sysPromptOptions.value=promptTemplates.value.filter(item => item.type === '模型系统Prompt') - userPromptOptions.value=promptTemplates.value.filter(item => item.type === '模型用户Prompt') + sysPromptOptions.value = promptTemplates.value.filter(item => item.type === '模型系统Prompt') + userPromptOptions.value = promptTemplates.value.filter(item => item.type === '模型用户Prompt') //console.log("userPromptOptions",userPromptOptions.value) //console.log("sysPromptOptions",sysPromptOptions.value) @@ -215,24 +212,15 @@ onBeforeMount(()=>{ 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) GetFollowList(currentGroupId.value).then(result => { - followList.value = result + followList.value = result for (const followedStock of result) { - if(followedStock.StockCode.startsWith("us")){ - followedStock.StockCode="gb_"+ followedStock.StockCode.replace("us", "").toLowerCase() + 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) } } @@ -253,7 +241,7 @@ onMounted(() => { }; ws.value.onmessage = (event) => { - if(data.enableDanmu){ + if (data.enableDanmu) { danmus.value.push(event.data); } }; @@ -268,7 +256,7 @@ onMounted(() => { }) onBeforeUnmount(() => { - // //console.log(`the component is now unmounted.`) + // //console.log(`the component is now unmounted.`) //clearInterval(ticker.value) ws.value.close() message.destroyAll() @@ -286,80 +274,69 @@ onBeforeUnmount(() => { EventsOff("loadingDone") }) -EventsOn("loadingDone",(data)=>{ +EventsOn("loadingDone", (data) => { message.loading("刷新股票基础数据...") GetStockList("").then(result => { stockList.value = result - options.value=result.map(item => { + options.value = result.map(item => { return { - label: item.name+" - "+item.ts_code, + label: item.name + " - " + item.ts_code, value: item.ts_code } }) }) }) -EventsOn("refresh",(data)=>{ +EventsOn("refresh", (data) => { message.success(data) }) -EventsOn("showSearch",(data)=>{ +EventsOn("showSearch", (data) => { addBTN.value = data === 1; }) -EventsOn("stock_price",(data)=>{ +EventsOn("stock_price", (data) => { updateData(data) }) -EventsOn("refreshFollowList",(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) => { +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) + 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 - } + 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("changeTab" ,async (msg) => { +EventsOn("changeTab", async (msg) => { //console.log("changeTab",msg) - currentGroupId.value=msg.ID + currentGroupId.value = msg.ID updateTab(currentGroupId.value) }) -EventsOn("updateVersion",async (msg) => { +EventsOn("updateVersion", async (msg) => { const githubTimeStr = msg.published_at; // 创建一个 Date 对象 const utcDate = new Date(githubTimeStr); @@ -392,10 +369,10 @@ EventsOn("updateVersion",async (msg) => { 'text-align': 'left', 'font-size': '14px', } - }, { default: () => msg.commit?.message }) + }, {default: () => msg.commit?.message}) }, duration: 5000, - meta: "发布时间:"+formattedDate, + meta: "发布时间:" + formattedDate, action: () => { return h(NButton, { type: 'primary', @@ -403,12 +380,12 @@ EventsOn("updateVersion",async (msg) => { onClick: () => { window.open(msg.html_url) } - }, { default: () => '查看' }) + }, {default: () => '查看'}) } }) }) -EventsOn("warnMsg",async (msg) => { +EventsOn("warnMsg", async (msg) => { notify.error({ avatar: () => h(NAvatar, { @@ -424,7 +401,7 @@ EventsOn("warnMsg",async (msg) => { 'text-align': 'left', 'font-size': '14px', } - }, { default: () => msg }) + }, {default: () => msg}) }, }) }) @@ -449,34 +426,34 @@ function isTradingTime() { return false; } -function AddStock(){ +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) - GetFollowList(currentGroupId.value).then(result => { - followList.value = result - }) - monitor(); - }else{ - message.error(result) - } - }) - }else{ + Follow(data.code).then(result => { + if (result === "关注成功") { + data.code= "gb_" + data.code.replace("us", "").toLowerCase() + stocks.value.push(data.code) + message.success(result) + GetFollowList(currentGroupId.value).then(result => { + followList.value = result + }) + monitor(); + } else { + message.error(result) + } + }) + } else { message.error("已经关注了") } } - -function removeMonitor(code,name,key) { +function removeMonitor(code, name, key) { //console.log("removeMonitor",name,code,key) - stocks.value.splice(stocks.value.indexOf(code),1) + stocks.value.splice(stocks.value.indexOf(code), 1) //console.log("removeMonitor-key",key) //console.log("removeMonitor-v",results.value[key]) @@ -489,61 +466,58 @@ function removeMonitor(code,name,key) { } -function SendDanmu(){ +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){ +function getStockList(value) { - - // //console.log("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 => { + 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, + label: item.name + " - " + item.ts_code, value: item.ts_code } }) - if(value&&value.indexOf("-")<=0){ - data.code=value + if (value && value.indexOf("-") <= 0) { + data.code = value } //console.log("getStockList-options",data.code) - if(data.code){ - let findId=data.code - if(findId.startsWith("us")){ - findId="gb_"+ findId.replace("us", "").toLowerCase() + if (data.code) { + let findId = data.code + if (findId.startsWith("us")) { + findId = "gb_" + findId.replace("us", "").toLowerCase() } blinkBorder(findId) } - - } -function blinkBorder(findId){ +function blinkBorder(findId) { // 获取要滚动到的元素 let element = document.getElementById(findId); //console.log("blinkBorder",findId,element) if (element) { // 滚动到该元素 - element.scrollIntoView({ behavior: 'smooth'}); - const pelement = document.getElementById(findId +'_gi'); - if(pelement){ + element.scrollIntoView({behavior: 'smooth'}); + const pelement = document.getElementById(findId + '_gi'); + if (pelement) { // 添加闪烁效果 pelement.classList.add('blink-border'); // 3秒后移除闪烁效果 setTimeout(() => { pelement.classList.remove('blink-border'); - }, 1000*5); - }else{ + }, 1000 * 5); + } else { console.error(`Element with ID ${findId}_gi not found`); } } @@ -552,57 +526,60 @@ function blinkBorder(findId){ async function updateData(result) { ////console.log("stock_price",result['日期'],result['时间'],result['股票代码'],result['股票名称'],result['当前价格'],result['盘前盘后']) - if(result["当前价格"]<=0){ - 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.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) - } + 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) } - //result.key=result.sort - result.key=GetSortKey(result.sort,result["股票代码"]) - results.value[GetSortKey(result.sort,result["股票代码"])]=result - if(!stocks.value.includes(result["股票代码"])) { + if (result.alarmPrice > 0 && result["当前价格"] >= result.alarmPrice) { + SendMessage(result, 2) + } + + if (result.costPrice > 0 && result["当前价格"] >= result.costPrice) { + SendMessage(result, 3) + } + } + + // result.key=result.sort + results.value = Object.fromEntries( + Object.entries(results.value).filter( + ([key]) => !key.includes(result["股票代码"]) + )); + + result.key = GetSortKey(result.sort, result["股票代码"]) + results.value[result.key] = result + if (!stocks.value.includes(result["股票代码"])) { delete results.value[result.key] } - - ////console.log("updateData",result) } async function monitor() { - if(stocks.value&&stocks.value.length===0){ - showPopover.value=true + if (stocks.value && stocks.value.length === 0) { + showPopover.value = true } for (let code of stocks.value) { - // //console.log(code) + Greet(code).then(result => { updateData(result) }) @@ -610,51 +587,53 @@ async function monitor() { } -function GetSortKey(sort,code){ - let sortKey= padStart(sort,8,'0')+"_"+code - ////console.log("GetSortKey:",sortKey) +function GetSortKey(sort, code) { + let sortKey = padStart(sort, 8, '0') + "_" + code return sortKey } function onSelect(item) { ////console.log("onSelect",item) - if(item.indexOf("-")>0){ - item=item.split("-")[1].toLowerCase() + if (item.indexOf("-") > 0) { + item = item.split("-")[1].toLowerCase() } - if(item.indexOf(".")>0){ - data.code=item.split(".")[1].toLowerCase()+item.split(".")[0] + if (item.indexOf(".") > 0) { + data.code = item.split(".")[1].toLowerCase() + item.split(".")[0] } } -function search(code,name){ +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/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 - formModel.value.cron=res[0].Cron - modalShow.value=true + +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 + formModel.value.cron = res[0].Cron + modalShow.value = true } -function clearFeishi(){ + +function clearFeishi() { //console.log("clearFeishi") clearInterval(feishiInterval.value) } + function showFsChart(code, name) { data.name = name data.code = code @@ -664,14 +643,14 @@ function showFsChart(code, name) { const priceData = result.priceData let category = [] let price = [] - let openprice=0 - let closeprice=0 + let openprice = 0 + let closeprice = 0 let volume = [] let volumeRate = [] let min = 0 let max = 0 - openprice=priceData[0].price - closeprice=priceData[priceData.length-1].price + openprice = priceData[0].price + closeprice = priceData[priceData.length - 1].price for (let i = 0; i < priceData.length; i++) { category.push(priceData[i].time) price.push(priceData[i].price) @@ -682,8 +661,8 @@ function showFsChart(code, name) { max = priceData[i].price } if (i > 0) { - let b=priceData[i].volume - priceData[i - 1].volume - volumeRate.push(((b-volume[i-1])/volume[i-1]*100).toFixed(2)) + let b = priceData[i].volume - priceData[i - 1].volume + volumeRate.push(((b - volume[i - 1]) / volume[i - 1] * 100).toFixed(2)) volume.push(b) } else { volume.push(priceData[i].volume) @@ -693,7 +672,7 @@ function showFsChart(code, name) { let option = { title: { - subtext: "["+result.date+"] 开盘:"+openprice+" 最新:"+closeprice+" 最高:"+max+" 最低:"+min, + subtext: "[" + result.date + "] 开盘:" + openprice + " 最新:" + closeprice + " 最高:" + max + " 最低:" + min, left: 'center', top: '10', textStyle: { @@ -730,13 +709,13 @@ function showFsChart(code, name) { } }, xAxis: [ - { - type: 'category', - data: category, - axisLabel: { - show: false - } - }, + { + type: 'category', + data: category, + axisLabel: { + show: false + } + }, { gridIndex: 1, type: 'category', @@ -765,8 +744,8 @@ function showFsChart(code, name) { show: false }, name: "股价", - min: (min - min*0.01).toFixed(2), - max: (max + max*0.01).toFixed(2), + min: (min - min * 0.01).toFixed(2), + max: (max + max * 0.01).toFixed(2), minInterval: 0.01, type: 'value' }, @@ -784,7 +763,7 @@ function showFsChart(code, name) { ], visualMap: { type: 'piecewise', - seriesIndex:0, + seriesIndex: 0, top: 0, left: 10, orient: 'horizontal', @@ -827,13 +806,13 @@ function showFsChart(code, name) { type: 'line', smooth: false, showSymbol: false, - lineStyle: { + lineStyle: { width: 3 }, markPoint: { symbol: 'arrow', - symbolRotate:90, - symbolSize: [10,20], + symbolRotate: 90, + symbolSize: [10, 20], symbolOffset: [10, 0], itemStyle: { color: '#FC290D' @@ -849,24 +828,24 @@ function showFsChart(code, name) { markLine: { symbol: 'none', data: [ - { type: 'average', name: 'Average' }, - { - lineStyle:{ - color: '#FFCB00', - width: 0.5 - }, - yAxis: openprice, - name: '开盘价' + {type: 'average', name: 'Average'}, + { + lineStyle: { + color: '#FFCB00', + width: 0.5 }, - { - yAxis: closeprice , - symbol: 'none', - lineStyle:{ - color: 'red', - width: 0.5 - }, - } - ] + yAxis: openprice, + name: '开盘价' + }, + { + yAxis: closeprice, + symbol: 'none', + lineStyle: { + color: 'red', + width: 0.5 + }, + } + ] }, }, { @@ -883,30 +862,30 @@ function showFsChart(code, name) { }) } -function showFenshi(code,name,changePercent){ - data.code=code - data.name=name - data.changePercent=changePercent - data.fenshiURL='http://image.sinajs.cn/newchart/min/n/'+data.code+'.gif'+"?t="+Date.now() +function showFenshi(code, name, changePercent) { + data.code = code + data.name = name + data.changePercent = changePercent + 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('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() + if (code.startsWith('gb_')) { + data.fenshiURL = 'http://image.sinajs.cn/newchart/usstock/min/' + data.code.replace("gb_", "") + '.gif' + "?t=" + Date.now() } - modalShow2.value=true + modalShow2.value = true } -function handleFeishi(){ +function handleFeishi() { showFsChart(data.code, data.name); - feishiInterval.value=setInterval(() => { + feishiInterval.value = setInterval(() => { showFsChart(data.code, data.name); - }, 1000*10) + }, 1000 * 10) } -function calculateMA(dayCount,values) { +function calculateMA(dayCount, values) { var result = []; for (var i = 0, len = values.length; i < len; i++) { if (i < dayCount) { @@ -921,39 +900,40 @@ function calculateMA(dayCount,values) { } return result; } -function handleKLine(){ - GetStockKLine(data.code,data.name,365).then(result => { + +function handleKLine() { + GetStockKLine(data.code, data.name, 365).then(result => { //console.log("GetStockKLine",result) const chart = echarts.init(kLineChartRef.value); const categoryData = []; const values = []; - const volumns=[]; + const volumns = []; for (let i = 0; i < result.length; i++) { - let resultElement=result[i] + let resultElement = result[i] //console.log("resultElement:{}",resultElement) categoryData.push(resultElement.day) - let flag=resultElement.close>resultElement.open?1:-1 + let flag = resultElement.close > resultElement.open ? 1 : -1 values.push([ resultElement.open, resultElement.close, resultElement.low, resultElement.high ]) - volumns.push([i,resultElement.volume/10000,flag]) + volumns.push([i, resultElement.volume / 10000, flag]) } ////console.log("categoryData",categoryData) ////console.log("values",values) let option = { darkMode: data.darkTheme, //backgroundColor: '#1c1c1c', - // color:['#5470c6', '#91cc75', '#fac858', '#ee6666', '#73c0de', '#3ba272', '#fc8452', '#9a60b4', '#ea7ccc'], + // color:['#5470c6', '#91cc75', '#fac858', '#ee6666', '#73c0de', '#3ba272', '#fc8452', '#9a60b4', '#ea7ccc'], animation: false, legend: { bottom: 10, left: 'center', data: ['日K', 'MA5', 'MA10', 'MA20', 'MA30'], textStyle: { - color: data.darkTheme?'#ccc':'#456' + color: data.darkTheme ? '#ccc' : '#456' }, }, tooltip: { @@ -967,19 +947,19 @@ function handleKLine(){ } }, borderWidth: 2, - borderColor: data.darkTheme?'#456':'#ccc', - backgroundColor: data.darkTheme?'#456':'#fff', + borderColor: data.darkTheme ? '#456' : '#ccc', + backgroundColor: data.darkTheme ? '#456' : '#fff', padding: 10, textStyle: { - color: data.darkTheme?'#ccc':'#456' + color: data.darkTheme ? '#ccc' : '#456' }, formatter: function (params) {//修改鼠标划过显示为中文 //console.log("params",params) - let volum=params[5].data;//ma5的值 - let ma5=params[1].data;//ma5的值 - let ma10=params[2].data;//ma10的值 - let ma20=params[3].data;//ma20的值 - let ma30=params[4].data;//ma30的值 + let volum = params[5].data;//ma5的值 + let ma5 = params[1].data;//ma5的值 + let ma10 = params[2].data;//ma10的值 + let ma20 = params[3].data;//ma20的值 + let ma30 = params[4].data;//ma30的值 params = params[0];//开盘收盘最低最高数据汇总 let currentItemData = params.data; @@ -1046,8 +1026,8 @@ function handleKLine(){ type: 'category', data: categoryData, boundaryGap: false, - axisLine: { onZero: false }, - splitLine: { show: false }, + axisLine: {onZero: false}, + splitLine: {show: false}, min: 'dataMin', max: 'dataMax', axisPointer: { @@ -1059,10 +1039,10 @@ function handleKLine(){ gridIndex: 1, data: categoryData, boundaryGap: false, - axisLine: { onZero: false }, - axisTick: { show: false }, - splitLine: { show: false }, - axisLabel: { show: false }, + axisLine: {onZero: false}, + axisTick: {show: false}, + splitLine: {show: false}, + axisLabel: {show: false}, min: 'dataMin', max: 'dataMax' } @@ -1078,10 +1058,10 @@ function handleKLine(){ scale: true, gridIndex: 1, splitNumber: 2, - axisLabel: { show: false }, - axisLine: { show: false }, - axisTick: { show: false }, - splitLine: { show: false } + axisLabel: {show: false}, + axisLine: {show: false}, + axisTick: {show: false}, + splitLine: {show: false} } ], dataZoom: [ @@ -1109,8 +1089,8 @@ function handleKLine(){ itemStyle: { color: upColor, color0: downColor, - // borderColor: upBorderColor, - // borderColor0: downBorderColor + // borderColor: upBorderColor, + // borderColor0: downBorderColor }, markPoint: { label: { @@ -1191,7 +1171,7 @@ function handleKLine(){ { name: 'MA5', type: 'line', - data: calculateMA(5,values), + data: calculateMA(5, values), smooth: true, showSymbol: false, lineStyle: { @@ -1201,7 +1181,7 @@ function handleKLine(){ { name: 'MA10', type: 'line', - data: calculateMA(10,values), + data: calculateMA(10, values), smooth: true, showSymbol: false, lineStyle: { @@ -1211,7 +1191,7 @@ function handleKLine(){ { name: 'MA20', type: 'line', - data: calculateMA(20,values), + data: calculateMA(20, values), smooth: true, showSymbol: false, lineStyle: { @@ -1221,7 +1201,7 @@ function handleKLine(){ { name: 'MA30', type: 'line', - data: calculateMA(30,values), + data: calculateMA(30, values), smooth: true, showSymbol: false, lineStyle: { @@ -1241,58 +1221,57 @@ function handleKLine(){ ] }; chart.setOption(option); - chart.on('click',{seriesName:'日K'}, function(params) { + chart.on('click', {seriesName: '日K'}, function (params) { //console.log("click:",params); }); }) } -function showMoney(code,name){ - data.code=code - data.name=name - modalShow5.value=true + +function showMoney(code, name) { + data.code = code + data.name = name + modalShow5.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() +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() + if (code.startsWith('gb_')) { + data.kURL = 'http://image.sinajs.cn/newchart/usstock/daily/' + data.code.replace("gb_", "") + '.gif' + "?t=" + Date.now() } - modalShow3.value=true + modalShow3.value = true //https://image.sinajs.cn/newchart/usstock/daily/dji.gif //https://image.sinajs.cn/newchart/hk_stock/daily/06030.gif?1740729404273 } - - -function updateCostPriceAndVolumeNew(code,price,volume,alarm,formModel){ - - if(formModel.sort){ - SetStockSort(formModel.sort,code).then(result => { +function updateCostPriceAndVolumeNew(code, price, volume, alarm, formModel) { + if (formModel.sort) { + SetStockSort(formModel.sort, code).then(result => { //message.success(result) }) } - if(formModel.cron){ - SetStockAICron(formModel.cron,code).then(result => { + if (formModel.cron) { + SetStockAICron(formModel.cron, code).then(result => { //message.success(result) }) } - if(alarm||formModel.alarmPrice){ - SetAlarmChangePercent(alarm,formModel.alarmPrice,code).then(result => { + if (alarm || formModel.alarmPrice) { + SetAlarmChangePercent(alarm, formModel.alarmPrice, code).then(result => { //message.success(result) }) } - SetCostPriceAndVolume(code,price,volume).then(result => { - modalShow.value=false + SetCostPriceAndVolume(code, price, volume).then(result => { + modalShow.value = false message.success(result) GetFollowList(currentGroupId.value).then(result => { followList.value = result + stocks.value = [] for (const followedStock of result) { if (!stocks.value.includes(followedStock.StockCode)) { stocks.value.push(followedStock.StockCode) @@ -1304,73 +1283,75 @@ function updateCostPriceAndVolumeNew(code,price,volume,alarm,formModel){ }) } -function fullscreen(){ - if(data.fullscreen){ +function fullscreen() { + if (data.fullscreen) { WindowUnfullscreen() - }else{ + } else { WindowFullscreen() } - data.fullscreen=!data.fullscreen + 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"+ - "\n" - let title=result["股票名称"]+"("+result["股票代码"]+") "+result["当前价格"]+" "+result.changePercent +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" + + "\n" + let title = result["股票名称"] + "(" + result["股票代码"] + ") " + result["当前价格"] + " " + result.changePercent - let msg='{' + + let msg = '{' + ' "msgtype": "markdown",' + ' "markdown": {' + - ' "title":"['+typeName+"]"+title+'",' + - ' "text": "'+markdown+'"' + + ' "title":"[' + typeName + "]" + title + '",' + + ' "text": "' + markdown + '"' + ' },' + ' "at": {' + ' "isAtAll": true' + ' }' + ' }' - // SendDingDingMessage(msg,result["股票代码"]) - SendDingDingMessageByType(msg,result["股票代码"],type) + // 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检测中...",{ + +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, }) // //message.info("sysPromptId:"+data.sysPromptId) - NewChatStream(stock,stockCode,data.question,data.sysPromptId) + NewChatStream(stock, stockCode, data.question, data.sysPromptId) } -function aiCheckStock(stock,stockCode){ + +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 + 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'); @@ -1378,27 +1359,26 @@ function aiCheckStock(stock,stockCode){ 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检测中...",{ + 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,"",data.sysPromptId) + NewChatStream(stock, stockCode, "", data.sysPromptId) } }) } -function getTypeName(type){ - switch (type) - { +function getTypeName(type) { + switch (type) { case 1: return "涨跌报警" case 2: @@ -1424,28 +1404,28 @@ window.onerror = function (msg, source, lineno, colno, error) { lineno: lineno, colno: colno, error: error ? error.stack : null, - data:data, - results:results, - followList:followList, - stockList:stockList, - stocks:stocks, - formModel:formModel, + data: data, + results: results, + followList: followList, + stockList: stockList, + stocks: stocks, + formModel: formModel, }); - message.error("发生错误:"+msg) + message.error("发生错误:" + msg) return true; }; -function saveAsImage(name,code) { +function saveAsImage(name, code) { const element = document.querySelector('.md-editor-preview'); if (element) { - html2canvas(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.download = name + "[" + code + ']-ai-analysis-result.png'; link.click(); }); } else { @@ -1461,13 +1441,15 @@ async function copyToClipboard() { message.error('复制失败: ' + err); } } -function saveAsMarkdown(){ - SaveAsMarkdown(data.code,data.name).then(result => { + +function saveAsMarkdown() { + SaveAsMarkdown(data.code, data.name).then(result => { message.success(result) }) } + function saveAsMarkdown_old() { - const blob = new Blob([data.airesult], { type: 'text/markdown;charset=utf-8' }); + 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}]-${data.time}ai-analysis-result.md`; @@ -1475,6 +1457,7 @@ function saveAsMarkdown_old() { URL.revokeObjectURL(link.href); link.remove() } + function getHtml(ref) { if (ref.value) { // 获取 MdPreview 组件的根元素 @@ -1492,7 +1475,7 @@ async function saveAsWord() { // 将富文本内容拼接为一个完整的html const html = getHtml(mdPreviewRef) const tipsHtml = getHtml(tipsRef) - const value = ` + const value = ` ${html}