新闻数据

This commit is contained in:
dongzp 2026-07-21 14:59:43 +08:00
parent 563708400e
commit 22905aeb39
9 changed files with 156 additions and 62 deletions

View File

@ -15,7 +15,13 @@
</div>
</section>
<NewsList v-if="news" :items="news" />
<NewsList
v-if="news"
:items="news"
:loading="newsLoading"
:error="newsError"
@refresh="refreshNews"
/>
<PositionPanel v-if="positions" :data="positions" />
<template #ai>
@ -45,7 +51,7 @@ import { usePositions } from './composables/usePositions'
import { useAiAdvice } from './composables/useAiAdvice'
const { data: quote } = useQuote()
const { data: news } = useNews()
const { data: news, loading: newsLoading, error: newsError, refresh: refreshNews } = useNews()
const { data: positions } = usePositions()
const { data: advice, loading: adviceLoading, refresh: refreshAdvice } = useAiAdvice()
</script>

22
src/api/baidu/mapNews.ts Normal file
View File

@ -0,0 +1,22 @@
import type { NewsItem } from '../../types'
import type { BaiduFuturesNewsItem } from './types'
/** 将 Unix 秒时间戳格式化为「MM-DD HH:mm」 */
function formatPublishTime(raw: string): string {
const sec = Number(raw)
if (!Number.isFinite(sec) || sec <= 0) return ''
const d = new Date(sec * 1000)
if (Number.isNaN(d.getTime())) return ''
const pad = (n: number) => String(n).padStart(2, '0')
return `${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}`
}
export function mapBaiduNewsToItems(list: BaiduFuturesNewsItem[]): NewsItem[] {
return list.map((item) => ({
id: item.news_id,
source: item.source || item.provider || '未知来源',
time: formatPublishTime(item.publish_time),
title: item.title,
url: item.loc || item.third_url,
}))
}

38
src/api/baidu/news.ts Normal file
View File

@ -0,0 +1,38 @@
import { baiduHttp } from '../http'
import type { BaiduFuturesNewsItem, BaiduFuturesNewsResponse } from './types'
export interface GetFuturesNewsParams {
/** 合约编码,如 FG609 */
code: string
/** 页码,从 0 起 */
pn?: number
/** 每页条数 */
rn?: number
}
/**
* 百度财经 — 期货相关新闻
* GET /vapi/getfuturesnews?code=&pn=&rn=&finClientType=pc
*/
export async function getFuturesNews(
params: GetFuturesNewsParams,
): Promise<BaiduFuturesNewsItem[]> {
const { data } = await baiduHttp.get<BaiduFuturesNewsResponse>(
'/vapi/getfuturesnews',
{
params: {
code: params.code,
pn: params.pn ?? 0,
rn: params.rn ?? 20,
finClientType: 'pc',
},
},
)
const codeOk = data.ResultCode === 0 || data.ResultCode === '0'
if (!codeOk) {
throw new Error(`新闻接口失败: ResultCode=${data.ResultCode}`)
}
return Array.isArray(data.Result) ? data.Result : []
}

View File

@ -101,3 +101,24 @@ export interface BaiduQuotationResponse {
ResultCode: string
Result: BaiduQuotationResult | BaiduQuotationResult[]
}
/** 百度财经 — 期货相关新闻单条 */
export interface BaiduFuturesNewsItem {
loc: string
provider: string
source: string
/** Unix 秒级时间戳(字符串) */
publish_time: string
third_url?: string
title: string
is_self_build?: string
news_id: string
locate_url?: string
}
export interface BaiduFuturesNewsResponse {
ResultCode: number | string
ResultNum: number
QueryID: string
Result: BaiduFuturesNewsItem[]
}

View File

@ -5,7 +5,7 @@
<a v-if="item.url" :href="item.url" target="_blank" rel="noopener">查看原文 &gt;</a>
</div>
<h3 class="title">{{ item.title }}</h3>
<p class="summary">{{ item.summary }}</p>
<p v-if="item.summary" class="summary">{{ item.summary }}</p>
</article>
</template>

View File

