feat(frontend):添加龙虎榜功能

- 在前端 App.vue 中添加龙虎榜相关路由和图标
- 实现龙虎榜数据获取和展示功能
- 添加龙虎榜数据模型和 API 接口
- 更新后端 MarketNewsApi 类,增加 LongTiger 方法获取龙虎榜数据
This commit is contained in:
ArvinLovegood 2025-06-12 15:38:42 +08:00
parent 9eded54d8d
commit a8ecbf9329
13 changed files with 458 additions and 126 deletions

15
app_common.go Normal file
View File

@ -0,0 +1,15 @@
package main
import (
"go-stock/backend/data"
"go-stock/backend/models"
)
// @Author spark
// @Date 2025/6/8 20:45
// @Desc
//-----------------------------------------------------------------------------------
func (a *App) LongTigerRank(date string) *[]models.LongTigerRankData {
return data.NewMarketNewsApi().LongTiger(date)
}

View File

@ -10,6 +10,7 @@ import (
"github.com/go-resty/resty/v2" "github.com/go-resty/resty/v2"
"github.com/robertkrimen/otto" "github.com/robertkrimen/otto"
"github.com/samber/lo" "github.com/samber/lo"
"github.com/tidwall/gjson"
"go-stock/backend/db" "go-stock/backend/db"
"go-stock/backend/logger" "go-stock/backend/logger"
"go-stock/backend/models" "go-stock/backend/models"
@ -318,3 +319,52 @@ func (m MarketNewsApi) TopStocksRankingList(date string) {
}) })
} }
func (m MarketNewsApi) LongTiger(date string) *[]models.LongTigerRankData {
ranks := &[]models.LongTigerRankData{}
url := "https://datacenter-web.eastmoney.com/api/data/v1/get"
logger.SugaredLogger.Infof("url:%s", url)
params := make(map[string]string)
params["callback"] = "callback"
params["sortColumns"] = "TURNOVERRATE,TRADE_DATE,SECURITY_CODE"
params["sortTypes"] = "-1,-1,1"
params["pageSize"] = "500"
params["pageNumber"] = "1"
params["reportName"] = "RPT_DAILYBILLBOARD_DETAILSNEW"
params["columns"] = "SECURITY_CODE,SECUCODE,SECURITY_NAME_ABBR,TRADE_DATE,EXPLAIN,CLOSE_PRICE,CHANGE_RATE,BILLBOARD_NET_AMT,BILLBOARD_BUY_AMT,BILLBOARD_SELL_AMT,BILLBOARD_DEAL_AMT,ACCUM_AMOUNT,DEAL_NET_RATIO,DEAL_AMOUNT_RATIO,TURNOVERRATE,FREE_MARKET_CAP,EXPLANATION,D1_CLOSE_ADJCHRATE,D2_CLOSE_ADJCHRATE,D5_CLOSE_ADJCHRATE,D10_CLOSE_ADJCHRATE,SECURITY_TYPE_CODE"
params["source"] = "WEB"
params["client"] = "WEB"
params["filter"] = fmt.Sprintf("(TRADE_DATE<='%s')(TRADE_DATE>='%s')", date, date)
resp, err := resty.New().SetTimeout(time.Duration(15)*time.Second).R().
SetHeader("Host", "datacenter-web.eastmoney.com").
SetHeader("Referer", "https://data.eastmoney.com/stock/tradedetail.html").
SetHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:140.0) Gecko/20100101 Firefox/140.0").
SetQueryParams(params).
Get(url)
if err != nil {
return ranks
}
js := string(resp.Body())
logger.SugaredLogger.Infof("resp:%s", js)
js = strutil.ReplaceWithMap(js,
map[string]string{
"callback(": "var data=",
");": ";",
})
//logger.SugaredLogger.Info(js)
vm := otto.New()
_, err = vm.Run(js)
_, err = vm.Run("var data = JSON.stringify(data);")
value, err := vm.Get("data")
logger.SugaredLogger.Infof("resp-json:%s", value.String())
data := gjson.Get(value.String(), "result.data")
logger.SugaredLogger.Infof("resp:%v", data)
err = json.Unmarshal([]byte(data.String()), ranks)
if err != nil {
logger.SugaredLogger.Error(err)
return ranks
}
db.Dao.Create(*ranks)
return ranks
}

View File

