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

361 lines
8.9 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="课题详情" :xloading="true" @right-click="onMore">
<template v-slot:right>
<x-icon name="more-fill" color="#6B7280" font-size="18"></x-icon>
</template>
<!-- ============ 课题 hero ============ -->
<view class="subj-bar">
<view class="subj-c1"></view>
<view class="subj-info">
<view class="subj-nav" @click="prevSubject">
<x-icon name="arrow-left-s-line" color="#FFFFFF" font-size="18"></x-icon>
</view>
<view class="subj-mid">
<view class="hr-flex1">
<text class="subj-name">{{ subject }}</text>
<text class="subj-sub">急救基础 · 心肺复苏 · 资料 6 份 · 题库 50</text>
</view>
</view>
<view class="subj-nav" @click="nextSubject">
<x-icon name="arrow-right-s-line" color="#FFFFFF" font-size="18"></x-icon>
</view>
</view>
</view>
<view class="hr-body">
<!-- ============ 学习进度 ============ -->
<hr-card>
<view class="hr-row-between" style="margin-bottom:9px">
<text class="sj-lb">学习进度</text>
<text class="sj-pct">{{ progress }}%</text>
</view>
<hr-progress :percent="progress"></hr-progress>
<view class="hr-row" style="margin-top:11px">
<text class="sj-meta">已学资料 <text class="sj-meta-v">4/6</text></text>
<text class="sj-meta sj-meta-gap">已练题 <text class="sj-meta-v">32/50</text></text>
<text class="sj-meta sj-meta-gap">正确率 <text class="sj-meta-ok">{{ rate }}%</text></text>
</view>
</hr-card>
<!-- ============ 功能入口 ============ -->
<hr-sec title="功能入口"></hr-sec>
<view class="sj-entries">
<view v-for="(e, i) in entries" :key="i" class="sj-entry"
:class="i == entries.length - 1 ? 'sj-entry-last' : ''" @click="onEntry(i)">
<view class="sj-entry-ic" :style="{ backgroundColor: e.bg }">
<x-icon :name="entryIcon(e.icon)" :color="e.color" font-size="40rpx"></x-icon>
</view>
<text class="sj-entry-b">{{ e.name }}</text>
<text class="sj-entry-s">{{ e.sub }}</text>
</view>
</view>
<!-- ============ 文档资料 ============ -->
<hr-sec title="文档资料" more="共 6 份 " @more-click="onMore"></hr-sec>
<hr-card variant="list">
<view v-for="(m, i) in materials" :key="m.id" class="doc-row"
:class="i == materials.length - 1 ? 'doc-row-last' : ''" @click="openMaterial(m)">
<view class="doc-ic" :style="{ backgroundColor: m.badgeColor }">
<x-icon v-if="m.kind == 'video'" name="play-fill" color="#FFFFFF" font-size="32rpx"></x-icon>
<text v-else class="doc-ic-tx">{{ m.icon }}</text>
</view>
<view class="hr-flex1">
<text class="doc-b">{{ m.title }}</text>
<text class="doc-s">{{ m.meta }}</text>
</view>
<view class="doc-go">
<x-icon name="arrow-right-s-line" color="#9CA3AF" font-size="34rpx"></x-icon>
</view>
</view>
</hr-card>
<view class="doc-hint">
<text class="doc-hint-t">✦ 点击任意资料查看详情(视频与文档共用同一详情页)</text>
</view>
<view style="height:14px"></view>
</view>
</hr-page>
</template>
<script lang="uts" setup>
import { ref, computed } from 'vue'
import { subjectMaterials, courseGroups } from '@/mock/index.uts'
import { HrMaterial, HrSubject } from '@/mock/types.uts'
import { navTo } from '@/utils/nav.uts'
import { toRemixIcon } from '@/utils/icon.uts'
/**
* s35 课题详情
* 不再设章节目录与章节学习;改为「学习进度 + 错题/收藏/顺序练习 三入口 + 文档资料列表」。
*/
type Entry = { icon : string, name : string, sub : string, bg : string, color : string }
/*
* ⚠ 模板里不能直接调用「import 进来的函数」:
* uvue 编译器会把 `<x-icon :name="toRemixIcon(e.icon)">` 编译成 `unref(toRemixIcon)(e.icon)`
* UTS/Kotlin 侧报 `找不到名称"invoke"` / `Function invocation 'toRemixIcon(...)' expected`。
* 必须包一层本文件内定义的函数(模板对 script 内定义的函数会走 _ctx.xxx,正常)。
*/
function entryIcon(glyph : string) : string {
return toRemixIcon(glyph)
}
const subject = ref('成人 CPR(高级生命支持)')
const progress = ref(68)
const rate = ref(78)
const materials = ref<HrMaterial[]>(subjectMaterials)
const entries : Entry[] = [
{ icon: '✗', name: '我的错题', sub: '12 道', bg: '#FEF2F2', color: '#DC2626' } as Entry,
{ icon: '★', name: '我的收藏', sub: '8 道', bg: '#FEF3C7', color: '#B45309' } as Entry,
{ icon: '▸', name: '顺序练习', sub: '50 题 · 约 30 分钟', bg: '#ECFDF5', color: '#059669' } as Entry
]
/** 全部三级课题,用于上一个 / 下一个切换 */
const allSubjects = computed(() : HrSubject[] => {
const list : HrSubject[] = []
for (let gi = 0; gi < courseGroups.length; gi++) {
const g = courseGroups[gi]
for (let ii = 0; ii < g.items.length; ii++) {
list.push(g.items[ii])
}
}
return list
})
function indexOfSubject() : number {
for (let i = 0; i < allSubjects.value.length; i++) {
if (allSubjects.value[i].name == subject.value) { return i }
}
return 0
}
function prevSubject() : void {
let i = indexOfSubject() - 1
if (i < 0) { i = allSubjects.value.length - 1 }
subject.value = allSubjects.value[i].name
}
function nextSubject() : void {
let i = indexOfSubject() + 1
if (i >= allSubjects.value.length) { i = 0 }
subject.value = allSubjects.value[i].name
}
function onEntry(i : number) : void {
if (i == 0) { navTo('/pages/study/wrong-book'); return }
if (i == 1) { uni.showToast({ title: '我的收藏 · 8 道题', icon: 'none' }); return }
navTo('/pages/course/practice')
}
function openMaterial(m : HrMaterial) : void {
navTo('/pages/course/media?id=' + m.id)
}
function onMore() : void {
uni.showToast({ title: '资料管理 / 下载全部', icon: 'none' })
}
</script>
<style>
/* ---------- hero ---------- */
.subj-bar {
background-image: linear-gradient(to bottom right, #2563EB, #1D4ED8);
padding: 24rpx 28rpx 24rpx 28rpx;
overflow: hidden;
position: relative;
}
.subj-c1 {
position: absolute;
right: -72rpx;
top: -92rpx;
width: 260rpx;
height: 260rpx;
border-radius: 130rpx;
background-color: rgba(255, 255, 255, 0.09);
}
.subj-info {
display: flex;
flex-direction: row;
align-items: center;
}
.subj-nav {
width: 52rpx;
height: 52rpx;
border-radius: 16rpx;
background-color: rgba(255, 255, 255, 0.18);
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
}
.subj-mid {
flex: 1;
display: flex;
flex-direction: row;
align-items: center;
margin-left: 18rpx;
margin-right: 18rpx;
}
.subj-name {
font-size: 31.4rpx;
font-weight: bold;
color: #FFFFFF;
lines: 1;
}
.subj-sub {
font-size: 23.6rpx;
color: rgba(255, 255, 255, 0.85);
margin-top: 4rpx;
lines: 1;
}
/* ---------- 学习进度 ---------- */
.sj-lb {
font-size: 29.2rpx;
font-weight: bold;
color: #1F2937;
}
.sj-pct {
font-size: 25.8rpx;
font-weight: bold;
color: #2563EB;
}
.sj-meta {
font-size: 24.6rpx;
color: #6B7280;
}
.sj-meta-gap {
margin-left: 28rpx;
}
.sj-meta-v {
font-size: 24.6rpx;
color: #1F2937;
font-weight: bold;
}
.sj-meta-ok {
font-size: 24.6rpx;
color: #059669;
font-weight: bold;
}
/* ---------- 功能入口 ---------- */
.sj-entries {
display: flex;
flex-direction: row;
}
.sj-entry {
flex: 1;
background-color: #FFFFFF;
border: 2rpx solid #E5E7EB;
border-radius: 26rpx;
padding: 26rpx 12rpx 22rpx 12rpx;
flex-direction: column;
align-items: center;
margin-right: 18rpx;
}
.sj-entry-last {
margin-right: 0rpx;
}
.sj-entry-ic {
width: 80rpx;
height: 80rpx;
border-radius: 26rpx;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
margin-bottom: 14rpx;
}
.sj-entry-b {
font-size: 25.8rpx;
font-weight: bold;
color: #1F2937;
}
.sj-entry-s {
font-size: 21.2rpx;
color: #9CA3AF;
margin-top: 4rpx;
}
/* ---------- 资料行 ---------- */
.doc-row {
display: flex;
flex-direction: row;
align-items: center;
padding-top: 24rpx;
padding-bottom: 24rpx;
border-bottom: 2rpx solid #F3F4F6;
}
.doc-row-last {
border-bottom-width: 0rpx;
}
.doc-ic {
width: 72rpx;
height: 72rpx;
border-radius: 20rpx;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
margin-right: 22rpx;
}
.doc-ic-tx {
font-size: 21.2rpx;
font-weight: bold;
color: #FFFFFF;
}
.doc-b {
font-size: 29.2rpx;
color: #1F2937;
lines: 1;
}
.doc-s {
font-size: 23.6rpx;
color: #9CA3AF;
margin-top: 4rpx;
}
.doc-go {
margin-left: 16rpx;
display: flex;
flex-direction: row;
align-items: center;
}
.doc-hint {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
padding: 16rpx 0rpx 4rpx 0rpx;
}
.doc-hint-t {
font-size: 23.6rpx;
color: #9CA3AF;
}
</style>