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

289 lines
7.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="onSort">
<template v-slot:right>
<x-icon name="arrow-up-down-line" color="#6B7280" font-size="17"></x-icon>
</template>
<view class="hr-body">
<!-- ============ 四大类目(xTabs ============ -->
<x-tabs :list="catTabs" :model-value="activeId" item-width="25%" height="84rpx" font-size="28rpx"
active-font-size="28rpx" title-color="#6B7280" active-title-color="#2563EB" line-color="#2563EB"
title-padding="0" :is-item-center="true" @change="onCatChange"></x-tabs>
<!-- ============ 课题筛选(组件库 xRadioButton 分段切换) ============ -->
<view style="margin-top:10px;margin-bottom:8rpx">
<x-radio-button v-model="chipId" :list="chipTabs" height="64rpx" space="4rpx" round="22rpx"
text-style="font-weight:bold;" bg-color="#E5E7EB" active-color="#2563EB"
active-font-color="#FFFFFF" font-color="#6B7280" font-size="22rpx"></x-radio-button>
</view>
<!-- ============ 题目列表 ============ -->
<view v-if="cat == 0" style="margin-top:10px">
<view v-for="(q, i) in wrongList" :key="q.id" class="qa-card">
<view class="qa-top">
<text class="qa-stem">{{ q.stem }}</text>
<hr-chip :text="wrongTimes(i)" theme="red"></hr-chip>
</view>
<text class="qa-ans">正确答案 <text class="qa-ans-hl">{{ rightAnswer(q) }}</text></text>
<view class="qa-meta">
<text class="qa-meta-t">来自 <text class="qa-meta-hl">{{ q.from }}</text></text>
<text class="qa-meta-t qa-meta-date">{{ wrongDates[i] }}</text>
<view class="hr-flex1"></view>
<view class="qa-fold" @click="toggle(i)">
<x-icon :name="opened[i] ? 'arrow-up-s-line' : 'arrow-down-s-line'" color="#2563EB"
font-size="26rpx"></x-icon>
<text class="qa-fold-t">{{ opened[i] ? '收起解析' : '查看解析' }}</text>
</view>
</view>
<!-- 手风琴展开:展开进度由 accStyle() 逐帧下发 -->
<view v-if="opened[i]" :style="accStyle(i)">
<view class="qa-analysis">
<text class="qa-analysis-t">{{ q.analysis }}</text>
</view>
</view>
</view>
</view>
<!-- 其它类目占位 -->
<hr-empty v-else :icon="emptyIcon" :title="emptyTitle" :sub="emptySub" action="去课程库学习"
@action="goLibrary"></hr-empty>
<view style="height:14px"></view>
</view>
</hr-page>
</template>
<script lang="uts" setup>
import { ref, computed } from 'vue'
import { wrongQuestions } from '@/mock/index.uts'
import { HrQuestion } from '@/mock/types.uts'
import { navTo } from '@/utils/nav.uts'
import { TABS_ITEM_INFO, TABS_ITEM, RADIO_BUTTON } from '@/uni_modules/tmx-ui/interface.uts'
/**
* s38 学习中心
* 汇总「错题 / 收藏 / 历史 / 笔记」四类目,替代原先散落各处的入口。
* 四类目改用组件库 xTabs(原 lc-tabs 自绘分段控件已移除)。
*/
const catTabs : TABS_ITEM_INFO[] = [
{ title: '错题 12', id: '0' } as TABS_ITEM_INFO,
{ title: '收藏 8', id: '1' } as TABS_ITEM_INFO,
{ title: '历史', id: '2' } as TABS_ITEM_INFO,
{ title: '笔记 5', id: '3' } as TABS_ITEM_INFO
]
/**
* 课题筛选(组件库 xRadioButton 分段切换,替代原 hr-chip 横向滚动)
* ⚠ RADIO_BUTTON 的 id 是 string:统一用下标字符串,选中值即筛选序号。
*/
const chipTabs : RADIO_BUTTON[] = [
{ id: '0', title: '全部' } as RADIO_BUTTON,
{ id: '1', title: '心肺复苏 5' } as RADIO_BUTTON,
{ id: '2', title: '气道管理 4' } as RADIO_BUTTON,
{ id: '3', title: 'AED 3' } as RADIO_BUTTON
]
const chipId = ref('0')
const wrongDates : string[] = ['09-15', '09-14', '09-12']
const activeId = ref('0')
const opened = ref<boolean[]>([false, false, false])
const wrongList = ref<HrQuestion[]>(wrongQuestions)
const cat = computed(() : number => {
const i = parseInt(activeId.value)
return isNaN(i) ? 0 : i
})
// ⚠ x-tabs 的 change 下发的是 TABS_ITEM(不是 TABS_ITEM_INFO),形参类型必须一致
function onCatChange(item : TABS_ITEM, index : number) : void {
activeId.value = item.id == '' ? index.toString() : item.id
}
const emptyIcon = computed(() : string => {
if (cat.value == 1) { return '★' }
if (cat.value == 2) { return '◔' }
return '✎'
})
const emptyTitle = computed(() : string => {
if (cat.value == 1) { return '暂无收藏题目' }
if (cat.value == 2) { return '暂无学习历史' }
return '暂无学习笔记'
})
const emptySub = computed(() : string => {
return '在课题中收藏题目、记录笔记后会自动汇总到这里'
})
function wrongTimes(i : number) : string {
if (i == 0) { return '错 2 次' }
return '错 1 次'
}
function rightAnswer(q : HrQuestion) : string {
if (q.answer < q.options.length) {
const o = q.options[q.answer]
return `${o.key}. ${o.text}`
}
return '—'
}
/*
* 手风琴展开入场动画
* ⚠ App 端 uvue 不支持 @keyframes / animation(编译期直接报 ERROR),
* 改为定时器逐帧推进 accT0 → 1),再由 accStyle() 下发 opacity + translateY。
* 240ms30ms × 8 帧),与设计稿 qa-acc-in 一致。
*/
const accIdx = ref(-1)
const accT = ref(1)
let accTimer : number = 0
function stopAcc() : void {
if (accTimer != 0) {
clearInterval(accTimer)
accTimer = 0
}
}
function runAcc(i : number) : void {
stopAcc()
accIdx.value = i
accT.value = 0
accTimer = setInterval(() => {
const t = accT.value + 0.16
if (t >= 1) {
accT.value = 1
stopAcc()
return
}
accT.value = t
}, 30)
}
/** 仅正在展开的那一项需要过渡样式,其余返回空串(即最终态) */
function accStyle(i : number) : string {
if (i != accIdx.value || accT.value >= 1) { return '' }
const t = accT.value
const y = Math.round(-16 * (1 - t))
return 'opacity:' + t.toString() + ';transform:translateY(' + y.toString() + 'rpx);'
}
/** 手风琴:展开一项时其余自动收起 */
function toggle(i : number) : void {
const wasOpen = opened.value[i]
const arr : boolean[] = []
for (let k = 0; k < wrongList.value.length; k++) { arr.push(false) }
arr[i] = !wasOpen
opened.value = arr
if (wasOpen) {
stopAcc()
accIdx.value = -1
accT.value = 1
} else {
runAcc(i)
}
}
onUnload(() => {
stopAcc()
})
function goLibrary() : void {
navTo('/pages/course/library')
}
function onSort() : void {
uni.showToast({ title: '按时间 / 次数排序', icon: 'none' })
}
</script>
<style>
/* ---------- 题目卡 ---------- */
.qa-card {
background-color: #FFFFFF;
border: 2rpx solid #E5E7EB;
border-left: 8rpx solid #2563EB;
border-radius: 20rpx;
padding: 22rpx 24rpx 22rpx 24rpx;
margin-bottom: 20rpx;
}
.qa-top {
display: flex;
flex-direction: row;
align-items: flex-start;
}
.qa-stem {
flex: 1;
font-size: 29.2rpx;
font-weight: bold;
color: #1F2937;
margin-right: 18rpx;
}
.qa-ans {
font-size: 26.8rpx;
color: #6B7280;
margin-top: 14rpx;
}
.qa-ans-hl {
font-size: 26.8rpx;
color: #059669;
font-weight: bold;
}
.qa-meta {
display: flex;
flex-direction: row;
align-items: center;
margin-top: 18rpx;
}
.qa-meta-t {
font-size: 24.6rpx;
color: #9CA3AF;
}
.qa-meta-hl {
font-size: 24.6rpx;
color: #1F2937;
font-weight: bold;
}
.qa-meta-date {
margin-left: 24rpx;
}
.qa-fold {
display: flex;
flex-direction: row;
align-items: center;
}
.qa-fold-t {
font-size: 25.8rpx;
color: #2563EB;
font-weight: bold;
margin-left: 4rpx;
}
/* ---------- 手风琴展开 ----------
⚠ App 端 uvue 不支持 @keyframes/animation
入场过渡已改为 script 内定时器推进 accT + accStyle() 下发样式。 */
.qa-analysis {
margin-top: 20rpx;
padding: 18rpx 20rpx 18rpx 20rpx;
border-radius: 18rpx;
background-color: #F9FAFB;
}
.qa-analysis-t {
font-size: 25.8rpx;
color: #6B7280;
}
</style>