feat(plan): 添加生成学案功能及界面支持

- 新增 AddLessonPlanReqVO 数据模型用于生成学案请求参数封装
- 新增 LessonPlanController 提供生成学案的后端接口,支持异步任务执行
- 新增 LessonPlanDialog 组件,实现前端学案生成弹窗及交互逻辑
- 在班级页面添加生成学案按钮,支持单个学生选择后调用弹窗
- 添加 plan.js 接口调用封装,调用后端生成学案接口
- 完成前后端联动,实现生成学案操作的完整流程和提示信息
This commit is contained in:
lbw
2025-12-17 11:47:28 +08:00
parent 07b9b56e8a
commit 49cd146bc3
6 changed files with 165 additions and 0 deletions

View File

@@ -0,0 +1,41 @@
package com.yinlihupo.enlish.service.controller;
import com.yinlihupo.enlish.service.model.vo.plan.AddLessonPlanReqVO;
import com.yinlihupo.enlish.service.service.LessonPlansService;
import com.yinlihupo.framework.biz.operationlog.aspect.ApiOperationLog;
import com.yinlihupo.framework.common.response.Response;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.concurrent.Executor;
@RequestMapping("/plan/")
@RestController
@Slf4j
public class LessonPlanController {
@Resource
private LessonPlansService lessonPlanService;
@Resource(name = "taskExecutor")
private Executor taskExecutor;
@PostMapping("generate")
@ApiOperationLog(description = "生成学案")
public Response<String> generateLessonPlan(@RequestBody AddLessonPlanReqVO addLessonPlanReqVO) {
Integer studentId = addLessonPlanReqVO.getStudentId();
Integer unitId = addLessonPlanReqVO.getUnitId();
try {
taskExecutor.execute(() -> lessonPlanService.generateLessonPlans(studentId, unitId));
return Response.success("生成学案成功,请等待 10 分钟");
} catch (Exception e) {
log.error(e.getMessage());
return Response.fail("生成学案失败" + e.getMessage());
}
}
}

View File

@@ -0,0 +1,16 @@
package com.yinlihupo.enlish.service.model.vo.plan;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Data
public class AddLessonPlanReqVO {
private Integer studentId;
private Integer unitId;
}

View File

