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 { 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 { 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 { 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([]) 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 } }