314 lines
7.9 KiB
TypeScript
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
|
|
}
|
|
}
|