ArvinLovegood 013de869f4 feat(backend):新增 top stocks 排行榜功能并更新相关模块
- 在 MarketNewsApi 中添加 TopStocksRankingList 方法,实现 top stocks 排行榜数据的获取和解析
- 更新 App.vue 中的 content 文本,增加未经授权禁止商业用途的声明- 在 market_news_api_test.go 中添加 TopStocksRankingList 的测试用例
2025-05-21 09:59:38 +08:00

518 lines
13 KiB
Vue

<script setup>
import {
EventsEmit,
EventsOff,
EventsOn,
Quit,
WindowFullscreen,
WindowHide,
WindowUnfullscreen
} from '../wailsjs/runtime'
import {h, onBeforeMount, onBeforeUnmount, onMounted, ref} from "vue";
import {RouterLink, useRouter} from 'vue-router'
import {darkTheme, NIcon, NText,} from 'naive-ui'
import {
AlarmOutline,
AnalyticsOutline,
BarChartSharp,
ExpandOutline,
Flame,
LogoGithub,
NewspaperOutline,
NewspaperSharp,
PowerOutline,
ReorderTwoOutline,
SettingsOutline,
SparklesOutline,
StarOutline,
Wallet,
} from '@vicons/ionicons5'
import {GetConfig, GetGroupList} from "../wailsjs/go/main/App";
const router = useRouter()
const loading = ref(true)
const loadingMsg = ref("加载数据中...")
const enableNews = ref(false)
const contentStyle = ref("")
const enableFund = ref(false)
const enableDarkTheme = ref(null)
const content = ref('数据来源于网络,仅供参考;投资有风险,入市需谨慎\n\n未经授权,禁止商业目的!')
const isFullscreen = ref(false)
const activeKey = ref('')
const containerRef = ref({})
const realtimeProfit = ref(0)
const telegraph = ref([])
const groupList = ref([])
const menuOptions = ref([
{
label: () =>
h(
RouterLink,
{
to: {
name: 'stock',
query: {
groupName: '全部',
groupId: 0,
},
params: {},
}
},
{default: () => '股票自选',}
),
key: 'stock',
icon: renderIcon(StarOutline),
children: [
{
label: () =>
h(
'a',
{
href: '#',
type: 'info',
onClick: () => {
//console.log("push",item)
router.push({
name: 'stock',
query: {
groupName: '全部',
groupId: 0,
},
})
EventsEmit("changeTab", {ID: 0, name: '全部'})
},
to: {
name: 'stock',
query: {
groupName: '全部',
groupId: 0,
},
}
},
{default: () => '全部',}
),
key: 0,
}
],
},
{
label: () =>
h(
RouterLink,
{
href: '#',
to: {
name: 'market',
params: {}
},
onClick: () => {
EventsEmit("changeMarketTab", {ID: 0, name: '市场快讯'})
},
},
{default: () => '市场行情'}
),
key: 'market',
icon: renderIcon(NewspaperOutline),
children: [
{
label: () =>
h(
RouterLink,
{
href: '#',
to: {
name: 'market',
query: {
name: "市场快讯",
}
},
onClick: () => {
EventsEmit("changeMarketTab", {ID: 0, name: '市场快讯'})
},
},
{default: () => '市场快讯',}
),
key: 'market1',
icon: renderIcon(NewspaperSharp),
},
{
label: () =>
h(
RouterLink,
{
href: '#',
to: {
name: 'market',
query: {
name: "全球股指",
},
},
onClick: () => {
EventsEmit("changeMarketTab", {ID: 0, name: '全球股指'})
},
},
{default: () => '全球股指',}
),
key: 'market2',
icon: renderIcon(BarChartSharp),
},
{
label: () =>
h(
RouterLink,
{
href: '#',
to: {
name: 'market',
query: {
name: "指标行情",
}
},
onClick: () => {
EventsEmit("changeMarketTab", {ID: 0, name: '指标行情'})
},
},
{default: () => '指标行情',}
),
key: 'market3',
icon: renderIcon(AnalyticsOutline),
},
{
label: () =>
h(
RouterLink,
{
href: '#',
to: {
name: 'market',
query: {
name: "行业排名",
}
},
onClick: () => {
EventsEmit("changeMarketTab", {ID: 0, name: '行业排名'})
},
},
{default: () => '行业排名',}
),
key: 'market4',
icon: renderIcon(Flame),
},
{
label: () =>
h(
RouterLink,
{
href: '#',
to: {
name: 'market',
query: {
name: "个股资金流向",
}
},
onClick: () => {
EventsEmit("changeMarketTab", {ID: 0, name: '个股资金流向'})
},
},
{default: () => '个股资金流向',}
),
key: 'market5',
icon: renderIcon(Wallet),
}
]
},
{
label: () =>
h(
RouterLink,
{
to: {
name: 'fund',
params: {},
}
},
{default: () => '基金自选',}
),
show: enableFund.value,
key: 'fund',
icon: renderIcon(SparklesOutline),
children: [
{
label: () => h(NText, {type: realtimeProfit.value > 0 ? 'error' : 'success'}, {default: () => '功能完善中!'}),
key: 'realtimeProfit',
show: realtimeProfit.value,
icon: renderIcon(AlarmOutline),
},
]
},
{
label: () =>
h(
RouterLink,
{
to: {
name: 'settings',
params: {}
}
},
{default: () => '设置'}
),
key: 'settings',
icon: renderIcon(SettingsOutline),
},
{
label: () =>
h(
RouterLink,
{
to: {
name: 'about',
params: {}
}
},
{default: () => '关于'}
),
key: 'about',
icon: renderIcon(LogoGithub),
},
{
label: () => h("a", {
href: '#',
onClick: toggleFullscreen,
title: '全屏 Ctrl+F 退出全屏 Esc',
}, {default: () => isFullscreen.value ? '取消全屏' : '全屏'}),
key: 'full',
icon: renderIcon(ExpandOutline),
},
{
label: () => h("a", {
href: '#',
onClick: WindowHide,
title: '隐藏到托盘区 Ctrl+H',
}, {default: () => '隐藏到托盘区'}),
key: 'hide',
icon: renderIcon(ReorderTwoOutline),
},
// {
// label: ()=> h("a", {
// href: 'javascript:void(0)',
// style: 'cursor: move;',
// onClick: toggleStartMoveWindow,
// }, { default: () => '移动' }),
// key: 'move',
// icon: renderIcon(MoveOutline),
// },
{
label: () => h("a", {
href: '#',
onClick: Quit,
}, {default: () => '退出程序'}),
key: 'exit',
icon: renderIcon(PowerOutline),
},
])
function renderIcon(icon) {
return () => h(NIcon, null, {default: () => h(icon)})
}
function toggleFullscreen(e) {
//console.log(e)
if (isFullscreen.value) {
WindowUnfullscreen()
//e.target.innerHTML = '全屏'
} else {
WindowFullscreen()
// e.target.innerHTML = '取消全屏'
}
isFullscreen.value = !isFullscreen.value
}
// const drag = ref(false)
// const lastPos= ref({x:0,y:0})
// function toggleStartMoveWindow(e) {
// drag.value=!drag.value
// lastPos.value={x:e.clientX,y:e.clientY}
// }
// function dragstart(e) {
// if (drag.value) {
// let x=e.clientX-lastPos.value.x
// let y=e.clientY-lastPos.value.y
// WindowGetPosition().then((pos) => {
// WindowSetPosition(pos.x+x,pos.y+y)
// })
// }
// }
// window.addEventListener('mousemove', dragstart)
EventsOn("realtime_profit", (data) => {
realtimeProfit.value = data
})
EventsOn("telegraph", (data) => {
telegraph.value = data
})
EventsOn("loadingMsg", (data) => {
if(data==="done"){
loadingMsg.value = "加载完成..."
EventsEmit("loadingDone", "app")
loading.value = false
}else{
loading.value = true
loadingMsg.value = data
}
})
onBeforeUnmount(() => {
EventsOff("realtime_profit")
EventsOff("loadingMsg")
EventsOff("telegraph")
})
window.onerror = function (msg, source, lineno, colno, error) {
// 将错误信息发送给后端
EventsEmit("frontendError", {
page: "App.vue",
message: msg,
source: source,
lineno: lineno,
colno: colno,
error: error ? error.stack : null,
});
return true;
};
onBeforeMount(() => {
GetGroupList().then(result => {
groupList.value = result
menuOptions.value.map((item) => {
//console.log(item)
if (item.key === 'stock') {
item.children.push(...groupList.value.map(item => {
return {
label: () =>
h(
'a',
{
href: '#',
type: 'info',
onClick: () => {
//console.log("push",item)
router.push({
name: 'stock',
query: {
groupName: item.name,
groupId: item.ID,
},
})
setTimeout(() => {
EventsEmit("changeTab", item)
}, 100)
},
to: {
name: 'stock',
query: {
groupName: item.name,
groupId: item.ID,
},
}
},
{default: () => item.name,}
),
key: item.ID,
}
}))
}
})
})
GetConfig().then((res) => {
//console.log(res)
enableFund.value = res.enableFund
menuOptions.value.filter((item) => {
if (item.key === 'fund') {
item.show = res.enableFund
}
})
if (res.darkTheme) {
enableDarkTheme.value = darkTheme
} else {
enableDarkTheme.value = null
}
})
})
onMounted(() => {
contentStyle.value = "max-height: calc(92vh);overflow: hidden"
GetConfig().then((res) => {
if (res.enableNews) {
enableNews.value = true
}
enableFund.value = res.enableFund
})
})
</script>
<template>
<n-config-provider ref="containerRef" :theme="enableDarkTheme">
<n-message-provider>
<n-notification-provider>
<n-modal-provider>
<n-dialog-provider>
<n-watermark
:content="content"
cross
selectable
:font-size="16"
:line-height="16"
:width="500"
:height="400"
:x-offset="50"
:y-offset="150"
:rotate="-15"
>
<n-flex>
<n-grid x-gap="12" :cols="1">
<n-gi>
<n-spin :show="loading">
<template #description>
{{ loadingMsg }}
</template>
<n-marquee :speed="100" style="position: relative;top:0;z-index: 19;width: 100%"
v-if="(telegraph.length>0)&&(enableNews)">
<n-tag type="warning" v-for="item in telegraph" style="margin-right: 10px">
{{ item }}
</n-tag>
</n-marquee>
<n-scrollbar :style="contentStyle">
<n-skeleton v-if="loading" height="calc(100vh)" />
<RouterView/>
</n-scrollbar>
</n-spin>
</n-gi>
<n-gi style="position: fixed;bottom:0;z-index: 9;width: 100%;">
<n-card size="small" style="--wails-draggable:drag">
<n-menu style="font-size: 18px;"
v-model:value="activeKey"
mode="horizontal"
:options="menuOptions"
responsive
/>
</n-card>
</n-gi>
</n-grid>
</n-flex>
</n-watermark>
</n-dialog-provider>
</n-modal-provider>
</n-notification-provider>
</n-message-provider>
</n-config-provider>
</template>
<style>
</style>