20260821「单身广场修复路由缓存」
This commit is contained in:
parent
7b93838080
commit
af3f48d703
@ -20,6 +20,7 @@
|
||||
"Get_again": "重新获取",
|
||||
"Have_evaluation": "您已经评价过该课程啦",
|
||||
"I_have_a_line_in_the_sand": "我也是有底线的",
|
||||
"please_input_search_keyword": "请输入搜索内容",
|
||||
"In_the_authentication": "认证中...",
|
||||
"In_the_land": "登陆中...",
|
||||
"Know_the": "知道了",
|
||||
|
||||
287
src/components/HlsPlayer.vue
Normal file
287
src/components/HlsPlayer.vue
Normal file
@ -0,0 +1,287 @@
|
||||
<template>
|
||||
<div class="hls-player" ref="playerContainer">
|
||||
<video
|
||||
ref="videoRef"
|
||||
class="video-element"
|
||||
playsinline
|
||||
webkit-playsinline
|
||||
x5-playsinline
|
||||
:muted="muted"
|
||||
:poster="poster"
|
||||
@click="togglePlay"
|
||||
></video>
|
||||
<!-- 控制条 -->
|
||||
<div class="controls" v-if="controls">
|
||||
<button @click.stop="togglePlay" class="control-btn">
|
||||
{{ playing ? '暂停' : '播放' }}
|
||||
</button>
|
||||
<button @click.stop="toggleFullscreen" class="control-btn">
|
||||
{{ isFullscreen ? '退出全屏' : '全屏' }}
|
||||
</button>
|
||||
<span class="time">{{ currentTimeDisplay }}</span>
|
||||
</div>
|
||||
<!-- 加载中或错误 -->
|
||||
<div v-if="loading" class="loading">加载中...</div>
|
||||
<div v-if="error" class="error">{{ error }}</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onBeforeUnmount, watch, computed, nextTick } from 'vue'
|
||||
import Hls from 'hls.js'
|
||||
|
||||
const props = defineProps({
|
||||
src: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
poster: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
muted: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
controls: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
autoplay: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
})
|
||||
|
||||
const videoRef = ref<HTMLVideoElement | null>(null)
|
||||
const playerContainer = ref<HTMLElement | null>(null)
|
||||
let hls: Hls | null = null
|
||||
const playing = ref(false)
|
||||
const loading = ref(true)
|
||||
const error = ref<string | null>(null)
|
||||
const currentTime = ref(0)
|
||||
const duration = ref(0)
|
||||
|
||||
// 全屏状态
|
||||
const isFullscreen = ref(false)
|
||||
|
||||
// 播放/暂停
|
||||
const togglePlay = () => {
|
||||
if (!videoRef.value) return
|
||||
if (playing.value) {
|
||||
videoRef.value.pause()
|
||||
} else {
|
||||
videoRef.value.play().catch(err => {
|
||||
// 自动播放被拒绝
|
||||
console.warn('Play failed:', err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 全屏切换
|
||||
const toggleFullscreen = () => {
|
||||
if (!playerContainer.value) return
|
||||
if (!document.fullscreenElement && !document.webkitFullscreenElement) {
|
||||
// 进入全屏
|
||||
const el = playerContainer.value
|
||||
if (el.requestFullscreen) {
|
||||
el.requestFullscreen()
|
||||
} else if (el.webkitRequestFullscreen) {
|
||||
el.webkitRequestFullscreen()
|
||||
} else {
|
||||
// 降级:尝试 video 原生全屏 (仅 iOS)
|
||||
if (videoRef.value && videoRef.value.webkitEnterFullscreen) {
|
||||
videoRef.value.webkitEnterFullscreen()
|
||||
} else {
|
||||
alert('您的浏览器不支持全屏')
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 退出全屏
|
||||
if (document.exitFullscreen) {
|
||||
document.exitFullscreen()
|
||||
} else if (document.webkitExitFullscreen) {
|
||||
document.webkitExitFullscreen()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 监听全屏变化
|
||||
const onFullscreenChange = () => {
|
||||
isFullscreen.value = !!document.fullscreenElement || !!document.webkitFullscreenElement
|
||||
}
|
||||
|
||||
// 更新播放状态
|
||||
const updatePlayState = () => {
|
||||
if (videoRef.value) {
|
||||
playing.value = !videoRef.value.paused
|
||||
}
|
||||
}
|
||||
|
||||
// 时间格式化
|
||||
const formatTime = (seconds: number) => {
|
||||
if (isNaN(seconds)) return '00:00'
|
||||
const m = Math.floor(seconds / 60)
|
||||
const s = Math.floor(seconds % 60)
|
||||
return `${String(m).padStart(2, '0')}:${String(s).padStart(2, '0')}`
|
||||
}
|
||||
const currentTimeDisplay = computed(() => formatTime(currentTime.value))
|
||||
|
||||
// 初始化播放器
|
||||
const initPlayer = () => {
|
||||
if (!videoRef.value || !props.src) return
|
||||
const video = videoRef.value
|
||||
|
||||
// 清理旧实例
|
||||
if (hls) {
|
||||
hls.destroy()
|
||||
hls = null
|
||||
}
|
||||
loading.value = true
|
||||
error.value = null
|
||||
|
||||
// 检查 HLS 支持
|
||||
if (Hls.isSupported()) {
|
||||
hls = new Hls({
|
||||
enableWorker: true,
|
||||
lowLatencyMode: true,
|
||||
})
|
||||
hls.loadSource(props.src)
|
||||
hls.attachMedia(video)
|
||||
|
||||
hls.on(Hls.Events.MANIFEST_PARSED, () => {
|
||||
loading.value = false
|
||||
if (props.autoplay) {
|
||||
video.play().catch(err => {
|
||||
console.warn('Autoplay prevented:', err)
|
||||
// 显示播放按钮让用户交互
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
hls.on(Hls.Events.ERROR, (event, data) => {
|
||||
if (data.fatal) {
|
||||
error.value = '播放出错,请刷新重试'
|
||||
loading.value = false
|
||||
}
|
||||
})
|
||||
} else if (video.canPlayType('application/vnd.apple.mpegurl')) {
|
||||
// 原生 HLS 支持 (iOS Safari)
|
||||
video.src = props.src
|
||||
video.addEventListener('loadedmetadata', () => {
|
||||
loading.value = false
|
||||
if (props.autoplay) {
|
||||
video.play().catch(() => {})
|
||||
}
|
||||
})
|
||||
video.addEventListener('error', () => {
|
||||
error.value = '视频加载失败'
|
||||
loading.value = false
|
||||
})
|
||||
} else {
|
||||
error.value = '您的浏览器不支持播放此视频'
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 监听 src 变化
|
||||
watch(() => props.src, () => {
|
||||
initPlayer()
|
||||
}, { immediate: false })
|
||||
|
||||
// 生命周期
|
||||
onMounted(() => {
|
||||
initPlayer()
|
||||
// 绑定事件
|
||||
const video = videoRef.value
|
||||
if (video) {
|
||||
video.addEventListener('play', updatePlayState)
|
||||
video.addEventListener('pause', updatePlayState)
|
||||
video.addEventListener('timeupdate', () => {
|
||||
currentTime.value = video.currentTime
|
||||
})
|
||||
video.addEventListener('durationchange', () => {
|
||||
duration.value = video.duration
|
||||
})
|
||||
}
|
||||
// 全屏事件监听
|
||||
document.addEventListener('fullscreenchange', onFullscreenChange)
|
||||
document.addEventListener('webkitfullscreenchange', onFullscreenChange)
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
if (hls) {
|
||||
hls.destroy()
|
||||
hls = null
|
||||
}
|
||||
const video = videoRef.value
|
||||
if (video) {
|
||||
video.removeEventListener('play', updatePlayState)
|
||||
video.removeEventListener('pause', updatePlayState)
|
||||
video.removeEventListener('timeupdate', () => {})
|
||||
video.removeEventListener('durationchange', () => {})
|
||||
}
|
||||
document.removeEventListener('fullscreenchange', onFullscreenChange)
|
||||
document.removeEventListener('webkitfullscreenchange', onFullscreenChange)
|
||||
})
|
||||
|
||||
// 暴露方法 (供父组件调用)
|
||||
defineExpose({
|
||||
play: () => videoRef.value?.play(),
|
||||
pause: () => videoRef.value?.pause(),
|
||||
togglePlay,
|
||||
toggleFullscreen,
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.hls-player {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
background: #000;
|
||||
overflow: hidden;
|
||||
}
|
||||
.video-element {
|
||||
width: 100%;
|
||||
display: block;
|
||||
background: #000;
|
||||
}
|
||||
.controls {
|
||||
position: absolute;
|
||||
bottom: 10px;
|
||||
left: 10px;
|
||||
right: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
background: rgba(0,0,0,0.5);
|
||||
padding: 8px 12px;
|
||||
border-radius: 4px;
|
||||
color: #fff;
|
||||
}
|
||||
.control-btn {
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: #fff;
|
||||
font-size: 16px;
|
||||
cursor: pointer;
|
||||
padding: 4px 8px;
|
||||
}
|
||||
.time {
|
||||
margin-left: auto;
|
||||
font-size: 14px;
|
||||
}
|
||||
.loading, .error {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
color: #fff;
|
||||
background: rgba(0,0,0,0.6);
|
||||
padding: 10px 20px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.error {
|
||||
background: rgba(255,0,0,0.6);
|
||||
}
|
||||
</style>
|
||||
@ -57,10 +57,11 @@ Vue.prototype.$clipboard = clipboard
|
||||
Vue.prototype.$md5 = md5
|
||||
Vue.use(wxShare)
|
||||
Vue.use(CanvasPoster)
|
||||
import { RadioGroup, Radio, AddressEdit, Icon, Loading } from 'vant'
|
||||
import { RadioGroup, Radio, AddressEdit, Icon, Loading,Search } from 'vant'
|
||||
Vue.use(AddressEdit)
|
||||
Vue.use(Radio)
|
||||
Vue.use(RadioGroup)
|
||||
Vue.use(Search)
|
||||
|
||||
Vue.use(Icon)
|
||||
Vue.use(Loading)
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<div class="courseDetailList animation-slide-left" v-wechat-title="titles">
|
||||
|
||||
<div>
|
||||
<div class='phone_vedio'>
|
||||
<span v-show='has_buy == 1 || list.paymentStatus || free_play'>
|
||||
|
||||
@ -1,7 +1,17 @@
|
||||
<template>
|
||||
<div class="makingFriendsList">
|
||||
<!-- 搜索栏 -->
|
||||
<div class="search-wrap ">
|
||||
<van-search
|
||||
v-model="keyword"
|
||||
:placeholder="$t('please_input_search_keyword')"
|
||||
@search="onSearch"
|
||||
@cancel="onCancelSearch"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<van-pull-refresh v-model="refreshing" @refresh="onRefresh" :loosing-text="$t('loosing_text')" :loading-text="$t('loading_text')">
|
||||
<van-list v-model="loading" :finished="finished" @load="getList" style="min-height: 100vh">
|
||||
<van-list v-model="loading" :finished="finished" @load="getList" style="min-height: 100vh">
|
||||
<div class="text-center" style="padding: 170px 0" v-if="!emptyData">
|
||||
<img class="emptyDataIcon" src="https://image.fulllinkai.com/202108/23/939c529ca16a91e3ee3b22bef2fe92ca.png" alt="" />
|
||||
<div class="color9 font14" style="margin-top: 14px">{{ $t('Temporarily_no_data') }}</div>
|
||||
@ -48,15 +58,29 @@ export default {
|
||||
finished: false,
|
||||
refreshing: false,
|
||||
emptyData: true,
|
||||
noMoreData: false
|
||||
noMoreData: false,
|
||||
keyword: '' //搜索关键词
|
||||
}
|
||||
},
|
||||
watch: {},
|
||||
methods: {
|
||||
//触发搜索
|
||||
onSearch() {
|
||||
this.page = 1
|
||||
this.finished = false
|
||||
this.loading = true
|
||||
this.getList()
|
||||
},
|
||||
//取消搜索清空
|
||||
onCancelSearch() {
|
||||
this.keyword = ''
|
||||
this.onSearch()
|
||||
},
|
||||
getList() {
|
||||
const vm = this
|
||||
$toastLoading(vm.$i18n.t('loading_text'))
|
||||
service.get(`s/h5/friend/user/list?page=${vm.page}`).then(data => {
|
||||
//拼接keyword参数,空字符串后端自动忽略
|
||||
service.get(`s/h5/friend/user/list?page=${vm.page}&keyword=${encodeURIComponent(vm.keyword)}`).then(data => {
|
||||
const dataV = vm.page === 1 ? [] : vm.list
|
||||
dataV.push(...data.data)
|
||||
vm.list = dataV
|
||||
@ -85,6 +109,7 @@ export default {
|
||||
})
|
||||
.catch(error => {
|
||||
console.log(error)
|
||||
$toastClear()
|
||||
})
|
||||
},
|
||||
onRefresh() {
|
||||
@ -113,6 +138,17 @@ export default {
|
||||
width: 100vw;
|
||||
min-height: 100vh;
|
||||
background: #ffffff;
|
||||
|
||||
.search-wrap {
|
||||
//height: 120px;
|
||||
padding: 12px 12px 0 12px;
|
||||
}
|
||||
.search-wrap .van-search{
|
||||
padding: 0;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.emptyDataIcon {
|
||||
width: 135px;
|
||||
height: 100px;
|
||||
|
||||
@ -202,7 +202,8 @@ export default {
|
||||
const vm = this
|
||||
const url = 's/h5/BusinessUser'
|
||||
service.get(url).then(data => {
|
||||
vm.isCompletedProfile = data.isCompletedProfile
|
||||
// vm.isCompletedProfile = data.isCompletedProfile。暂时不判断用户的资料完善度
|
||||
vm.isCompletedProfile = true
|
||||
vm.authorization_status = data.authorization_status
|
||||
vm.qr_code = data.share_qrcode
|
||||
}).catch(error => {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user