| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132 |
- <template>
- <view class="knowledge-tree-node">
- <view class="" @click.stop="handleClick">
- <view class="flex items-center border-0 border-b border-solid border-[#E6E6E6] py-20">
- <view class="flex-1 min-w-1 flex items-center">
- <uv-icon v-if="!nodeData.isLeaf" name="arrow-right" size="14" color="#888"
- :custom-class="['mr-16 transition-transform duration-300', nodeData.isExpanded ? 'rotate-90' : '']" />
- <view>
- <view class="block text-28 text-fore-title font-bold ellipsis-1">{{ nodeData.name }}</view>
- <view class="mt-8 text-24 text-fore-light flex items-center">
- <progress class="w-100 rounded-full overflow-hidden" :percent="getProgressPercent(nodeData)"
- :show-text="false" activeColor="#31a0fc" backgroundColor="#efefef" />
- <text class="ml-10 text-primary">{{ nodeData.finishedCount }}</text>
- <text>/{{ nodeData.questionCount }}道</text>
- <text class="ml-10">正确率</text>
- <text class="ml-10 text-primary font-bold">{{ getCorrectRate(nodeData.finishedRatio) }}%</text>
- </view>
- </view>
- </view>
- <view v-if="nodeData.isLeaf"
- class="px-20 py-8 border border-solid border-primary rounded-full text-24 text-primary"
- @click.stop="handleStartPractice">开始练习</view>
- </view>
- </view>
- <!-- 子节点容器 -->
- <view v-if="nodeData.children && nodeData.children.length > 0" ref="childrenRef" class="knowledge-children"
- :id="`knowledge-children-${nodeData.id}`"
- :class="['ml-80 overflow-hidden transition-[height] duration-300', { 'is-measuring': isMeasuringHeight }]"
- :style="{ height: measuredHeight + 'px' }">
- <knowledge-tree-node v-for="child in nodeData.children" :key="child.name" :node-data="child"
- :parent-data="nodeData" @start-practice="handleChildStartPractice">
- </knowledge-tree-node>
- </view>
- </view>
- </template>
- <script lang="ts" setup>
- import KnowledgeTreeNode from './knowledge-tree-node.vue';
- import * as Study from '@/types/study';
- import { getCurrentInstance } from 'vue';
- const instance = getCurrentInstance();
- // 定义 props
- const props = defineProps({
- nodeData: {
- type: Object as PropType<Study.KnowledgeNode>,
- required: true
- },
- parentData: {
- type: Object as PropType<Study.KnowledgeNode>,
- default: null
- }
- });
- const isMeasuringHeight = ref(false);
- const childrenRef = ref();
- const measuredHeight = ref(0);
- // 定义 emits
- const emit = defineEmits(['startPractice']);
- const getRect = (selector: string) => {
- return new Promise((resolve: (rect: { top: number, height: number }) => void) => {
- const query = uni.createSelectorQuery().in(instance?.proxy);
- query.select(selector).boundingClientRect(function (rect) {
- resolve(rect as { top: number, height: number });
- }).exec();
- });
- }
- const measureHeight = () => {
- isMeasuringHeight.value = true;
- setTimeout(() => {
- nextTick(() => {
- getRect(`#knowledge-children-${props.nodeData.id}`).then((res: any) => {
- isMeasuringHeight.value = false;
- setTimeout(() => {
- nextTick(() => {
- measuredHeight.value = res?.height ?? 0;
- });
- }, 50);
- });
- });
- }, 50);
- }
- // 处理节点点击
- const handleClick = () => {
- // 切换展开状态
- props.nodeData.isExpanded = !props.nodeData.isExpanded;
- if (props.nodeData.isExpanded) {
- measureHeight();
- } else {
- measuredHeight.value = 0;
- }
- };
- // 处理开始练习事件
- const handleStartPractice = () => {
- emit('startPractice', props.nodeData);
- };
- // 处理子节点开始练习事件
- const handleChildStartPractice = (nodeData: Study.KnowledgeNode) => {
- emit('startPractice', nodeData);
- };
- const getCorrectRate = (rate: number): number => {
- if (!rate) {
- return 0;
- }
- if (rate > 0 && rate < 0.1) {
- return 0.1;
- }
- return rate;
- };
- const getProgressPercent = (nodeData: Study.KnowledgeNode): number => {
- const { finishedCount, questionCount } = nodeData;
- if (!finishedCount || !questionCount) {
- return 0;
- }
- return Math.min(finishedCount / questionCount, 1) * 100;
- };
- </script>
- <style lang="scss" scoped>
- .is-measuring {
- opacity: 0;
- position: fixed;
- z-index: -1000;
- transform: translateX(0);
- height: auto !important;
- }
- </style>
|