Files
hospital-rescue-app-v2/pages/course/media.uvue
T
2026-09-24 16:25:22 +08:00

491 lines
12 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<hr-page page-bg="#F3F4F6" status-bg="#FFFFFF" :nav-title="material.title" @right-click="onStar">
<template v-slot:right>
<x-icon :name="starred ? 'star-fill' : 'star-line'" :color="starred ? '#F59E0B' : '#6B7280'"
font-size="19"></x-icon>
</template>
<view class="hr-body" style="padding-bottom:22px">
<!-- ============ 视频资料 ============ -->
<view v-if="isVideo">
<view class="vd" :style="{ height: videoH + 'rpx' }">
<!-- 播放/暂停 -->
<view class="vd-play" @click="togglePlay">
<x-icon :name="playing ? 'pause-fill' : 'play-fill'" :color="playing ? '#FFFFFF' : '#1D4ED8'"
font-size="20"></x-icon>
</view>
<!-- 进度条 -->
<view class="vd-bar">
<view class="vd-track">
<view class="vd-track-i" :style="{ width: played + '%' }"></view>
</view>
<view class="vd-row">
<text class="vd-time">{{ curTime }} / {{ totalTime }}</text>
<view class="vd-acts">
<view @click="togglePlay">
<x-icon :name="playing ? 'pause-line' : 'play-line'" color="#FFFFFF"
font-size="16"></x-icon>
</view>
<view style="margin-left:14px" @click="onFullscreen">
<x-icon name="fullscreen-line" color="#FFFFFF" font-size="16"></x-icon>
</view>
</view>
</view>
</view>
</view>
<hr-card style="margin-top:10px">
<view class="hr-row-top">
<text class="md-title">{{ material.title }}</text>
<view class="md-badge" :style="{ backgroundColor: material.badgeColor }">
<text class="md-badge-t">{{ material.badge }}</text>
</view>
</view>
<text class="md-meta">{{ material.meta }}</text>
<text class="md-from">来自 {{ material.from }}</text>
<view class="hr-row" style="margin-top:12px">
<view style="flex:1">
<hr-progress :percent="played"></hr-progress>
</view>
<text class="md-seen">已看 {{ played }}%</text>
</view>
</hr-card>
</view>
<!-- ============ 文档资料 ============ -->
<view v-else>
<view class="dv">
<view class="dv-sheet">
<view class="dv-t"></view>
<view class="dv-l dv-l-s"></view>
<view class="dv-l"></view>
<view class="dv-l dv-l-m"></view>
<view class="dv-img"></view>
<view class="dv-l dv-l-s"></view>
<view class="dv-l dv-l-m"></view>
<text class="dv-pg">{{ page }} / 12</text>
</view>
</view>
<view class="dv-pager">
<view class="dv-pager-b" @click="prevPage">
<x-icon name="arrow-left-s-line" color="#6B7280" font-size="16"></x-icon>
</view>
<text class="dv-pager-t">第 {{ page }} 页 / 共 12 页</text>
<view class="dv-pager-b" @click="nextPage">
<x-icon name="arrow-right-s-line" color="#6B7280" font-size="16"></x-icon>
</view>
</view>
<hr-card style="margin-top:10px">
<view class="hr-row-top">
<text class="md-title">{{ material.title }}</text>
<view class="md-badge" :style="{ backgroundColor: material.badgeColor }">
<text class="md-badge-t">{{ material.badge }}</text>
</view>
</view>
<text class="md-meta">{{ material.meta }}</text>
<text class="md-from">来自 {{ material.from }}</text>
</hr-card>
</view>
<!-- ============ 资料简介 ============ -->
<hr-sec title="资料简介" :first="false"></hr-sec>
<hr-card>
<text class="md-desc">{{ material.summary }}</text>
</hr-card>
<!-- ============ 学习笔记(仅视频) ============ -->
<view v-if="isVideo">
<hr-sec title="学习笔记" more="编辑 " @more-click="onEditNote"></hr-sec>
<hr-card>
<text class="md-desc">{{ note }}</text>
<text class="md-note-time">最后编辑:昨天 21:32</text>
</hr-card>
</view>
<!-- ============ 离线下载 ============ -->
<hr-sec title="离线下载" :more="downloaded ? '⇅ 管理 ' : '⇅ 下载 '" @more-click="onDownload"></hr-sec>
<hr-card variant="list">
<hr-lrow :icon="downloaded ? '✓' : '⇅'" :theme="downloaded ? 'grn' : 'pri'"
:title="downloaded ? '已下载到本机' : '尚未下载'" :sub="downloaded ? '2.4 MB · 无网络时仍可查阅' : '点击下载后可离线查阅'"
:value="downloaded ? '已存' : '下载'" :value-theme="downloaded ? 'grn' : 'pri'" :value-bold="true"
:is-last="true" @click="onDownload"></hr-lrow>
</hr-card>
<view style="height:8px"></view>
</view>
<template v-slot:footer>
<view class="hr-fixedbar">
<view class="hr-btn hr-btn-ghost md-fav" @click="onStar">
<x-icon :name="starred ? 'star-fill' : 'star-line'" :color="starred ? '#F59E0B' : '#1F2937'"
font-size="17"></x-icon>
<text class="md-fav-t">{{ starred ? '已收藏' : '收藏' }}</text>
</view>
<view class="hr-btn hr-btn-primary md-main" @click="onMainAction">
<text class="md-main-t">{{ isVideo ? (downloaded ? '已下载离线观看' : '下载离线观看') : '在线查看' }}</text>
</view>
</view>
</template>
</hr-page>
</template>
<script lang="uts" setup>
import { ref, computed } from 'vue'
import { subjectMaterials } from '@/mock/index.uts'
import { HrMaterial } from '@/mock/types.uts'
/**
* s02 / s41 资料详情(视频与文档共用同一页)
* 标题即资料名称;不再有章节目录与章节学习。
*/
const material = ref<HrMaterial>(subjectMaterials[1])
const starred = ref(false)
const downloaded = ref(false)
const page = ref(1)
const playing = ref(false)
const played = ref(62)
const videoH = ref(386)
const totalSeconds = 504
let timer : number = 0
const note = '按压深度 5~6 cm,频率 100~120 次/分;每次按压后必须让胸廓完全回弹,尽量减少中断(<10 秒)……'
// ⚠ UTS 不提升函数声明(App 端编译报 UTSC error18: 找不到名称),
// 被调用的函数一律写在调用点之前
function formatTime(sec : number) : string {
const m = Math.floor(sec / 60)
const s = sec % 60
const ss = s < 10 ? '0' + s.toString() : s.toString()
return `${m}:${ss}`
}
function stopTimer() : void {
if (timer != 0) {
clearInterval(timer)
timer = 0
}
}
const isVideo = computed(() : boolean => {
return material.value.kind == 'video'
})
const curTime = computed(() : string => {
const sec = Math.floor(totalSeconds * played.value / 100)
return formatTime(sec)
})
const totalTime = computed(() : string => {
return formatTime(totalSeconds)
})
onLoad((opt) => {
const raw = opt['id']
if (raw != null) {
const id = raw as string
for (let i = 0; i < subjectMaterials.length; i++) {
if (subjectMaterials[i].id == id) {
material.value = subjectMaterials[i]
played.value = subjectMaterials[i].progress > 0 ? subjectMaterials[i].progress : 0
break
}
}
}
// 视频区保持 16:9:内容宽 = 750rpx - 页面左右各 32rpx
videoH.value = Math.round((750 - 64) * 9 / 16)
})
onUnload(() => {
stopTimer()
})
function togglePlay() : void {
playing.value = !playing.value
if (playing.value) {
stopTimer()
timer = setInterval(() => {
if (played.value >= 100) {
played.value = 100
playing.value = false
stopTimer()
return
}
played.value = played.value + 1
}, 1000)
} else {
stopTimer()
}
}
function prevPage() : void {
if (page.value <= 1) {
uni.showToast({ title: '已是第一页', icon: 'none' })
return
}
page.value = page.value - 1
}
function nextPage() : void {
if (page.value >= 12) {
uni.showToast({ title: '已是最后一页', icon: 'none' })
return
}
page.value = page.value + 1
}
function onStar() : void {
starred.value = !starred.value
uni.showToast({ title: starred.value ? '已加入收藏' : '已取消收藏', icon: 'none' })
}
function onDownload() : void {
if (downloaded.value) {
uni.showToast({ title: '已下载到本机', icon: 'none' })
return
}
uni.showLoading({ title: '下载中' })
setTimeout(() => {
uni.hideLoading()
downloaded.value = true
uni.showToast({ title: '下载完成 · 可离线查阅', icon: 'success' })
}, 900)
}
function onMainAction() : void {
if (isVideo.value) {
onDownload()
return
}
uni.showToast({ title: '已用系统阅读器打开', icon: 'none' })
}
function onEditNote() : void {
uni.showToast({ title: '编辑学习笔记', icon: 'none' })
}
function onFullscreen() : void {
uni.showToast({ title: '已进入全屏播放', icon: 'none' })
}
</script>
<style>
/* ---------- 视频 ---------- */
.vd {
width: 100%;
background-color: #111827;
border-radius: 24rpx;
overflow: hidden;
position: relative;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
}
.vd-play {
width: 104rpx;
height: 104rpx;
border-radius: 52rpx;
background-color: rgba(255, 255, 255, 0.92);
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
}
.vd-bar {
position: absolute;
left: 0rpx;
right: 0rpx;
bottom: 0rpx;
padding: 0rpx 24rpx 20rpx 24rpx;
}
.vd-track {
height: 6rpx;
border-radius: 6rpx;
background-color: rgba(255, 255, 255, 0.28);
}
.vd-track-i {
height: 6rpx;
border-radius: 6rpx;
background-color: #2563EB;
}
.vd-row {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
margin-top: 16rpx;
}
.vd-time {
font-size: 24.6rpx;
color: #FFFFFF;
}
.vd-acts {
display: flex;
flex-direction: row;
align-items: center;
}
/* ---------- 文档预览 ---------- */
.dv {
background-color: #E5E7EB;
border-radius: 24rpx;
padding: 24rpx 24rpx 24rpx 24rpx;
display: flex;
flex-direction: row;
justify-content: center;
}
.dv-sheet {
width: 80%;
background-color: #FFFFFF;
border-radius: 8rpx;
padding: 30rpx 26rpx 32rpx 26rpx;
box-shadow: 0 6rpx 24rpx rgba(15, 23, 42, 0.16);
position: relative;
}
.dv-t {
height: 18rpx;
width: 58%;
border-radius: 6rpx;
background-color: #CBD5E1;
margin-bottom: 22rpx;
}
.dv-l {
height: 12rpx;
border-radius: 6rpx;
background-color: #E5E7EB;
margin-bottom: 16rpx;
}
.dv-l-s {
width: 88%;
}
.dv-l-m {
width: 70%;
}
.dv-img {
height: 100rpx;
border-radius: 12rpx;
background-color: #F1F5F9;
margin: 6rpx 0rpx 20rpx 0rpx;
}
.dv-pg {
font-size: 20.2rpx;
color: #9CA3AF;
}
.dv-pager {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
padding-top: 16rpx;
}
.dv-pager-b {
width: 48rpx;
height: 48rpx;
border-radius: 16rpx;
background-color: #FFFFFF;
border: 2rpx solid #E5E7EB;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
}
.dv-pager-t {
font-size: 23.6rpx;
color: #9CA3AF;
margin-left: 24rpx;
margin-right: 24rpx;
}
/* ---------- 详情 ---------- */
.md-title {
flex: 1;
font-size: 33.6rpx;
font-weight: bold;
color: #1F2937;
margin-right: 18rpx;
}
.md-badge {
border-radius: 14rpx;
padding: 6rpx 18rpx 6rpx 18rpx;
}
.md-badge-t {
font-size: 23.6rpx;
font-weight: bold;
color: #FFFFFF;
}
.md-meta {
font-size: 25.8rpx;
color: #6B7280;
margin-top: 12rpx;
}
.md-from {
font-size: 24.6rpx;
color: #9CA3AF;
margin-top: 8rpx;
}
.md-seen {
font-size: 24.6rpx;
font-weight: bold;
color: #2563EB;
margin-left: 20rpx;
}
.md-desc {
font-size: 28rpx;
color: #6B7280;
line-height: 1.8;
}
.md-note-time {
font-size: 23.6rpx;
color: #9CA3AF;
margin-top: 18rpx;
}
.md-fav {
flex: 0 0 220rpx;
margin-right: 18rpx;
}
.md-fav-t {
font-size: 31.4rpx;
font-weight: bold;
color: #1F2937;
margin-left: 10rpx;
}
.md-main {
flex: 1;
}
.md-main-t {
font-size: 32.4rpx;
font-weight: bold;
color: #FFFFFF;
}
</style>