TaskCascaderFilter.vue 4.52 KB
<template>
  <div class="inline-block" style="width: 60%;">
    <div @click="show = true" class="text-sm text-gray-500 flex items-center justify-end cursor-pointer ml-2">
      <span class="mr-1">{{ selectedLabel }}</span>
      <van-icon name="arrow-down" />
    </div>

    <van-popup v-model:show="show" round position="bottom">
      <van-cascader
        v-model="cascaderValue"
        title="请选择作业"
        :options="options"
        :field-names="fieldNames"
        active-color="#22c55e"
        @close="show = false"
        @finish="onFinish"
        @change="onChange"
      />
    </van-popup>
  </div>
</template>

<script setup>
import { ref, watch, computed } from 'vue'
import { getTeacherTaskListAPI, getTeacherTaskDetailAPI } from '@/api/teacher'

const props = defineProps({
  groupId: {
    type: [String, Number],
    default: ''
  }
})

const emit = defineEmits(['change'])

const show = ref(false)
const cascaderValue = ref('')
const options = ref([])
const fieldNames = {
  text: 'text',
  value: 'value',
  children: 'children',
  color: '#374151'
}

const selectedTask = ref(null)
const selectedSubtask = ref(null)

const selectedLabel = computed(() => {
  if (selectedSubtask.value) {
    return selectedSubtask.value.text
  }
  if (selectedTask.value) {
    return selectedTask.value.text
  }
  return '切换作业'
})

// 获取作业列表(第一级)
const fetchTaskList = async () => {
  if (!props.groupId) {
    options.value = []
    return
  }

  try {
    const res = await getTeacherTaskListAPI({
      group_id: props.groupId,
      limit: 1000,
      page: 0
    })

    if (res.code === 1) {
      // 构造第一级数据
      // 注意:为了让级联选择器知道还有下一级,需要给children一个空数组
      // 或者我们可以一次性加载?如果不确定数据量,建议动态加载
      // 这里采用动态加载策略:给一个占位符,或者如果Vant Cascader支持,可以不给children但通过change事件动态添加
      // Vant Cascader如果不给children,就认为是叶子节点。所以必须给children。
      options.value = (res.data || []).map(item => ({
        text: item.title,
        value: item.id,
        children: [], // 标记有子节点
        color: '#374151'
      }))

      // 添加"全部作业"选项
      options.value.unshift({
        text: '全部作业',
        value: '',
        children: null, // 叶子节点
        color: '#374151'
      })
    }
  } catch (error) {
    console.error('获取作业列表失败:', error)
  }
}

// 获取子作业(第二级)
const fetchSubtaskList = async (taskId) => {
  try {
    const res = await getTeacherTaskDetailAPI({ id: taskId })
    if (res.code === 1) {
      const subtasks = res.data.subtask_list || []
      return subtasks.map(item => ({
        text: item.title,
        value: item.id,
        // 小作业没有下一级了
        children: null,
        color: '#374151'
      }))
    }
    return []
  } catch (error) {
    console.error('获取作业详情失败:', error)
    return []
  }
}

// 监听 groupId 变化
watch(() => props.groupId, () => {
  // 重置
  cascaderValue.value = ''
  selectedTask.value = null
  selectedSubtask.value = null
  fetchTaskList()
}, { immediate: true })

const onChange = async ({ value, selectedOptions, tabIndex }) => {
  // 当选中第一级(大作业)时
  if (tabIndex === 0) {
    const targetOption = selectedOptions[0]

    // 如果是"全部作业"(value为空字符串),它没有children,会直接触发finish(如果它是叶子节点)
    // 但我们在构造时设置了children: null,所以它就是叶子节点
    if (targetOption.value === '') {
        return
    }

    // 如果还没有加载过子节点
    if (targetOption.children && targetOption.children.length === 0) {
      // 加载子作业
      const subtasks = await fetchSubtaskList(targetOption.value)

      // 添加"全部小作业"选项
      const allSubtaskOption = {
        text: '全部小作业',
        value: '',
        children: null,
        color: '#374151'
      }

      targetOption.children = [allSubtaskOption, ...subtasks]
    }
  }
}

const onFinish = ({ selectedOptions }) => {
  show.value = false

  const taskOption = selectedOptions[0]
  const subtaskOption = selectedOptions[1]

  selectedTask.value = taskOption
  selectedSubtask.value = subtaskOption

  emit('change', {
    task_id: taskOption ? taskOption.value : '',
    subtask_id: subtaskOption ? subtaskOption.value : ''
  })
}
</script>

<style scoped>
/* 样式调整 */
</style>