From 38d2cf7532f8980e6b29030424499ae3c2dd83ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=A8?= Date: Wed, 27 May 2026 10:42:23 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A4=84=E7=90=86=E5=81=9A=E5=B8=82=E5=95=86?= =?UTF-8?q?=E5=88=97=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/stores/modules/orderPosition.ts | 20 ++-- src/stores/modules/transaction.ts | 5 +- src/utils/ListFrameThrottler.ts | 166 ++++++++++++++++----------- src/views/order/position/index.vue | 5 + src/views/order/position/options.ts | 38 ++++-- src/views/profile/business/index.vue | 18 ++- 6 files changed, 155 insertions(+), 97 deletions(-) diff --git a/src/stores/modules/orderPosition.ts b/src/stores/modules/orderPosition.ts index cb3e9ca..1af69d3 100644 --- a/src/stores/modules/orderPosition.ts +++ b/src/stores/modules/orderPosition.ts @@ -26,15 +26,16 @@ interface PositionObj { export const useOrderPositionStore = defineStore('orderPosition', () => { const positionOrderList = ref([]) - const handleInfoListData = (positionObj: PositionObj) => { - if (positionObj?.orderNo) { - const existingIndex = positionOrderList.value.findIndex( - (p: PositionObj) => p.orderNo === positionObj.orderNo, - ) - if (existingIndex !== -1) { - positionOrderList.value[existingIndex] = positionObj - } else { - positionOrderList.value.unshift(positionObj) + const handleInfoListData = (list: PositionObj[]) => { + if (list?.length) { + for (let i = 0; i < list.length; i++) { + const idx = positionOrderList.value.findIndex((item)=>item.id === list[i]?.id) + if (idx !== -1) { + positionOrderList.value.splice(idx, 1, list[i] as PositionObj) + }else{ + positionOrderList.value.push(list[i] as PositionObj) + } + } } @@ -45,6 +46,7 @@ export const useOrderPositionStore = defineStore('orderPosition', () => { const handlePositionOrderList = async (item:string) => { const fields = item.split(':') + console.log(fields) const positionObj: PositionObj = { id: fields[2], // 订单号 account: fields[0], // 账户id diff --git a/src/stores/modules/transaction.ts b/src/stores/modules/transaction.ts index 6e752f0..b80233f 100644 --- a/src/stores/modules/transaction.ts +++ b/src/stores/modules/transaction.ts @@ -56,7 +56,7 @@ export const useTransactionStore = defineStore('transaction', () => { */ const initMarketConditionsMQTT = async (): Promise => { const { redis_key, token } = userStore.userInfo as UserInfo - +console.log("initMarketConditionsMQTT", redis_key, token) await mqttClient.connect(import.meta.env.VITE_MQTT_TRANSACTION_HOST as string, { username: `${redis_key}`, password: `${token}`, @@ -117,7 +117,7 @@ export const useTransactionStore = defineStore('transaction', () => { * 关闭订阅,清空数组 * */ const disconnect = () => { - mqttClient.disconnect() + mqttClient?.disconnect() positionList.value = [] } @@ -127,5 +127,6 @@ export const useTransactionStore = defineStore('transaction', () => { positionList, initMarketConditionsMQTT, getPositionList, + disconnect } }) diff --git a/src/utils/ListFrameThrottler.ts b/src/utils/ListFrameThrottler.ts index 87da20f..4de5459 100644 --- a/src/utils/ListFrameThrottler.ts +++ b/src/utils/ListFrameThrottler.ts @@ -1,74 +1,108 @@ -/** - * 帧级节流器数据项接口 - */ -interface ThrottlerDataItem { - id: string | number; - [key: string]: any; -} - /** * listFrameThrottler - 帧级节流器 -- 只用于持仓订单 * 收集同一帧内的所有数据,帧结束时只推送最后一个值 */ -export class ListFrameThrottler { - private callback: (data: T) => void; - private pendingData: Record; - private rafId: number | null; - constructor(callback: (data: T) => void) { - this.callback = callback; - this.pendingData = {}; - this.rafId = null; - } +export type FrameThrottlerData = object; - /** - * 推送数据(同一帧内多次调用只保留最后一个) - * @param data - 要推送的数据 - */ - push(data: T): void { - // 将数据放入待处理数组 - this.pendingData[data.id] = data; +export type FrameThrottlerCallback = (data: FrameThrottlerData[]) => void; - // 如果还没有安排帧回调,则注册一个 - if (!this.rafId) { - this.rafId = requestAnimationFrame(() => this._flush()); - } +export class ListFrameThrottler { + private callback: FrameThrottlerCallback; + private pendingData: Map; + private rafId: number | null; + private fallbackTimer: ReturnType | null; + private isVisible: boolean; + + constructor(callback: FrameThrottlerCallback) { + this.callback = callback; + this.pendingData = new Map(); + this.rafId = null; + this.fallbackTimer = null; + this.isVisible = true; + this._bindVisibility(); + } + + // 监听页面可见性变化 + _bindVisibility() { + if (typeof document === 'undefined') return; + + document.addEventListener('visibilitychange', () => { + this.isVisible = !document.hidden; + if (this.isVisible) { + // 切回前台:立即刷新积压数据 + this._clearFallback(); + this.flush(); + } else { + // 切入后台:启动定时器兜底 + this._startFallback(); + } + }); + } + + // 后台兜底:用 setTimeout 替代 rAF + private _startFallback() { + this._clearFallback(); + this.fallbackTimer = setInterval(() => { + if (this.pendingData.size > 0) { + this._flush(); + } + }, 500); + } + + private _clearFallback() { + if (this.fallbackTimer) { + clearInterval(this.fallbackTimer); + this.fallbackTimer = null; + } + } + + /** + * 推送数据(同一帧内多次调用只保留最后一个) + */ + push(data: FrameThrottlerData) { + const dataRecord = data as Record; + const key = String((dataRecord.id as string | number | undefined) ?? JSON.stringify(data)); + this.pendingData.set(key, data); + + if (!this.rafId) { + this.rafId = requestAnimationFrame(() => this._flush()); + } + } + + /** + * 帧结束时的回调:只推送最后一个数据 + */ + _flush() { + this.rafId = null; + const list = [...this.pendingData.values()] + if (list.length === 0) return; + this.pendingData = new Map(); // 清空缓存 + this.callback(list); + } + + /** + * 立即刷新(手动触发) + */ + flush() { + if (this.rafId) { + cancelAnimationFrame(this.rafId); + this.rafId = null; + } + this._clearFallback(); + this._flush(); + } + + /** + * 销毁实例 + */ + destroy() { + if (this.rafId) { + cancelAnimationFrame(this.rafId); + this.rafId = null; + } + this._clearFallback(); + this.pendingData = new Map(); + this.callback = () => {}; + } } - - /** - * 帧结束时的回调:只推送最后一个数据 - */ - private _flush(): void { - this.rafId = null; - const list = Object.values(this.pendingData); - if (list.length === 0) return; - - this.pendingData = {}; // 清空缓存 - for (let i = 0; i < list.length; i++) { - this.callback(list[i]); - } -} - - /** - * 立即刷新(手动触发) - */ - flush(): void { - if (this.rafId) { - cancelAnimationFrame(this.rafId); - this.rafId = null; - } - this._flush(); -} - - /** - * 销毁实例 - */ - destroy(): void { - if (this.rafId) { - cancelAnimationFrame(this.rafId); - this.rafId = null; - } - this.pendingData = {}; - this.callback = () => {}; // 避免 null 后可能的调用问题,或保持 null 但需调整类型 -} -} \ No newline at end of file diff --git a/src/views/order/position/index.vue b/src/views/order/position/index.vue index 0ce0cd7..c0e0311 100644 --- a/src/views/order/position/index.vue +++ b/src/views/order/position/index.vue @@ -77,6 +77,11 @@ const handleExport = () => { }) } onMounted(() => { + transactionStore?.disconnect() transactionStore.initMarketConditionsMQTT() }) +onUnmounted(()=>{ + transactionStore?.disconnect() +}) + diff --git a/src/views/order/position/options.ts b/src/views/order/position/options.ts index 93cd04b..28d4d82 100644 --- a/src/views/order/position/options.ts +++ b/src/views/order/position/options.ts @@ -9,16 +9,34 @@ export const search = [ ] export const tableColumns = [ + // 账户ID + { prop: 'account', label: '账户ID' ,width: '140px'}, // 订单号 - { prop: 'orderNo', label: '订单号' }, - // 操作类型 - { prop: 'tradeType', label: '操作类型' }, - // 金额 - { prop: 'amount', label: '金额', isNumber: true }, - // 交易数量 - { prop: 'tradeQty', label: '交易数量'}, - // 交易时间 - { prop: 'tradeTime', label: '交易时间' }, + { prop: 'orderNo', label: '订单号' ,width: '140px'}, + // 品种代码 + { prop: 'typeCode', label: '品种代码' ,width: '140px'}, + // 方向 + { prop: 'direction', label: '方向' ,width: '140px'}, + // 手数 + { prop: 'lots', label: '手数', isNumber: true ,width: '140px'}, + // 买入价格 + { prop: 'entryPrice', label: '买入价格', isNumber: true ,width: '140px'}, + // 当前价格 + { prop: 'currentPrice', label: '当前价格', isNumber: true,width: '140px' }, + // 止盈 + { prop: 'takeProfit', label: '止盈', isNumber: true,width: '140px' }, + // 止损 + { prop: 'stopLoss', label: '止损', isNumber: true,width: '140px' }, // 浮动盈亏 - { prop: 'floatProfit', label: '浮动盈亏' }, + { prop: 'floatingPL', label: '浮动盈亏', isNumber: true ,width: '140px'}, + // 保证金 + { prop: 'margin', label: '保证金', isNumber: true ,width: '140px'}, + // 手续费 + { prop: 'commission', label: '手续费', isNumber: true,width: '140px' }, + // 隔夜费 + { prop: 'overnightFee', label: '隔夜费', isNumber: true ,width: '140px'}, + // 交易时间 + { prop: 'timestamp', label: '交易时间',width: '140px' }, + // 带单人ID + { prop: 'singleId', label: '带单人ID',width: '140px' }, ] \ No newline at end of file diff --git a/src/views/profile/business/index.vue b/src/views/profile/business/index.vue index 6c77cbf..7afcbde 100644 --- a/src/views/profile/business/index.vue +++ b/src/views/profile/business/index.vue @@ -45,33 +45,31 @@ /> - +