import {createGlobalState, useLocalStorage} from '@vueuse/core' import {ref} from "vue"; import {goodStore} from "@/stores/goods/index.js"; import {authStore} from "@/stores/auth/index.js"; import {message} from "~/components/x-message/useMessage.js"; import { WebSocketClient } from '@/utils/websocket' import {logSendlog} from "~/api/goods/index.js"; import CryptoJS from "crypto-js"; import {useI18n} from 'vue-i18n' export const liveStore = createGlobalState(() => { const {auctionDetail} = goodStore(); const { token } = authStore() const t=useI18n().t const fullLive = ref(false) const quoteStatus = ref(false) const show = ref(false) const cleanup = ref(null) const show1=ref(false) const playerId=ref('J_prismPlayer') const auctionData=useLocalStorage('auctionData',{}) const socket=ref(null) const config = useRuntimeConfig() const pullLink=ref('') const isMinWindow = ref(false) const lastSnapshot = ref('') const liveInfo = ref(null) // 设置最小化状态 const setMinWindow = (status) => { isMinWindow.value = status } // 设置截图 const setSnapshot = (snapshot) => { lastSnapshot.value = snapshot } // 设置直播信息 const setLiveInfo = (info) => { liveInfo.value = info } // 重置状态 const reset = () => { isMinWindow.value = false lastSnapshot.value = '' liveInfo.value = null } // 解密工具函数 const decryptUtils = { // 解密配置 cryptConfig: { password: 'live-skkoql-1239-key', salt: 'aldk100128ls', iterations: 10000, keySize: 32 }, // 生成密钥 generateKey(password, salt, iterations, keySize) { return CryptoJS.PBKDF2(password, salt, { keySize: keySize / 4, iterations: iterations, hasher: CryptoJS.algo.SHA1 }).toString(CryptoJS.enc.Hex) }, // AES解密 decrypt(ciphertextBase64, key) { const combined = CryptoJS.enc.Base64.parse(ciphertextBase64) const iv = CryptoJS.lib.WordArray.create(combined.words.slice(0, 4)) const ciphertext = CryptoJS.lib.WordArray.create(combined.words.slice(4)) const decrypted = CryptoJS.AES.decrypt( {ciphertext: ciphertext}, CryptoJS.enc.Hex.parse(key), { iv: iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 } ) return decrypted.toString(CryptoJS.enc.Utf8) }, // 解密数据的主函数 decryptData(encryptedData) { const keyDerived = this.generateKey( this.cryptConfig.password, this.cryptConfig.salt, this.cryptConfig.iterations, this.cryptConfig.keySize ) return this.decrypt(encryptedData, keyDerived) } } const getLiveLink = () => { return new Promise(async(resolve, reject) => { const res = await logSendlog({ uuid: auctionDetail.value.uuid }) if (res.status===0){ pullLink.value=decryptUtils.decryptData(res.data.code) resolve(decryptUtils.decryptData(res.data.code)) } }) } // 定义常量 const WS_TYPES = { TIP: 'tip', STOP_ARTWORK: 'stopArtwork', OVER: 'over' } const TIP_TYPES = { FALLING: 'falling', OTHERS_BID: 'othersBid', SUCCESS_BID: 'successBid', ARTWORK_OVER: 'artworkOver', FAIL_BID: 'failBid' } // 基础消息配置 const BASE_MESSAGE_STYLE = { width: '151px', bottom: '265px' } const createMessageConfig = (text, color, subText = '', extraStyle = {}) => ({ title: { text, color, align: 'center' }, icon: false, ...(subText && { subTitle: { text: subText, color: '#939393', align: 'center' } }), style: { ...BASE_MESSAGE_STYLE, ...extraStyle } }) const getSocketData = async () => { const wsClient = new WebSocketClient( config.public.NUXT_PUBLIC_SOCKET_URL, token.value ) const ws = wsClient.connect('/api/v1/m/auction/live', { auctionUuid: auctionDetail.value.uuid, }) // 处理消息提示 const handleTipMessage = (tipType) => { const tipConfigs = { [TIP_TYPES.FALLING]: () => message.warning(createMessageConfig(t('live_room.text1'), '#F09F1F')), [TIP_TYPES.OTHERS_BID]: () => message.error(createMessageConfig(t('live_room.text2'), '#CF3050', t('live_room.text3'))), [TIP_TYPES.SUCCESS_BID]: ()=>{ quoteStatus.value=false message.success(createMessageConfig(t('live_room.text4'), '#18A058', t('live_room.text5'))) }, [TIP_TYPES.ARTWORK_OVER]: () =>{ quoteStatus.value=false message.success(createMessageConfig( t('live_room.text6'), '#575757', t('live_room.text7'), { backgroundColor: '#fff', borderColor: '#fff' } )) }, [TIP_TYPES.FAIL_BID]: () => message.error(createMessageConfig( t('live_room.text8'), '#CF3050', t('live_room.text9'), { width: '186px' } )) } const handler = tipConfigs[tipType] if (handler) handler() } // WebSocket 事件处理 ws.onOpen(() => { }) ws.onMessage((data) => { auctionData.value = data.data const { wsType, tip } = data.data || {} switch (wsType) { case WS_TYPES.TIP: handleTipMessage(tip?.tipType) break case WS_TYPES.STOP_ARTWORK: //quoteStatus.value = false break case WS_TYPES.OVER: quoteStatus.value = false message.success(createMessageConfig( t('live_room.text10'), '#575757', '', { width: '195px', backgroundColor: '#fff', borderColor: '#fff' } )) break } }) ws.onClose(() => { }) ws.onError((error) => { }) } const changeStatus = () => { if (auctionData.value.artwork?.isSelling&&!auctionData.value.artwork.isSoled){ quoteStatus.value = !quoteStatus.value }else { if (quoteStatus.value){ quoteStatus.value = false } } } return{ fullLive, isMinWindow, lastSnapshot, liveInfo, setMinWindow, setSnapshot, setLiveInfo, reset, pullLink, getLiveLink, auctionData, getSocketData, show1, playerId, show, quoteStatus, changeStatus } })