@ -1,17 +1,49 @@
<template>
<section class="news-list card">
<div class="head">
<h2 class="section-title">相关新闻</h2>
<el-button text type="primary" :loading="loading" @click="$emit('refresh')">
刷新
</el-button>
</div>
<el-alert
v-if="error"
class="err"
type="error"
:closable="false"
title="新闻加载失败"
:description="errorMessage"
show-icon
/>
<el-skeleton v-if="loading && !items.length" :rows="4" animated />
<el-empty v-else-if="!items.length && !error" description="暂无相关新闻" />
<template v-else-if="items.length">
<NewsItem v-for="item in items" :key="item.id" :item="item" />
</template>
</section>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import type { NewsItem as NewsItemType } from '../../types'
import NewsItem from './NewsItem.vue'
defineProps<{
const props = defineProps<{
items: NewsItemType[]
loading?: boolean
error?: unknown
}>()
defineEmits<{
refresh: []
}>()
const errorMessage = computed(() => {
if (!props.error) return ''
if (props.error instanceof Error) return props.error.message
return String(props.error)
})
</script>
<style scoped>
@ -19,4 +51,19 @@ defineProps<{
padding: 16px 20px;
margin-top: 16px;
}
.head {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 4px;
}
.head .section-title {
margin: 0;
}
.err {
margin-bottom: 12px;
}
</style>

View File

@ -1,10 +1,9 @@
import { ref } from 'vue'
import { onMounted, ref } from 'vue'
import type { NewsItem } from '../types'
import { newsMock } from '../mocks/news'
function delay(ms = 300): Promise<void> {
return new Promise((r) => setTimeout(r, ms))
}
import { contractConfig } from '../config/contract'
import { getFuturesNews } from '../api/baidu/news'
import { mapBaiduNewsToItems } from '../api/baidu/mapNews'
export function useNews() {
const data = ref<NewsItem[]>(structuredClone(newsMock))
@ -15,14 +14,19 @@ export function useNews() {
loading.value = true
error.value = null
try {
await delay()
data.value = structuredClone(newsMock)
const list = await getFuturesNews({ code: contractConfig.code })
data.value = mapBaiduNewsToItems(list)
} catch (e) {
error.value = e
console.error('[useNews] 拉取新闻失败', e)
} finally {
loading.value = false
}
}
onMounted(() => {
void refresh()
})
return { data, loading, error, refresh }
}

View File

@ -1,49 +1,4 @@
import type { NewsItem } from '../types'
export const newsMock: NewsItem[] = [
{
id: 1,
source: 'LSEG',
time: '07-16 04:07',
title: 'Precipitate Gold appoints Pelayo Troncoso and John Wenger to the Board',
summary:
'Precipitate Gold Corp announced the appointment of Pelayo Troncoso and John Wenger as independent directors, strengthening governance ahead of exploration milestones.',
url: 'https://example.com/news/1',
},
{
id: 2,
source: '路透',
time: '07-16 03:42',
title: '原油短线承压,美油回落关注库存数据',
summary:
'隔夜油价震荡回落,市场等待本周库存与炼厂开工率数据,短线交易者对风险偏好趋于谨慎。',
url: 'https://example.com/news/2',
},
{
id: 3,
source: '财联社',
time: '07-15 21:18',
title: '玻璃期货日内走弱,期现基差小幅收敛',
summary:
'盘面跟随黑色系情绪回落,现货报价相对坚挺,基差有所收窄;机构提示关注沙河地区库存变化。',
url: 'https://example.com/news/3',
},
{
id: 4,
source: 'Bloomberg',
time: '07-15 18:05',
title: 'Gold holds near highs as markets await PPI print',
summary:
'Bullion stayed firm as traders positioned for U.S. producer price data, with real yields and dollar moves still the key drivers.',
url: 'https://example.com/news/4',
},
{
id: 5,
source: '新华财经',
time: '07-15 15:30',
title: '碳酸锂价格波动加剧,产业链观望情绪升温',
summary:
'下游备货节奏放缓,部分贸易商报价分歧加大,锂盐市场短期或以区间震荡为主。',
url: 'https://example.com/news/5',
},
]
/** 首屏占位;打开页面后由百度 getfuturesnews 覆盖 */
export const newsMock: NewsItem[] = []

View File

@ -65,11 +65,12 @@ export interface QuoteData {
}
export interface NewsItem {
id: number
id: string | number
source: string
time: string
title: string
summary: string
/** 接口无摘要时可不填 */
summary?: string
url?: string
}