| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155 |
- <template>
- <view class="knowledge-tree-node">
- <view class="min-h-[68px]" @click.stop="handleClick">
- <view class="flex items-center border-0 border-b border-solid border-[#E6E6E6] py-26">
- <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-4 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>
- <slot>
- <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>
- </slot>
- </view>
- </view>
- <!-- 子节点容器 -->
- <view v-if="nodeData.children && nodeData.children.length > 0"
- :class="['ml-40 overflow-hidden transition-all duration-300 h-0']"
- :style="{ height: nodeData.actualHeight + 'px' }">
- <knowledge-tree-node v-for="child in nodeData.children" :key="child.name" :node-data="child"
- :parent-data="nodeData" @node-click="handleNodeClick" @update-height="handleUpdateHeight"
- @start-practice="handleChildStartPractice">
- <template #default>
- <slot></slot>
- </template>
- </knowledge-tree-node>
- </view>
- </view>
- </template>
- <script lang="ts" setup>
- import * as Study from '@/types/study';
- // 定义 props
- const props = defineProps({
- nodeData: {
- type: Object as PropType<Study.KnowledgeNode>,
- required: true
- },
- parentData: {
- type: Object as PropType<Study.KnowledgeNode>,
- default: null
- }
- });
- // 定义 emits
- const emit = defineEmits(['nodeClick', 'updateHeight', 'startPractice']);
- // 计算子节点高度
- const calculateChildrenHeight = (node: Study.KnowledgeNode): number => {
- if (!node.children || node.children.length === 0) {
- return 0;
- }
- let height = 0;
- node.children.forEach(child => {
- // 每个子节点的基础高度
- height += 68;
- // 如果子节点已展开,递归计算其子节点高度
- if (child.isExpanded && child.children && child.children.length > 0) {
- height += calculateChildrenHeight(child);
- }
- });
- return height;
- };
- // 处理节点点击
- const handleClick = () => {
- // 切换展开状态
- props.nodeData.isExpanded = !props.nodeData.isExpanded;
- // 计算并设置实际高度
- if (props.nodeData.isExpanded) {
- props.nodeData.actualHeight = calculateChildrenHeight(props.nodeData);
- } else {
- props.nodeData.actualHeight = 0;
- }
- // 通知父组件节点被点击
- emit('nodeClick', {
- node: props.nodeData,
- parent: props.parentData
- });
- // 如果有父节点,通知父节点更新高度
- if (props.parentData) {
- emit('updateHeight', props.parentData);
- }
- };
- // 处理开始练习事件
- const handleStartPractice = () => {
- emit('startPractice', props.nodeData);
- };
- // 处理子节点开始练习事件
- const handleChildStartPractice = (nodeData: Study.KnowledgeNode) => {
- emit('startPractice', nodeData);
- };
- // 处理子节点点击事件
- const handleNodeClick = (eventData: { node: Study.KnowledgeNode; parent: Study.KnowledgeNode }) => {
- // 向上传递点击事件
- emit('nodeClick', eventData);
- };
- // 处理高度更新事件
- const handleUpdateHeight = (parentNode: Study.KnowledgeNode) => {
- // 重新计算父节点高度
- if (parentNode.isExpanded) {
- parentNode.actualHeight = calculateChildrenHeight(parentNode);
- }
- // 继续向上传递高度更新事件
- if (props.parentData) {
- emit('updateHeight', props.parentData);
- }
- };
- 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);
- };
- </script>
- <style lang="scss" scoped></style>
|