@ -58,3 +58,9 @@ func TestGetStockMoneyTrendByDay(t *testing.T) {
func TestTopStocksRankingList(t *testing.T) { func TestTopStocksRankingList(t *testing.T) {
NewMarketNewsApi().TopStocksRankingList("2025-05-19") NewMarketNewsApi().TopStocksRankingList("2025-05-19")
} }
func TestLongTiger(t *testing.T) {
db.Init("../../data/stock.db")
NewMarketNewsApi().LongTiger("2025-06-08")
}

View File

@ -161,7 +161,7 @@ func (o OpenAi) NewSummaryStockNewsStream(userQuestion string, sysPromptId *int)
}() }()
wg.Wait() wg.Wait()
news := NewMarketNewsApi().GetNewsList("财联社电报", 100) news := NewMarketNewsApi().GetNewsList("新浪财经", 100)
messageText := strings.Builder{} messageText := strings.Builder{}
for _, telegraph := range *news { for _, telegraph := range *news {
messageText.WriteString("## " + telegraph.Time + ":" + "\n") messageText.WriteString("## " + telegraph.Time + ":" + "\n")

View File

@ -277,3 +277,28 @@ type SinaStockInfo struct {
MarketValue string `json:"market_value"` MarketValue string `json:"market_value"`
PeRatio string `json:"pe_ratio"` PeRatio string `json:"pe_ratio"`
} }
type LongTigerRankData struct {
ACCUMAMOUNT float64 `json:"ACCUM_AMOUNT"`
BILLBOARDBUYAMT float64 `json:"BILLBOARD_BUY_AMT"`
BILLBOARDDEALAMT float64 `json:"BILLBOARD_DEAL_AMT"`
BILLBOARDNETAMT float64 `json:"BILLBOARD_NET_AMT"`
BILLBOARDSELLAMT float64 `json:"BILLBOARD_SELL_AMT"`
CHANGERATE float64 `json:"CHANGE_RATE"`
CLOSEPRICE float64 `json:"CLOSE_PRICE"`
DEALAMOUNTRATIO float64 `json:"DEAL_AMOUNT_RATIO"`
DEALNETRATIO float64 `json:"DEAL_NET_RATIO"`
EXPLAIN string `json:"EXPLAIN"`
EXPLANATION string `json:"EXPLANATION"`
FREEMARKETCAP float64 `json:"FREE_MARKET_CAP"`
SECUCODE string `json:"SECUCODE"`
SECURITYCODE string `json:"SECURITY_CODE"`
SECURITYNAMEABBR string `json:"SECURITY_NAME_ABBR"`
SECURITYTYPECODE string `json:"SECURITY_TYPE_CODE"`
TRADEDATE string `json:"TRADE_DATE"`
TURNOVERRATE float64 `json:"TURNOVERRATE"`
}
func (l LongTigerRankData) TableName() string {
return "long_tiger_rank"
}

View File

@ -10,19 +10,19 @@ import {
} from '../wailsjs/runtime' } from '../wailsjs/runtime'
import {h, onBeforeMount, onBeforeUnmount, onMounted, ref} from "vue"; import {h, onBeforeMount, onBeforeUnmount, onMounted, ref} from "vue";
import {RouterLink, useRouter} from 'vue-router' import {RouterLink, useRouter} from 'vue-router'
import {darkTheme, NIcon, NText,} from 'naive-ui' import {darkTheme, NIcon, NText,dateZhCN,zhCN} from 'naive-ui'
import { import {
AlarmOutline, AlarmOutline,
AnalyticsOutline, AnalyticsOutline,
BarChartSharp, BarChartSharp, EaselSharp,
ExpandOutline, ExpandOutline, Flag,
Flame, Flame, FlameSharp,
LogoGithub, LogoGithub,
NewspaperOutline, NewspaperOutline,
NewspaperSharp, NewspaperSharp,
PowerOutline, PowerOutline, Pulse,
ReorderTwoOutline, ReorderTwoOutline,
SettingsOutline, SettingsOutline, Skull, SkullOutline, SkullSharp,
SparklesOutline, SparklesOutline,
StarOutline, StarOutline,
Wallet, Wallet,
@ -196,7 +196,7 @@ const menuOptions = ref([
{default: () => '行业排名',} {default: () => '行业排名',}
), ),
key: 'market4', key: 'market4',
icon: renderIcon(Flame), icon: renderIcon(Flag),
}, },
{ {
label: () => label: () =>
@ -217,8 +217,29 @@ const menuOptions = ref([
{default: () => '个股资金流向',} {default: () => '个股资金流向',}
), ),
key: 'market5', key: 'market5',
icon: renderIcon(Wallet), icon: renderIcon(Pulse),
} },
{
label: () =>
h(
RouterLink,
{
href: '#',
to: {
name: 'market',
query: {
name: "龙虎榜",
}
},
onClick: () => {
EventsEmit("changeMarketTab", {ID: 0, name: '龙虎榜'})
},
},
{default: () => '龙虎榜',}
),
key: 'market6',
icon: renderIcon(Skull),
},
] ]
}, },
{ {
@ -457,7 +478,7 @@ onMounted(() => {
</script> </script>
<template> <template>
<n-config-provider ref="containerRef" :theme="enableDarkTheme"> <n-config-provider ref="containerRef" :theme="enableDarkTheme" :locale="zhCN" :date-locale="dateZhCN">
<n-message-provider> <n-message-provider>
<n-notification-provider> <n-notification-provider>
<n-modal-provider> <n-modal-provider>

View File

@ -1,23 +1,30 @@
<script setup> <script setup>
import {computed, h, onBeforeMount, onBeforeUnmount, ref} from 'vue' import {computed, h, onBeforeMount, onBeforeUnmount, ref} from 'vue'
import _ from "lodash";
import { import {
GetAIResponseResult, GetAIResponseResult,
GetConfig, GetIndustryRank, GetPromptTemplates, GetConfig,
GetIndustryRank,
GetPromptTemplates,
GetTelegraphList, GetTelegraphList,
GlobalStockIndexes, ReFleshTelegraphList, GlobalStockIndexes, LongTigerRank,
SaveAIResponseResult, SaveAsMarkdown, ShareAnalysis, ReFleshTelegraphList,
SaveAIResponseResult,
SaveAsMarkdown,
ShareAnalysis,
SummaryStockNews SummaryStockNews
} from "../../wailsjs/go/main/App"; } from "../../wailsjs/go/main/App";
import {EventsOff, EventsOn} from "../../wailsjs/runtime"; import {EventsOff, EventsOn} from "../../wailsjs/runtime";
import NewsList from "./newsList.vue"; import NewsList from "./newsList.vue";
import KLineChart from "./KLineChart.vue"; import KLineChart from "./KLineChart.vue";
import {Add, CaretDown, CaretUp, ChatboxOutline, PulseOutline,} from "@vicons/ionicons5"; import {ArrowDownOutline, CaretDown, CaretUp, PulseOutline,} from "@vicons/ionicons5";
import {NAvatar, NButton, NFlex, NText, useMessage, useNotification} from "naive-ui"; import {NAvatar, NButton, NFlex, NText, useMessage, useNotification} from "naive-ui";
import {ExportPDF} from "@vavt/v3-extension"; import {MdPreview} from "md-editor-v3";
import {MdEditor, MdPreview} from "md-editor-v3"; import {useRoute} from 'vue-router'
import { useRoute } from 'vue-router'
import RankTable from "./rankTable.vue"; import RankTable from "./rankTable.vue";
import IndustryMoneyRank from "./industryMoneyRank.vue"; import IndustryMoneyRank from "./industryMoneyRank.vue";
import MoneyTrend from "./moneyTrend.vue";
const route = useRoute() const route = useRoute()
const icon = ref('https://raw.githubusercontent.com/ArvinLovegood/go-stock/master/build/appicon.png'); const icon = ref('https://raw.githubusercontent.com/ArvinLovegood/go-stock/master/build/appicon.png');
@ -27,36 +34,49 @@ const panelHeight = ref(window.innerHeight - 240)
const telegraphList = ref([]) const telegraphList = ref([])
const sinaNewsList = ref([]) const sinaNewsList = ref([])
const lhbList= ref([])
const common = ref([]) const common = ref([])
const america = ref([]) const america = ref([])
const europe = ref([]) const europe = ref([])
const asia = ref([]) const asia = ref([])
const other = ref([]) const other = ref([])
const globalStockIndexes = ref(null) const globalStockIndexes = ref(null)
const summaryModal= ref(false) const summaryModal = ref(false)
const summaryBTN= ref(true) const summaryBTN = ref(true)
const darkTheme= ref(false) const darkTheme = ref(false)
const theme=computed(() => { const theme = computed(() => {
return darkTheme ? 'dark' : 'light' return darkTheme ? 'dark' : 'light'
}) })
const aiSummary=ref(``) const aiSummary = ref(``)
const aiSummaryTime=ref("") const aiSummaryTime = ref("")
const modelName=ref("") const modelName = ref("")
const chatId=ref("") const chatId = ref("")
const question=ref(``) const question = ref(``)
const sysPromptId=ref(0) const sysPromptId = ref(0)
const loading=ref(true) const loading = ref(true)
const sysPromptOptions=ref([]) const sysPromptOptions = ref([])
const userPromptOptions=ref([]) const userPromptOptions = ref([])
const promptTemplates=ref([]) const promptTemplates = ref([])
const industryRanks=ref([]) const industryRanks = ref([])
const industryMoneyRankSina=ref([]) const industryMoneyRankSina = ref([])
const sort = ref("0") const sort = ref("0")
const sortIcon= ref(h(CaretDown)) const sortIcon = ref(h(CaretDown))
const nowTab=ref("市场快讯") const nowTab = ref("市场快讯")
const indexInterval= ref(null) const indexInterval = ref(null)
const indexIndustryRank= ref(null) const indexIndustryRank = ref(null)
const drawerShow= ref(false)
const today = new Date();
const year = today.getFullYear();
const month = String(today.getMonth() + 1).padStart(2, '0'); // 0+1
const day = String(today.getDate()).padStart(2, '0');
// YYYY-MM-DD
const formattedDate = `${year}-${month}-${day}`;
const SearchForm= ref({
dateValue: formattedDate,
})
function getIndex() { function getIndex() {
GlobalStockIndexes().then((res) => { GlobalStockIndexes().then((res) => {
@ -69,16 +89,38 @@ function getIndex() {
}) })
} }
onBeforeMount(() => { function longTiger(date) {
nowTab.value=route.query.name if(date) {
GetConfig().then(result => { SearchForm.value.dateValue = date
summaryBTN.value= result.openAiEnable }
darkTheme.value = result.darkTheme let loading1=message.loading("正在获取龙虎榜数据...",{
duration: 0,
}) })
GetPromptTemplates("","").then(res=>{ LongTigerRank(date).then(res => {
promptTemplates.value=res lhbList.value = res
sysPromptOptions.value=promptTemplates.value.filter(item => item.type === '模型系统Prompt') loading1.destroy()
userPromptOptions.value=promptTemplates.value.filter(item => item.type === '模型用户Prompt') if (res.length === 0) {
message.info("暂无数据,请切换日期")
}
})
}
function sortLongTigerRank(e){
console.log(e.target.dataset)
// let field= e.target.dataset.field;
// lhbList.value= _.sortBy(lhbList.value, function(o) { return o[field]; });
}
onBeforeMount(() => {
nowTab.value = route.query.name
GetConfig().then(result => {
summaryBTN.value = result.openAiEnable
darkTheme.value = result.darkTheme
})
GetPromptTemplates("", "").then(res => {
promptTemplates.value = res
sysPromptOptions.value = promptTemplates.value.filter(item => item.type === '模型系统Prompt')
userPromptOptions.value = promptTemplates.value.filter(item => item.type === '模型用户Prompt')
}) })
GetTelegraphList("财联社电报").then((res) => { GetTelegraphList("财联社电报").then((res) => {
@ -89,13 +131,14 @@ onBeforeMount(() => {
}) })
getIndex(); getIndex();
industryRank(); industryRank();
indexInterval.value=setInterval(() => { indexInterval.value = setInterval(() => {
getIndex() getIndex()
}, 3000) }, 3000)
indexIndustryRank.value=setInterval(() => { indexIndustryRank.value = setInterval(() => {
industryRank() industryRank()
},1000*10) }, 1000 * 10)
longTiger(formattedDate);
}) })
onBeforeUnmount(() => { onBeforeUnmount(() => {
@ -107,7 +150,7 @@ onBeforeUnmount(() => {
clearInterval(indexIndustryRank.value) clearInterval(indexIndustryRank.value)
}) })
EventsOn("changeMarketTab" ,async (msg) => { EventsOn("changeMarketTab", async (msg) => {
//message.info(msg.name) //message.info(msg.name)
updateTab(msg.name) updateTab(msg.name)
}) })
@ -130,7 +173,7 @@ window.onresize = () => {
panelHeight.value = window.innerHeight - 240 panelHeight.value = window.innerHeight - 240
} }
function getAreaName(code){ function getAreaName(code) {
switch (code) { switch (code) {
case "america": case "america":
return "美洲" return "美洲"
@ -144,40 +187,42 @@ function getAreaName(code){
return "其他" return "其他"
} }
} }
function changeIndustryRankSort() { function changeIndustryRankSort() {
if(sort.value==="0"){ if (sort.value === "0") {
sort.value="1" sort.value = "1"
}else{ } else {
sort.value="0" sort.value = "0"
} }
industryRank() industryRank()
} }
function industryRank(){ function industryRank() {
GetIndustryRank(sort.value,150).then(result => { GetIndustryRank(sort.value, 150).then(result => {
if(result.length>0){ if (result.length > 0) {
console.log(result) console.log(result)
industryRanks.value = result industryRanks.value = result
}else{ } else {
message.info("暂无数据") message.info("暂无数据")
} }
}) })
} }
function reAiSummary(){ function reAiSummary() {
aiSummary.value="" aiSummary.value = ""
summaryModal.value = true summaryModal.value = true
loading.value = true loading.value = true
SummaryStockNews(question.value,sysPromptId.value) SummaryStockNews(question.value, sysPromptId.value)
} }
function getAiSummary(){
function getAiSummary() {
summaryModal.value = true summaryModal.value = true
loading.value = true loading.value = true
GetAIResponseResult("市场资讯").then(result => { GetAIResponseResult("市场资讯").then(result => {
if(result.content){ if (result.content) {
aiSummary.value=result.content aiSummary.value = result.content
question.value=result.question question.value = result.question
loading.value = false loading.value = false
const date = new Date(result.CreatedAt); const date = new Date(result.CreatedAt);
@ -187,13 +232,13 @@ function getAiSummary(){
const hours = String(date.getHours()).padStart(2, '0'); const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0'); const minutes = String(date.getMinutes()).padStart(2, '0');
const seconds = String(date.getSeconds()).padStart(2, '0'); const seconds = String(date.getSeconds()).padStart(2, '0');
aiSummaryTime.value=`${year}-${month}-${day} ${hours}:${minutes}:${seconds}` aiSummaryTime.value = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
modelName.value=result.modelName modelName.value = result.modelName
}else{ } else {
aiSummaryTime.value="" aiSummaryTime.value = ""
aiSummary.value="" aiSummary.value = ""
modelName.value="" modelName.value = ""
SummaryStockNews(question.value,sysPromptId.value) SummaryStockNews(question.value, sysPromptId.value)
} }
}) })
} }
@ -203,31 +248,31 @@ function updateTab(name) {
nowTab.value = name nowTab.value = name
} }
EventsOn("summaryStockNews",async (msg) => { EventsOn("summaryStockNews", async (msg) => {
loading.value = false loading.value = false
////console.log(msg) ////console.log(msg)
if (msg === "DONE") { if (msg === "DONE") {
SaveAIResponseResult("市场资讯","市场资讯", aiSummary.value, chatId.value,question.value) SaveAIResponseResult("市场资讯", "市场资讯", aiSummary.value, chatId.value, question.value)
message.info("AI分析完成") message.info("AI分析完成")
message.destroyAll() message.destroyAll()
} else { } else {
if(msg.chatId){ if (msg.chatId) {
chatId.value = msg.chatId chatId.value = msg.chatId
} }
if(msg.question){ if (msg.question) {
question.value = msg.question question.value = msg.question
} }
if(msg.content){ if (msg.content) {
aiSummary.value =aiSummary.value + msg.content aiSummary.value = aiSummary.value + msg.content
} }
if(msg.extraContent){ if (msg.extraContent) {
aiSummary.value = aiSummary.value + msg.extraContent aiSummary.value = aiSummary.value + msg.extraContent
} }
if(msg.model){ if (msg.model) {
modelName.value=msg.model modelName.value = msg.model
} }
if(msg.time){ if (msg.time) {
aiSummaryTime.value = msg.time aiSummaryTime.value = msg.time
} }
} }
@ -241,13 +286,15 @@ async function copyToClipboard() {
message.error('复制失败: ' + err); message.error('复制失败: ' + err);
} }
} }
function saveAsMarkdown(){
SaveAsMarkdown('市场资讯','市场资讯').then(result => { function saveAsMarkdown() {
SaveAsMarkdown('市场资讯', '市场资讯').then(result => {
message.success(result) message.success(result)
}) })
} }
function share(){
ShareAnalysis('市场资讯','市场资讯').then(msg => { function share() {
ShareAnalysis('市场资讯', '市场资讯').then(msg => {
//message.info(msg) //message.info(msg)
notify.info({ notify.info({
avatar: () => avatar: () =>
@ -257,26 +304,26 @@ function share(){
src: icon.value src: icon.value
}), }),
title: '分享到社区', title: '分享到社区',
duration:1000*30, duration: 1000 * 30,
content: () => { content: () => {
return h('div', { return h('div', {
style: { style: {
'text-align': 'left', 'text-align': 'left',
'font-size': '14px', 'font-size': '14px',
} }
}, { default: () => msg }) }, {default: () => msg})
}, },
}) })
}) })
} }
function ReFlesh(source){ function ReFlesh(source) {
console.log("ReFlesh:",source) console.log("ReFlesh:", source)
ReFleshTelegraphList(source).then(res => { ReFleshTelegraphList(source).then(res => {
if(source==="财联社电报"){ if (source === "财联社电报") {
telegraphList.value = res telegraphList.value = res
} }
if(source==="新浪财经"){ if (source === "新浪财经") {
sinaNewsList.value = res sinaNewsList.value = res
} }
}) })
@ -285,8 +332,8 @@ function ReFlesh(source){
<template> <template>
<n-card> <n-card>
<n-tabs type="line" animated @update-value="updateTab" :value="nowTab" > <n-tabs type="line" animated @update-value="updateTab" :value="nowTab">
<n-tab-pane name="市场快讯" tab="市场快讯" > <n-tab-pane name="市场快讯" tab="市场快讯">
<n-grid :cols="2" :y-gap="0"> <n-grid :cols="2" :y-gap="0">
<n-gi> <n-gi>
<news-list :newsList="telegraphList" :header-title="'财联社电报'" @update:message="ReFlesh"></news-list> <news-list :newsList="telegraphList" :header-title="'财联社电报'" @update:message="ReFlesh"></news-list>
@ -309,11 +356,16 @@ function ReFlesh(source){
<n-grid :cols="3" :y-gap="0"> <n-grid :cols="3" :y-gap="0">
<n-gi> <n-gi>
<n-text :type="item.zdf>0?'error':'success'"><n-image :src="item.img" width="20"/> &nbsp;{{ item.name }}</n-text> <n-text :type="item.zdf>0?'error':'success'">
<n-image :src="item.img" width="20"/> &nbsp;{{ item.name }}
</n-text>
</n-gi> </n-gi>
<n-gi> <n-gi>
<n-text :type="item.zdf>0?'error':'success'">{{ item.zxj }}</n-text>&nbsp; <n-text :type="item.zdf>0?'error':'success'">{{ item.zxj }}</n-text>&nbsp;
<n-text :type="item.zdf>0?'error':'success'"><n-number-animation :precision="2" :from="0" :to="item.zdf" />%</n-text> <n-text :type="item.zdf>0?'error':'success'">
<n-number-animation :precision="2" :from="0" :to="item.zdf"/>
%
</n-text>
</n-gi> </n-gi>
<n-gi> <n-gi>
@ -401,7 +453,10 @@ function ReFlesh(source){
<n-thead> <n-thead>
<n-tr> <n-tr>
<n-th>行业名称</n-th> <n-th>行业名称</n-th>
<n-th @click="changeIndustryRankSort">行业涨幅<n-icon v-if="sort==='0'" :component="CaretDown"/><n-icon v-if="sort==='1'" :component="CaretUp"/></n-th> <n-th @click="changeIndustryRankSort">行业涨幅
<n-icon v-if="sort==='0'" :component="CaretDown"/>
<n-icon v-if="sort==='1'" :component="CaretUp"/>
</n-th>
<n-th>行业5日涨幅</n-th> <n-th>行业5日涨幅</n-th>
<n-th>行业20日涨幅</n-th> <n-th>行业20日涨幅</n-th>
<n-th>领涨股</n-th> <n-th>领涨股</n-th>
@ -411,13 +466,29 @@ function ReFlesh(source){
</n-thead> </n-thead>
<n-tbody> <n-tbody>
<n-tr v-for="item in industryRanks" :key="item.bd_code"> <n-tr v-for="item in industryRanks" :key="item.bd_code">
<n-td><n-tag :bordered=false type="info">{{ item.bd_name }}</n-tag></n-td> <n-td>
<n-td><n-text :type="item.bd_zdf>0?'error':'success'">{{item.bd_zdf}}%</n-text></n-td> <n-tag :bordered=false type="info">{{ item.bd_name }}</n-tag>
<n-td><n-text :type="item.bd_zdf5>0?'error':'success'">{{item.bd_zdf5}}%</n-text></n-td> </n-td>
<n-td><n-text :type="item.bd_zdf20>0?'error':'success'">{{item.bd_zdf20}}%</n-text></n-td> <n-td>
<n-td><n-text :type="item.nzg_zdf>0?'error':'success'"> {{item.nzg_name}} <n-text type="info">{{item.nzg_code}}</n-text></n-text></n-td> <n-text :type="item.bd_zdf>0?'error':'success'">{{ item.bd_zdf }}%</n-text>
<n-td><n-text :type="item.nzg_zdf>0?'error':'success'"> {{item.nzg_zdf}}%</n-text></n-td> </n-td>
<n-td> <n-text :type="item.nzg_zdf>0?'error':'success'">{{item.nzg_zxj}}</n-text></n-td> <n-td>
<n-text :type="item.bd_zdf5>0?'error':'success'">{{ item.bd_zdf5 }}%</n-text>
</n-td>
<n-td>
<n-text :type="item.bd_zdf20>0?'error':'success'">{{ item.bd_zdf20 }}%</n-text>
</n-td>
<n-td>
<n-text :type="item.nzg_zdf>0?'error':'success'"> {{ item.nzg_name }}
<n-text type="info">{{ item.nzg_code }}</n-text>
</n-text>
</n-td>
<n-td>
<n-text :type="item.nzg_zdf>0?'error':'success'"> {{ item.nzg_zdf }}%</n-text>
</n-td>
<n-td>
<n-text :type="item.nzg_zdf>0?'error':'success'">{{ item.nzg_zxj }}</n-text>
</n-td>
</n-tr> </n-tr>
</n-tbody> </n-tbody>
</n-table> </n-table>
@ -425,7 +496,10 @@ function ReFlesh(source){
<n-thead> <n-thead>
<n-tr> <n-tr>
<n-th>行业名称</n-th> <n-th>行业名称</n-th>
<n-th @click="changeIndustryRankSort">行业涨幅<n-icon v-if="sort==='0'" :component="CaretDown"/><n-icon v-if="sort==='1'" :component="CaretUp"/></n-th> <n-th @click="changeIndustryRankSort">行业涨幅
<n-icon v-if="sort==='0'" :component="CaretDown"/>
<n-icon v-if="sort==='1'" :component="CaretUp"/>
</n-th>
<n-th>行业5日涨幅</n-th> <n-th>行业5日涨幅</n-th>
<n-th>行业20日涨幅</n-th> <n-th>行业20日涨幅</n-th>
<n-th>领涨股</n-th> <n-th>领涨股</n-th>
@ -435,13 +509,29 @@ function ReFlesh(source){
</n-thead> </n-thead>
<n-tbody> <n-tbody>
<n-tr v-for="item in industryRanks" :key="item.bd_code"> <n-tr v-for="item in industryRanks" :key="item.bd_code">
<n-td><n-tag :bordered=false type="info">{{ item.bd_name }}</n-tag></n-td> <n-td>
<n-td><n-text :type="item.bd_zdf>0?'error':'success'">{{item.bd_zdf}}%</n-text></n-td> <n-tag :bordered=false type="info">{{ item.bd_name }}</n-tag>
<n-td><n-text :type="item.bd_zdf5>0?'error':'success'">{{item.bd_zdf5}}%</n-text></n-td> </n-td>
<n-td><n-text :type="item.bd_zdf20>0?'error':'success'">{{item.bd_zdf20}}%</n-text></n-td> <n-td>
<n-td><n-text :type="item.nzg_zdf>0?'error':'success'"> {{item.nzg_name}} <n-text type="info">{{item.nzg_code}}</n-text></n-text></n-td> <n-text :type="item.bd_zdf>0?'error':'success'">{{ item.bd_zdf }}%</n-text>
<n-td><n-text :type="item.nzg_zdf>0?'error':'success'"> {{item.nzg_zdf}}%</n-text></n-td> </n-td>
<n-td> <n-text :type="item.nzg_zdf>0?'error':'success'">{{item.nzg_zxj}}</n-text></n-td> <n-td>
<n-text :type="item.bd_zdf5>0?'error':'success'">{{ item.bd_zdf5 }}%</n-text>
</n-td>
<n-td>
<n-text :type="item.bd_zdf20>0?'error':'success'">{{ item.bd_zdf20 }}%</n-text>
</n-td>
<n-td>
<n-text :type="item.nzg_zdf>0?'error':'success'"> {{ item.nzg_name }}
<n-text type="info">{{ item.nzg_code }}</n-text>
</n-text>
</n-td>
<n-td>
<n-text :type="item.nzg_zdf>0?'error':'success'"> {{ item.nzg_zdf }}%</n-text>
</n-td>
<n-td>
<n-text :type="item.nzg_zdf>0?'error':'success'">{{ item.nzg_zxj }}</n-text>
</n-td>
</n-tr> </n-tr>
</n-tbody> </n-tbody>
</n-table> </n-table>
@ -488,28 +578,130 @@ function ReFlesh(source){
</n-tab-pane> </n-tab-pane>
</n-tabs> </n-tabs>
</n-tab-pane> </n-tab-pane>
<n-tab-pane name="龙虎榜" tab="龙虎榜">
<n-form :model="SearchForm" >
<n-form-item-gi label="日期" path="dateValue" label-placement="left">
<n-date-picker v-model:formatted-value="SearchForm.dateValue"
value-format="yyyy-MM-dd" type="date" :on-update:value="(v,v2)=>longTiger(v2)"/>
</n-form-item-gi>
</n-form>
<n-table :single-line="false" striped>
<n-thead>
<n-tr>
<n-th>代码</n-th>
<!-- <n-th width="90px">日期</n-th>-->
<n-th width="60px">名称</n-th>
<n-th>收盘价</n-th>
<n-th width="60px">涨跌幅</n-th>
<n-th>龙虎榜净买额()</n-th>
<n-th>龙虎榜买入额()</n-th>
<n-th>龙虎榜卖出额()</n-th>
<n-th>龙虎榜成交额()</n-th>
<!-- <n-th>市场总成交额()</n-th>-->
<!-- <n-th>净买额占总成交比</n-th>-->
<!-- <n-th>成交额占总成交比</n-th>-->
<n-th width="60px" data-field="TURNOVERRATE" @click="sortLongTigerRank">换手率<n-icon :component="ArrowDownOutline" /></n-th>
<n-th>流通市值(亿)</n-th>
<n-th>上榜原因</n-th>
<!-- <n-th>解读</n-th>-->
</n-tr>
</n-thead>
<n-tbody>
<n-tr v-for="(item, index) in lhbList" :key="index">
<n-td>
<n-tag :bordered=false type="info">{{ item.SECUCODE.split('.')[1].toLowerCase()+item.SECUCODE.split('.')[0] }}</n-tag>
</n-td>
<!-- <n-td>
{{item.TRADE_DATE.substring(0,10)}}
</n-td>-->
<n-td>
<!-- <n-text :type="item.CHANGE_RATE>0?'error':'success'">{{ item.SECURITY_NAME_ABBR }}</n-text>-->
<n-popover trigger="hover" placement="right">
<template #trigger>
<n-button tag="a" text :type="item.CHANGE_RATE>0?'error':'success'" :bordered=false >{{ item.SECURITY_NAME_ABBR }}</n-button>
</template>
<k-line-chart style="width: 800px" :code="item.SECUCODE.split('.')[1].toLowerCase()+item.SECUCODE.split('.')[0]" :chart-height="500" :name="item.SECURITY_NAME_ABBR" :k-days="20" :dark-theme="true"></k-line-chart>
</n-popover>
</n-td>
<n-td>
<n-text :type="item.CHANGE_RATE>0?'error':'success'">{{ item.CLOSE_PRICE }}</n-text>
</n-td>
<n-td>
<n-text :type="item.CHANGE_RATE>0?'error':'success'">{{ (item.CHANGE_RATE).toFixed(2) }}%</n-text>
</n-td>
<n-td>
<!-- <n-text :type="item.BILLBOARD_NET_AMT>0?'error':'success'">{{ (item.BILLBOARD_NET_AMT/10000).toFixed(2) }}</n-text>-->
<n-popover trigger="hover" placement="right">
<template #trigger>
<n-button tag="a" text :type="item.BILLBOARD_NET_AMT>0?'error':'success'" :bordered=false >{{ (item.BILLBOARD_NET_AMT/10000).toFixed(2) }}</n-button>
</template>
<money-trend :code="item.SECUCODE.split('.')[1].toLowerCase()+item.SECUCODE.split('.')[0]" :name="item.SECURITY_NAME_ABBR" :days="360" :dark-theme="true" :chart-height="500" style="width: 800px"></money-trend>
</n-popover>
</n-td>
<n-td>
<n-text :type="'error'">{{ (item.BILLBOARD_BUY_AMT/10000).toFixed(2) }}</n-text>
</n-td>
<n-td>
<n-text :type="'success'">{{ (item.BILLBOARD_SELL_AMT/10000).toFixed(2) }}</n-text>
</n-td>
<n-td>
<n-text :type="'info'">{{ (item.BILLBOARD_DEAL_AMT/10000).toFixed(2) }}</n-text>
</n-td>
<!-- <n-td>-->
<!-- <n-text :type="'info'">{{ (item.ACCUM_AMOUNT/10000).toFixed(2) }}</n-text>-->
<!-- </n-td>-->
<!-- <n-td>-->
<!-- <n-text :type="item.DEAL_NET_RATIO>0?'error':'success'">{{ (item.DEAL_NET_RATIO).toFixed(2) }}%</n-text>-->
<!-- </n-td>-->
<!-- <n-td>-->
<!-- <n-text :type="'info'">{{ (item.DEAL_AMOUNT_RATIO).toFixed(2) }}%</n-text>-->
<!-- </n-td>-->
<n-td>
<n-text :type="'info'">{{ (item.TURNOVERRATE).toFixed(2) }}%</n-text>
</n-td>
<n-td>
<n-text :type="'info'">{{ (item.FREE_MARKET_CAP/100000000).toFixed(2) }}</n-text>
</n-td>
<n-td>
<n-text :type="'info'">{{ item.EXPLANATION }}</n-text>
</n-td>
<!-- <n-td>
<n-text :type="item.CHANGE_RATE>0?'error':'success'">{{ item.EXPLAIN }}</n-text>
</n-td>-->
</n-tr>
</n-tbody>
</n-table>
</n-tab-pane>
</n-tabs> </n-tabs>
</n-card> </n-card>
<n-modal transform-origin="center" v-model:show="summaryModal" preset="card" style="width: 800px;" :title="'AI市场资讯总结'" > <n-modal transform-origin="center" v-model:show="summaryModal" preset="card" style="width: 800px;"
:title="'AI市场资讯总结'">
<n-spin size="small" :show="loading"> <n-spin size="small" :show="loading">
<MdPreview style="height: 440px;text-align: left" :modelValue="aiSummary" :theme="theme"/> <MdPreview style="height: 440px;text-align: left" :modelValue="aiSummary" :theme="theme"/>
</n-spin> </n-spin>
<template #footer> <template #footer>
<n-flex justify="space-between" ref="tipsRef"> <n-flex justify="space-between" ref="tipsRef">
<n-text type="info" v-if="aiSummaryTime" > <n-text type="info" v-if="aiSummaryTime">
<n-tag v-if="modelName" type="warning" round :title="chatId" :bordered="false">{{modelName}}</n-tag> <n-tag v-if="modelName" type="warning" round :title="chatId" :bordered="false">{{ modelName }}</n-tag>
{{aiSummaryTime}} {{ aiSummaryTime }}
</n-text> </n-text>
<n-text type="error" >*AI分析结果仅供参考请以实际行情为准投资需谨慎风险自担</n-text> <n-text type="error">*AI分析结果仅供参考请以实际行情为准投资需谨慎风险自担</n-text>
</n-flex> </n-flex>
</template> </template>
<template #action> <template #action>
<n-flex justify="space-between" style="margin-bottom: 10px"> <n-flex justify="space-between" style="margin-bottom: 10px">
<n-select style="width: 49%" v-model:value="sysPromptId" label-field="name" value-field="ID" :options="sysPromptOptions" placeholder="请选择系统提示词" /> <n-select style="width: 49%" v-model:value="sysPromptId" label-field="name" value-field="ID"
<n-select style="width: 49%" v-model:value="question" label-field="name" value-field="content" :options="userPromptOptions" placeholder="请选择用户提示词" /> :options="sysPromptOptions" placeholder="请选择系统提示词"/>
<n-select style="width: 49%" v-model:value="question" label-field="name" value-field="content"
:options="userPromptOptions" placeholder="请选择用户提示词"/>
</n-flex> </n-flex>
<n-flex justify="right"> <n-flex justify="right">
<n-input v-model:value="question" style="text-align: left" clearable <n-input v-model:value="question" style="text-align: left" clearable
type="textarea" type="textarea"
:show-count="true" :show-count="true"
placeholder="请输入您的问题:例如 总结和分析股票市场新闻中的投资机会" placeholder="请输入您的问题:例如 总结和分析股票市场新闻中的投资机会"
@ -518,7 +710,7 @@ function ReFlesh(source){
maxRows: 5 maxRows: 5
}" }"
/> />
<n-button size="tiny" type="warning" @click="reAiSummary">再次总结</n-button> <n-button size="tiny" type="warning" @click="reAiSummary">再次总结</n-button>
<n-button size="tiny" type="success" @click="copyToClipboard">复制到剪切板</n-button> <n-button size="tiny" type="success" @click="copyToClipboard">复制到剪切板</n-button>
<n-button size="tiny" type="primary" @click="saveAsMarkdown">保存为Markdown文件</n-button> <n-button size="tiny" type="primary" @click="saveAsMarkdown">保存为Markdown文件</n-button>
<n-button size="tiny" type="error" @click="share">分享到项目社区</n-button> <n-button size="tiny" type="error" @click="share">分享到项目社区</n-button>
@ -527,13 +719,19 @@ function ReFlesh(source){
</n-modal> </n-modal>
<div style="position: fixed;bottom: 18px;right:25px;z-index: 10;" v-if="summaryBTN"> <div style="position: fixed;bottom: 18px;right:25px;z-index: 10;" v-if="summaryBTN">
<n-input-group > <n-input-group>
<n-button type="primary" @click="getAiSummary"> <n-button type="primary" @click="getAiSummary">
<n-icon :component="PulseOutline"/> &nbsp;AI总结 <n-icon :component="PulseOutline"/> &nbsp;AI总结
</n-button> </n-button>
</n-input-group> </n-input-group>
</div> </div>
<n-drawer v-model:show="drawerShow" :width="502">
<n-drawer-content title="斯通纳" closable>
斯通纳是美国作家约翰·威廉姆斯在 1965 年出版的小说
</n-drawer-content>
</n-drawer>
</template> </template>
<style scoped> <style scoped>

View File

@ -12,6 +12,7 @@ const routes = [
{ path: '/settings', component: settingsView,name: 'settings' }, { path: '/settings', component: settingsView,name: 'settings' },
{ path: '/about', component: about,name: 'about' }, { path: '/about', component: about,name: 'about' },
{ path: '/market', component: market,name: 'market' }, { path: '/market', component: market,name: 'market' },
] ]
const router = createRouter({ const router = createRouter({

View File

@ -61,6 +61,8 @@ export function GlobalStockIndexes():Promise<Record<string, any>>;
export function Greet(arg1:string):Promise<data.StockInfo>; export function Greet(arg1:string):Promise<data.StockInfo>;
export function LongTigerRank(arg1:string):Promise<any>;
export function NewChatStream(arg1:string,arg2:string,arg3:string,arg4:any):Promise<void>; export function NewChatStream(arg1:string,arg2:string,arg3:string,arg4:any):Promise<void>;
export function ReFleshTelegraphList(arg1:string):Promise<any>; export function ReFleshTelegraphList(arg1:string):Promise<any>;

View File

@ -118,6 +118,10 @@ export function Greet(arg1) {
return window['go']['main']['App']['Greet'](arg1); return window['go']['main']['App']['Greet'](arg1);
} }
export function LongTigerRank(arg1) {
return window['go']['main']['App']['LongTigerRank'](arg1);
}
export function NewChatStream(arg1, arg2, arg3, arg4) { export function NewChatStream(arg1, arg2, arg3, arg4) {
return window['go']['main']['App']['NewChatStream'](arg1, arg2, arg3, arg4); return window['go']['main']['App']['NewChatStream'](arg1, arg2, arg3, arg4);
} }

3
go.mod
View File

@ -17,6 +17,7 @@ require (
github.com/robfig/cron/v3 v3.0.1 github.com/robfig/cron/v3 v3.0.1
github.com/samber/lo v1.49.1 github.com/samber/lo v1.49.1
github.com/stretchr/testify v1.10.0 github.com/stretchr/testify v1.10.0
github.com/tidwall/gjson v1.14.2
github.com/wailsapp/wails/v2 v2.10.1 github.com/wailsapp/wails/v2 v2.10.1
go.uber.org/zap v1.27.0 go.uber.org/zap v1.27.0
golang.org/x/sys v0.30.0 golang.org/x/sys v0.30.0
@ -62,6 +63,8 @@ require (
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/rivo/uniseg v0.4.7 // indirect github.com/rivo/uniseg v0.4.7 // indirect
github.com/tevino/abool v0.0.0-20220530134649-2bfc934cb23c // indirect github.com/tevino/abool v0.0.0-20220530134649-2bfc934cb23c // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.0 // indirect
github.com/tkrajina/go-reflector v0.5.8 // indirect github.com/tkrajina/go-reflector v0.5.8 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.2 // indirect github.com/valyala/fasttemplate v1.2.2 // indirect

6
go.sum
View File

@ -117,6 +117,12 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tevino/abool v0.0.0-20220530134649-2bfc934cb23c h1:coVla7zpsycc+kA9NXpcvv2E4I7+ii6L5hZO2S6C3kw= github.com/tevino/abool v0.0.0-20220530134649-2bfc934cb23c h1:coVla7zpsycc+kA9NXpcvv2E4I7+ii6L5hZO2S6C3kw=
github.com/tevino/abool v0.0.0-20220530134649-2bfc934cb23c/go.mod h1:qc66Pna1RiIsPa7O4Egxxs9OqkuxDX55zznh9K07Tzg= github.com/tevino/abool v0.0.0-20220530134649-2bfc934cb23c/go.mod h1:qc66Pna1RiIsPa7O4Egxxs9OqkuxDX55zznh9K07Tzg=
github.com/tidwall/gjson v1.14.2 h1:6BBkirS0rAHjumnjHF6qgy5d2YAJ1TLIaFE2lzfOLqo=
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tkrajina/go-reflector v0.5.8 h1:yPADHrwmUbMq4RGEyaOUpz2H90sRsETNVpjzo3DLVQQ= github.com/tkrajina/go-reflector v0.5.8 h1:yPADHrwmUbMq4RGEyaOUpz2H90sRsETNVpjzo3DLVQQ=
github.com/tkrajina/go-reflector v0.5.8/go.mod h1:ECbqLgccecY5kPmPmXg1MrHW585yMcDkVl6IvJe64T4= github.com/tkrajina/go-reflector v0.5.8/go.mod h1:ECbqLgccecY5kPmPmXg1MrHW585yMcDkVl6IvJe64T4=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=

View File

@ -202,6 +202,7 @@ func AutoMigrate() {
db.Dao.AutoMigrate(&models.Tags{}) db.Dao.AutoMigrate(&models.Tags{})
db.Dao.AutoMigrate(&models.Telegraph{}) db.Dao.AutoMigrate(&models.Telegraph{})
db.Dao.AutoMigrate(&models.TelegraphTags{}) db.Dao.AutoMigrate(&models.TelegraphTags{})
db.Dao.AutoMigrate(&models.LongTigerRankData{})
} }
func initStockDataUS(ctx context.Context) { func initStockDataUS(ctx context.Context) {