Merge remote-tracking branch 'origin/master'
| | |
| | | <script setup lang="ts"> |
| | | import { computed, getCurrentInstance, nextTick, ref } from 'vue' |
| | | import { onHide, onLoad, onShow, onUnload } from '@dcloudio/uni-app' |
| | | import config from '@/config/index.js' |
| | | import { |
| | | favoriteHealthCourse, |
| | | getHealthCourseDetail, |
| | |
| | | } |
| | | |
| | | /** |
| | | * 生成课程 H5 分享链接,供微信好友打开页面详情。 |
| | | */ |
| | | function buildCourseShareUrl() { |
| | | if (!courseId.value) { |
| | | return `${config.BASE_URL}/#/pages/healthCourses/healthCourses` |
| | | } |
| | | return `${config.BASE_URL}/#/pages/courseDetail/courseDetail?id=${encodeURIComponent(courseId.value)}` |
| | | } |
| | | |
| | | /** |
| | | * 通过微信好友聊天场景分享当前课程。 |
| | | */ |
| | | function shareToWechatSession() { |
| | |
| | | return |
| | | } |
| | | weixinService.send({ |
| | | type: 'text', |
| | | type: 0, |
| | | title: courseDetail.value.title.trim() || '养生课程', |
| | | content: buildCourseShareText(), |
| | | href: buildCourseShareUrl(), |
| | | thumbs: courseDetail.value.bannerImages[0] ? [courseDetail.value.bannerImages[0]] : undefined, |
| | | extra: { |
| | | scene: 'WXSceneSession', |
| | | }, |
| | |
| | | coverImage?: string |
| | | durationText?: string |
| | | savedFilePath?: string |
| | | videoUrl?: string |
| | | } |
| | | |
| | | const PLAYER_ID = 'download-video-player' |
| | |
| | | let watermarkTimer: ReturnType<typeof setInterval> | null = null |
| | | const instance = getCurrentInstance() |
| | | |
| | | const videoSource = computed(() => normalizeText(taskInfo.value?.savedFilePath)) |
| | | const videoSource = computed(() => { |
| | | const savedFilePath = normalizeText(taskInfo.value?.savedFilePath) |
| | | if (savedFilePath) { |
| | | return savedFilePath |
| | | } |
| | | return normalizeText(taskInfo.value?.videoUrl) |
| | | }) |
| | | const coverImage = computed(() => normalizeText(taskInfo.value?.coverImage) || DEFAULT_COVER) |
| | | const title = computed(() => normalizeText(taskInfo.value?.title) || '本地视频') |
| | | const subtitle = computed(() => normalizeText(taskInfo.value?.subtitle) || '养生课程') |
| | |
| | | statusBarHeight.value = systemInfo.statusBarHeight || 20 |
| | | const clientTaskId = typeof options?.clientTaskId === 'string' ? options.clientTaskId : '' |
| | | const currentUserId = resolveCurrentAppUserId() |
| | | if (!currentUserId || !clientTaskId) { |
| | | return |
| | | const fallbackTaskInfo = { |
| | | clientTaskId: clientTaskId || 'download-player-fallback', |
| | | title: typeof options?.title === 'string' ? decodeURIComponent(options.title) : '', |
| | | subtitle: typeof options?.subtitle === 'string' ? decodeURIComponent(options.subtitle) : '', |
| | | coverImage: typeof options?.coverImage === 'string' ? decodeURIComponent(options.coverImage) : '', |
| | | durationText: typeof options?.durationText === 'string' ? decodeURIComponent(options.durationText) : '', |
| | | savedFilePath: typeof options?.savedFilePath === 'string' ? decodeURIComponent(options.savedFilePath) : '', |
| | | videoUrl: typeof options?.videoUrl === 'string' ? decodeURIComponent(options.videoUrl) : '', |
| | | } |
| | | taskInfo.value = getDownloadTask(currentUserId, clientTaskId) as DownloadTaskItem | null |
| | | if (currentUserId && clientTaskId) { |
| | | taskInfo.value = getDownloadTask(currentUserId, clientTaskId) as DownloadTaskItem | null |
| | | } |
| | | if (!taskInfo.value) { |
| | | taskInfo.value = fallbackTaskInfo |
| | | } else if (!normalizeText(taskInfo.value.savedFilePath) && normalizeText(fallbackTaskInfo.savedFilePath || fallbackTaskInfo.videoUrl)) { |
| | | taskInfo.value = { |
| | | ...taskInfo.value, |
| | | ...fallbackTaskInfo, |
| | | clientTaskId: taskInfo.value.clientTaskId || fallbackTaskInfo.clientTaskId, |
| | | } |
| | | } |
| | | videoContext = uni.createVideoContext(PLAYER_ID, instance?.proxy) |
| | | }) |
| | | |
| | |
| | | sourceType: DownloadSourceType |
| | | sortTime: number |
| | | savedFilePath: string |
| | | videoUrl: string |
| | | } |
| | | |
| | | const DEFAULT_COURSE_IMAGE = '/static/image/home/class.png' |
| | |
| | | return |
| | | } |
| | | const playableTask = resolvePlayableDownloadTask(currentItem) |
| | | if (!playableTask) { |
| | | if (!playableTask && !normalizeOptionalText(currentItem.videoUrl)) { |
| | | uni.showToast({ |
| | | title: '本地文件不存在,请重新下载', |
| | | title: '视频资源不存在,请重新下载', |
| | | icon: 'none', |
| | | }) |
| | | return |
| | | } |
| | | uni.navigateTo({ |
| | | url: `/pages/downloadPlayer/downloadPlayer?clientTaskId=${encodeURIComponent(playableTask.clientTaskId)}`, |
| | | url: buildDownloadPlayerUrl(currentItem, playableTask), |
| | | }) |
| | | } |
| | | |
| | |
| | | && !!normalizeOptionalText(task.savedFilePath) |
| | | }) as LocalDownloadTask | undefined |
| | | return matchedTask || null |
| | | } |
| | | |
| | | /** |
| | | * 生成下载播放器页面地址,优先携带本地文件路径,缺失时回退到远程视频地址。 |
| | | */ |
| | | function buildDownloadPlayerUrl(item: DownloadListItem, playableTask: LocalDownloadTask | null) { |
| | | const query = [ |
| | | `clientTaskId=${encodeURIComponent(normalizeOptionalText(playableTask?.clientTaskId) || normalizeOptionalText(item.clientTaskId))}`, |
| | | `title=${encodeURIComponent(normalizeOptionalText(playableTask?.title) || item.title)}`, |
| | | `subtitle=${encodeURIComponent(normalizeOptionalText(playableTask?.subtitle) || item.subtitle)}`, |
| | | `coverImage=${encodeURIComponent(normalizeOptionalText(playableTask?.coverImage) || item.image)}`, |
| | | `durationText=${encodeURIComponent(normalizeOptionalText(playableTask?.durationText) || item.duration)}`, |
| | | `savedFilePath=${encodeURIComponent(normalizeOptionalText(playableTask?.savedFilePath) || item.savedFilePath)}`, |
| | | `videoUrl=${encodeURIComponent(normalizeOptionalText(playableTask?.videoUrl) || item.videoUrl)}`, |
| | | ] |
| | | return `/pages/downloadPlayer/downloadPlayer?${query.join('&')}` |
| | | } |
| | | |
| | | /** |
| | |
| | | sourceType: 'remoteCompleted', |
| | | sortTime: resolveDateTime(record.downloadedAt), |
| | | savedFilePath: '', |
| | | videoUrl: normalizeOptionalText(record.videoUrl), |
| | | } |
| | | }) |
| | | } |
| | |
| | | sourceType: 'localTask', |
| | | sortTime: resolveDateTime(item.savedAt || item.updatedAt), |
| | | savedFilePath: normalizeOptionalText(item.savedFilePath), |
| | | videoUrl: normalizeOptionalText(item.videoUrl), |
| | | } |
| | | }) |
| | | } |
| | |
| | | subtitle: localItem.subtitle || remoteItem.subtitle, |
| | | duration: localItem.duration || remoteItem.duration, |
| | | image: localItem.image || remoteItem.image, |
| | | videoUrl: localItem.videoUrl || remoteItem.videoUrl, |
| | | sortTime: Math.max(localItem.sortTime, remoteItem.sortTime), |
| | | } |
| | | }) |
| | |
| | | import { computed, ref } from 'vue' |
| | | import { onLoad, onShow } from '@dcloudio/uni-app' |
| | | import { getLiveCourseDetail, reserveLiveCourseSession } from './service' |
| | | import config from '@/config/index.js' |
| | | |
| | | type LiveDisplayStatus = 'NOT_STARTED' | 'LIVING' | 'ENDED' |
| | | type LiveActionStatus = 'RESERVABLE' | 'RESERVED' | 'WATCHABLE' | 'CLOSED' |
| | |
| | | } |
| | | |
| | | /** |
| | | * 生成直播详情 H5 分享链接,供微信好友直接打开页面。 |
| | | */ |
| | | function buildLiveShareUrl() { |
| | | const currentCourseId = String(liveDetail.value.courseId || courseId.value || '').trim() |
| | | if (!currentCourseId) { |
| | | return `${config.BASE_URL}/#/pages/home/home` |
| | | } |
| | | return `${config.BASE_URL}/#/pages/liveDetail/liveDetail?id=${encodeURIComponent(currentCourseId)}` |
| | | } |
| | | |
| | | /** |
| | | * 直接拉起微信好友聊天分享当前直播。 |
| | | */ |
| | | function shareToWechatSession() { |
| | |
| | | return |
| | | } |
| | | weixinService.send({ |
| | | type: 'text', |
| | | type: 0, |
| | | title: liveDetail.value.title?.trim() || '养生直播', |
| | | content: buildLiveShareText(), |
| | | href: buildLiveShareUrl(), |
| | | thumbs: liveDetail.value.displayImage || liveDetail.value.coverImage ? [liveDetail.value.displayImage || liveDetail.value.coverImage] : undefined, |
| | | extra: { |
| | | scene: 'WXSceneSession', |
| | | }, |
| | |
| | | import { computed, getCurrentInstance, nextTick, ref } from 'vue'; |
| | | import { onHide, onLoad, onShow, onUnload } from '@dcloudio/uni-app'; |
| | | import { getLiveCourseDetail, getLiveQuizStatus, submitLiveQuiz, uploadLiveWatchRecord } from './service'; |
| | | import config from '@/config/index.js'; |
| | | |
| | | type LiveActionStatus = 'RESERVABLE' | 'RESERVED' | 'WATCHABLE' | 'CLOSED' |
| | | type QuizViewState = 'answering' | 'success' | 'alreadyAwarded' | 'wrong' | 'unopened' |
| | |
| | | } |
| | | |
| | | /** |
| | | * 生成直播观看页 H5 分享链接,供微信好友直接打开页面。 |
| | | */ |
| | | function buildWatchLiveShareUrl() { |
| | | if (!courseId.value) { |
| | | return `${config.BASE_URL}/#/pages/home/home` |
| | | } |
| | | const queryList = [`courseId=${encodeURIComponent(courseId.value)}`] |
| | | if (activeSessionId.value) { |
| | | queryList.push(`sessionId=${encodeURIComponent(activeSessionId.value)}`) |
| | | } |
| | | return `${config.BASE_URL}/#/pages/watchLive/watchLive?${queryList.join('&')}` |
| | | } |
| | | |
| | | /** |
| | | * 直接拉起微信好友聊天分享当前直播。 |
| | | */ |
| | | function shareToWechatSession() { |
| | |
| | | return |
| | | } |
| | | weixinService.send({ |
| | | type: 'text', |
| | | type: 0, |
| | | title: liveDetail.value.title?.trim() || '直播观看', |
| | | content: buildWatchLiveShareText(), |
| | | href: buildWatchLiveShareUrl(), |
| | | thumbs: liveDetail.value.displayImage || liveDetail.value.coverImage ? [liveDetail.value.displayImage || liveDetail.value.coverImage] : undefined, |
| | | extra: { |
| | | scene: 'WXSceneSession', |
| | | }, |