/** * 实时金价 hooks(UniApp 单例模式实现) * @description 封装实时金价、黄金9999、铂金、白银、K金的价格获取与实时更新逻辑,通过单例WebSocket避免多页面连接超限,结合缓存减少接口请求 * * 核心特性: * 1. 单例WebSocket:全局唯一连接,通过引用计数管理生命周期(页面全部隐藏时断开,任一页面显示时重连) * 2. 多金属支持:基础黄金(RTJ_Au)、黄金9999(AU9999)、铂金(RTJ_Pt)、白银(RTJ_Ag)、K金(自动计算,基础黄金75%) * 3. 数据自动处理:实时更新销售价/回收价/调整价,自动维护当日最高/最低价 * 4. 缓存优化:接口请求结果缓存30秒,避免重复请求减轻服务器压力 * 5. 错误容错:WebSocket断连自动重连(3秒延迟),数据解析异常捕获日志 * * 依赖项: * - vue:ref/watch 响应式能力 * - @dcloudio/uni-app:页面生命周期(onShow/onHide/onUnload) * - @/api/abacus:getCurrentPrice 接口(获取初始金价) * - pako:WebSocket消息解压(gzip格式) * - @/hooks/useSocket:WebSocket基础封装(需支持on/send/connect/disconnect方法) * - @/utils/weAtob:base64解码工具(处理WebSocket消息) * * 注意事项: * - WebSocket地址(wss://litews.jjh9999.com/wsv2/)、token(junit:test) * - 缓存时长(CACHE_DURATION)默认30秒,可根据需求调整 * - K金价格依赖基础黄金自动计算(75%比例),无需额外接口请求 */ import { ref, watch } from "vue"; import { onShow, onHide, onUnload } from "@dcloudio/uni-app"; import { getCurrentPrice } from "@/api/abacus"; import pako from "pako/dist/pako.min.js"; import useWebSocket from "@/hooks/useSocket"; import { weAtob } from "@/utils"; // WebSocket单例核心变量 let wsInstance = null; let connectCount = 0; let isConnected = false; const dataCallbacks = new Set(); // 初始化WebSocket单例 const initWSSingleton = () => { if (wsInstance) return wsInstance; // 创建WebSocket实例 const ws = useWebSocket("wss://litews.jjh9999.com/wsv2/", { heartbeat: false, heartbeatInterval: 20000, }); // 初始化参数 ws.on("open", () => { console.log("WS单例连接成功"); isConnected = true; ws.send({ company_id: "2289041670615554", id: "2189041670615552", code: 1, token: "junit:test", type: 1, }); }); // 接收消息:统一解析并分发给所有注册的回调 ws.on("message", (msg) => { try { const strData = weAtob(msg.data); const charData = strData.split("").map((x) => x.charCodeAt(0)); const binData = new Uint8Array(charData); const tempData = pako.inflate(binData, { to: "string" }); const { data } = JSON.parse(tempData); if (data && data?.code !== 0) { const itemData = JSON.parse(data); if (itemData?.priceDataNew) { const priceData = { itemAu: itemData.priceDataNew.find((v) => v.gmCode === "RTJ_Au"), itemAu9999: itemData.priceDataNew.find( (v) => v.gmCode === "AU9999" ), itemPt: itemData.priceDataNew.find((v) => v.gmCode === "RTJ_Pt"), itemAg: itemData.priceDataNew.find((v) => v.gmCode === "RTJ_Ag"), }; dataCallbacks.forEach((callback) => callback(priceData)); } } } catch (error) { console.error("WS单例消息处理错误:", error); } }); // 连接关闭:若仍有页面使用,3秒后自动重连 ws.on("close", () => { isConnected = false; if (connectCount > 0) { setTimeout(() => ws.connect(), 3000); } }); ws.on("error", (error) => { console.error("WS单例连接错误:", error); isConnected = false; }); wsInstance = ws; return ws; }; const CACHE_DURATION = 30 * 1000; // 缓存有效期:30秒 const fetchCache = new Map(); export default function useRealGoldPrice(options) { const { realCode = "All" } = options || {}; // 基础黄金(RTJ_Au) const realGoldprice = ref(0); // 实时销售价 const realGoldRecyclePrice = ref(0); // 实时回收价 const goldAdjustPrice = ref(0); // 调整价 const goldTodayHigh = ref(0); // 今日最高价 const goldTodayLow = ref(0); // 今日最低价 // 黄金9999(AU9999) const realAu9999price = ref(0); const realAu9999RecyclePrice = ref(0); const Au9999AdjustPrice = ref(0); const au9999TodayHigh = ref(0); const au9999TodayLow = ref(0); // 铂金(RTJ_Pt) const realPtprice = ref(0); const realPtRecyclePrice = ref(0); const PtAdjustPrice = ref(0); const ptTodayHigh = ref(0); const ptTodayLow = ref(0); // 白银(RTJ_Ag) const realAgprice = ref(0); const realAgRecyclePrice = ref(0); const AgAdjustPrice = ref(0); const agTodayHigh = ref(0); const agTodayLow = ref(0); // K金(基础黄金75%自动计算) const realKGoldprice = ref(0); const realKGoldRecyclePrice = ref(0); const KGoldAdjustPrice = ref(0); const kGoldTodayHigh = ref(0); const kGoldTodayLow = ref(0); // 当前价(简化快捷访问) const currentGoldPrice = ref(0); const currentAu9999Price = ref(0); const currentPtPrice = ref(0); const currentAgPrice = ref(0); const currentKGoldPrice = ref(0); // 更新当日最高/最低价 const updateHighLowPrices = (currentPrice, highRef, lowRef) => { const price = Number(currentPrice); if (isNaN(price)) return; // 首次设置价格时,同步初始化最高/最低价 if (highRef.value === 0 && lowRef.value === 0) { highRef.value = price; lowRef.value = price; return; } highRef.value = Math.max(highRef.value, price); lowRef.value = Math.min(lowRef.value, price); }; // 统一更新金属价格(销售价/回收价) const updatePrice = ( item, sellPriceRef, recyclePriceRef, adjustPriceRef, highRef, lowRef ) => { if (item?.sellPrice?.price) { const sellPrice = ( Number(item.sellPrice.price) + Number(adjustPriceRef.value) ).toFixed(2); sellPriceRef.value = sellPrice; updateHighLowPrices(sellPrice, highRef, lowRef); } if (item?.buyPrice?.price) { recyclePriceRef.value = ( Number(item.buyPrice.price) + Number(adjustPriceRef.value) ).toFixed(2); } }; watch( [realGoldprice, realGoldRecyclePrice, goldAdjustPrice], ([newSell, newRecycle, newAdjust]) => { const kSell = (Number(newSell) * 0.75).toFixed(2); realKGoldprice.value = kSell; realKGoldRecyclePrice.value = (Number(newRecycle) * 0.75).toFixed(2); KGoldAdjustPrice.value = (Number(newAdjust) * 0.75).toFixed(2); currentKGoldPrice.value = kSell; updateHighLowPrices(kSell, kGoldTodayHigh, kGoldTodayLow); }, { immediate: true } ); // WebSocket单例集成 const ws = initWSSingleton(); // 当前页面的价格更新回调(接收单例分发的数据) const handlePriceUpdate = (priceData) => { const { itemAu, itemAu9999, itemPt, itemAg } = priceData; // 根据realCode筛选需要更新的金属类型 switch (realCode) { case "RTJ_Au": updatePrice( itemAu, realGoldprice, realGoldRecyclePrice, goldAdjustPrice, goldTodayHigh, goldTodayLow ); currentGoldPrice.value = realGoldprice.value; break; case "AU9999": updatePrice( itemAu9999, realAu9999price, realAu9999RecyclePrice, Au9999AdjustPrice, au9999TodayHigh, au9999TodayLow ); currentAu9999Price.value = realAu9999price.value; break; case "RTJ_Pt": updatePrice( itemPt, realPtprice, realPtRecyclePrice, PtAdjustPrice, ptTodayHigh, ptTodayLow ); currentPtPrice.value = realPtprice.value; break; case "RTJ_Ag": updatePrice( itemAg, realAgprice, realAgRecyclePrice, AgAdjustPrice, agTodayHigh, agTodayLow ); currentAgPrice.value = realAgprice.value; break; case "RTJ_KGold": break; // K金依赖基础黄金,无需额外处理 case "All": updatePrice( itemAu, realGoldprice, realGoldRecyclePrice, goldAdjustPrice, goldTodayHigh, goldTodayLow ); updatePrice( itemAu9999, realAu9999price, realAu9999RecyclePrice, Au9999AdjustPrice, au9999TodayHigh, au9999TodayLow ); updatePrice( itemPt, realPtprice, realPtRecyclePrice, PtAdjustPrice, ptTodayHigh, ptTodayLow ); updatePrice( itemAg, realAgprice, realAgRecyclePrice, AgAdjustPrice, agTodayHigh, agTodayLow ); currentGoldPrice.value = realGoldprice.value; currentAu9999Price.value = realAu9999price.value; currentPtPrice.value = realPtprice.value; currentAgPrice.value = realAgprice.value; break; } }; onShow(() => { connectCount++; if (connectCount === 1 && !isConnected) { ws.connect(); } initPriceData(); }); onHide(() => { connectCount = Math.max(connectCount - 1, 0); if (connectCount === 0 && isConnected) { ws.disconnect(); } }); onUnload(() => { // 页面卸载时移除回调,避免内存泄漏 dataCallbacks.delete(handlePriceUpdate); connectCount = Math.max(connectCount - 1, 0); if (connectCount === 0 && isConnected) { ws.disconnect(); } }); // 注册当前页面的价格更新回调 dataCallbacks.add(handlePriceUpdate); // 主动获取金属价格(支持缓存) async function fetchGoldPrice(code = "RTJ_Au", tradeType = 3) { const cacheKey = `${code}_${tradeType}`; const now = Date.now(); // 优先使用缓存(30秒内有效) const cached = fetchCache.get(cacheKey); if (cached && now - cached.lastFetchTime < CACHE_DURATION) { handleFetchData(cached.data, code, tradeType); return cached.data; } // 缓存失效,发起接口请求 const params = { tradeType }; if (code === "AU9999" || code === "RTJ_Pt" || code === "RTJ_Ag") { params.code = code; } try { const { data } = await getCurrentPrice(params); // 缓存请求结果 fetchCache.set(cacheKey, { lastFetchTime: now, data }); // 处理并更新价格 handleFetchData(data, code, tradeType); return data; } catch (error) { console.error(`获取${code}价格失败:`, error); throw error; } } // 根据realCode初始化对应金属价格 const initPriceData = () => { const tasks = []; if (realCode === "All" || realCode === "RTJ_Au") tasks.push(fetchGoldPrice("RTJ_Au")); if (realCode === "All" || realCode === "AU9999") tasks.push(fetchGoldPrice("AU9999")); if (realCode === "All" || realCode === "RTJ_Pt") tasks.push(fetchGoldPrice("RTJ_Pt")); if (realCode === "All" || realCode === "RTJ_Ag") tasks.push(fetchGoldPrice("RTJ_Ag")); Promise.all(tasks).catch((err) => console.error("初始化价格失败:", err)); }; // 处理接口返回的价格数据 const handleFetchData = (data, code, tradeType) => { let sellRef, recycleRef, adjustRef, highRef, lowRef; // 匹配对应金属的响应式变量 switch (code) { case "AU9999": [sellRef, recycleRef, adjustRef, highRef, lowRef] = [ realAu9999price, realAu9999RecyclePrice, Au9999AdjustPrice, au9999TodayHigh, au9999TodayLow, ]; break; case "RTJ_Pt": [sellRef, recycleRef, adjustRef, highRef, lowRef] = [ realPtprice, realPtRecyclePrice, PtAdjustPrice, ptTodayHigh, ptTodayLow, ]; break; case "RTJ_Ag": [sellRef, recycleRef, adjustRef, highRef, lowRef] = [ realAgprice, realAgRecyclePrice, AgAdjustPrice, agTodayHigh, agTodayLow, ]; break; default: // RTJ_Au(基础黄金) [sellRef, recycleRef, adjustRef, highRef, lowRef] = [ realGoldprice, realGoldRecyclePrice, goldAdjustPrice, goldTodayHigh, goldTodayLow, ]; break; } // 计算最终价格(调整价+基础价) const adjustPrice = tradeType === 3 ? 0 : data.adjustPrice; adjustRef.value = adjustPrice; const sellPrice = (Number(adjustPrice) + Number(data.sellPrice)).toFixed(2); sellRef.value = sellPrice; recycleRef.value = ( Number(adjustPrice) + Number(data.buyPrice || 0) ).toFixed(2); updateHighLowPrices(sellPrice, highRef, lowRef); // 更新当前价快捷访问变量 if (code === "RTJ_Au") currentGoldPrice.value = sellPrice; if (code === "AU9999") currentAu9999Price.value = sellPrice; if (code === "RTJ_Pt") currentPtPrice.value = sellPrice; if (code === "RTJ_Ag") currentAgPrice.value = sellPrice; }; return { // 基础黄金相关 realGoldprice, realGoldRecyclePrice, goldAdjustPrice, goldTodayHigh, goldTodayLow, // 黄金9999相关 realAu9999price, realAu9999RecyclePrice, Au9999AdjustPrice, currentAu9999Price, au9999TodayHigh, au9999TodayLow, // 铂金相关 realPtprice, realPtRecyclePrice, PtAdjustPrice, ptTodayHigh, ptTodayLow, // 白银相关 realAgprice, realAgRecyclePrice, AgAdjustPrice, agTodayHigh, agTodayLow, // K金相关 realKGoldprice, realKGoldRecyclePrice, KGoldAdjustPrice, kGoldTodayHigh, kGoldTodayLow, // 当前价快捷访问 currentGoldPrice, currentPtPrice, currentAgPrice, currentKGoldPrice, // 主动获取价格方法 fetchGoldPrice, }; }