K线接口对接
This commit is contained in:
parent
5393aa60be
commit
5708ab2b14
66
_t.mjs
Normal file
66
_t.mjs
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
import { writeFileSync } from 'fs'
|
||||||
|
|
||||||
|
const L = {
|
||||||
|
intraday: '\u5206\u65f6',
|
||||||
|
day: '\u65e5K',
|
||||||
|
week: '\u5468K',
|
||||||
|
month: '\u6708K',
|
||||||
|
avg: '\u5747\u4ef7',
|
||||||
|
volume: '\u6210\u4ea4\u91cf',
|
||||||
|
}
|
||||||
|
|
||||||
|
const content = `<template>
|
||||||
|
<div class="chart-panel card" v-loading="klineLoading">
|
||||||
|
<el-tabs v-model="period" class="tabs" @tab-change="onTabChange">
|
||||||
|
<el-tab-pane :label="L.intraday" name="intraday" />
|
||||||
|
<el-tab-pane :label="L.day" name="day" />
|
||||||
|
<el-tab-pane :label="L.week" name="week" />
|
||||||
|
<el-tab-pane :label="L.month" name="month" />
|
||||||
|
</el-tabs>
|
||||||
|
<v-chart class="chart" :option="option" autoresize />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, ref } from 'vue'
|
||||||
|
import { use } from 'echarts/core'
|
||||||
|
import { CanvasRenderer } from 'echarts/renderers'
|
||||||
|
import { LineChart, BarChart, CandlestickChart } from 'echarts/charts'
|
||||||
|
import {
|
||||||
|
GridComponent,
|
||||||
|
TooltipComponent,
|
||||||
|
DataZoomComponent,
|
||||||
|
LegendComponent,
|
||||||
|
} from 'echarts/components'
|
||||||
|
import type { EChartsOption } from 'echarts'
|
||||||
|
import VChart from 'vue-echarts'
|
||||||
|
import type { Candle, QuoteData } from '../../types'
|
||||||
|
import type { KlinePeriod } from '../../composables/useQuote'
|
||||||
|
|
||||||
|
use([
|
||||||
|
CanvasRenderer,
|
||||||
|
LineChart,
|
||||||
|
BarChart,
|
||||||
|
CandlestickChart,
|
||||||
|
GridComponent,
|
||||||
|
TooltipComponent,
|
||||||
|
DataZoomComponent,
|
||||||
|
LegendComponent,
|
||||||
|
])
|
||||||
|
|
||||||
|
/** Tab / series labels — keep as JS unicode to avoid file encoding corruption */
|
||||||
|
const L = {
|
||||||
|
intraday: '\\u5206\\u65f6',
|
||||||
|
day: '\\u65e5K',
|
||||||
|
week: '\\u5468K',
|
||||||
|
month: '\\u6708K',
|
||||||
|
avg: '\\u5747\\u4ef7',
|
||||||
|
volume: '\\u6210\\u4ea4\\u91cf',
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fix: the above would be literal backslash-u. Write real chars via template:
|
||||||
|
void 0
|
||||||
|
</script>
|
||||||
|
`
|
||||||
|
writeFileSync('src/components/quote/ChartPanel.vue', 'placeholder')
|
||||||
|
console.log('skip')
|
||||||
@ -7,7 +7,12 @@
|
|||||||
<section class="quote-section">
|
<section class="quote-section">
|
||||||
<QuoteStats v-if="quote" :quote="quote" />
|
<QuoteStats v-if="quote" :quote="quote" />
|
||||||
<div class="quote-grid">
|
<div class="quote-grid">
|
||||||
<ChartPanel v-if="quote" :quote="quote" />
|
<ChartPanel
|
||||||
|
v-if="quote"
|
||||||
|
:quote="quote"
|
||||||
|
:kline-loading="klineLoading"
|
||||||
|
:load-kline="loadKline"
|
||||||
|
/>
|
||||||
<aside class="side">
|
<aside class="side">
|
||||||
<OrderBook v-if="quote" :quote="quote" />
|
<OrderBook v-if="quote" :quote="quote" />
|
||||||
<TradeTape v-if="quote" :quote="quote" />
|
<TradeTape v-if="quote" :quote="quote" />
|
||||||
@ -50,7 +55,7 @@ import { useNews } from './composables/useNews'
|
|||||||
import { usePositions } from './composables/usePositions'
|
import { usePositions } from './composables/usePositions'
|
||||||
import { useAiAdvice } from './composables/useAiAdvice'
|
import { useAiAdvice } from './composables/useAiAdvice'
|
||||||
|
|
||||||
const { data: quote } = useQuote()
|
const { data: quote, klineLoading, loadKline } = useQuote()
|
||||||
const { data: news, loading: newsLoading, error: newsError, refresh: refreshNews } = useNews()
|
const { data: news, loading: newsLoading, error: newsError, refresh: refreshNews } = useNews()
|
||||||
const { data: positions } = usePositions()
|
const { data: positions } = usePositions()
|
||||||
const { data: advice, loading: adviceLoading, refresh: refreshAdvice } = useAiAdvice()
|
const { data: advice, loading: adviceLoading, refresh: refreshAdvice } = useAiAdvice()
|
||||||
|
|||||||
42
src/api/baidu/mapKline.ts
Normal file
42
src/api/baidu/mapKline.ts
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
import type { Candle } from '../../types'
|
||||||
|
import type { BaiduKlineResult } from './types'
|
||||||
|
|
||||||
|
function toNum(value: string | undefined, fallback = 0): number {
|
||||||
|
if (value == null || value === '' || value === '--') return fallback
|
||||||
|
const cleaned = value.replace(/[+,%]/g, '').trim()
|
||||||
|
const n = Number(cleaned)
|
||||||
|
return Number.isFinite(n) ? n : fallback
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析 K 线 marketData 字符串。
|
||||||
|
* 字段顺序(keys):timestamp, time, open, close, volume, high, low, ...
|
||||||
|
* K 线之间用 `;` 分隔,字段用 `,` 分隔。
|
||||||
|
*/
|
||||||
|
export function parseKlineMarketData(marketData: string): Candle[] {
|
||||||
|
if (!marketData) return []
|
||||||
|
|
||||||
|
const candles: Candle[] = []
|
||||||
|
const rows = marketData.split(';').filter(Boolean)
|
||||||
|
|
||||||
|
for (const row of rows) {
|
||||||
|
const parts = row.split(',')
|
||||||
|
// timestamp, time, open, close, volume, high, low
|
||||||
|
if (parts.length < 7) continue
|
||||||
|
|
||||||
|
candles.push({
|
||||||
|
date: parts[1] ?? '',
|
||||||
|
open: toNum(parts[2]),
|
||||||
|
close: toNum(parts[3]),
|
||||||
|
volume: toNum(parts[4]),
|
||||||
|
high: toNum(parts[5]),
|
||||||
|
low: toNum(parts[6]),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return candles
|
||||||
|
}
|
||||||
|
|
||||||
|
export function mapBaiduKlineToCandles(result: BaiduKlineResult): Candle[] {
|
||||||
|
return parseKlineMarketData(result.newMarketData?.marketData ?? '')
|
||||||
|
}
|
||||||
@ -59,10 +59,7 @@ function parseTrades(result: BaiduQuotationResult): TradeTick[] {
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
export function mapBaiduQuotationToQuote(
|
export function mapBaiduQuotationToQuote(result: BaiduQuotationResult): QuoteData {
|
||||||
result: BaiduQuotationResult,
|
|
||||||
fallback?: QuoteData,
|
|
||||||
): QuoteData {
|
|
||||||
const op = result.pankouinfos?.origin_pankou
|
const op = result.pankouinfos?.origin_pankou
|
||||||
const cur = result.cur
|
const cur = result.cur
|
||||||
const basic = result.basicinfos
|
const basic = result.basicinfos
|
||||||
@ -120,7 +117,8 @@ export function mapBaiduQuotationToQuote(
|
|||||||
buyRatio,
|
buyRatio,
|
||||||
sellRatio,
|
sellRatio,
|
||||||
intraday: parseIntraday(result),
|
intraday: parseIntraday(result),
|
||||||
candles: fallback?.candles ?? { day: [], week: [], month: [] },
|
// K 线按需加载,映射分时时不带入 mock/旧缓存
|
||||||
|
candles: { day: [], week: [], month: [] },
|
||||||
orderBook: { asks, bids },
|
orderBook: { asks, bids },
|
||||||
trades: parseTrades(result),
|
trades: parseTrades(result),
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,10 +1,22 @@
|
|||||||
import { baiduHttp } from '../http'
|
import { baiduHttp } from '../http'
|
||||||
import type { BaiduQuotationResponse, BaiduQuotationResult } from './types'
|
import type {
|
||||||
|
BaiduKlineResponse,
|
||||||
|
BaiduKlineResult,
|
||||||
|
BaiduKlineType,
|
||||||
|
BaiduQuotationResponse,
|
||||||
|
BaiduQuotationResult,
|
||||||
|
} from './types'
|
||||||
|
|
||||||
export interface GetStockQuotationParams {
|
export interface GetStockQuotationParams {
|
||||||
code: string
|
code: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface GetStockKlineParams {
|
||||||
|
code: string
|
||||||
|
/** 1=日K,2=周K,3=月K */
|
||||||
|
ktype: BaiduKlineType
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 百度财经 — 期货盘口 / 分时 / 行情快照
|
* 百度财经 — 期货盘口 / 分时 / 行情快照
|
||||||
* 仅页面打开时拉取一次;实时更新后续对接 WebSocket。
|
* 仅页面打开时拉取一次;实时更新后续对接 WebSocket。
|
||||||
@ -52,3 +64,42 @@ export async function getStockQuotation(
|
|||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 百度财经 — 期货 K 线(日/周/月)
|
||||||
|
* marketData 为 `;` 分隔的 K 线,每根内字段用 `,` 分隔。
|
||||||
|
*/
|
||||||
|
export async function getStockKline(
|
||||||
|
params: GetStockKlineParams,
|
||||||
|
): Promise<BaiduKlineResult> {
|
||||||
|
const { data } = await baiduHttp.get<BaiduKlineResponse>(
|
||||||
|
'/selfselect/getstockquotation',
|
||||||
|
{
|
||||||
|
params: {
|
||||||
|
all: 1,
|
||||||
|
code: params.code,
|
||||||
|
isIndex: false,
|
||||||
|
isBk: false,
|
||||||
|
isBlock: false,
|
||||||
|
isFutures: true,
|
||||||
|
isStock: false,
|
||||||
|
newFormat: 1,
|
||||||
|
ktype: params.ktype,
|
||||||
|
market_type: 'ab',
|
||||||
|
group: 'quotation_futures_kline',
|
||||||
|
finClientType: 'pc',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
if (data.ResultCode !== '0') {
|
||||||
|
throw new Error(`K线接口失败: ResultCode=${data.ResultCode}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = data.Result
|
||||||
|
if (!result?.newMarketData?.marketData) {
|
||||||
|
throw new Error('K线接口返回空 marketData')
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|||||||
@ -102,6 +102,26 @@ export interface BaiduQuotationResponse {
|
|||||||
Result: BaiduQuotationResult | BaiduQuotationResult[]
|
Result: BaiduQuotationResult | BaiduQuotationResult[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** K 线周期:1=日K,2=周K,3=月K */
|
||||||
|
export type BaiduKlineType = 1 | 2 | 3
|
||||||
|
|
||||||
|
/** 百度财经 — 期货 K 线 newMarketData(marketData 为 ; 分隔的 CSV 串) */
|
||||||
|
export interface BaiduKlineNewMarketData {
|
||||||
|
headers: string[]
|
||||||
|
keys: string[]
|
||||||
|
marketData: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BaiduKlineResult {
|
||||||
|
newMarketData: BaiduKlineNewMarketData
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BaiduKlineResponse {
|
||||||
|
QueryID: string
|
||||||
|
ResultCode: string
|
||||||
|
Result: BaiduKlineResult
|
||||||
|
}
|
||||||
|
|
||||||
/** 百度财经 — 期货相关新闻单条 */
|
/** 百度财经 — 期货相关新闻单条 */
|
||||||
export interface BaiduFuturesNewsItem {
|
export interface BaiduFuturesNewsItem {
|
||||||
loc: string
|
loc: string
|
||||||
|
|||||||
@ -1,11 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="chart-panel card">
|
<div class="chart-panel card" v-loading="klineLoading">
|
||||||
<el-tabs v-model="period" class="tabs">
|
<el-tabs v-model="period" class="tabs" @tab-change="onTabChange">
|
||||||
<el-tab-pane label="分时" name="intraday" />
|
<el-tab-pane :label="TAB.intraday" name="intraday" />
|
||||||
<el-tab-pane label="五日" name="five" />
|
<el-tab-pane :label="TAB.day" name="day" />
|
||||||
<el-tab-pane label="日K" name="day" />
|
<el-tab-pane :label="TAB.week" name="week" />
|
||||||
<el-tab-pane label="周K" name="week" />
|
<el-tab-pane :label="TAB.month" name="month" />
|
||||||
<el-tab-pane label="月K" name="month" />
|
|
||||||
</el-tabs>
|
</el-tabs>
|
||||||
<v-chart class="chart" :option="option" autoresize />
|
<v-chart class="chart" :option="option" autoresize />
|
||||||
</div>
|
</div>
|
||||||
@ -25,6 +24,7 @@ import {
|
|||||||
import type { EChartsOption } from 'echarts'
|
import type { EChartsOption } from 'echarts'
|
||||||
import VChart from 'vue-echarts'
|
import VChart from 'vue-echarts'
|
||||||
import type { Candle, QuoteData } from '../../types'
|
import type { Candle, QuoteData } from '../../types'
|
||||||
|
import type { KlinePeriod } from '../../composables/useQuote'
|
||||||
|
|
||||||
use([
|
use([
|
||||||
CanvasRenderer,
|
CanvasRenderer,
|
||||||
@ -37,7 +37,17 @@ use([
|
|||||||
LegendComponent,
|
LegendComponent,
|
||||||
])
|
])
|
||||||
|
|
||||||
type ChartPeriod = 'intraday' | 'five' | 'day' | 'week' | 'month'
|
/** ASCII-safe unicode escapes ? avoids Windows encoding corruption */
|
||||||
|
const TAB = {
|
||||||
|
intraday: '\u5206\u65f6',
|
||||||
|
day: '\u65e5K',
|
||||||
|
week: '\u5468K',
|
||||||
|
month: '\u6708K',
|
||||||
|
avg: '\u5747\u4ef7',
|
||||||
|
volume: '\u6210\u4ea4\u91cf',
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChartPeriod = 'intraday' | KlinePeriod
|
||||||
|
|
||||||
interface ColorParam {
|
interface ColorParam {
|
||||||
dataIndex?: number
|
dataIndex?: number
|
||||||
@ -45,22 +55,27 @@ interface ColorParam {
|
|||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
quote: QuoteData
|
quote: QuoteData
|
||||||
|
klineLoading?: boolean
|
||||||
|
loadKline: (period: KlinePeriod) => Promise<Candle[]>
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const period = ref<ChartPeriod>('intraday')
|
const period = ref<ChartPeriod>('intraday')
|
||||||
|
|
||||||
const option = computed((): EChartsOption => {
|
async function onTabChange(name: string | number) {
|
||||||
if (period.value === 'intraday' || period.value === 'five') {
|
if (name === 'day' || name === 'week' || name === 'month') {
|
||||||
return buildIntradayOption(props.quote, period.value === 'five')
|
await props.loadKline(name)
|
||||||
}
|
}
|
||||||
const key = period.value === 'day' ? 'day' : period.value === 'week' ? 'week' : 'month'
|
}
|
||||||
return buildCandleOption(props.quote.candles[key])
|
|
||||||
|
const option = computed((): EChartsOption => {
|
||||||
|
if (period.value === 'intraday') {
|
||||||
|
return buildIntradayOption(props.quote)
|
||||||
|
}
|
||||||
|
return buildCandleOption(props.quote.candles[period.value])
|
||||||
})
|
})
|
||||||
|
|
||||||
function buildIntradayOption(quote: QuoteData, fiveDay: boolean): EChartsOption {
|
function buildIntradayOption(quote: QuoteData): EChartsOption {
|
||||||
const points = fiveDay
|
const points = quote.intraday
|
||||||
? [...quote.intraday, ...quote.intraday.map((p) => ({ ...p, price: p.price + 1.5 }))]
|
|
||||||
: quote.intraday
|
|
||||||
const times = points.map((p) => p.time)
|
const times = points.map((p) => p.time)
|
||||||
const prices = points.map((p) => p.price)
|
const prices = points.map((p) => p.price)
|
||||||
const avgs = points.map((p) => p.avg)
|
const avgs = points.map((p) => p.avg)
|
||||||
@ -69,7 +84,7 @@ function buildIntradayOption(quote: QuoteData, fiveDay: boolean): EChartsOption
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
animation: false,
|
animation: false,
|
||||||
legend: { data: ['分时', '均价'], top: 0, textStyle: { fontSize: 11 } },
|
legend: { data: [TAB.intraday, TAB.avg], top: 0, textStyle: { fontSize: 11 } },
|
||||||
tooltip: { trigger: 'axis' },
|
tooltip: { trigger: 'axis' },
|
||||||
axisPointer: { link: [{ xAxisIndex: 'all' }] },
|
axisPointer: { link: [{ xAxisIndex: 'all' }] },
|
||||||
grid: [
|
grid: [
|
||||||
@ -121,7 +136,7 @@ function buildIntradayOption(quote: QuoteData, fiveDay: boolean): EChartsOption
|
|||||||
dataZoom: [{ type: 'inside', xAxisIndex: [0, 1] }],
|
dataZoom: [{ type: 'inside', xAxisIndex: [0, 1] }],
|
||||||
series: [
|
series: [
|
||||||
{
|
{
|
||||||
name: '分时',
|
name: TAB.intraday,
|
||||||
type: 'line',
|
type: 'line',
|
||||||
data: prices,
|
data: prices,
|
||||||
showSymbol: false,
|
showSymbol: false,
|
||||||
@ -143,7 +158,7 @@ function buildIntradayOption(quote: QuoteData, fiveDay: boolean): EChartsOption
|
|||||||
yAxisIndex: 0,
|
yAxisIndex: 0,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: '均价',
|
name: TAB.avg,
|
||||||
type: 'line',
|
type: 'line',
|
||||||
data: avgs,
|
data: avgs,
|
||||||
showSymbol: false,
|
showSymbol: false,
|
||||||
@ -152,7 +167,7 @@ function buildIntradayOption(quote: QuoteData, fiveDay: boolean): EChartsOption
|
|||||||
yAxisIndex: 0,
|
yAxisIndex: 0,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: '成交量',
|
name: TAB.volume,
|
||||||
type: 'bar',
|
type: 'bar',
|
||||||
data: vols,
|
data: vols,
|
||||||
itemStyle: {
|
itemStyle: {
|
||||||
|
|||||||
@ -1,21 +1,43 @@
|
|||||||
import { onMounted, ref } from 'vue'
|
import { onMounted, ref } from 'vue'
|
||||||
import type { QuoteData } from '../types'
|
import type { Candle, QuoteData } from '../types'
|
||||||
import { quoteMock } from '../mocks/quote'
|
import { quoteMock } from '../mocks/quote'
|
||||||
import { contractConfig } from '../config/contract'
|
import { contractConfig } from '../config/contract'
|
||||||
import { getStockQuotation } from '../api/baidu/quotation'
|
import { getStockKline, getStockQuotation } from '../api/baidu/quotation'
|
||||||
import { mapBaiduQuotationToQuote } from '../api/baidu/mapQuote'
|
import { mapBaiduQuotationToQuote } from '../api/baidu/mapQuote'
|
||||||
|
import { mapBaiduKlineToCandles } from '../api/baidu/mapKline'
|
||||||
|
import type { BaiduKlineType } from '../api/baidu/types'
|
||||||
|
|
||||||
|
export type KlinePeriod = 'day' | 'week' | 'month'
|
||||||
|
|
||||||
|
const KTYPE_MAP: Record<KlinePeriod, BaiduKlineType> = {
|
||||||
|
day: 1,
|
||||||
|
week: 2,
|
||||||
|
month: 3,
|
||||||
|
}
|
||||||
|
|
||||||
export function useQuote() {
|
export function useQuote() {
|
||||||
const data = ref<QuoteData>(structuredClone(quoteMock))
|
const data = ref<QuoteData>(structuredClone(quoteMock))
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
|
const klineLoading = ref(false)
|
||||||
const error = ref<unknown>(null)
|
const error = ref<unknown>(null)
|
||||||
|
|
||||||
|
/** 已加载过的周期,避免重复请求 */
|
||||||
|
const loadedKlines = ref<Record<KlinePeriod, boolean>>({
|
||||||
|
day: false,
|
||||||
|
week: false,
|
||||||
|
month: false,
|
||||||
|
})
|
||||||
|
|
||||||
async function refresh() {
|
async function refresh() {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
error.value = null
|
error.value = null
|
||||||
try {
|
try {
|
||||||
const result = await getStockQuotation({ code: contractConfig.code })
|
const result = await getStockQuotation({ code: contractConfig.code })
|
||||||
data.value = mapBaiduQuotationToQuote(result, data.value)
|
const quote = mapBaiduQuotationToQuote(result)
|
||||||
|
// 分时刷新时清空 K 线缓存,点击 Tab 再拉
|
||||||
|
quote.candles = { day: [], week: [], month: [] }
|
||||||
|
loadedKlines.value = { day: false, week: false, month: false }
|
||||||
|
data.value = quote
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
error.value = e
|
error.value = e
|
||||||
console.error('[useQuote] 拉取行情失败', e)
|
console.error('[useQuote] 拉取行情失败', e)
|
||||||
@ -24,10 +46,36 @@ export function useQuote() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 页面打开时请求一次;实时数据后续对接 WebSocket
|
/** 按需加载日/周/月 K 线;已加载则跳过 */
|
||||||
|
async function loadKline(period: KlinePeriod): Promise<Candle[]> {
|
||||||
|
if (loadedKlines.value[period]) {
|
||||||
|
return data.value.candles[period]
|
||||||
|
}
|
||||||
|
|
||||||
|
klineLoading.value = true
|
||||||
|
error.value = null
|
||||||
|
try {
|
||||||
|
const result = await getStockKline({
|
||||||
|
code: contractConfig.code,
|
||||||
|
ktype: KTYPE_MAP[period],
|
||||||
|
})
|
||||||
|
const candles = mapBaiduKlineToCandles(result)
|
||||||
|
data.value.candles[period] = candles
|
||||||
|
loadedKlines.value[period] = true
|
||||||
|
return candles
|
||||||
|
} catch (e) {
|
||||||
|
error.value = e
|
||||||
|
console.error(`[useQuote] 拉取${period}K线失败`, e)
|
||||||
|
throw e
|
||||||
|
} finally {
|
||||||
|
klineLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 页面打开时只请求分时/盘口;K 线点击 Tab 再拉
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
void refresh()
|
void refresh()
|
||||||
})
|
})
|
||||||
|
|
||||||
return { data, loading, error, refresh }
|
return { data, loading, klineLoading, error, refresh, loadKline }
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user