volunteer-secondary/src/pages/index/hooks/useChannelLive.ts

314 lines
7.9 KiB
TypeScript

import { ref } from 'vue'
type ChannelLiveStatus = 1 | 2 | 3 | 4
type ChannelNoticeStatus = 0 | 1 | 2
type ChannelVideoType = 'live' | 'notice'
interface ChannelLiveInfo {
feedId?: string
nonceId?: string
description?: string
status?: ChannelLiveStatus
headUrl?: string
nickname?: string
replayStatus?: string
otherInfos?: ChannelLiveInfo[]
}
interface ChannelNoticeInfo {
noticeId?: string
status?: ChannelNoticeStatus
startTime?: string
headUrl?: string
nickname?: string
reservable?: boolean
otherInfos?: ChannelNoticeInfo[]
}
export interface ChannelVideoItem {
id: string
type: ChannelVideoType
title: string
coverUrl: string
statusText: string
buttonText: string
nickname: string
startTimeText: string
startTimeStamp: number
feedId?: string
nonceId?: string
noticeId?: string
reservable?: boolean
}
interface ChannelsQueryApi {
getChannelsLiveInfo: (options: {
finderUserName: string
success: (res: ChannelLiveInfo) => void
fail: (err: unknown) => void
}) => void
getChannelsLiveNoticeInfo: (options: {
finderUserName: string
success: (res: ChannelNoticeInfo) => void
fail: (err: unknown) => void
}) => void
}
interface ChannelsActionApi {
openChannelsLive: (options: {
finderUserName: string
feedId?: string
nonceId?: string
fail: (err: unknown) => void
}) => void
reserveChannelsLive: (options: {
noticeId: string
success: () => void
fail: (err: unknown) => void
}) => void
}
const DEFAULT_CHANNEL_COVER = 'https://lwzk.ycymedu.com/img/home/sy_daoxiao.png'
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === 'object' && value !== null
}
function isFunction(value: unknown): value is (...args: unknown[]) => unknown {
return typeof value === 'function'
}
function getChannelsQueryApi(): ChannelsQueryApi | null {
const api = uni as unknown
if (!isRecord(api)) {
return null
}
const canGetLiveInfo = isFunction(api.getChannelsLiveInfo)
const canGetNoticeInfo = isFunction(api.getChannelsLiveNoticeInfo)
if (!canGetLiveInfo || !canGetNoticeInfo) {
return null
}
return api as unknown as ChannelsQueryApi
}
function getChannelsActionApi(): ChannelsActionApi | null {
const api = uni as unknown
if (!isRecord(api)) {
return null
}
const canOpenLive = isFunction(api.openChannelsLive)
const canReserveLive = isFunction(api.reserveChannelsLive)
if (!canOpenLive || !canReserveLive) {
return null
}
return api as unknown as ChannelsActionApi
}
function getStartTimeStamp(startTime?: string): number {
if (!startTime) {
return Number.MAX_SAFE_INTEGER
}
const timestamp = Number(startTime)
if (Number.isFinite(timestamp)) {
return String(Math.trunc(timestamp)).length === 10 ? timestamp * 1000 : timestamp
}
const dateTime = new Date(startTime).getTime()
return Number.isFinite(dateTime) ? dateTime : Number.MAX_SAFE_INTEGER
}
function formatStartTime(startTime?: string): string {
const timestamp = getStartTimeStamp(startTime)
if (timestamp === Number.MAX_SAFE_INTEGER) {
return startTime ? `${startTime}开播` : '直播预告'
}
const date = new Date(timestamp)
const month = date.getMonth() + 1
const day = date.getDate()
const hours = String(date.getHours()).padStart(2, '0')
const minutes = String(date.getMinutes()).padStart(2, '0')
return `${month}.${day} ${hours}:${minutes}开播`
}
function flattenLiveInfo(info: ChannelLiveInfo): ChannelLiveInfo[] {
return [info, ...(info.otherInfos ?? [])]
}
function flattenNoticeInfo(info: ChannelNoticeInfo): ChannelNoticeInfo[] {
return [info, ...(info.otherInfos ?? [])]
}
function normalizeLiveInfo(info: ChannelLiveInfo, index: number): ChannelVideoItem {
const id = info.feedId || info.nonceId || `live-${index}`
return {
id: `live-${id}`,
type: 'live',
title: info.description || '视频号直播',
coverUrl: info.headUrl || DEFAULT_CHANNEL_COVER,
statusText: '正在直播',
buttonText: '查看直播',
nickname: info.nickname || '',
startTimeText: '',
startTimeStamp: 0,
feedId: info.feedId,
nonceId: info.nonceId,
}
}
function normalizeNoticeInfo(info: ChannelNoticeInfo, index: number): ChannelVideoItem {
const id = info.noticeId || `notice-${index}`
const startTimeStamp = getStartTimeStamp(info.startTime)
return {
id: `notice-${id}`,
type: 'notice',
title: info.nickname ? `${info.nickname}的直播预告` : '视频号直播预告',
coverUrl: info.headUrl || DEFAULT_CHANNEL_COVER,
statusText: formatStartTime(info.startTime),
buttonText: info.reservable === false ? '直播预告' : '预约直播',
nickname: info.nickname || '',
startTimeText: formatStartTime(info.startTime),
startTimeStamp,
noticeId: info.noticeId,
reservable: info.reservable,
}
}
function getLiveVideoList(finderUserName: string, channelsApi: ChannelsQueryApi): Promise<ChannelVideoItem[]> {
return new Promise((resolve) => {
channelsApi.getChannelsLiveInfo({
finderUserName,
success: (res) => {
const liveList = flattenLiveInfo(res)
.filter(item => item.status === 2)
.map(normalizeLiveInfo)
resolve(liveList)
},
fail: (err) => {
console.error('获取视频号直播信息失败:', err)
resolve([])
},
})
})
}
function getNoticeVideoList(finderUserName: string, channelsApi: ChannelsQueryApi): Promise<ChannelVideoItem[]> {
return new Promise((resolve) => {
channelsApi.getChannelsLiveNoticeInfo({
finderUserName,
success: (res) => {
const noticeList = flattenNoticeInfo(res)
.filter(item => item.status === 0)
.map(normalizeNoticeInfo)
.sort((prev, next) => prev.startTimeStamp - next.startTimeStamp)
resolve(noticeList)
},
fail: (err) => {
console.error('获取视频号直播预告失败:', err)
resolve([])
},
})
})
}
export function useChannelLive(finderUserName: string) {
const channelVideoList = ref<ChannelVideoItem[]>([])
function goToLiveHomePage(){
uni.openChannelsUserProfile({
finderUserName,
success: () => {
console.log('跳转视频号主页成功')
},
fail: (err) => {
console.error('跳转视频号主页失败:', err)
},
})
}
async function getChannelLiveNoticeInfo() {
if (!finderUserName) {
uni.showToast({ title: '直播链接获取中,请稍后再试', icon: 'none' })
return
}
const channelsApi = getChannelsQueryApi()
if (!channelsApi) {
channelVideoList.value = []
return
}
const [liveList, noticeList] = await Promise.all([
getLiveVideoList(finderUserName, channelsApi),
getNoticeVideoList(finderUserName, channelsApi),
])
channelVideoList.value = [...liveList, ...noticeList]
}
function handleChannelVideoAction(item: ChannelVideoItem) {
const channelsApi = getChannelsActionApi()
if (!channelsApi) {
return
}
if (item.type === 'live') {
channelsApi.openChannelsLive({
finderUserName,
feedId: item.feedId,
nonceId: item.nonceId,
fail: (err) => {
console.error('跳转视频号直播失败:', err)
},
})
return
}
if (!item.noticeId || item.reservable === false) {
uni.showToast({ title: '该直播暂不可预约', icon: 'none' })
return
}
channelsApi.reserveChannelsLive({
noticeId: item.noticeId,
success: () => {
uni.showModal({
title: '预约成功',
content: `开播时间: ${item.startTimeText} 请留意视频号消息提醒。`,
showCancel: false,
confirmText:'我知道了',
})
},
fail: (err) => {
console.error('预约视频号直播失败:', err)
},
})
}
return {
channelVideoList,
getChannelLiveNoticeInfo,
handleChannelVideoAction,
goToLiveHomePage
}
}