@@ -13,6 +13,7 @@ import lombok.extern.slf4j.Slf4j;
import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.NonNull;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.io.IOException; import java.io.IOException;
import java.util.*; import java.util.*;
@@ -43,6 +44,7 @@ public class LessonPlansServiceImpl implements LessonPlansService {
@Override @Override
@Transactional(rollbackFor = Exception.class)
public void generateLessonPlans(Integer studentId, Integer unitId) { public void generateLessonPlans(Integer studentId, Integer unitId) {
List<VocabularyBankDO> vocabularyBankDOS = vocabularyBankDOMapper.selectVocabularyBankDOAllByUnitId(unitId); List<VocabularyBankDO> vocabularyBankDOS = vocabularyBankDOMapper.selectVocabularyBankDOAllByUnitId(unitId);
UnitDO unitDO = unitDOMapper.selectByPrimaryKey(unitId); UnitDO unitDO = unitDOMapper.selectByPrimaryKey(unitId);

View File

@@ -0,0 +1,8 @@
import axios from "@/axios";
export function generateLessonPlan(studentId, unitId) {
return axios.post('/plan/generate', {
studentId: studentId,
unitId: unitId
})
}

View File

@@ -0,0 +1,88 @@
<template>
<el-dialog v-model="visible" title="生成学案" width="520px" :close-on-click-modal="false">
<div class="space-y-4" v-loading="loading">
<el-form label-width="80px">
<el-form-item label="单元">
<el-select v-model="unitId" placeholder="请选择单元" style="width: 300px" filterable>
<el-option
v-for="u in unitOptions"
:key="u.id"
:label="u.title"
:value="u.id"
/>
</el-select>
</el-form-item>
</el-form>
<div class="text-sm text-gray-500">
学生ID{{ studentId }}
</div>
</div>
<template #footer>
<div class="flex justify-end gap-2">
<el-button @click="visible = false">取消</el-button>
<el-button type="primary" :disabled="!unitId" @click="handleGenerate">生成</el-button>
</div>
</template>
</el-dialog>
</template>
<script setup>
import { ref, computed, watch } from 'vue'
import { getUnitList } from '@/api/unit'
import { generateLessonPlan } from '@/api/plan'
import { showMessage } from '../../composables/util'
const props = defineProps({
modelValue: { type: Boolean, default: false },
studentId: { type: [Number, String], required: true }
})
const emit = defineEmits(['update:modelValue'])
const visible = computed({
get: () => props.modelValue,
set: (val) => emit('update:modelValue', val)
})
const loading = ref(false)
const unitId = ref(null)
const unitOptions = ref([])
async function fetchUnits() {
loading.value = true
try {
const res = await getUnitList(1, 100)
const d = res?.data
if (d.success) {
unitOptions.value = Array.isArray(d?.data) ? d.data : []
} else {
}
} finally {
loading.value = false
}
}
async function handleGenerate() {
if (!unitId.value || !props.studentId) return
const res = await generateLessonPlan(Number(props.studentId), Number(unitId.value))
const d = res?.data
if (d.success) {
ElMessage.success('生成学案任务已提交,请等待十分钟')
visible.value = false
} else {
showMessage(d.message || '生成学案失败,请联系管理员', 'error')
visible.value = false
}
}
watch(
() => props.modelValue,
(v) => {
if (v) {
unitId.value = null
fetchUnits()
}
}
)
</script>
<style scoped></style>

View File

@@ -49,6 +49,10 @@
@click="showGenerateDialog = true"> @click="showGenerateDialog = true">
生成试题 生成试题
</el-button> </el-button>
<el-button type="warning" :disabled="selectedStudentIds.length !== 1"
@click="showLessonPlanDialog = true">
生成学案
</el-button>
</div> </div>
<el-table ref="studentTableRef" :data="students" border class="w-full" <el-table ref="studentTableRef" :data="students" border class="w-full"
v-loading="studentLoading" @selection-change="onStudentSelectionChange"> v-loading="studentLoading" @selection-change="onStudentSelectionChange">
@@ -79,6 +83,10 @@
:default-grade-id="selectedGradeId" :default-grade-id="selectedGradeId"
@success="fetchStudents" @success="fetchStudents"
/> />
<LessonPlanDialog
v-model="showLessonPlanDialog"
:student-id="selectedStudentIds[0]"
/>
</div> </div>
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6" v-loading="gradeLoading"> <div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6" v-loading="gradeLoading">
@@ -156,6 +164,7 @@ import ExamGenerateDialog from '@/layouts/components/ExamGenerateDialog.vue'
import AddClassDialog from '@/layouts/components/AddClassDialog.vue' import AddClassDialog from '@/layouts/components/AddClassDialog.vue'
import AddGradeDialog from '@/layouts/components/AddGradeDialog.vue' import AddGradeDialog from '@/layouts/components/AddGradeDialog.vue'
import AddStudentDialog from '@/layouts/components/AddStudentDialog.vue' import AddStudentDialog from '@/layouts/components/AddStudentDialog.vue'
import LessonPlanDialog from '@/layouts/components/LessonPlanDialog.vue'
import { getUnitList, deleteUnit } from '@/api/unit' import { getUnitList, deleteUnit } from '@/api/unit'
import AddUnitDialog from '@/layouts/components/AddUnitDialog.vue' import AddUnitDialog from '@/layouts/components/AddUnitDialog.vue'
@@ -189,6 +198,7 @@ const studentTableRef = ref(null)
const selectedStudentIds = ref([]) const selectedStudentIds = ref([])
const showGenerateDialog = ref(false) const showGenerateDialog = ref(false)
const showAddStudentDialog = ref(false) const showAddStudentDialog = ref(false)
const showLessonPlanDialog = ref(false)
const units = ref([]) const units = ref([])
const unitPageNo = ref(1) const unitPageNo = ref(1)