mirror of
https://github.com/longbridge/developers.git
synced 2026-09-19 03:34:09 +08:00
e7e9a2d1ba
## Summary - Rename project from "OpenAPI" to "Longbridge Developers" across all locales, config, and docs - Add local SVG assets for homepage features (replacing external CDN URLs) - **TipContainer**: replace emoji with SVG icons, Shadcn-style border/bg/text per type using `data-type` + CSS `@apply` - **Tabs**: improved tab styling, fix `ul`/`ol` indentation in tab content - **Sidebar**: active item uses brand color background (color-mix) with rounded corners - **Doc footer**: LLMs Text + Edit this page on same row, subdued colors, lighter "Updated at" - **Breadcrumb**: remove primary color - **UserMenu**: rename Profile → Dashboard (控制台) - **Style**: inline code color fix, blockquote updates, font improvements ## Test plan - [ ] Verify rename appears correctly in EN / zh-CN / zh-HK locales - [ ] Check homepage SVGs load from `/assets/` - [ ] Check TipContainer all 6 types render with correct border/bg/text/icon colors in light and dark mode - [ ] Check Tabs styling and list indentation - [ ] Check sidebar active item background - [ ] Check doc footer layout (LLMs Text + Edit this page same row) 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
389 lines
11 KiB
TypeScript
389 lines
11 KiB
TypeScript
/**
|
||
* Longbridge Developers WebSocket 使用示例
|
||
* 演示如何连接、订阅行情、处理推送数据等
|
||
*/
|
||
|
||
import { createWebSocketClient, type WebSocketConfig, type QuoteData } from './websocket-client'
|
||
|
||
// React hooks (需要在 React 项目中导入)
|
||
// import { useState, useEffect } from 'react'
|
||
|
||
// Vue hooks (需要在 Vue 项目中导入)
|
||
// import { ref, watch, onUnmounted, readonly, type Ref } from 'vue'
|
||
|
||
// 为了避免编译错误,这里声明这些函数(实际使用时请导入真实的 hooks)
|
||
declare function useState<T>(initialState: T): [T, (value: T | ((prev: T) => T)) => void]
|
||
declare function useEffect(effect: () => void | (() => void), deps?: any[]): void
|
||
declare function ref<T>(value: T): { value: T }
|
||
declare function watch<T>(source: T, callback: () => void, options?: any): void
|
||
declare function onUnmounted(callback: () => void): void
|
||
declare function readonly<T>(value: T): T
|
||
declare type Ref<T> = { value: T }
|
||
|
||
// ==================== 配置示例 ====================
|
||
|
||
const wsConfig: WebSocketConfig = {
|
||
appKey: '',
|
||
accessToken: '',
|
||
appSecret: '',
|
||
// 可选配置
|
||
wsUrl: 'wss://openapi-quote.longbridge.com', // 默认值
|
||
timeout: 30000, // 30 秒超时
|
||
autoReconnect: true, // 自动重连
|
||
reconnectInterval: 5000, // 重连间隔 5 秒
|
||
maxReconnectAttempts: 3, // 最大重连 3 次
|
||
}
|
||
|
||
// ==================== 基础使用示例 ====================
|
||
|
||
async function basicExample() {
|
||
console.log('=== 基础 WebSocket 示例 ===')
|
||
|
||
// 创建 WebSocket 客户端
|
||
const wsClient = createWebSocketClient(wsConfig)
|
||
|
||
try {
|
||
// 连接到服务器
|
||
await wsClient.connect()
|
||
console.log('✅ WebSocket 连接成功')
|
||
|
||
// 订阅港股腾讯和美股苹果的实时行情
|
||
await wsClient.subscribeQuote({
|
||
symbol: ['700.HK', 'AAPL.US'],
|
||
sub_type: [1, 2], // 1: 实时价格,2: 买卖盘
|
||
is_first_push: true, // 立即推送当前数据
|
||
})
|
||
console.log('✅ 行情订阅成功')
|
||
|
||
// 等待一段时间接收数据
|
||
await new Promise((resolve) => setTimeout(resolve, 10000))
|
||
|
||
// 获取当前订阅列表
|
||
const subscriptions = await wsClient.getSubscriptions()
|
||
console.log('📊 当前订阅:', subscriptions)
|
||
|
||
// 取消订阅
|
||
await wsClient.unsubscribeQuote(['AAPL.US'])
|
||
console.log('✅ 取消 AAPL.US 订阅成功')
|
||
} catch (error) {
|
||
console.error('❌ WebSocket 示例失败:', error)
|
||
} finally {
|
||
// 断开连接
|
||
wsClient.disconnect()
|
||
console.log('🔌 WebSocket 连接已断开')
|
||
}
|
||
}
|
||
|
||
// ==================== 事件监听示例 ====================
|
||
|
||
async function eventListenerExample() {
|
||
console.log('=== WebSocket 事件监听示例 ===')
|
||
|
||
const wsClient = createWebSocketClient(wsConfig)
|
||
|
||
// 设置事件监听器
|
||
wsClient.on('connected', () => {
|
||
console.log('🔗 WebSocket 已连接')
|
||
})
|
||
|
||
wsClient.on('disconnected', () => {
|
||
console.log('📡 WebSocket 已断开')
|
||
})
|
||
|
||
wsClient.on('error', (error) => {
|
||
console.error('❌ WebSocket 错误:', error)
|
||
})
|
||
|
||
wsClient.on('quote', (data: QuoteData) => {
|
||
console.log('📈 收到行情数据:', {
|
||
symbol: data.symbol,
|
||
price: data.last_done,
|
||
open: data.open,
|
||
high: data.high,
|
||
low: data.low,
|
||
volume: data.volume,
|
||
timestamp: new Date(data.timestamp || 0).toLocaleString(),
|
||
})
|
||
})
|
||
|
||
wsClient.on('push', (data) => {
|
||
console.log('📨 收到推送数据:', data)
|
||
})
|
||
|
||
try {
|
||
await wsClient.connect()
|
||
|
||
// 订阅多个股票
|
||
await wsClient.subscribeQuote({
|
||
symbol: ['700.HK', 'AAPL.US', 'BABA.US'],
|
||
sub_type: [1], // 只订阅实时价格
|
||
is_first_push: true,
|
||
})
|
||
|
||
// 保持连接 30 秒
|
||
console.log('⏱️ 保持连接 30 秒...')
|
||
await new Promise((resolve) => setTimeout(resolve, 30000))
|
||
} catch (error) {
|
||
console.error('❌ 事件监听示例失败:', error)
|
||
} finally {
|
||
wsClient.disconnect()
|
||
}
|
||
}
|
||
|
||
// ==================== 错误处理和重连示例 ====================
|
||
|
||
async function reconnectExample() {
|
||
console.log('=== 重连机制示例 ===')
|
||
|
||
// 配置自动重连
|
||
const reconnectConfig: WebSocketConfig = {
|
||
...wsConfig,
|
||
autoReconnect: true,
|
||
reconnectInterval: 3000, // 3 秒重连间隔
|
||
maxReconnectAttempts: 5, // 最多重连 5 次
|
||
}
|
||
|
||
const wsClient = createWebSocketClient(reconnectConfig)
|
||
|
||
let reconnectCount = 0
|
||
|
||
wsClient.on('connected', () => {
|
||
console.log('🔗 WebSocket 连接成功')
|
||
reconnectCount = 0
|
||
})
|
||
|
||
wsClient.on('disconnected', () => {
|
||
console.log('📡 WebSocket 连接断开')
|
||
})
|
||
|
||
wsClient.on('error', (error) => {
|
||
reconnectCount++
|
||
console.error(`❌ WebSocket 错误 (第${reconnectCount}次):`, error)
|
||
})
|
||
|
||
try {
|
||
await wsClient.connect()
|
||
|
||
await wsClient.subscribeQuote({
|
||
symbol: ['700.HK'],
|
||
sub_type: [1],
|
||
is_first_push: true,
|
||
})
|
||
|
||
// 模拟网络问题 - 手动断开连接测试重连
|
||
console.log('🔌 5 秒后将断开连接测试重连...')
|
||
setTimeout(() => {
|
||
console.log('🔌 手动断开连接')
|
||
wsClient.disconnect()
|
||
}, 5000)
|
||
|
||
// 保持脚本运行
|
||
await new Promise((resolve) => setTimeout(resolve, 30000))
|
||
} catch (error) {
|
||
console.error('❌ 重连示例失败:', error)
|
||
} finally {
|
||
wsClient.disconnect()
|
||
}
|
||
}
|
||
|
||
// ==================== 行情数据分析示例 ====================
|
||
|
||
async function quoteAnalysisExample() {
|
||
console.log('=== 行情数据分析示例 ===')
|
||
|
||
const wsClient = createWebSocketClient(wsConfig)
|
||
const quoteHistory: Map<string, QuoteData[]> = new Map()
|
||
|
||
wsClient.on('quote', (data: QuoteData) => {
|
||
if (!quoteHistory.has(data.symbol)) {
|
||
quoteHistory.set(data.symbol, [])
|
||
}
|
||
|
||
const history = quoteHistory.get(data.symbol)!
|
||
history.push({ ...data, timestamp: Date.now() })
|
||
|
||
// 保持最近 50 条数据
|
||
if (history.length > 50) {
|
||
history.shift()
|
||
}
|
||
|
||
// 简单的价格变化分析
|
||
if (history.length >= 2) {
|
||
const current = history[history.length - 1]
|
||
const previous = history[history.length - 2]
|
||
|
||
if (current.last_done && previous.last_done) {
|
||
const change = current.last_done - previous.last_done
|
||
const changePercent = (change / previous.last_done) * 100
|
||
|
||
const trend = change > 0 ? '📈' : change < 0 ? '📉' : '➡️'
|
||
|
||
console.log(
|
||
`${trend} ${data.symbol}: ${current.last_done} (${change >= 0 ? '+' : ''}${change.toFixed(4)}, ${changePercent.toFixed(2)}%)`
|
||
)
|
||
}
|
||
}
|
||
})
|
||
|
||
try {
|
||
await wsClient.connect()
|
||
|
||
// 订阅热门股票
|
||
await wsClient.subscribeQuote({
|
||
symbol: ['700.HK', 'AAPL.US', 'BABA.US', 'TSLA.US', '9988.HK'],
|
||
sub_type: [1],
|
||
is_first_push: true,
|
||
})
|
||
|
||
console.log('📊 开始监控股票价格变化...')
|
||
await new Promise((resolve) => setTimeout(resolve, 60000)) // 运行 1 分钟
|
||
|
||
// 输出统计信息
|
||
console.log('\n📈 行情统计:')
|
||
for (const [symbol, history] of quoteHistory) {
|
||
if (history.length >= 2) {
|
||
const first = history[0]
|
||
const last = history[history.length - 1]
|
||
|
||
if (first.last_done && last.last_done) {
|
||
const totalChange = last.last_done - first.last_done
|
||
const totalChangePercent = (totalChange / first.last_done) * 100
|
||
|
||
console.log(
|
||
` ${symbol}: ${first.last_done} → ${last.last_done} (${totalChange >= 0 ? '+' : ''}${totalChange.toFixed(4)}, ${totalChangePercent.toFixed(2)}%) [${history.length} 个数据点]`
|
||
)
|
||
}
|
||
}
|
||
}
|
||
} catch (error) {
|
||
console.error('❌ 行情分析示例失败:', error)
|
||
} finally {
|
||
wsClient.disconnect()
|
||
}
|
||
}
|
||
|
||
// ==================== React Hook 示例 ====================
|
||
|
||
/**
|
||
* React Hook 示例 - 在 React 组件中使用 WebSocket
|
||
*/
|
||
export function useWebSocketQuote(symbols: string[]) {
|
||
const [quotes, setQuotes] = useState<Map<string, QuoteData>>(new Map())
|
||
const [isConnected, setIsConnected] = useState(false)
|
||
const [error, setError] = useState<string | null>(null)
|
||
|
||
useEffect(() => {
|
||
const wsClient = createWebSocketClient(wsConfig)
|
||
|
||
wsClient.on('connected', () => {
|
||
setIsConnected(true)
|
||
setError(null)
|
||
})
|
||
|
||
wsClient.on('disconnected', () => {
|
||
setIsConnected(false)
|
||
})
|
||
|
||
wsClient.on('error', (err: Error) => {
|
||
setError(err.message)
|
||
})
|
||
|
||
wsClient.on('quote', (data: QuoteData) => {
|
||
setQuotes((prev) => new Map(prev.set(data.symbol, data)))
|
||
})
|
||
|
||
// 连接并订阅
|
||
wsClient
|
||
.connect()
|
||
.then(() => {
|
||
return wsClient.subscribeQuote({
|
||
symbol: symbols,
|
||
sub_type: [1],
|
||
is_first_push: true,
|
||
})
|
||
})
|
||
.catch((err) => {
|
||
setError(err.message)
|
||
})
|
||
|
||
return () => {
|
||
wsClient.disconnect()
|
||
}
|
||
}, [symbols.join(',')])
|
||
|
||
return { quotes, isConnected, error }
|
||
}
|
||
|
||
// ==================== Vue Composition API 示例 ====================
|
||
|
||
/**
|
||
* Vue Composition API 示例
|
||
*/
|
||
export function useWebSocketQuoteVue(symbols: Ref<string[]>) {
|
||
const quotes = ref<Map<string, QuoteData>>(new Map())
|
||
const isConnected = ref(false)
|
||
const error = ref<string | null>(null)
|
||
let wsClient: ReturnType<typeof createWebSocketClient> | null = null
|
||
|
||
const connect = async () => {
|
||
if (wsClient) {
|
||
wsClient.disconnect()
|
||
}
|
||
|
||
wsClient = createWebSocketClient(wsConfig)
|
||
|
||
wsClient.on('connected', () => {
|
||
isConnected.value = true
|
||
error.value = null
|
||
})
|
||
|
||
wsClient.on('disconnected', () => {
|
||
isConnected.value = false
|
||
})
|
||
|
||
wsClient.on('error', (err: Error) => {
|
||
error.value = err.message
|
||
})
|
||
|
||
wsClient.on('quote', (data: QuoteData) => {
|
||
quotes.value.set(data.symbol, data)
|
||
// 触发响应式更新
|
||
quotes.value = new Map(quotes.value)
|
||
})
|
||
|
||
try {
|
||
await wsClient.connect()
|
||
await wsClient.subscribeQuote({
|
||
symbol: symbols.value,
|
||
sub_type: [1],
|
||
is_first_push: true,
|
||
})
|
||
} catch (err) {
|
||
error.value = (err as Error).message
|
||
}
|
||
}
|
||
|
||
watch(symbols, connect, { immediate: true })
|
||
|
||
onUnmounted(() => {
|
||
wsClient?.disconnect()
|
||
})
|
||
|
||
return { quotes: readonly(quotes), isConnected: readonly(isConnected), error: readonly(error) }
|
||
}
|
||
|
||
// ==================== 导出示例函数 ====================
|
||
|
||
export { basicExample, eventListenerExample, reconnectExample, quoteAnalysisExample, wsConfig as exampleConfig }
|
||
|
||
// 如果直接运行此文件,执行基础示例
|
||
if (typeof window !== 'undefined' && window.location?.pathname) {
|
||
// 浏览器环境
|
||
console.log('🌐 在浏览器中运行 WebSocket 示例')
|
||
basicExample()
|
||
} else if (typeof process !== 'undefined' && process.argv) {
|
||
// Node.js 环境
|
||
console.log('🖥️ 在 Node.js 中运行 WebSocket 示例')
|
||
basicExample()
|
||
}
|