feat(live): 优化直播间功能并添加画中画支持
- 更新直播源地址 - 添加画中画功能,支持视频拖动和缩放 - 实现直播加密密钥生成和数据解密 - 优化直播房间组件,支持全屏和缩略图模式 - 新增日志发送接口
This commit is contained in:
parent
10cba595b0
commit
86198811aa
@ -45,4 +45,12 @@ export async function artworkBuy(data) {
|
||||
method: 'POST',
|
||||
data
|
||||
})
|
||||
}
|
||||
export async function logSendlog(data) {
|
||||
|
||||
return await request( {
|
||||
url:'/api/v1/m/auction/log/sendlog',
|
||||
method: 'POST',
|
||||
data
|
||||
})
|
||||
}
|
207
app/components/floatingVideo/index.vue
Normal file
207
app/components/floatingVideo/index.vue
Normal file
@ -0,0 +1,207 @@
|
||||
<template>
|
||||
<div
|
||||
v-show="isVisible"
|
||||
:class="['floating-video', { minimized: isMinimized }]"
|
||||
class="!aspect-video"
|
||||
:style="[positionStyle, containerStyle]"
|
||||
@touchstart.prevent="handleTouchStart"
|
||||
@touchmove.prevent="handleTouchMove"
|
||||
@touchend="handleTouchEnd"
|
||||
>
|
||||
<video
|
||||
ref="videoRef"
|
||||
class="video-player "
|
||||
controls
|
||||
@loadedmetadata="handleVideoMetadata"
|
||||
>
|
||||
<source src="@/static/video/example.mp4" type="video/mp4" />
|
||||
您的浏览器不支持 HTML5 视频。
|
||||
</video>
|
||||
<div class="control-bar">
|
||||
<div class="minimize-btn" @click="toggleMinimize">
|
||||
<span v-if="isMinimized">⤢</span>
|
||||
<span v-else>⤓</span>
|
||||
</div>
|
||||
<div class="close-btn" @click="closeVideo">✕</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted, onBeforeUnmount } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
isVisible: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['close'])
|
||||
|
||||
const videoRef = ref(null)
|
||||
const aspectRatio = ref(16 / 9) // 默认宽高比
|
||||
const isMinimized = ref(false)
|
||||
const position = ref({ x: 20, y: 20 })
|
||||
const isDragging = ref(false)
|
||||
const dragStart = ref({ x: 0, y: 0 })
|
||||
|
||||
// 初始化窗口尺寸为0
|
||||
const windowSize = ref({
|
||||
width: 0,
|
||||
height: 0
|
||||
})
|
||||
|
||||
const handleVideoMetadata = () => {
|
||||
const video = videoRef.value
|
||||
if (video.videoWidth && video.videoHeight) {
|
||||
aspectRatio.value = video.videoWidth / video.videoHeight
|
||||
}
|
||||
}
|
||||
|
||||
const containerDimensions = computed(() => {
|
||||
const baseWidth = isMinimized.value ? 150 : 300
|
||||
const height = baseWidth / aspectRatio.value
|
||||
return { width: baseWidth, height }
|
||||
})
|
||||
|
||||
const containerStyle = computed(() => {
|
||||
return {
|
||||
width: `${containerDimensions.value.width}px`,
|
||||
height: `${containerDimensions.value.height}px`
|
||||
}
|
||||
})
|
||||
|
||||
const positionStyle = computed(() => ({
|
||||
transform: `translate3d(${position.value.x}px, ${position.value.y}px, 0)`
|
||||
}))
|
||||
|
||||
const handleTouchStart = (event) => {
|
||||
isDragging.value = true
|
||||
const touch = event.touches[0]
|
||||
dragStart.value = {
|
||||
x: touch.clientX - position.value.x,
|
||||
y: touch.clientY - position.value.y
|
||||
}
|
||||
}
|
||||
|
||||
const handleTouchMove = (event) => {
|
||||
if (!isDragging.value) return
|
||||
|
||||
const touch = event.touches[0]
|
||||
let newX = touch.clientX - dragStart.value.x
|
||||
let newY = touch.clientY - dragStart.value.y
|
||||
|
||||
// 添加边界检测
|
||||
const maxX = windowSize.value.width - containerDimensions.value.width
|
||||
const maxY = windowSize.value.height - containerDimensions.value.height
|
||||
|
||||
newX = Math.max(0, Math.min(newX, maxX))
|
||||
newY = Math.max(0, Math.min(newY, maxY))
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
position.value = { x: newX, y: newY }
|
||||
})
|
||||
}
|
||||
|
||||
const handleTouchEnd = () => {
|
||||
isDragging.value = false
|
||||
}
|
||||
|
||||
const toggleMinimize = () => {
|
||||
isMinimized.value = !isMinimized.value
|
||||
|
||||
const maxX = windowSize.value.width - containerDimensions.value.width
|
||||
const maxY = windowSize.value.height - containerDimensions.value.height
|
||||
|
||||
position.value = {
|
||||
x: Math.max(0, Math.min(position.value.x, maxX)),
|
||||
y: Math.max(0, Math.min(position.value.y, maxY))
|
||||
}
|
||||
}
|
||||
|
||||
const closeVideo = () => {
|
||||
emit('close')
|
||||
}
|
||||
|
||||
const handleResize = () => {
|
||||
windowSize.value = {
|
||||
width: window.innerWidth,
|
||||
height: window.innerHeight
|
||||
}
|
||||
|
||||
const maxX = windowSize.value.width - containerDimensions.value.width
|
||||
const maxY = windowSize.value.height - containerDimensions.value.height
|
||||
|
||||
position.value = {
|
||||
x: Math.max(0, Math.min(position.value.x, maxX)),
|
||||
y: Math.max(0, Math.min(position.value.y, maxY))
|
||||
}
|
||||
}
|
||||
|
||||
// 在组件挂载后初始化窗口尺寸和事件监听
|
||||
onMounted(() => {
|
||||
// 初始化窗口尺寸
|
||||
windowSize.value = {
|
||||
width: window.innerWidth,
|
||||
height: window.innerHeight
|
||||
}
|
||||
|
||||
window.addEventListener('resize', handleResize)
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('resize', handleResize)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 样式部分保持不变 */
|
||||
.floating-video {
|
||||
position: fixed;
|
||||
z-index: 1000;
|
||||
background: #000;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
|
||||
transition: width 0.3s ease, height 0.3s ease;
|
||||
overflow: hidden;
|
||||
transform: translate3d(0, 0, 0);
|
||||
will-change: transform;
|
||||
touch-action: none;
|
||||
}
|
||||
|
||||
.video-player {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: fill!important;
|
||||
}
|
||||
|
||||
.control-bar {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
padding: 8px;
|
||||
background: linear-gradient(to bottom, rgba(0, 0, 0, 0.5), transparent);
|
||||
}
|
||||
|
||||
.minimize-btn,
|
||||
.close-btn {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
color: white;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.minimize-btn:hover,
|
||||
.close-btn:hover {
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
</style>
|
@ -4,16 +4,21 @@ import {goodStore} from "@/stores/goods/index.js";
|
||||
import ItemList from './components/ItemList/index.vue'
|
||||
import Cescribe from './components/Cescribe/index.vue'
|
||||
import {message} from '@/components/x-message/useMessage.js'
|
||||
import floatingVideo from '@/components/floatingVideo/index.vue'
|
||||
import {liveStore} from "~/stores/live/index.js";
|
||||
const {fullLive,getAuctionDetail,auctionDetail} = goodStore();
|
||||
const {getLiveLink}= liveStore()
|
||||
const changeLive = () => {
|
||||
fullLive.value = true;
|
||||
};
|
||||
if (!auctionDetail.value.uuid){
|
||||
await getAuctionDetail()
|
||||
}
|
||||
|
||||
</script>
|
||||
<template>
|
||||
<div class="flex-grow-1">
|
||||
<!-- <floatingVideo :is-visible="true"></floatingVideo>-->
|
||||
<client-only>
|
||||
<liveRoom @click="changeLive" :class="['changeLive', fullLive ? 'expanded' : 'collapsed']"
|
||||
:fullLive="fullLive"/>
|
||||
@ -29,6 +34,7 @@ if (!auctionDetail.value.uuid){
|
||||
</van-tabs>
|
||||
<van-back-top right="15vw" bottom="10vh"/>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
<style scoped lang="scss">
|
||||
|
@ -1,50 +1,94 @@
|
||||
<script setup>
|
||||
import {ref, onMounted, onBeforeUnmount} from 'vue'
|
||||
import {ref, onMounted, onBeforeUnmount, computed, watch} from 'vue'
|
||||
import Aliplayer from 'aliyun-aliplayer'
|
||||
import 'aliyun-aliplayer/build/skins/default/aliplayer-min.css'
|
||||
import sideButton from '@/pages/liveRoom/components/SideButton/index.vue'
|
||||
import broadcast from '@/pages/liveRoom/components/Broadcast/index.vue'
|
||||
import {liveStore} from "@/stores/live/index.js";
|
||||
import {liveStore} from "@/stores/live/index.js"
|
||||
import paymentResults from '@/pages/liveRoom/components/PaymentResults/index.vue'
|
||||
import paymentInput from '@/pages/liveRoom/components/PaymentInput/index.vue'
|
||||
import xButton from '@/components/x-button/index.vue'
|
||||
import {goodStore} from "@/stores/goods/index.js";
|
||||
import {message} from "~/components/x-message/useMessage.js";
|
||||
import {artworkBuy} from "@/api/goods/index.js";
|
||||
const {auctionDetail,getAuctionDetail} = goodStore();
|
||||
import {goodStore} from "@/stores/goods/index.js"
|
||||
import {message} from "~/components/x-message/useMessage.js"
|
||||
import {artworkBuy} from "@/api/goods/index.js"
|
||||
import CryptoJS from 'crypto-js'
|
||||
|
||||
const {auctionDetail, getAuctionDetail} = goodStore()
|
||||
const player = ref(null)
|
||||
const {quoteStatus, changeStatus, show, playerId, show1,auctionData,getSocketData} = liveStore()
|
||||
const {quoteStatus, changeStatus, show, playerId, show1, auctionData, getSocketData,getLiveLink} = liveStore()
|
||||
const isPlayerReady = ref(false)
|
||||
const pullLink=ref('')
|
||||
const props = defineProps({
|
||||
fullLive: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
})
|
||||
const isPiPActive = ref(false)
|
||||
const videoRef = ref(null)
|
||||
|
||||
// 检查浏览器是否支持画中画
|
||||
const isPiPSupported = computed(() => {
|
||||
return document.pictureInPictureEnabled ||
|
||||
document.webkitPictureInPictureEnabled
|
||||
})
|
||||
|
||||
// 进入画中画模式
|
||||
const enterPiP = async () => {
|
||||
try {
|
||||
if (!videoRef.value) return
|
||||
|
||||
if (document.pictureInPictureElement) {
|
||||
await document.exitPictureInPicture()
|
||||
}
|
||||
|
||||
await videoRef.value.requestPictureInPicture()
|
||||
isPiPActive.value = true
|
||||
} catch (error) {
|
||||
console.error('进入画中画模式失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 退出画中画模式
|
||||
const exitPiP = async () => {
|
||||
try {
|
||||
if (document.pictureInPictureElement) {
|
||||
await document.exitPictureInPicture()
|
||||
isPiPActive.value = false
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('退出画中画模式失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
definePageMeta({
|
||||
title: '主页',
|
||||
i18n: 'login.title',
|
||||
})
|
||||
|
||||
const config = useRuntimeConfig()
|
||||
const playerConfig = {
|
||||
id: playerId.value,
|
||||
source: config.public.NUXT_PUBLIC_PLAYER_SOURCE,
|
||||
isLive: true,
|
||||
preload: true,
|
||||
autoplayPolicy: {fallbackToMute: true},
|
||||
controlBarVisibility: 'never',
|
||||
}
|
||||
|
||||
|
||||
const handlePlayerError = (error) => {
|
||||
console.error('播放器错误:', error)
|
||||
if (player.value) {
|
||||
player.value?.play()
|
||||
}
|
||||
}
|
||||
|
||||
const initializePlayer = () => {
|
||||
try {
|
||||
if (player.value) {
|
||||
player.value?.dispose()
|
||||
}
|
||||
const playerConfig = {
|
||||
id: playerId.value,
|
||||
source:pullLink.value,
|
||||
isLive: true,
|
||||
preload: true,
|
||||
autoplayPolicy: {fallbackToMute: true},
|
||||
controlBarVisibility: 'never',
|
||||
}
|
||||
player.value = new Aliplayer(playerConfig, (playerInstance) => {
|
||||
isPlayerReady.value = true
|
||||
playerInstance?.play()
|
||||
@ -60,19 +104,23 @@ const initializePlayer = () => {
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
/* initializePlayer()*/
|
||||
pullLink.value= await getLiveLink()
|
||||
initializePlayer()
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
if (player.value) {
|
||||
player.value?.dispose()
|
||||
player.value = null
|
||||
}
|
||||
})
|
||||
|
||||
const goPay = () => {
|
||||
show.value = true
|
||||
}
|
||||
|
||||
const fullLive1 = ref(false)
|
||||
watch(()=>{
|
||||
watch(() => {
|
||||
return props.fullLive
|
||||
}, (newVal) => {
|
||||
if (newVal) {
|
||||
@ -80,59 +128,98 @@ watch(()=>{
|
||||
setTimeout(() => {
|
||||
fullLive1.value = true
|
||||
}, 400)
|
||||
}else {
|
||||
} else {
|
||||
fullLive1.value = false
|
||||
}
|
||||
})
|
||||
const goBuy=async ()=>{
|
||||
|
||||
const res= await artworkBuy({
|
||||
auctionArtworkUuid:auctionData.value?.artwork?.uuid,
|
||||
buyMoney:String(auctionData.value?.nowAuctionPrice?.nextPrice??0)
|
||||
const goBuy = async () => {
|
||||
const res = await artworkBuy({
|
||||
auctionArtworkUuid: auctionData.value?.artwork?.uuid,
|
||||
buyMoney: String(auctionData.value?.nowAuctionPrice?.nextPrice ?? 0)
|
||||
})
|
||||
if (res.status===0){
|
||||
message.success('出价成功')
|
||||
if (res.status === 0) {
|
||||
message.success('出价成功')
|
||||
}
|
||||
}
|
||||
const tipOpen=()=>{
|
||||
|
||||
const tipOpen = () => {
|
||||
message.warning('出价状态未开启')
|
||||
}
|
||||
|
||||
// 加密相关配置
|
||||
const cryptConfig = {
|
||||
password: 'live-skkoql-1239-key',
|
||||
salt: 'aldk100128ls',
|
||||
iterations: 10000,
|
||||
keySize: 32
|
||||
}
|
||||
|
||||
// 生成密钥
|
||||
const generateKey = (password, salt, iterations, keySize) => {
|
||||
return CryptoJS.PBKDF2(password, salt, {
|
||||
keySize: keySize / 4,
|
||||
iterations: iterations,
|
||||
hasher: CryptoJS.algo.SHA1
|
||||
}).toString(CryptoJS.enc.Hex)
|
||||
}
|
||||
|
||||
// 解密方法
|
||||
const 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)
|
||||
}
|
||||
|
||||
// 在需要使用时可以调用的解密函数
|
||||
const decryptData = (encryptedData) => {
|
||||
const keyDerived = generateKey(
|
||||
cryptConfig.password,
|
||||
cryptConfig.salt,
|
||||
cryptConfig.iterations,
|
||||
cryptConfig.keySize
|
||||
)
|
||||
return decrypt(encryptedData, keyDerived)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="relative h-full ">
|
||||
<div class="w-full h-full">
|
||||
<video
|
||||
class="h-full w-full"
|
||||
autoplay
|
||||
loop
|
||||
muted
|
||||
playsinline
|
||||
style=" object-fit: cover"
|
||||
>
|
||||
<source src="@/static/video/example.mp4" type="video/mp4" />
|
||||
您的浏览器不支持 HTML5 视频。
|
||||
</video>
|
||||
</div>
|
||||
<!-- <div :id="playerId" class="w-screen"
|
||||
:style="fullLive?'height: calc(100vh - var(--van-nav-bar-height))':'height:100%'"></div>-->
|
||||
<div :id="playerId" class="w-full h-full"></div>
|
||||
|
||||
<transition>
|
||||
<div v-if="fullLive1">
|
||||
|
||||
<sideButton class="absolute top-196px right-0 z-999"></sideButton>
|
||||
<div class="absolute left-1/2 transform -translate-x-1/2 flex flex-col items-center" style="bottom:calc(var(--safe-area-inset-bottom) + 26px)">
|
||||
<div class="absolute left-1/2 transform -translate-x-1/2 flex flex-col items-center"
|
||||
style="bottom:calc(var(--safe-area-inset-bottom) + 26px)">
|
||||
<div class="text-16px text-#FFB25F font-600">
|
||||
当前价:{{auctionData?.nowAuctionPrice?.currency}}
|
||||
<van-rolling-text class="my-rolling-text" :start-num="0" :duration="0.5" :target-num="auctionData?.nowAuctionPrice?.nowPrice??0" direction="up"/>
|
||||
当前价:{{ auctionData?.nowAuctionPrice?.currency }}
|
||||
<van-rolling-text class="my-rolling-text" :start-num="0" :duration="0.5"
|
||||
:target-num="auctionData?.nowAuctionPrice?.nowPrice??0" direction="up"/>
|
||||
</div>
|
||||
<div class="text-16px text-#fff font-600">
|
||||
下口价:{{auctionData?.nowAuctionPrice?.currency}}
|
||||
<van-rolling-text class="my-rolling-text1" :start-num="0" :duration="0.5" :target-num="auctionData?.nowAuctionPrice?.nextPrice??0" direction="up"/>
|
||||
下口价:{{ auctionData?.nowAuctionPrice?.currency }}
|
||||
<van-rolling-text class="my-rolling-text1" :start-num="0" :duration="0.5"
|
||||
:target-num="auctionData?.nowAuctionPrice?.nextPrice??0" direction="up"/>
|
||||
</div>
|
||||
<div v-if="quoteStatus" class="mt-10px mb-10px">
|
||||
<van-button @click="goBuy" color="#FFB25F" class="w-344px !h-[40px]">
|
||||
<div>{{`确认出价 ${auctionData?.nowAuctionPrice?.currency} ${auctionData?.nowAuctionPrice?.nextPrice??0}`}}</div>
|
||||
<van-button @click="goBuy" color="#FFB25F" class="w-344px !h-[40px]">
|
||||
<div>{{
|
||||
`确认出价 ${auctionData?.nowAuctionPrice?.currency} ${auctionData?.nowAuctionPrice?.nextPrice ?? 0}`
|
||||
}}
|
||||
</div>
|
||||
</van-button>
|
||||
</div>
|
||||
<div v-else class="mt-10px mb-10px">
|
||||
@ -147,19 +234,26 @@ const tipOpen=()=>{
|
||||
<div>
|
||||
</div>
|
||||
<paymentResults v-model:show="show1" type="error"/>
|
||||
<div v-if="auctionData?.wsType==='newArtwork'" class="w-344px h-31px rounded-4px absolute top-9px bg-[#151824]/45 backdrop-blur-[10px] backdrop-saturate-[180%] left-1/2 transform translate-x--1/2 flex text-#fff text-14px items-center px-12px line-height-none">
|
||||
<div class="mr-11px whitespace-nowrap">LOT{{auctionData.artwork.index}}</div>
|
||||
<div class="mr-10px truncate">{{auctionData.artwork.name}}</div>
|
||||
<div v-if="auctionData?.wsType==='newArtwork'"
|
||||
class="w-344px h-31px rounded-4px absolute top-9px bg-[#151824]/45 backdrop-blur-[10px] backdrop-saturate-[180%] left-1/2 transform translate-x--1/2 flex text-#fff text-14px items-center px-12px line-height-none">
|
||||
<div class="mr-11px whitespace-nowrap">LOT{{ auctionData.artwork.index }}</div>
|
||||
<div class="mr-10px truncate">{{ auctionData.artwork.name }}</div>
|
||||
<div class="whitespace-nowrap">开始拍卖</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</transition>
|
||||
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
#J_prismPlayer{
|
||||
width: 100%;
|
||||
height: 100%!important;
|
||||
&>video{
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<style scoped>
|
||||
.v-enter-active,
|
||||
.v-leave-active {
|
||||
@ -170,6 +264,7 @@ const tipOpen=()=>{
|
||||
.v-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.my-rolling-text {
|
||||
--van-rolling-text-item-width: 10px;
|
||||
--van-rolling-text-font-size: 16px;
|
||||
|
0
app/stores/floating/index.js
Normal file
0
app/stores/floating/index.js
Normal file
@ -4,9 +4,11 @@ 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";
|
||||
|
||||
export const liveStore = createGlobalState(() => {
|
||||
const {auctionDetail,getAuctionDetail} = goodStore();
|
||||
const {auctionDetail} = goodStore();
|
||||
const { token } = authStore()
|
||||
const quoteStatus = ref(false)
|
||||
const show = ref(false)
|
||||
@ -16,6 +18,70 @@ export const liveStore = createGlobalState(() => {
|
||||
const auctionData=ref({})
|
||||
const socket=ref(null)
|
||||
const config = useRuntimeConfig()
|
||||
const pullLink=ref('')
|
||||
// 解密工具函数
|
||||
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 getSocketData = async () => {
|
||||
const wsClient = new WebSocketClient(
|
||||
config.public.NUXT_PUBLIC_SOCKET_URL,
|
||||
@ -163,6 +229,8 @@ export const liveStore = createGlobalState(() => {
|
||||
|
||||
}
|
||||
return{
|
||||
pullLink,
|
||||
getLiveLink,
|
||||
auctionData,
|
||||
getSocketData,
|
||||
show1,
|
||||
|
2
env/.env.test
vendored
2
env/.env.test
vendored
@ -4,4 +4,4 @@ NUXT_PUBLIC_API_COLLECT_CODE=https://auction-test.szjixun.cn
|
||||
NUXT_API_SECRET=test-secret
|
||||
NUXT_PUBLIC_SOCKET_URL=ws://172.16.100.99:8005
|
||||
# 阿里云播放器配置
|
||||
NUXT_PUBLIC_PLAYER_SOURCE=artc://live-pull-sh-01.szjixun.cn/live/live?auth_key=1737080180-0-0-42ad4cf26ba26eee78ca7de9c524d1e0
|
||||
NUXT_PUBLIC_PLAYER_SOURCE=artc://live-push-sh-01.szjixun.cn/live001/86180cae-1e07-4b8d-b45e-50d8ce800110?auth_key=1739255918-0-0-5251017e725a860570a59de7e4e2fd98
|
||||
|
@ -23,6 +23,7 @@
|
||||
"@yeger/vue-masonry-wall": "^5.0.17",
|
||||
"aliyun-aliplayer": "^2.28.5",
|
||||
"axios": "^1.7.9",
|
||||
"crypto-js": "^4.2.0",
|
||||
"dayjs": "^1.11.13",
|
||||
"dotenv": "^16.4.7",
|
||||
"nuxt": "^3.15.0",
|
||||
|
@ -29,6 +29,9 @@ importers:
|
||||
axios:
|
||||
specifier: ^1.7.9
|
||||
version: 1.7.9
|
||||
crypto-js:
|
||||
specifier: ^4.2.0
|
||||
version: 4.2.0
|
||||
dayjs:
|
||||
specifier: ^1.11.13
|
||||
version: 1.11.13
|
||||
|
Loading…
Reference in New Issue
Block a user