useRealGoldPrice.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482
  1. /**
  2. * 实时金价 hooks(UniApp 单例模式实现)
  3. * @description 封装实时金价、黄金9999、铂金、白银、K金的价格获取与实时更新逻辑,通过单例WebSocket避免多页面连接超限,结合缓存减少接口请求
  4. *
  5. * 核心特性:
  6. * 1. 单例WebSocket:全局唯一连接,通过引用计数管理生命周期(页面全部隐藏时断开,任一页面显示时重连)
  7. * 2. 多金属支持:基础黄金(RTJ_Au)、黄金9999(AU9999)、铂金(RTJ_Pt)、白银(RTJ_Ag)、K金(自动计算,基础黄金75%)
  8. * 3. 数据自动处理:实时更新销售价/回收价/调整价,自动维护当日最高/最低价
  9. * 4. 缓存优化:接口请求结果缓存30秒,避免重复请求减轻服务器压力
  10. * 5. 错误容错:WebSocket断连自动重连(3秒延迟),数据解析异常捕获日志
  11. *
  12. * 依赖项:
  13. * - vue:ref/watch 响应式能力
  14. * - @dcloudio/uni-app:页面生命周期(onShow/onHide/onUnload)
  15. * - @/api/abacus:getCurrentPrice 接口(获取初始金价)
  16. * - pako:WebSocket消息解压(gzip格式)
  17. * - @/hooks/useSocket:WebSocket基础封装(需支持on/send/connect/disconnect方法)
  18. * - @/utils/weAtob:base64解码工具(处理WebSocket消息)
  19. *
  20. * 注意事项:
  21. * - WebSocket地址(wss://litews.jjh9999.com/wsv2/)、token(junit:test)
  22. * - 缓存时长(CACHE_DURATION)默认30秒,可根据需求调整
  23. * - K金价格依赖基础黄金自动计算(75%比例),无需额外接口请求
  24. */
  25. import { ref, watch } from "vue";
  26. import { onShow, onHide, onUnload } from "@dcloudio/uni-app";
  27. import { getCurrentPrice } from "@/api/abacus";
  28. import pako from "pako/dist/pako.min.js";
  29. import useWebSocket from "@/hooks/useSocket";
  30. import { weAtob } from "@/utils";
  31. // WebSocket单例核心变量
  32. let wsInstance = null;
  33. let connectCount = 0;
  34. let isConnected = false;
  35. const dataCallbacks = new Set();
  36. // 初始化WebSocket单例
  37. const initWSSingleton = () => {
  38. if (wsInstance) return wsInstance;
  39. // 创建WebSocket实例
  40. const ws = useWebSocket("wss://litews.jjh9999.com/wsv2/", {
  41. heartbeat: false,
  42. heartbeatInterval: 20000,
  43. });
  44. // 初始化参数
  45. ws.on("open", () => {
  46. console.log("WS单例连接成功");
  47. isConnected = true;
  48. ws.send({
  49. company_id: "2289041670615554",
  50. id: "2189041670615552",
  51. code: 1,
  52. token: "junit:test",
  53. type: 1,
  54. });
  55. });
  56. // 接收消息:统一解析并分发给所有注册的回调
  57. ws.on("message", (msg) => {
  58. try {
  59. const strData = weAtob(msg.data);
  60. const charData = strData.split("").map((x) => x.charCodeAt(0));
  61. const binData = new Uint8Array(charData);
  62. const tempData = pako.inflate(binData, { to: "string" });
  63. const { data } = JSON.parse(tempData);
  64. if (data && data?.code !== 0) {
  65. const itemData = JSON.parse(data);
  66. if (itemData?.priceDataNew) {
  67. const priceData = {
  68. itemAu: itemData.priceDataNew.find((v) => v.gmCode === "RTJ_Au"),
  69. itemAu9999: itemData.priceDataNew.find(
  70. (v) => v.gmCode === "AU9999"
  71. ),
  72. itemPt: itemData.priceDataNew.find((v) => v.gmCode === "RTJ_Pt"),
  73. itemAg: itemData.priceDataNew.find((v) => v.gmCode === "RTJ_Ag"),
  74. };
  75. dataCallbacks.forEach((callback) => callback(priceData));
  76. }
  77. }
  78. } catch (error) {
  79. console.error("WS单例消息处理错误:", error);
  80. }
  81. });
  82. // 连接关闭:若仍有页面使用,3秒后自动重连
  83. ws.on("close", () => {
  84. isConnected = false;
  85. if (connectCount > 0) {
  86. setTimeout(() => ws.connect(), 3000);
  87. }
  88. });
  89. ws.on("error", (error) => {
  90. console.error("WS单例连接错误:", error);
  91. isConnected = false;
  92. });
  93. wsInstance = ws;
  94. return ws;
  95. };
  96. const CACHE_DURATION = 30 * 1000; // 缓存有效期:30秒
  97. const fetchCache = new Map();
  98. export default function useRealGoldPrice(options) {
  99. const { realCode = "All" } = options || {};
  100. // 基础黄金(RTJ_Au)
  101. const realGoldprice = ref(0); // 实时销售价
  102. const realGoldRecyclePrice = ref(0); // 实时回收价
  103. const goldAdjustPrice = ref(0); // 调整价
  104. const goldTodayHigh = ref(0); // 今日最高价
  105. const goldTodayLow = ref(0); // 今日最低价
  106. // 黄金9999(AU9999)
  107. const realAu9999price = ref(0);
  108. const realAu9999RecyclePrice = ref(0);
  109. const Au9999AdjustPrice = ref(0);
  110. const au9999TodayHigh = ref(0);
  111. const au9999TodayLow = ref(0);
  112. // 铂金(RTJ_Pt)
  113. const realPtprice = ref(0);
  114. const realPtRecyclePrice = ref(0);
  115. const PtAdjustPrice = ref(0);
  116. const ptTodayHigh = ref(0);
  117. const ptTodayLow = ref(0);
  118. // 白银(RTJ_Ag)
  119. const realAgprice = ref(0);
  120. const realAgRecyclePrice = ref(0);
  121. const AgAdjustPrice = ref(0);
  122. const agTodayHigh = ref(0);
  123. const agTodayLow = ref(0);
  124. // K金(基础黄金75%自动计算)
  125. const realKGoldprice = ref(0);
  126. const realKGoldRecyclePrice = ref(0);
  127. const KGoldAdjustPrice = ref(0);
  128. const kGoldTodayHigh = ref(0);
  129. const kGoldTodayLow = ref(0);
  130. // 当前价(简化快捷访问)
  131. const currentGoldPrice = ref(0);
  132. const currentAu9999Price = ref(0);
  133. const currentPtPrice = ref(0);
  134. const currentAgPrice = ref(0);
  135. const currentKGoldPrice = ref(0);
  136. // 更新当日最高/最低价
  137. const updateHighLowPrices = (currentPrice, highRef, lowRef) => {
  138. const price = Number(currentPrice);
  139. if (isNaN(price)) return;
  140. // 首次设置价格时,同步初始化最高/最低价
  141. if (highRef.value === 0 && lowRef.value === 0) {
  142. highRef.value = price;
  143. lowRef.value = price;
  144. return;
  145. }
  146. highRef.value = Math.max(highRef.value, price);
  147. lowRef.value = Math.min(lowRef.value, price);
  148. };
  149. // 统一更新金属价格(销售价/回收价)
  150. const updatePrice = (
  151. item,
  152. sellPriceRef,
  153. recyclePriceRef,
  154. adjustPriceRef,
  155. highRef,
  156. lowRef
  157. ) => {
  158. if (item?.sellPrice?.price) {
  159. const sellPrice = (
  160. Number(item.sellPrice.price) + Number(adjustPriceRef.value)
  161. ).toFixed(2);
  162. sellPriceRef.value = sellPrice;
  163. updateHighLowPrices(sellPrice, highRef, lowRef);
  164. }
  165. if (item?.buyPrice?.price) {
  166. recyclePriceRef.value = (
  167. Number(item.buyPrice.price) + Number(adjustPriceRef.value)
  168. ).toFixed(2);
  169. }
  170. };
  171. watch(
  172. [realGoldprice, realGoldRecyclePrice, goldAdjustPrice],
  173. ([newSell, newRecycle, newAdjust]) => {
  174. const kSell = (Number(newSell) * 0.75).toFixed(2);
  175. realKGoldprice.value = kSell;
  176. realKGoldRecyclePrice.value = (Number(newRecycle) * 0.75).toFixed(2);
  177. KGoldAdjustPrice.value = (Number(newAdjust) * 0.75).toFixed(2);
  178. currentKGoldPrice.value = kSell;
  179. updateHighLowPrices(kSell, kGoldTodayHigh, kGoldTodayLow);
  180. },
  181. { immediate: true }
  182. );
  183. // WebSocket单例集成
  184. const ws = initWSSingleton();
  185. // 当前页面的价格更新回调(接收单例分发的数据)
  186. const handlePriceUpdate = (priceData) => {
  187. const { itemAu, itemAu9999, itemPt, itemAg } = priceData;
  188. // 根据realCode筛选需要更新的金属类型
  189. switch (realCode) {
  190. case "RTJ_Au":
  191. updatePrice(
  192. itemAu,
  193. realGoldprice,
  194. realGoldRecyclePrice,
  195. goldAdjustPrice,
  196. goldTodayHigh,
  197. goldTodayLow
  198. );
  199. currentGoldPrice.value = realGoldprice.value;
  200. break;
  201. case "AU9999":
  202. updatePrice(
  203. itemAu9999,
  204. realAu9999price,
  205. realAu9999RecyclePrice,
  206. Au9999AdjustPrice,
  207. au9999TodayHigh,
  208. au9999TodayLow
  209. );
  210. currentAu9999Price.value = realAu9999price.value;
  211. break;
  212. case "RTJ_Pt":
  213. updatePrice(
  214. itemPt,
  215. realPtprice,
  216. realPtRecyclePrice,
  217. PtAdjustPrice,
  218. ptTodayHigh,
  219. ptTodayLow
  220. );
  221. currentPtPrice.value = realPtprice.value;
  222. break;
  223. case "RTJ_Ag":
  224. updatePrice(
  225. itemAg,
  226. realAgprice,
  227. realAgRecyclePrice,
  228. AgAdjustPrice,
  229. agTodayHigh,
  230. agTodayLow
  231. );
  232. currentAgPrice.value = realAgprice.value;
  233. break;
  234. case "RTJ_KGold":
  235. break; // K金依赖基础黄金,无需额外处理
  236. case "All":
  237. updatePrice(
  238. itemAu,
  239. realGoldprice,
  240. realGoldRecyclePrice,
  241. goldAdjustPrice,
  242. goldTodayHigh,
  243. goldTodayLow
  244. );
  245. updatePrice(
  246. itemAu9999,
  247. realAu9999price,
  248. realAu9999RecyclePrice,
  249. Au9999AdjustPrice,
  250. au9999TodayHigh,
  251. au9999TodayLow
  252. );
  253. updatePrice(
  254. itemPt,
  255. realPtprice,
  256. realPtRecyclePrice,
  257. PtAdjustPrice,
  258. ptTodayHigh,
  259. ptTodayLow
  260. );
  261. updatePrice(
  262. itemAg,
  263. realAgprice,
  264. realAgRecyclePrice,
  265. AgAdjustPrice,
  266. agTodayHigh,
  267. agTodayLow
  268. );
  269. currentGoldPrice.value = realGoldprice.value;
  270. currentAu9999Price.value = realAu9999price.value;
  271. currentPtPrice.value = realPtprice.value;
  272. currentAgPrice.value = realAgprice.value;
  273. break;
  274. }
  275. };
  276. onShow(() => {
  277. connectCount++;
  278. if (connectCount === 1 && !isConnected) {
  279. ws.connect();
  280. }
  281. initPriceData();
  282. });
  283. onHide(() => {
  284. connectCount = Math.max(connectCount - 1, 0);
  285. if (connectCount === 0 && isConnected) {
  286. ws.disconnect();
  287. }
  288. });
  289. onUnload(() => {
  290. // 页面卸载时移除回调,避免内存泄漏
  291. dataCallbacks.delete(handlePriceUpdate);
  292. connectCount = Math.max(connectCount - 1, 0);
  293. if (connectCount === 0 && isConnected) {
  294. ws.disconnect();
  295. }
  296. });
  297. // 注册当前页面的价格更新回调
  298. dataCallbacks.add(handlePriceUpdate);
  299. // 主动获取金属价格(支持缓存)
  300. async function fetchGoldPrice(code = "RTJ_Au", tradeType = 3) {
  301. const cacheKey = `${code}_${tradeType}`;
  302. const now = Date.now();
  303. // 优先使用缓存(30秒内有效)
  304. const cached = fetchCache.get(cacheKey);
  305. if (cached && now - cached.lastFetchTime < CACHE_DURATION) {
  306. handleFetchData(cached.data, code, tradeType);
  307. return cached.data;
  308. }
  309. // 缓存失效,发起接口请求
  310. const params = { tradeType };
  311. if (code === "AU9999" || code === "RTJ_Pt" || code === "RTJ_Ag") {
  312. params.code = code;
  313. }
  314. try {
  315. const { data } = await getCurrentPrice(params);
  316. // 缓存请求结果
  317. fetchCache.set(cacheKey, { lastFetchTime: now, data });
  318. // 处理并更新价格
  319. handleFetchData(data, code, tradeType);
  320. return data;
  321. } catch (error) {
  322. console.error(`获取${code}价格失败:`, error);
  323. throw error;
  324. }
  325. }
  326. // 根据realCode初始化对应金属价格
  327. const initPriceData = () => {
  328. const tasks = [];
  329. if (realCode === "All" || realCode === "RTJ_Au")
  330. tasks.push(fetchGoldPrice("RTJ_Au"));
  331. if (realCode === "All" || realCode === "AU9999")
  332. tasks.push(fetchGoldPrice("AU9999"));
  333. if (realCode === "All" || realCode === "RTJ_Pt")
  334. tasks.push(fetchGoldPrice("RTJ_Pt"));
  335. if (realCode === "All" || realCode === "RTJ_Ag")
  336. tasks.push(fetchGoldPrice("RTJ_Ag"));
  337. Promise.all(tasks).catch((err) => console.error("初始化价格失败:", err));
  338. };
  339. // 处理接口返回的价格数据
  340. const handleFetchData = (data, code, tradeType) => {
  341. let sellRef, recycleRef, adjustRef, highRef, lowRef;
  342. // 匹配对应金属的响应式变量
  343. switch (code) {
  344. case "AU9999":
  345. [sellRef, recycleRef, adjustRef, highRef, lowRef] = [
  346. realAu9999price,
  347. realAu9999RecyclePrice,
  348. Au9999AdjustPrice,
  349. au9999TodayHigh,
  350. au9999TodayLow,
  351. ];
  352. break;
  353. case "RTJ_Pt":
  354. [sellRef, recycleRef, adjustRef, highRef, lowRef] = [
  355. realPtprice,
  356. realPtRecyclePrice,
  357. PtAdjustPrice,
  358. ptTodayHigh,
  359. ptTodayLow,
  360. ];
  361. break;
  362. case "RTJ_Ag":
  363. [sellRef, recycleRef, adjustRef, highRef, lowRef] = [
  364. realAgprice,
  365. realAgRecyclePrice,
  366. AgAdjustPrice,
  367. agTodayHigh,
  368. agTodayLow,
  369. ];
  370. break;
  371. default: // RTJ_Au(基础黄金)
  372. [sellRef, recycleRef, adjustRef, highRef, lowRef] = [
  373. realGoldprice,
  374. realGoldRecyclePrice,
  375. goldAdjustPrice,
  376. goldTodayHigh,
  377. goldTodayLow,
  378. ];
  379. break;
  380. }
  381. // 计算最终价格(调整价+基础价)
  382. const adjustPrice = tradeType === 3 ? 0 : data.adjustPrice;
  383. adjustRef.value = adjustPrice;
  384. const sellPrice = (Number(adjustPrice) + Number(data.sellPrice)).toFixed(2);
  385. sellRef.value = sellPrice;
  386. recycleRef.value = (
  387. Number(adjustPrice) + Number(data.buyPrice || 0)
  388. ).toFixed(2);
  389. updateHighLowPrices(sellPrice, highRef, lowRef);
  390. // 更新当前价快捷访问变量
  391. if (code === "RTJ_Au") currentGoldPrice.value = sellPrice;
  392. if (code === "AU9999") currentAu9999Price.value = sellPrice;
  393. if (code === "RTJ_Pt") currentPtPrice.value = sellPrice;
  394. if (code === "RTJ_Ag") currentAgPrice.value = sellPrice;
  395. };
  396. return {
  397. // 基础黄金相关
  398. realGoldprice,
  399. realGoldRecyclePrice,
  400. goldAdjustPrice,
  401. goldTodayHigh,
  402. goldTodayLow,
  403. // 黄金9999相关
  404. realAu9999price,
  405. realAu9999RecyclePrice,
  406. Au9999AdjustPrice,
  407. currentAu9999Price,
  408. au9999TodayHigh,
  409. au9999TodayLow,
  410. // 铂金相关
  411. realPtprice,
  412. realPtRecyclePrice,
  413. PtAdjustPrice,
  414. ptTodayHigh,
  415. ptTodayLow,
  416. // 白银相关
  417. realAgprice,
  418. realAgRecyclePrice,
  419. AgAdjustPrice,
  420. agTodayHigh,
  421. agTodayLow,
  422. // K金相关
  423. realKGoldprice,
  424. realKGoldRecyclePrice,
  425. KGoldAdjustPrice,
  426. kGoldTodayHigh,
  427. kGoldTodayLow,
  428. // 当前价快捷访问
  429. currentGoldPrice,
  430. currentPtPrice,
  431. currentAgPrice,
  432. currentKGoldPrice,
  433. // 主动获取价格方法
  434. fetchGoldPrice,
  435. };
  436. }