From c7655d2adffcd6b8586e760a530d6a691817e8e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B5=93=E7=9D=A1=E4=B8=8D=E6=B6=88=E6=AE=8B=E9=85=92?= <49932926+CodeNoobLH@users.noreply.github.com> Date: Mon, 16 Jun 2025 10:17:17 +0800 Subject: [PATCH 01/21] =?UTF-8?q?refactor(backend):=20=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E8=82=A1=E7=A5=A8=E6=8E=92=E5=BA=8F=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 重构了 SetStockSort 函数,增加了事务处理和错误处理 - 添加了对新排序位置是否被占用的检查 - 实现了向前和向后移动排序时对其他记录的影响 - 优化了数据库查询和更新操作,提高了代码的健壮性和性能 --- backend/data/stock_data_api.go | 75 ++++++++++++++++++++++++++++++++-- 1 file changed, 71 insertions(+), 4 deletions(-) diff --git a/backend/data/stock_data_api.go b/backend/data/stock_data_api.go index 4399358..f6daa0d 100644 --- a/backend/data/stock_data_api.go +++ b/backend/data/stock_data_api.go @@ -463,15 +463,82 @@ func (receiver StockDataApi) SetAlarmChangePercent(val, alarmPrice float64, stoc return "设置成功" } -func (receiver StockDataApi) SetStockSort(sort int64, stockCode string) { +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) } - err := db.Dao.Model(&FollowedStock{}).Where("stock_code = ?", strings.ToLower(stockCode)).Update("sort", sort).Error - if err != nil { - logger.SugaredLogger.Error(err.Error()) + + tx := db.Dao.Begin() + defer func() { + if r := recover(); r != nil { + tx.Rollback() + } + }() + + // 获取当前排序值 + var currentStock FollowedStock + if err := tx.Model(&FollowedStock{}).Where("stock_code = ?", strings.ToLower(stockCode)).First(¤tStock).Error; err != nil { + tx.Rollback() + logger.SugaredLogger.Error("找不到当前股票: ", err.Error()) + return } + + oldSort := currentStock.Sort + + // 如果排序值没有变化,直接返回 + if oldSort == newSort { + tx.Rollback() + return + } + + // 检查新排序位置是否被占用 + var count int64 + if err := tx.Model(&FollowedStock{}).Where("sort = ?", newSort).Count(&count).Error; err != nil { + tx.Rollback() + logger.SugaredLogger.Error("检查新排序位置被占用失败: ", err.Error()) + return + } + if count == 0 { + // 新位置未被占用,直接更新当前记录 + if err := tx.Model(&FollowedStock{}). + Where("stock_code = ?", strings.ToLower(stockCode)). + Update("sort", newSort).Error; err != nil { + tx.Rollback() + logger.SugaredLogger.Error("更新排序位置失败: ", err.Error()) + } + } else { + // 新位置已被占用,需要移动其他记录 + if newSort < oldSort { + // 向前移动:将中间记录向后移动 + if err := tx.Model(&FollowedStock{}). + Where("sort >= ? AND sort < ?", newSort, oldSort). + Update("sort", gorm.Expr("sort + 1")).Error; err != nil { + tx.Rollback() + logger.SugaredLogger.Error("向前排序更新失败: ", err.Error()) + } + } else { + // 向后移动:将中间记录向前移动 + if err := tx.Model(&FollowedStock{}). + Where("sort > ? AND sort <= ?", oldSort, newSort). + Update("sort", gorm.Expr("sort - 1")).Error; err != nil { + tx.Rollback() + logger.SugaredLogger.Error("向后排序更新失败: ", err.Error()) + } + } + + // 更新目标记录的排序 + if err := tx.Model(&FollowedStock{}). + Where("stock_code = ?", strings.ToLower(stockCode)). + Update("sort", newSort).Error; err != nil { + tx.Rollback() + logger.SugaredLogger.Error("Failed to update target stock sort: ", err.Error()) + } + } + if err := tx.Commit().Error; err != nil { + logger.SugaredLogger.Error("Failed to commit transaction: ", err.Error()) + } + } func (receiver StockDataApi) SetStockAICron(cron string, stockCode string) { if strutil.HasPrefixAny(stockCode, []string{"gb_"}) { From 9337084ebf4f2f259605a8f59327222d179fb07a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B5=93=E7=9D=A1=E4=B8=8D=E6=B6=88=E6=AE=8B=E9=85=92?= <49932926+CodeNoobLH@users.noreply.github.com> Date: Mon, 23 Jun 2025 16:37:20 +0800 Subject: [PATCH 02/21] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E6=8E=92=E5=BA=8F?= =?UTF-8?q?=E5=90=8E=E7=AB=AF=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/data/stock_data_api.go | 32 +++++++------------------------ frontend/src/components/stock.vue | 5 ++++- 2 files changed, 11 insertions(+), 26 deletions(-) diff --git a/backend/data/stock_data_api.go b/backend/data/stock_data_api.go index f6daa0d..b022cdb 100644 --- a/backend/data/stock_data_api.go +++ b/backend/data/stock_data_api.go @@ -469,17 +469,9 @@ func (receiver StockDataApi) SetStockSort(newSort int64, stockCode string) { stockCode = strings.Replace(stockCode, "gb_", "us", 1) } - tx := db.Dao.Begin() - defer func() { - if r := recover(); r != nil { - tx.Rollback() - } - }() - // 获取当前排序值 var currentStock FollowedStock - if err := tx.Model(&FollowedStock{}).Where("stock_code = ?", strings.ToLower(stockCode)).First(¤tStock).Error; err != nil { - tx.Rollback() + if err := db.Dao.Model(&FollowedStock{}).Where("stock_code = ?", strings.ToLower(stockCode)).First(¤tStock).Error; err != nil { logger.SugaredLogger.Error("找不到当前股票: ", err.Error()) return } @@ -488,56 +480,46 @@ func (receiver StockDataApi) SetStockSort(newSort int64, stockCode string) { // 如果排序值没有变化,直接返回 if oldSort == newSort { - tx.Rollback() return } - // 检查新排序位置是否被占用 var count int64 - if err := tx.Model(&FollowedStock{}).Where("sort = ?", newSort).Count(&count).Error; err != nil { - tx.Rollback() + 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 := tx.Model(&FollowedStock{}). + if err := db.Dao.Model(&FollowedStock{}). Where("stock_code = ?", strings.ToLower(stockCode)). Update("sort", newSort).Error; err != nil { - tx.Rollback() logger.SugaredLogger.Error("更新排序位置失败: ", err.Error()) } } else { // 新位置已被占用,需要移动其他记录 if newSort < oldSort { // 向前移动:将中间记录向后移动 - if err := tx.Model(&FollowedStock{}). + if err := db.Dao.Model(&FollowedStock{}). Where("sort >= ? AND sort < ?", newSort, oldSort). Update("sort", gorm.Expr("sort + 1")).Error; err != nil { - tx.Rollback() logger.SugaredLogger.Error("向前排序更新失败: ", err.Error()) } } else { // 向后移动:将中间记录向前移动 - if err := tx.Model(&FollowedStock{}). + if err := db.Dao.Model(&FollowedStock{}). Where("sort > ? AND sort <= ?", oldSort, newSort). Update("sort", gorm.Expr("sort - 1")).Error; err != nil { - tx.Rollback() logger.SugaredLogger.Error("向后排序更新失败: ", err.Error()) } } // 更新目标记录的排序 - if err := tx.Model(&FollowedStock{}). + if err := db.Dao.Model(&FollowedStock{}). Where("stock_code = ?", strings.ToLower(stockCode)). Update("sort", newSort).Error; err != nil { - tx.Rollback() - logger.SugaredLogger.Error("Failed to update target stock sort: ", err.Error()) + logger.SugaredLogger.Error("更新股票排序失败: ", err.Error()) } } - if err := tx.Commit().Error; err != nil { - logger.SugaredLogger.Error("Failed to commit transaction: ", err.Error()) - } } func (receiver StockDataApi) SetStockAICron(cron string, stockCode string) { diff --git a/frontend/src/components/stock.vue b/frontend/src/components/stock.vue index 1b958cd..8f4e4cb 100644 --- a/frontend/src/components/stock.vue +++ b/frontend/src/components/stock.vue @@ -226,7 +226,8 @@ onMounted(() => { GetFollowList(currentGroupId.value).then(result => { - followList.value = result + followList.value = result + console.log("onMounted",result) for (const followedStock of result) { if(followedStock.StockCode.startsWith("us")){ followedStock.StockCode="gb_"+ followedStock.StockCode.replace("us", "").toLowerCase() @@ -1273,8 +1274,10 @@ function showK(code,name){ function updateCostPriceAndVolumeNew(code,price,volume,alarm,formModel){ if(formModel.sort){ + console.log("sort:",formModel.sort) SetStockSort(formModel.sort,code).then(result => { //message.success(result) + console.log("sort result:",result) }) } if(formModel.cron){ From 9f2719cdbc13797ce912ef586adb0d567236ba59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B5=93=E7=9D=A1=E4=B8=8D=E6=B6=88=E6=AE=8B=E9=85=92?= <49932926+CodeNoobLH@users.noreply.github.com> Date: Mon, 23 Jun 2025 18:09:43 +0800 Subject: [PATCH 03/21] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E6=8E=92=E5=BA=8F?= =?UTF-8?q?=E5=89=8D=E7=AB=AF=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/stock.vue | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/frontend/src/components/stock.vue b/frontend/src/components/stock.vue index 5120caa..e231977 100644 --- a/frontend/src/components/stock.vue +++ b/frontend/src/components/stock.vue @@ -151,7 +151,6 @@ const danmakuColor = computed(()=> { 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 sortedObject = {}; @@ -227,7 +226,6 @@ onMounted(() => { GetFollowList(currentGroupId.value).then(result => { followList.value = result - console.log("onMounted",result) for (const followedStock of result) { if(followedStock.StockCode.startsWith("us")){ followedStock.StockCode="gb_"+ followedStock.StockCode.replace("us", "").toLowerCase() @@ -587,14 +585,16 @@ async function updateData(result) { } } - //result.key=result.sort + // 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[GetSortKey(result.sort,result["股票代码"])]=result if(!stocks.value.includes(result["股票代码"])) { delete results.value[result.key] } - - ////console.log("updateData",result) } @@ -1274,10 +1274,8 @@ function showK(code,name){ function updateCostPriceAndVolumeNew(code,price,volume,alarm,formModel){ if(formModel.sort){ - console.log("sort:",formModel.sort) SetStockSort(formModel.sort,code).then(result => { //message.success(result) - console.log("sort result:",result) }) } if(formModel.cron){ @@ -1296,6 +1294,7 @@ function updateCostPriceAndVolumeNew(code,price,volume,alarm,formModel){ 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) From be02343d681f75b946551d9777473ce5b25d0598 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B5=93=E7=9D=A1=E4=B8=8D=E6=B6=88=E6=AE=8B=E9=85=92?= <49932926+CodeNoobLH@users.noreply.github.com> Date: Tue, 24 Jun 2025 18:11:28 +0800 Subject: [PATCH 04/21] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=89=8D=E7=AB=AF?= =?UTF-8?q?=E5=85=B3=E6=B3=A8=E7=BE=8E=E8=82=A1=E5=90=8E=E4=B8=8D=E4=BC=9A?= =?UTF-8?q?=E5=B1=95=E7=A4=BA=E7=9A=84=E9=97=AE=E9=A2=98=20=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D=E5=89=8D=E7=AB=AF=E7=BE=8E=E8=82=A1=E9=BB=98=E8=AE=A4?= =?UTF-8?q?=E6=8E=92=E5=BA=8F=E9=9D=A0=E5=89=8D=E9=97=AE=E9=A2=98=20?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=90=8E=E7=AB=AF=E7=BE=8E=E8=82=A1=E6=97=A0?= =?UTF-8?q?=E6=B3=95=E6=8E=92=E5=BA=8F=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/data/stock_data_api.go | 16 +- frontend/src/components/stock.vue | 1455 +++++++++++++++-------------- 2 files changed, 765 insertions(+), 706 deletions(-) diff --git a/backend/data/stock_data_api.go b/backend/data/stock_data_api.go index 130d88b..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) @@ -464,10 +470,10 @@ func (receiver StockDataApi) SetAlarmChangePercent(val, alarmPrice float64, stoc } 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) - } + //if strutil.HasPrefixAny(stockCode, []string{"gb_"}) { + // stockCode = strings.ToLower(stockCode) + // stockCode = strings.Replace(stockCode, "gb_", "us", 1) + //} // 获取当前排序值 var currentStock FollowedStock diff --git a/frontend/src/components/stock.vue b/frontend/src/components/stock.vue index e231977..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,84 +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(() => { - 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 } }) @@ -200,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) @@ -214,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 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) } } @@ -252,7 +241,7 @@ onMounted(() => { }; ws.value.onmessage = (event) => { - if(data.enableDanmu){ + if (data.enableDanmu) { danmus.value.push(event.data); } }; @@ -267,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() @@ -285,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); @@ -391,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', @@ -402,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, { @@ -423,7 +401,7 @@ EventsOn("warnMsg",async (msg) => { 'text-align': 'left', 'font-size': '14px', } - }, { default: () => msg }) + }, {default: () => msg}) }, }) }) @@ -448,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]) @@ -488,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`); } } @@ -551,59 +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.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["当前价格"]){ - 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.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[GetSortKey(result.sort,result["股票代码"])]=result - if(!stocks.value.includes(result["股票代码"])) { + + result.key = GetSortKey(result.sort, result["股票代码"]) + results.value[result.key] = result + if (!stocks.value.includes(result["股票代码"])) { delete results.value[result.key] } } 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) }) @@ -611,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 @@ -665,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) @@ -683,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) @@ -694,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: { @@ -731,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', @@ -766,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' }, @@ -785,7 +763,7 @@ function showFsChart(code, name) { ], visualMap: { type: 'piecewise', - seriesIndex:0, + seriesIndex: 0, top: 0, left: 10, orient: 'horizontal', @@ -828,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' @@ -850,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 + }, + } + ] }, }, { @@ -884,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) { @@ -922,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: { @@ -968,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; @@ -1047,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: { @@ -1060,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' } @@ -1079,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: [ @@ -1110,8 +1089,8 @@ function handleKLine(){ itemStyle: { color: upColor, color0: downColor, - // borderColor: upBorderColor, - // borderColor0: downBorderColor + // borderColor: upBorderColor, + // borderColor0: downBorderColor }, markPoint: { label: { @@ -1192,7 +1171,7 @@ function handleKLine(){ { name: 'MA5', type: 'line', - data: calculateMA(5,values), + data: calculateMA(5, values), smooth: true, showSymbol: false, lineStyle: { @@ -1202,7 +1181,7 @@ function handleKLine(){ { name: 'MA10', type: 'line', - data: calculateMA(10,values), + data: calculateMA(10, values), smooth: true, showSymbol: false, lineStyle: { @@ -1212,7 +1191,7 @@ function handleKLine(){ { name: 'MA20', type: 'line', - data: calculateMA(20,values), + data: calculateMA(20, values), smooth: true, showSymbol: false, lineStyle: { @@ -1222,7 +1201,7 @@ function handleKLine(){ { name: 'MA30', type: 'line', - data: calculateMA(30,values), + data: calculateMA(30, values), smooth: true, showSymbol: false, lineStyle: { @@ -1242,59 +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=[] + stocks.value = [] for (const followedStock of result) { if (!stocks.value.includes(followedStock.StockCode)) { stocks.value.push(followedStock.StockCode) @@ -1306,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"+ - "![image]("+img+")\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" + + "![image](" + img + ")\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'); @@ -1380,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: @@ -1426,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 { @@ -1463,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`; @@ -1477,6 +1457,7 @@ function saveAsMarkdown_old() { URL.revokeObjectURL(link.href); link.remove() } + function getHtml(ref) { if (ref.value) { // 获取 MdPreview 组件的根元素 @@ -1494,7 +1475,7 @@ async function saveAsWord() { // 将富文本内容拼接为一个完整的html const html = getHtml(mdPreviewRef) const tipsHtml = getHtml(tipsRef) - const value = ` + const value = ` ${html}
@@ -1508,7 +1489,7 @@ AI赋能股票分析:自选股行情获取,成本盈亏展示,涨跌报警

` // landscape就是横着的,portrait是竖着的,默认是竖屏portrait。 - const blob = await asBlob(value, { orientation: '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`; @@ -1518,8 +1499,8 @@ AI赋能股票分析:自选股行情获取,成本盈亏展示,涨跌报警 a.remove() } -function share(code,name){ - ShareAnalysis(code,name).then(msg => { +function share(code, name) { + ShareAnalysis(code, name).then(msg => { //message.info(msg) notify.info({ avatar: () => @@ -1529,90 +1510,96 @@ function share(code,name){ src: icon.value }), title: '分享到社区', - duration:1000*30, + duration: 1000 * 30, content: () => { return h('div', { style: { 'text-align': 'left', 'font-size': '14px', } - }, { default: () => msg }) + }, {default: () => msg}) }, }) }) } -const addTabModel=ref({ + +const addTabModel = ref({ name: '', sort: 1, }) -const addTabPane=ref(false) -function addTab(){ - addTabPane.value=true +const addTabPane = ref(false) + +function addTab() { + addTabPane.value = true } -function saveTabPane(){ + +function saveTabPane() { AddGroup(addTabModel.value).then(result => { message.info(result) - addTabPane.value=false + addTabPane.value = false GetGroupList().then(result => { - groupList.value=result + groupList.value = result }) }) } -function AddStockGroupInfo(groupId,code,name){ - if(code.startsWith("gb_")){ - code="us"+ code.replace("gb_", "").toLowerCase() + +function AddStockGroupInfo(groupId, code, name) { + if (code.startsWith("gb_")) { + code = "us" + code.replace("gb_", "").toLowerCase() } - AddStockGroup(groupId,code).then(result => { + AddStockGroup(groupId, code).then(result => { message.info(result) GetGroupList().then(result => { - groupList.value=result + groupList.value = result }) }) } -function updateTab(name){ - currentGroupId.value=Number(name) + +function updateTab(name) { + currentGroupId.value = Number(name) GetFollowList(currentGroupId.value).then(result => { - stocks.value=[] - //console.log("GetFollowList",result) + stocks.value = [] 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() } - ////console.log("followList",followedStock.StockCode) - stocks.value.push(followedStock.StockCode) + ////console.log("followList",followedStock.StockCode) + stocks.value.push(followedStock.StockCode) } monitor() message.destroyAll() }) } -function delTab(name){ - let infos=groupList.value=groupList.value.filter(item => item.ID === Number(name)) + +function delTab(name) { + let infos = groupList.value = groupList.value.filter(item => item.ID === Number(name)) dialog.create({ title: '删除分组', type: 'warning', - content: '确定要删除['+infos[0].name+']分组吗?分组数据将不能恢复哟!', + content: '确定要删除[' + infos[0].name + ']分组吗?分组数据将不能恢复哟!', positiveText: '确定', negativeText: '取消', onPositiveClick: () => { RemoveGroup(name).then(result => { message.info(result) GetGroupList().then(result => { - groupList.value=result + groupList.value = result }) }) } }) } -function delStockGroup(code,name,groupId){ - RemoveStockGroup(code,name,groupId).then(result => { + +function delStockGroup(code, name, groupId) { + RemoveStockGroup(code, name, groupId).then(result => { updateTab(groupId) message.info(result) }) } -function searchNotice(stockCode){ +function searchNotice(stockCode) { router.push({ name: 'market', query: { @@ -1621,7 +1608,8 @@ function searchNotice(stockCode){ }, }) } -function searchStockReport(stockCode){ + +function searchStockReport(stockCode) { router.push({ name: 'market', query: { @@ -1633,384 +1621,447 @@ function searchStockReport(stockCode){ From 7dd10d443e4767e3da189acb61963591b4eeb3c1 Mon Sep 17 00:00:00 2001 From: ArvinLovegood Date: Wed, 25 Jun 2025 09:40:04 +0800 Subject: [PATCH 05/21] =?UTF-8?q?feat(frontend):=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E7=83=AD=E9=97=A8=E8=82=A1=E7=A5=A8=E3=80=81=E4=BA=8B=E4=BB=B6?= =?UTF-8?q?=E5=92=8C=E8=AF=9D=E9=A2=98=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在 App.d.ts 和 App.js 中添加了 HotEvent、HotStock 和 HotTopic 函数 - 在 app_common.go 中实现了相关功能的后端逻辑 - 新增 HotEvents、HotStockList 和 HotTopics 组件用于前端展示 - 更新 market.vue以包含新的热门股票和话题功能 - 在 KLineChart.vue 中添加了代码和名称的显示 --- app_common.go | 17 ++++++ backend/data/market_news_api.go | 69 +++++++++++++++++++++ backend/data/market_news_api_test.go | 27 +++++++++ backend/models/models.go | 45 ++++++++++++++ frontend/src/components/HotEvents.vue | 37 ++++++++++++ frontend/src/components/HotStockList.vue | 76 ++++++++++++++++++++++++ frontend/src/components/HotTopics.vue | 43 ++++++++++++++ frontend/src/components/KLineChart.vue | 2 +- frontend/src/components/market.vue | 31 +++++++++- frontend/wailsjs/go/main/App.d.ts | 6 ++ frontend/wailsjs/go/main/App.js | 12 ++++ 11 files changed, 362 insertions(+), 3 deletions(-) create mode 100644 frontend/src/components/HotEvents.vue create mode 100644 frontend/src/components/HotStockList.vue create mode 100644 frontend/src/components/HotTopics.vue diff --git a/app_common.go b/app_common.go index a73a611..587be55 100644 --- a/app_common.go +++ b/app_common.go @@ -31,3 +31,20 @@ func (a App) EMDictCode(code string) []any { func (a App) AnalyzeSentiment(text string) data.SentimentResult { return data.AnalyzeSentiment(text) } + +func (a App) HotStock(marketType string) *[]models.HotItem { + return data.NewMarketNewsApi().XUEQIUHotStock(50, marketType) +} + +func (a App) HotEvent(size int) *[]models.HotEvent { + if size <= 0 { + size = 10 + } + return data.NewMarketNewsApi().HotEvent(size) +} +func (a App) HotTopic(size int) []any { + if size <= 0 { + size = 10 + } + return data.NewMarketNewsApi().HotTopic(size) +} diff --git a/backend/data/market_news_api.go b/backend/data/market_news_api.go index d1e5938..ff74b2a 100644 --- a/backend/data/market_news_api.go +++ b/backend/data/market_news_api.go @@ -574,3 +574,72 @@ func (m MarketNewsApi) TradingViewNews() *[]models.TVNews { json.Unmarshal(items, TVNews) return TVNews } + +func (m MarketNewsApi) XUEQIUHotStock(size int, marketType string) *[]models.HotItem { + url := fmt.Sprintf("https://stock.xueqiu.com/v5/stock/hot_stock/list.json?page=1&size=%d&_type=%s&type=%s", size, marketType, marketType) + res := &models.XUEQIUHot{} + _, err := resty.New().SetTimeout(time.Duration(30)*time.Second).R(). + SetHeader("Host", "stock.xueqiu.com"). + SetHeader("Origin", "https://xueqiu.com"). + SetHeader("Referer", "https://xueqiu.com/"). + SetHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:140.0) Gecko/20100101 Firefox/140.0"). + SetHeader("Cookie", "cookiesu=2617378771242871; s=c2121pp1u71; device_id=237a58584ec58d8e4d4e1040700a644f1; Hm_lvt_1db88642e346389874251b5a1eded6e3=1744100219,1744599115; xq_a_token=b7259d09435458cc3f1a963479abb270a1a016ce; xqat=b7259d09435458cc3f1a963479abb270a1a016ce; xq_r_token=28108bfa1d92ac8a46bbb57722633746218621a3; xq_id_token=eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJ1aWQiOi0xLCJpc3MiOiJ1YyIsImV4cCI6MTc1MjU0MTk4OCwiY3RtIjoxNzUwMjMwNjA2NzI0LCJjaWQiOiJkOWQwbjRBWnVwIn0.kU_fz0luJoE7nr-K4UrNUi5-mAG-vMdXtuC4mUKIppILId4UpF70LB70yunxGiNSw6tPFR3-hyLvztKAHtekCUTm3XjUl5b3tEDP-ZUVqHnWXO_5hoeMI8h-Cfx6ZGlIr5x3icvTPkT0OV5CD5A33-ZDTKhKPf-DhJ_-m7CG5GbX4MseOBeMXuLUQUiYHPKhX1QUc0GTGrCzi8Mki0z49D0LVqCSgbsx3UGfowOOyx85_cXb4OAFvIjwbs2p0o_h-ibIT0ngVkkAyEDetVvlcZ_bkardhseCB7k9BEMgH2z8ihgkVxyy3P0degLmDUruhmqn5uZOCi1pVBDvCv9lBg; u=261737877124287; ssxmod_itna=QuG=D5AKiKDIqCqGKi7G7DgmmPlSDWFqKGHDyx4YK0CDmxjKiddDUQivnb8xpnQcGyGYoYhoqEeDBubrDSxD67DK4GTm+ogiw1o3B=xedQHDgBtN=7/i1K53N+rOjquLMU=kbqYxB3DExGkqj0tPi4DxaPD5xDTDWeDGDD3DnnsDQKDRx0kL0oDIxD1D0bmHUEvh38mDYePLmOmDYPYx94Y8KoDeEgsD7HUl/vIGGEAqjLPFegXLD0HolCqr4DCid1qDm+ECfkjDn9sD0KP8fn+CRoDv=tYr4ibx+o=W+8vstf9mjGe3cXseWdBmoFrmf4DA3bFAxnAxD7vYxADaDoerDGHPoxHF+PKGPtDKmiqQGeB5qbi4eg4KDHKDe3DeG0qeEP9xVUoHDDWMYYM0ICr4FBimBDM7D0x4QOECmhul5QCN/m5/74lGm=7x9Wp7A+i7xQ7wlMD4D; ssxmod_itna2=QuG=D5AKiKDIqCqGKi7G7DgmmPlSDWFqKGHDyx4YK0CDmxjKiddDUQivnb8xpnQcGyGYoYhoqoDirSDhPmGD24GajjDuGE3m7or4DlxOSGewHl6iaus2Q62SRX5CFjCds6ltF9xy6iaUuB262UkhRA8UXST=4/b+y3kGKzlGE8T29FA008ljy9jXXC7f7m7QsK667mlUooWrofk=qGZjxtcUrN1NtuAnne1hj+rQP5UnlFkxf+o7VjmatH7u7bCDlbTt3cz6CH9Fl4vye16W/ellc8I3Q37W7ZwiLGD/zPpZcnd2nsqqo/+zRbKAmz4plzwaDqGUe7f9E+P0IFRKqpRv+buQFHBSpcbwND7Q+9XWmnjI2UwKd98jIS3gPXwxvbx4OuiyH8gZ+OEt7DgE/AY/9W4VxDZrlFWyWnC4y4/I0IpAfaGKpbPmauKbkqawqv93vSf+9HamGe0Dt2PNgT3yiEB4vQP2/DdVpcGBOjFujWoHP32OshLPYI20LRCKddwEGkKqPzPwKPc3X5zuB=w2fUdtwKsAW5kQtsl8clNwjC5uDYrxR0h9xaj0xmD+YuI3GPT7xYTalRImPj2wL2=+91a304xa4bTWtP=dLGARhb/efRi0uktaz8i8C04G0x/ZWUzqRza8GGU=FfRfvb4GZM/q2rVsl0nLvRjGeAKgocLouyXs/uwZu3YxbAx30qCbjG1A533zAxIeIgD=0VAc3ixD"). + SetResult(res). + Get(url) + if err != nil { + logger.SugaredLogger.Errorf("XUEQIUHotStock err:%s", err.Error()) + return &[]models.HotItem{} + } + logger.SugaredLogger.Infof("XUEQIUHotStock:%+v", res) + return &res.Data.Items +} + +func (m MarketNewsApi) HotEvent(size int) *[]models.HotEvent { + events := &[]models.HotEvent{} + url := fmt.Sprintf("https://xueqiu.com/hot_event/list.json?count=%d", size) + resp, err := resty.New().SetTimeout(time.Duration(30)*time.Second).R(). + SetHeader("Host", "xueqiu.com"). + SetHeader("Origin", "https://xueqiu.com"). + SetHeader("Referer", "https://xueqiu.com/"). + SetHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:140.0) Gecko/20100101 Firefox/140.0"). + SetHeader("Cookie", "cookiesu=2617378771242871; s=c2121pp1u71; device_id=237a58584ec58d8e4d4e1040700a644f1; Hm_lvt_1db88642e346389874251b5a1eded6e3=1744100219,1744599115; xq_a_token=b7259d09435458cc3f1a963479abb270a1a016ce; xqat=b7259d09435458cc3f1a963479abb270a1a016ce; xq_r_token=28108bfa1d92ac8a46bbb57722633746218621a3; xq_id_token=eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJ1aWQiOi0xLCJpc3MiOiJ1YyIsImV4cCI6MTc1MjU0MTk4OCwiY3RtIjoxNzUwMjMwNjA2NzI0LCJjaWQiOiJkOWQwbjRBWnVwIn0.kU_fz0luJoE7nr-K4UrNUi5-mAG-vMdXtuC4mUKIppILId4UpF70LB70yunxGiNSw6tPFR3-hyLvztKAHtekCUTm3XjUl5b3tEDP-ZUVqHnWXO_5hoeMI8h-Cfx6ZGlIr5x3icvTPkT0OV5CD5A33-ZDTKhKPf-DhJ_-m7CG5GbX4MseOBeMXuLUQUiYHPKhX1QUc0GTGrCzi8Mki0z49D0LVqCSgbsx3UGfowOOyx85_cXb4OAFvIjwbs2p0o_h-ibIT0ngVkkAyEDetVvlcZ_bkardhseCB7k9BEMgH2z8ihgkVxyy3P0degLmDUruhmqn5uZOCi1pVBDvCv9lBg; u=261737877124287; ssxmod_itna=QuG=D5AKiKDIqCqGKi7G7DgmmPlSDWFqKGHDyx4YK0CDmxjKiddDUQivnb8xpnQcGyGYoYhoqEeDBubrDSxD67DK4GTm+ogiw1o3B=xedQHDgBtN=7/i1K53N+rOjquLMU=kbqYxB3DExGkqj0tPi4DxaPD5xDTDWeDGDD3DnnsDQKDRx0kL0oDIxD1D0bmHUEvh38mDYePLmOmDYPYx94Y8KoDeEgsD7HUl/vIGGEAqjLPFegXLD0HolCqr4DCid1qDm+ECfkjDn9sD0KP8fn+CRoDv=tYr4ibx+o=W+8vstf9mjGe3cXseWdBmoFrmf4DA3bFAxnAxD7vYxADaDoerDGHPoxHF+PKGPtDKmiqQGeB5qbi4eg4KDHKDe3DeG0qeEP9xVUoHDDWMYYM0ICr4FBimBDM7D0x4QOECmhul5QCN/m5/74lGm=7x9Wp7A+i7xQ7wlMD4D; ssxmod_itna2=QuG=D5AKiKDIqCqGKi7G7DgmmPlSDWFqKGHDyx4YK0CDmxjKiddDUQivnb8xpnQcGyGYoYhoqoDirSDhPmGD24GajjDuGE3m7or4DlxOSGewHl6iaus2Q62SRX5CFjCds6ltF9xy6iaUuB262UkhRA8UXST=4/b+y3kGKzlGE8T29FA008ljy9jXXC7f7m7QsK667mlUooWrofk=qGZjxtcUrN1NtuAnne1hj+rQP5UnlFkxf+o7VjmatH7u7bCDlbTt3cz6CH9Fl4vye16W/ellc8I3Q37W7ZwiLGD/zPpZcnd2nsqqo/+zRbKAmz4plzwaDqGUe7f9E+P0IFRKqpRv+buQFHBSpcbwND7Q+9XWmnjI2UwKd98jIS3gPXwxvbx4OuiyH8gZ+OEt7DgE/AY/9W4VxDZrlFWyWnC4y4/I0IpAfaGKpbPmauKbkqawqv93vSf+9HamGe0Dt2PNgT3yiEB4vQP2/DdVpcGBOjFujWoHP32OshLPYI20LRCKddwEGkKqPzPwKPc3X5zuB=w2fUdtwKsAW5kQtsl8clNwjC5uDYrxR0h9xaj0xmD+YuI3GPT7xYTalRImPj2wL2=+91a304xa4bTWtP=dLGARhb/efRi0uktaz8i8C04G0x/ZWUzqRza8GGU=FfRfvb4GZM/q2rVsl0nLvRjGeAKgocLouyXs/uwZu3YxbAx30qCbjG1A533zAxIeIgD=0VAc3ixD"). + Get(url) + if err != nil { + logger.SugaredLogger.Errorf("HotEvent err:%s", err.Error()) + return events + } + logger.SugaredLogger.Infof("HotEvent:%s", resp.Body()) + respMap := map[string]any{} + err = json.Unmarshal(resp.Body(), &respMap) + items, err := json.Marshal(respMap["list"]) + if err != nil { + return events + } + json.Unmarshal(items, events) + return events + +} + +func (m MarketNewsApi) HotTopic(size int) []any { + url := "https://gubatopic.eastmoney.com/interface/GetData.aspx?path=newtopic/api/Topic/HomePageListRead" + resp, err := resty.New().SetTimeout(time.Duration(30)*time.Second).R(). + SetHeader("Host", "gubatopic.eastmoney.com"). + SetHeader("Origin", "https://gubatopic.eastmoney.com"). + SetHeader("Referer", "https://gubatopic.eastmoney.com/"). + SetHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:140.0) Gecko/20100101 Firefox/140.0"). + SetFormData(map[string]string{ + "param": fmt.Sprintf("ps=%d&p=1&type=0", size), + "path": "newtopic/api/Topic/HomePageListRead", + "env": "2", + }). + Post(url) + if err != nil { + logger.SugaredLogger.Errorf("HotTopic err:%s", err.Error()) + return []any{} + } + logger.SugaredLogger.Infof("HotTopic:%s", resp.Body()) + respMap := map[string]any{} + err = json.Unmarshal(resp.Body(), &respMap) + return respMap["re"].([]any) + +} diff --git a/backend/data/market_news_api_test.go b/backend/data/market_news_api_test.go index 01d489b..d6279bd 100644 --- a/backend/data/market_news_api_test.go +++ b/backend/data/market_news_api_test.go @@ -108,3 +108,30 @@ func TestTradingViewNews(t *testing.T) { logger.SugaredLogger.Debugf("value: %+v", a) } } + +func TestXUEQIUHotStock(t *testing.T) { + db.Init("../../data/stock.db") + res := NewMarketNewsApi().XUEQIUHotStock(50, "10") + for _, a := range *res { + logger.SugaredLogger.Debugf("value: %+v", a) + } + +} + +func TestHotEvent(t *testing.T) { + db.Init("../../data/stock.db") + res := NewMarketNewsApi().HotEvent(50) + for _, a := range *res { + logger.SugaredLogger.Debugf("value: %+v", a) + } + +} + +func TestHotTopic(t *testing.T) { + db.Init("../../data/stock.db") + res := NewMarketNewsApi().HotTopic(10) + for _, a := range res { + logger.SugaredLogger.Debugf("value: %+v", a) + } + +} diff --git a/backend/models/models.go b/backend/models/models.go index f513055..10d237b 100644 --- a/backend/models/models.go +++ b/backend/models/models.go @@ -317,3 +317,48 @@ type TVNews struct { LogoId string `json:"logo_id"` } `json:"provider"` } + +type XUEQIUHot struct { + Data struct { + Items []HotItem `json:"items"` + ItemsSize int `json:"items_size"` + } `json:"data"` + ErrorCode int `json:"error_code"` + ErrorDescription string `json:"error_description"` +} + +type HotItem struct { + Type int `json:"type"` + Code string `json:"code"` + Name string `json:"name"` + Value float64 `json:"value"` + Increment int `json:"increment"` + RankChange int `json:"rank_change"` + HasExist interface{} `json:"has_exist"` + Symbol string `json:"symbol"` + Percent float64 `json:"percent"` + Current float64 `json:"current"` + Chg float64 `json:"chg"` + Exchange string `json:"exchange"` + StockType int `json:"stock_type"` + SubType string `json:"sub_type"` + Ad int `json:"ad"` + AdId interface{} `json:"ad_id"` + ContentId interface{} `json:"content_id"` + Page interface{} `json:"page"` + Model interface{} `json:"model"` + Location interface{} `json:"location"` + TradeSession interface{} `json:"trade_session"` + CurrentExt interface{} `json:"current_ext"` + PercentExt interface{} `json:"percent_ext"` +} + +type HotEvent struct { + PicSize interface{} `json:"pic_size"` + Tag string `json:"tag"` + Id int `json:"id"` + Pic string `json:"pic"` + Hot int `json:"hot"` + StatusCount int `json:"status_count"` + Content string `json:"content"` +} diff --git a/frontend/src/components/HotEvents.vue b/frontend/src/components/HotEvents.vue new file mode 100644 index 0000000..8571f3f --- /dev/null +++ b/frontend/src/components/HotEvents.vue @@ -0,0 +1,37 @@ + + + + + \ No newline at end of file diff --git a/frontend/src/components/HotStockList.vue b/frontend/src/components/HotStockList.vue new file mode 100644 index 0000000..5f978c5 --- /dev/null +++ b/frontend/src/components/HotStockList.vue @@ -0,0 +1,76 @@ + + + + + \ No newline at end of file diff --git a/frontend/src/components/HotTopics.vue b/frontend/src/components/HotTopics.vue new file mode 100644 index 0000000..6a3850f --- /dev/null +++ b/frontend/src/components/HotTopics.vue @@ -0,0 +1,43 @@ + + + + + \ No newline at end of file diff --git a/frontend/src/components/KLineChart.vue b/frontend/src/components/KLineChart.vue index 5791430..e1889c1 100644 --- a/frontend/src/components/KLineChart.vue +++ b/frontend/src/components/KLineChart.vue @@ -60,7 +60,7 @@ function handleKLine(code,name){ ////console.log("values",values) let option = { title: { - text: name, + text: name+" "+code, left: '20px', textStyle: { color: darkTheme?'#ccc':'#456' diff --git a/frontend/src/components/market.vue b/frontend/src/components/market.vue index 7944673..c41038e 100644 --- a/frontend/src/components/market.vue +++ b/frontend/src/components/market.vue @@ -26,6 +26,9 @@ import StockResearchReportList from "./StockResearchReportList.vue"; import StockNoticeList from "./StockNoticeList.vue"; import LongTigerRankList from "./LongTigerRankList.vue"; import IndustryResearchReportList from "./IndustryResearchReportList.vue"; +import HotStockList from "./HotStockList.vue"; +import HotEvents from "./HotEvents.vue"; +import HotTopics from "./HotTopics.vue"; const route = useRoute() const icon = ref('https://raw.githubusercontent.com/ArvinLovegood/go-stock/master/build/appicon.png'); @@ -557,8 +560,32 @@ function ReFlesh(source) { - - + + + + + + + + + + + + + + + + + + + + + + + + + + >; export function Greet(arg1:string):Promise; +export function HotEvent(arg1:number):Promise; + +export function HotStock(arg1:string):Promise; + +export function HotTopic(arg1:number):Promise>; + export function IndustryResearchReport(arg1:string):Promise>; export function LongTigerRank(arg1:string):Promise; diff --git a/frontend/wailsjs/go/main/App.js b/frontend/wailsjs/go/main/App.js index 27d7441..0e93c54 100644 --- a/frontend/wailsjs/go/main/App.js +++ b/frontend/wailsjs/go/main/App.js @@ -126,6 +126,18 @@ export function Greet(arg1) { return window['go']['main']['App']['Greet'](arg1); } +export function HotEvent(arg1) { + return window['go']['main']['App']['HotEvent'](arg1); +} + +export function HotStock(arg1) { + return window['go']['main']['App']['HotStock'](arg1); +} + +export function HotTopic(arg1) { + return window['go']['main']['App']['HotTopic'](arg1); +} + export function IndustryResearchReport(arg1) { return window['go']['main']['App']['IndustryResearchReport'](arg1); } From 2f6c17fb2a60fae04f8d089485a5713375939800 Mon Sep 17 00:00:00 2001 From: ArvinLovegood Date: Wed, 25 Jun 2025 09:41:16 +0800 Subject: [PATCH 06/21] =?UTF-8?q?feat(frontend):=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E7=83=AD=E9=97=A8=E8=82=A1=E7=A5=A8=E3=80=81=E4=BA=8B=E4=BB=B6?= =?UTF-8?q?=E5=92=8C=E8=AF=9D=E9=A2=98=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在 App.d.ts 和 App.js 中添加了 HotEvent、HotStock 和 HotTopic 函数 - 在 app_common.go 中实现了相关功能的后端逻辑 - 新增 HotEvents、HotStockList 和 HotTopics 组件用于前端展示 - 更新 market.vue以包含新的热门股票和话题功能 - 在 KLineChart.vue 中添加了代码和名称的显示 --- backend/data/market_news_api.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/data/market_news_api.go b/backend/data/market_news_api.go index ff74b2a..0a55591 100644 --- a/backend/data/market_news_api.go +++ b/backend/data/market_news_api.go @@ -590,7 +590,7 @@ func (m MarketNewsApi) XUEQIUHotStock(size int, marketType string) *[]models.Hot logger.SugaredLogger.Errorf("XUEQIUHotStock err:%s", err.Error()) return &[]models.HotItem{} } - logger.SugaredLogger.Infof("XUEQIUHotStock:%+v", res) + // logger.SugaredLogger.Infof("XUEQIUHotStock:%+v", res) return &res.Data.Items } @@ -608,7 +608,7 @@ func (m MarketNewsApi) HotEvent(size int) *[]models.HotEvent { logger.SugaredLogger.Errorf("HotEvent err:%s", err.Error()) return events } - logger.SugaredLogger.Infof("HotEvent:%s", resp.Body()) + //logger.SugaredLogger.Infof("HotEvent:%s", resp.Body()) respMap := map[string]any{} err = json.Unmarshal(resp.Body(), &respMap) items, err := json.Marshal(respMap["list"]) @@ -637,7 +637,7 @@ func (m MarketNewsApi) HotTopic(size int) []any { logger.SugaredLogger.Errorf("HotTopic err:%s", err.Error()) return []any{} } - logger.SugaredLogger.Infof("HotTopic:%s", resp.Body()) + //logger.SugaredLogger.Infof("HotTopic:%s", resp.Body()) respMap := map[string]any{} err = json.Unmarshal(resp.Body(), &respMap) return respMap["re"].([]any) From e0225c41586e7600d356f25bfe5b8d89966f8b2a Mon Sep 17 00:00:00 2001 From: ArvinLovegood Date: Wed, 25 Jun 2025 09:43:04 +0800 Subject: [PATCH 07/21] =?UTF-8?q?docs(README):=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E7=83=AD=E9=97=A8=E8=82=A1=E7=A5=A8=E3=80=81=E4=BA=8B=E4=BB=B6?= =?UTF-8?q?=E5=92=8C=E8=AF=9D=E9=A2=98=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在 README.md 文件的更新日志中添加了 2025.06.25 的更新内容- 新增了热门股票、事件和话题功能 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e8511ef..62383f6 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,7 @@ | 不再强制依赖Chrome浏览器 | ✅ | 默认使用edge浏览器抓取新闻资讯 | ## 👀 更新日志 - +### 2025.06.25 添加热门股票、事件和话题功能 ### 2025.06.18 更新内置股票基础数据,软件内实时市场资讯信息提醒,添加行业研究功能 ### 2025.06.15 添加公司公告信息搜索/查看功能 ### 2025.06.15 添加个股研报到弹出菜单 From 0e45866421b6d30d0cea4559427d58f1e170eb18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B5=93=E7=9D=A1=E4=B8=8D=E6=B6=88=E6=AE=8B=E9=85=92?= <49932926+CodeNoobLH@users.noreply.github.com> Date: Wed, 25 Jun 2025 10:21:40 +0800 Subject: [PATCH 08/21] =?UTF-8?q?fix(stock):=20=E4=BC=98=E5=8C=96=E8=82=A1?= =?UTF-8?q?=E7=A5=A8=E4=BB=A3=E7=A0=81=E5=A4=84=E7=90=86=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在关注股票时,仅当股票代码以 "us" 开头时,才将其转换为 "gb_" 前缀的格式 --- frontend/src/components/stock.vue | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/frontend/src/components/stock.vue b/frontend/src/components/stock.vue index 424d655..89626f7 100644 --- a/frontend/src/components/stock.vue +++ b/frontend/src/components/stock.vue @@ -434,7 +434,9 @@ function AddStock() { if (!stocks.value.includes(data.code)) { Follow(data.code).then(result => { if (result === "关注成功") { - data.code= "gb_" + data.code.replace("us", "").toLowerCase() + if (data.code.startsWith("us")) { + data.code= "gb_" + data.code.replace("us", "").toLowerCase() + } stocks.value.push(data.code) message.success(result) GetFollowList(currentGroupId.value).then(result => { From 3f4cbca4a7f23c76fb5fbfbdad9886ac5772783e Mon Sep 17 00:00:00 2001 From: ArvinLovegood Date: Wed, 25 Jun 2025 10:34:57 +0800 Subject: [PATCH 09/21] =?UTF-8?q?docs(README):=E6=B7=BB=E5=8A=A0=E7=83=AD?= =?UTF-8?q?=E9=97=A8=E8=82=A1=E7=A5=A8=E3=80=81=E4=BA=8B=E4=BB=B6=E5=92=8C?= =?UTF-8?q?=E8=AF=9D=E9=A2=98=E5=8A=9F=E8=83=BD=EF=BC=8C=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=E5=BC=80=E5=8F=91=E8=80=85=E5=88=97=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在 README.md 文件的更新日志中添加了 2025.06.25 的更新内容- 新增了热门股票、事件和话题功能 --- frontend/src/components/about.vue | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/src/components/about.vue b/frontend/src/components/about.vue index 399760d..a40f660 100644 --- a/frontend/src/components/about.vue +++ b/frontend/src/components/about.vue @@ -135,6 +135,7 @@ EventsOn("updateVersion",async (msg) => {

感谢以下开发者: + 浓睡不消残酒 @gnim2600 @XXXiaohuayanGGG @2lovecode From 55839d332968aaad244aff1030df2dd1a21d5f8b Mon Sep 17 00:00:00 2001 From: ArvinLovegood Date: Wed, 25 Jun 2025 13:37:55 +0800 Subject: [PATCH 10/21] =?UTF-8?q?feat(frontend):=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E7=83=AD=E9=97=A8=E8=82=A1=E7=A5=A8=E5=92=8C=E8=AF=9D=E9=A2=98?= =?UTF-8?q?=E7=BB=84=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 更新热门股票列表,增加更多图标和数据字段 - 改进热门话题组件,添加点击事件和额外信息展示 - 调整股票搜索功能,使用居中弹窗打开链接 - 更新 App.vue 中的图标和菜单项 - 修改后端 HotStock 函数,增加返回数据量 --- app_common.go | 2 +- frontend/src/App.vue | 36 +++++++++++++++++--- frontend/src/components/HotStockList.vue | 16 +++++++-- frontend/src/components/HotTopics.vue | 42 ++++++++++++++++++++---- frontend/src/components/stock.vue | 16 ++++++++- 5 files changed, 97 insertions(+), 15 deletions(-) diff --git a/app_common.go b/app_common.go index 587be55..7a9f6f0 100644 --- a/app_common.go +++ b/app_common.go @@ -33,7 +33,7 @@ func (a App) AnalyzeSentiment(text string) data.SentimentResult { } func (a App) HotStock(marketType string) *[]models.HotItem { - return data.NewMarketNewsApi().XUEQIUHotStock(50, marketType) + return data.NewMarketNewsApi().XUEQIUHotStock(100, marketType) } func (a App) HotEvent(size int) *[]models.HotEvent { diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 930966c..b371eed 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -14,7 +14,7 @@ import {createDiscreteApi,darkTheme,lightTheme , NIcon, NText,dateZhCN,zhCN} fro import { AlarmOutline, AnalyticsOutline, - BarChartSharp, EaselSharp, + BarChartSharp, Bonfire, BonfireOutline, EaselSharp, ExpandOutline, Flag, Flame, FlameSharp, InformationOutline, LogoGithub, @@ -28,6 +28,11 @@ import { Wallet, WarningOutline, } from '@vicons/ionicons5' import {AnalyzeSentiment, GetConfig, GetGroupList} from "../wailsjs/go/main/App"; +import {Dragon, Fire, Gripfire} from "@vicons/fa"; +import {ReportSearch} from "@vicons/tabler"; +import {LocalFireDepartmentRound} from "@vicons/material"; +import {CommentNote20Filled} from "@vicons/fluent"; +import {FireFilled, FireOutlined, NotificationFilled, StockOutlined} from "@vicons/antd"; @@ -241,7 +246,7 @@ const menuOptions = ref([ {default: () => '龙虎榜',} ), key: 'market6', - icon: renderIcon(Skull), + icon: renderIcon(Dragon), }, { label: () => @@ -262,7 +267,7 @@ const menuOptions = ref([ {default: () => '个股研报',} ), key: 'market7', - icon: renderIcon(NewspaperSharp), + icon: renderIcon(StockOutlined), }, { label: () => @@ -283,7 +288,7 @@ const menuOptions = ref([ {default: () => '公司公告',} ), key: 'market8', - icon: renderIcon(NewspaperSharp), + icon: renderIcon(NotificationFilled), }, { label: () => @@ -304,7 +309,28 @@ const menuOptions = ref([ {default: () => '行业研究',} ), key: 'market9', - icon: renderIcon(NewspaperSharp), + icon: renderIcon(ReportSearch), + }, + { + label: () => + h( + RouterLink, + { + href: '#', + to: { + name: 'market', + query: { + name: "当前热门", + } + }, + onClick: () => { + EventsEmit("changeMarketTab", {ID: 0, name: '当前热门'}) + }, + }, + {default: () => '当前热门',} + ), + key: 'market10', + icon: renderIcon(Gripfire), }, ] }, diff --git a/frontend/src/components/HotStockList.vue b/frontend/src/components/HotStockList.vue index 5f978c5..9846deb 100644 --- a/frontend/src/components/HotStockList.vue +++ b/frontend/src/components/HotStockList.vue @@ -2,6 +2,7 @@ import {onBeforeMount, onUnmounted, ref} from 'vue' import {HotStock} from "../../wailsjs/go/main/App"; import KLineChart from "./KLineChart.vue"; +import {ArrowBack, ArrowDown, ArrowUp} from "@vicons/ionicons5"; const {marketType}=defineProps( { @@ -64,8 +65,19 @@ function getMarketCode(item) { {{item.percent}}% {{item.current}} {{item.value}} - {{item.increment}} - {{item.rank_change}} + + {{item.increment}} + + + + + + {{item.rank_change}} + + + + + diff --git a/frontend/src/components/HotTopics.vue b/frontend/src/components/HotTopics.vue index 6a3850f..e203788 100644 --- a/frontend/src/components/HotTopics.vue +++ b/frontend/src/components/HotTopics.vue @@ -13,26 +13,56 @@ onBeforeMount(async () => { onUnmounted(()=>{ clearInterval(task.value) }) + +function openCenteredWindow(url, width, height) { + const left = (window.screen.width - width) / 2; + const top = (window.screen.height - height) / 2; + + return window.open( + url, + 'centeredWindow', + `width=${width},height=${height},left=${left},top=${top}` + ); +} +function showPage(htid) { + openCenteredWindow(`https://gubatopic.eastmoney.com/topic_v3.html?htid=${htid}`, 1000, 600) +}