新闻数据
This commit is contained in:
parent
563708400e
commit
22905aeb39
10
src/App.vue
10
src/App.vue
@ -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
22
src/api/baidu/mapNews.ts
Normal 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
38
src/api/baidu/news.ts
Normal 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 : []
|
||||
}
|
||||
@ -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[]
|
||||
}
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
<a v-if="item.url" :href="item.url" target="_blank" rel="noopener">查看原文 ></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>
|
||||
|
||||
|
||||
@ -1,17 +1,49 @@
|
||||
<template>
|
||||
<section class="news-list card">
|
||||
<h2 class="section-title">相关新闻</h2>
|
||||
<NewsItem v-for="item in items" :key="item.id" :item="item" />
|
||||
<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>
|
||||
|
||||
@ -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 }
|
||||
}
|
||||
|
||||
@ -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[] = []
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user