SanHQin 10 ヶ月 前
コミット
ff963fe220

BIN
src/assets/icon/pblCourse/changeIcon.png


BIN
src/assets/icon/pblCourse/doWorkIcon.png


BIN
src/assets/icon/pblCourse/taskIcon.png


+ 37 - 7
src/components/pages/pblCourse/component/doWorkArea.vue

@@ -3,7 +3,7 @@
 		<div class="dw_header">
 			<div class="dw_h_left">
 				<img :src="require('../../../../assets/icon/pblCourse/phaseIcon.png')"> 
-				<span>阶段2:探究</span>
+				<span>阶段{{ phase.atPhase+1 }}:{{ task?task.name:'' }}</span>
 			</div>
 			<div class="dw_h_right">
 				<span class="dw_h_r_back" @click.stop="back()"></span>
@@ -11,10 +11,10 @@
 			</div>
 		</div>
 		<div class="dw_work">
-			<work/>
+			<work :task="task" @submitTask="submitTask" @choiceAnswer="choiceAnswer" @getTaskList="getTaskList" :phase="phase"/>
 		</div>
 		<div class="dw_bottom">
-			<div class="dw_b_btn" @click.stop="submitWork()">
+			<div class="dw_b_btn" @click.stop="submitTask()">
 				<img :src="require('../../../../assets/icon/pblCourse/bookIcon.png')">
 				<span>提交作业</span>
 			</div>
@@ -25,18 +25,48 @@
 <script>
 import work from './work'
 	export default {
+		emits:["changePhase","choiceAnswer","submitTask","getTaskList"],
+		props:{
+			phase:{
+				type:Object,
+				default:()=>{
+					return {
+						doPhase:0,
+						atPhase:0,
+					}
+				}
+			},
+			task:{
+				type:Object,
+				require:true,
+			},
+		},
+		computed:{
+			isOk(){
+				return !(this.phase.doPhase>this.phase.atPhase)
+			}
+		},
 		components:{
 			work,
 		},
 		methods:{
-			submitWork(){
-				this.$message.info("提交作业")
+			submitTask(){
+				this.$emit("submitTask")
 			},
 			down(){
-				this.$message.info("下一个")
+				if(this.isOk)return this.submitTask();
+				if(this.phase.atPhase>=4)return this.$message.info("已经是最后一个阶段啦")
+				this.$emit("changePhase",'atPhase',(this.phase.atPhase+1))
 			},
 			back(){
-				this.$message.info("上一个")
+				if(this.phase.atPhase<=0)return this.$message.info("已经是第一个阶段啦")
+				this.$emit("changePhase",'atPhase',(this.phase.atPhase-1))
+			},
+			choiceAnswer(arr){
+				this.$emit("choiceAnswer",arr)
+			},
+			getTaskList(_data){
+				this.$emit("getTaskList",_data)
 			}
 		}
 	}

+ 14 - 5
src/components/pages/pblCourse/component/procedureArea.vue

@@ -2,13 +2,13 @@
 	<div class="procedure">
 		<div class="title">5EX挑战</div>
 		<div class="content" ref="content">
-			<div class="procedure_content" v-for="(i, index) in 5" :key="index" :class="{active: aIndex >= index}">
-				<i class="img" :class="{ isDo : doIndex >= index}"></i>
+			<div class="procedure_content" v-for="(i, index) in 5" :key="index" :class="{active: phase.atPhase >= index}">
+				<i class="img" :class="{ isDo : phase.doPhase > index}"></i>
 				<i class="dot"></i>
 				<span class="name">
 					<span>阶段{{ index+1 }}</span>
 				</span>
-				<div class="stepBorder" v-if="i != 5" :style="{width: `calc(${contentWidth} / (${5 - 1}))`}" :class="{active: aIndex > index}"></div>
+				<div class="stepBorder" v-if="i != 5" :style="{width: `calc(${contentWidth} / (${5 - 1}))`}" :class="{active: phase.doPhase > index}"></div>
 			</div>
 			
 		</div>
@@ -17,11 +17,20 @@
 
 <script>
 	export default {
+		props:{
+			phase:{
+				type:Object,
+				default:()=>{
+					return {
+						doPhase:0,
+						atPhase:0,
+					}
+				}
+			}
+		},
 		data() {
 			return {
 				contentWidth:"100px",
-				aIndex: 2,
-				doIndex: 1,
 			}
 		},
 		mounted () {

+ 276 - 3
src/components/pages/pblCourse/component/work.vue

@@ -1,12 +1,99 @@
 <template>
-	<div class="work">
-		<div style="width: 100px;height: 1000px;background-color: yellow;"></div>
+	<div class="work" ref="workRef">
+		<div class="w_nowWork">
+			<div class="w_nw_header">
+				<div class="w_nw_h_title">
+					<img :src="require('../../../../assets/icon/pblCourse/taskIcon.png')">  
+					<span>当前任务</span>
+				</div>
+				<div class="w_nw_h_btn" @click.stop="changeTask" v-if="!(phase.doPhase>phase.atPhase)">
+					<img :src="require('../../../../assets/icon/pblCourse/changeIcon.png')">
+					<span>更换任务</span>
+				</div>
+			</div>
+			<div class="w_nw_introduce">
+				<div v-html="task.target" style="font-weight: bold;"></div>
+				<div v-html="task.detail"></div>
+				<div v-html="task.steps"></div>
+				<div v-html="task.tips"></div>
+			</div>
+		</div>
+		<div class="w_doWork">
+			<div class="w_dw_header">
+				<div class="w_dw_h_title">
+					<img :src="require('../../../../assets/icon/pblCourse/doWorkIcon.png')">  
+					<span>通关挑战({{taskIndex+1}}/{{task.answerArray?task.answerArray.length:5}})</span>
+				</div>
+				<div class="w_dw_h_controls">
+					<span @click.stop="back()">上一题</span>
+					<span @click.stop="down()">下一题</span>
+				</div>
+			</div>
+			<div class="w_dw_work">
+					<span class="w_dw_w_title">{{ taskIndex+1 }}.{{ task.answerArray[taskIndex].title }}</span>
+					<div class="w_dw_w_radio">
+						<el-radio-group class="w_dw_w_r_group" v-model="task.answerArray[taskIndex].userAnswer" size="medium" @input="choiceAnswer">
+  					  <el-radio class="w_dw_w_r_g_item" v-for="(item,index) in task.answerArray[taskIndex].option" :key="index+''+taskIndex" size="medium " :label="index" @input="choiceAnswer">{{ item }}</el-radio>
+  					</el-radio-group>
+					</div>
+				</div>
+		</div>
 	</div>
 </template>
 
 <script>
+	
 	export default {
-		
+		emits:['choiceAnswer','submitTask',"getTaskList"],
+		props:{
+			task:{
+				type:Object,
+				default:()=>{
+					return{
+						
+					}
+				},
+				
+			},
+			phase:{
+				type:Object,
+				default:()=>{
+					return {
+						doPhase:0,
+						atPhase:0,
+					}
+				}
+			},
+
+		},
+		data(){
+			return{
+				taskIndex:0,
+			}
+		},
+		watch:{
+			task(){
+				this.taskIndex = 0;
+				this.$refs.workRef.scrollTop = 0;
+			}
+		},
+		methods:{
+			changeTask(){
+				this.$emit("getTaskList",this.phase.atPhase)
+			},
+			back(){
+				if(this.taskIndex==0)return this.$message.info("已经是第一题咯");
+				this.taskIndex-=1;
+			},
+			down(){
+				if(this.taskIndex>=(this.task?this.task.answerArray.length-1:5))return this.$message.info("已经是最后一题咯");
+				this.taskIndex+=1;
+			},
+			choiceAnswer(_index){
+				this.$emit("choiceAnswer",[this.taskIndex,_index])
+				this.$forceUpdate();
+			}
+		}
 	}
 </script>
 
@@ -15,6 +102,192 @@
 	width: 100%;
 	max-height: 100%;
 	overflow: auto;
+	box-sizing: border-box;
+	padding: 25px;
 	/* background-color: aqua; */
 }
+
+.w_nowWork{
+	width: 100%;
+	height: auto;
+	margin-bottom: 20px;
+}
+
+.w_nw_header{
+	width: 100%;
+	display: flex;
+	justify-content: space-between;
+	height: 35px;
+	margin-bottom: 20px;
+}
+
+.w_nw_h_title{
+	display: flex;
+	align-items: center;
+}
+
+.w_nw_h_title>img{
+	width: 50px;
+	height: 50px;
+	margin-right: 10px;
+}
+
+.w_nw_h_title>span{
+	font-size: 24px;
+	font-weight: bold;
+	background: linear-gradient(to right, #3673E8, #AD88FD);
+	-webkit-background-clip: text;
+	color: transparent;
+}
+
+.w_nw_h_btn{
+	width: auto;
+	height: 100%;
+	border-radius: 100px;
+	box-sizing: border-box;
+	border: solid 1px #AD88FD;
+	background-color: white;
+	display: flex;
+	justify-content: center;
+	align-items: center;
+	padding: 0px 20px 0 10px;
+	cursor: pointer;
+	transition: .3s;
+}
+
+.w_nw_h_btn:hover{
+	background-color: rgb(248, 246, 246);
+}
+
+.w_nw_h_btn>img{
+	width: 20px;
+	height: 20px;
+	margin-right: 10px;
+}
+
+.w_nw_h_btn>span{
+	font-size: 16px;
+}
+
+.w_nw_introduce>div{
+	margin: 10px 0px;
+}
+
+.w_nw_introduce >>> ol{
+	margin-left: 25px;
+}
+
+.w_nw_introduce >>> ul {
+	margin-left: 25px;
+}
+
+.w_nw_introduce >>> h2{
+	margin-top: 10px;
+}
+.w_nw_introduce >>> h3{
+	margin-top: 10px;
+}
+.w_nw_introduce >>> h4{
+	margin-top: 10px;
+}
+.w_nw_introduce >>> h5{
+	margin-top: 10px;
+}
+.w_nw_introduce >>> h6{
+	margin-top: 10px;
+}
+
+
+
+.w_doWork{
+	width: 100%;
+	height: auto;
+}
+
+.w_dw_header{
+	width: 100%;
+	display: flex;
+	justify-content: space-between;
+	height: 35px;
+	margin-bottom: 20px;
+}
+
+.w_dw_h_title{
+	display: flex;
+	align-items: center;
+}
+
+.w_dw_h_title>img{
+	width: 50px;
+	height: 50px;
+	margin-right: 10px;
+}
+
+.w_dw_h_title>span{
+	font-size: 24px;
+	font-weight: bold;
+	background: linear-gradient(to right, #3673E8, #AD88FD);
+	-webkit-background-clip: text;
+	color: transparent;
+}
+
+.w_dw_h_controls{
+	display: flex;
+	align-items: center;
+}
+
+.w_dw_h_controls>span{
+	font-size: 16px;
+	margin-left: 20px;
+	cursor: pointer;
+	transition: .3s;
+	box-sizing: border-box;
+	padding: 0 15px;
+	background-color: white;
+	height: 100%;
+	display: flex;
+	justify-content: center;
+	align-items: center;
+	border-radius: 100px;
+	border: solid 1px #AD88FD;
+}
+
+.w_dw_h_controls>span:hover{
+	background-color: rgb(248, 246, 246);
+}
+
+.w_dw_work{
+	width: 100%;
+	height: auto;
+
+}
+
+.w_dw_w_title{
+	font-size: 24px;
+	font-weight: bold;
+}
+
+.w_dw_w_radio{
+	margin-top: 10px;
+}
+
+.w_dw_w_r_group{
+	display: flex;
+	flex-direction: column;
+	margin-top: 10px;
+}
+
+.w_dw_w_r_g_item{
+	margin-top: 15px;
+}
+
+.w_dw_w_r_g_item>>>.el-radio__label{
+	font-size: 22px;
+}
+
+.w_dw_w_r_g_item>>>.el-radio__inner{
+	width: 20px;
+	height: 20px;
+	margin-right: 5px;
+}
 </style>

+ 157 - 11
src/components/pages/pblCourse/index.vue

@@ -2,14 +2,14 @@
 	<div class="pblCourse" v-loading="loading">
 		<div class="pc_left">
 			<div class="pc_l_top">
-				<procedureArea/>
+				<procedureArea :phase="phase" />
 			</div>
 			<div class="pc_l_bottom">
-				<doWorkArea/>
+				<doWorkArea :phase="phase" @changePhase="changePhase" @choiceAnswer="choiceAnswer" @submitTask="submitTask" :task="taskList[phase.atPhase]" @getTaskList="getTaskList"/>
 			</div>
 		</div>
 		<div class="pc_right">
-			<chatArea/>
+			<chatArea />
 		</div>
 	</div>
 </template>
@@ -18,6 +18,8 @@
 import chatArea from './component/chatArea'
 import doWorkArea from './component/doWorkArea'
 import procedureArea from './component/procedureArea'
+import { v4 as uuidv4 } from "uuid";
+import MarkdownIt from "markdown-it";
 export default {
 	components: {
 		chatArea,
@@ -25,15 +27,160 @@ export default {
 		procedureArea,
 	},
 	data() {
-		return {	
+		return {
 			loading: false,
+			phase:{
+				doPhase:0,
+				atPhase:0,
+			},
+			taskList:[]
 		};
 	},
 	methods: {
-		
+		changePhase(type,newValue){
+			this.phase[type] = newValue;
+		},
+		getTaskList(phase = 0){
+			return new Promise((resolve,reject)=>{
+				if(this.loading)return this.$message.info("请稍等")
+			this.loading = true;
+		const _uuid = uuidv4()
+		const _msg = `
+			NOTICE
+			Role: 作为学生的学习指导Agent,你熟悉熟悉PBL(基于问题的学习)和5EX教学模型,能够根据学生的学情数据(当前的学习任务设计、学习表现数据、作业数据等)生成自适应的学习任务和对应的5道考核题目。
+			Language: Please use the same language as the user requirement, if the user speaks Chinese, the specific text of your answer should also be in Chinese.
+			ATTENTION: Use '##' to SPLIT SECTIONS, not '#'. Output format carefully referenced "Format example".
+			Instruction: Based on the context, follow "Format example", write content.
+
+			# Context
+			## 语气
+			你的语气应该是亲切地,有趣的,循循善诱的一个老师
+
+			## 工具能力
+			1. 5E教学模型应用:
+			你需要熟悉并应用5E教学模型(即引入、探索、解释、扩展和评估)于学习任务的设计中,确保学习过程的有效性和吸引力。5E教学模型是一种以学生为中心的教学方法,旨在通过五个阶段(Engage, Explore, Explain, Elaborate, Evaluate)来促进学生的学习和理解。
+			Engage(引入):在这个阶段,教师通过引人入胜的活动或问题来激发学生的兴趣和好奇心,帮助他们建立与新知识的联系。
+			Explore(探索):学生通过动手实验或调查活动来探索新概念,培养他们的探究能力和批判性思维。
+			Explain(解释):学生在这个阶段分享他们的发现,教师提供进一步的解释和指导,帮助学生理解新概念。
+			Elaborate(拓展):学生通过应用新知识来解决更复杂的问题,进一步深化他们的理解。
+			Evaluate(评估):教师和学生共同评估学习效果,反思学习过程,确定需要改进的地方。
+			2. 学生表现与选择的感知:
+			    通过与学生互动,实时感知学生的学习表现和选择,理解他们的学习需求和难点。
+			3. 自适应任务生成:
+			    基于学生的反馈和选择,自动生成个性化的学习任务,任务难度和类型随学生的表现和需求而变化。
+
+			## 工作流程
+			1. 判断学生当前处在5E模型中哪一个学习阶段。如果未提供学情数据,或无法判断学生当前处在5E教学模型中的哪一个解释,则默认处在第一个阶段(引入)阶段。请随机选择一个适合小学五年级学生的科学学习主题,并生成相应的符合引入阶段的学习任务。
+			2. 结合学生当前的学情数据,生成紧随其后的下一个阶段的学习任务,但是仅仅生成紧随其后的下一个阶段的学习任务。你需要沿着这个顺序判断:引入阶段→探索阶段→解释阶段→拓展阶段→评估阶段。比如,当你判断学情数据中,学生目前已经完成了引入阶段的学习,那么你需要提供探索阶段的学习任务。
+			3. 生成上一步中学习任务对应的5道考核选择题
+
+			## 限制
+			1. 请仅仅生成某一个阶段对应的学习任务。不要同时给出多个阶段、多个学习任务。
+			2. 请严格按照以下格式要求输出内容,请仅仅告知相应的5E阶段名称和对应的任务描述,不需要包含学情数据等与【任务】无关的内容。
+			3. 生成相应的考核题目,仅限单选题
+			4. 任务描述的格式以markdown方式输出
+			${
+				this.phase.doPhase==0?'':`
+				## 学情数据
+				这是你生成适应性学习任务时,需要参考的前置学情数据${JSON.stringify(this.taskList[this.phase.doPhase])}(当前的学习任务设计、学习表现数据、作业数据等)。`
+			}
+
+			# Format example
+			{
+			    "name": "任务名字",
+			    "detail": "任务描述(要求markdown的格式)",
+			    "target":"任务目标",
+			    "steps":"任务步骤",
+			    "tips":"任务提示",
+			    "answerArray":[
+			      {
+			        title: "标题",
+			        type: "单选题",
+			        option: ["选项1","选项2","选项3","选项4"],
+			        answer: "答案(最好是index)"
+			      },
+			      {
+			        title: "标题",
+			        type: "单选题",
+			        option: ["选项1","选项2","选项3","选项4"],
+			        answer: "答案(最好是index)"
+			      }
+			    ]
+			}`
+
+			// ${
+			// 	this.phase.doPhase==0?'':`
+			// 	## 学情数据
+			// 	这是你生成适应性学习任务时,需要参考的前置学情数据${JSON.stringify(this.taskList[this.phase.doPhase])}(当前的学习任务设计、学习表现数据、作业数据等)。`
+			// }
+
+			let params = {
+				model: "gpt-3.5-turbo",
+				temperature: 0,
+				max_tokens: 4096,
+				top_p: 1,
+				frequency_penalty: 0,
+				presence_penalty: 0,
+				messages: [{role:"user",content:_msg}],
+				uid: _uuid,
+				mind_map_question: "",
+				stream:false
+			}
+			this.ajax
+			.post("https://gpt4.cocorobo.cn/chat", params)
+			.then((res) => {
+				let _data = res.data.FunctionResponse.choices[0];
+				const content = _data.message.content;
+				const _result = JSON.parse(content);
+				const md = new MarkdownIt();
+				_result.detail = _result.detail?md.render(_result.detail):"",
+				_result.steps = _result.steps?md.render(_result.steps):"",
+				_result.target = _result.target?md.render(_result.target):"",
+				_result.tips =_result.tips?md.render(_result.tips):""
+				this.taskList[phase] = _result;
+				// this.phase.doPhase = phase;
+				this.phase.atPhase = phase;
+				console.log(this.taskList)
+				this.loading = false;
+				resolve();
+			})
+			.catch((e) => {
+				this.loading = false;
+				this.$message.error("获取任务失败")
+				resolve();
+				console.log(e);
+			});
+			})
+			
+		},
+		choiceAnswer(_data){
+			this.taskList[this.phase.atPhase].answerArray[_data[0]].userAnswer = _data[1];
+			this.$forceUpdate()
+		},
+		submitTask(){
+			
+			this.loading = true;
+			let sum = 0;
+			this.taskList[this.phase.atPhase].answerArray.forEach(i=>{
+				if('userAnswer' in i){
+					sum++;
+				}
+			})
+			if(sum<this.taskList[this.phase.atPhase].answerArray.length){
+				this.loading = false;
+				return this.$message.error("当前阶段还未完成")
+			}else if((this.phase.doPhase>this.phase.atPhase)){
+				this.loading = false;
+				return this.$message.error("该阶段已经提交过了")
+			}else{
+				this.loading = false;
+				this.phase.doPhase++;
+				this.getTaskList(this.phase.doPhase)
+			}
+		}
 	},
 	mounted() {
-		
+		this.getTaskList()
 	},
 };
 </script>
@@ -50,29 +197,28 @@ export default {
 	padding: 20px;
 }
 
-.pc_left{
+.pc_left {
 	width: calc(100% - 500px - 20px);
 	margin-right: 20px;
 	box-sizing: border-box;
 }
 
-.pc_l_top{
+.pc_l_top {
 	width: 100%;
 	height: 150px;
 	box-sizing: border-box;
 }
 
-.pc_l_bottom{
+.pc_l_bottom {
 	width: 100%;
 	height: calc(100% - 150px - 15px);
 	box-sizing: border-box;
 	margin-top: 15px;
 }
 
-.pc_right{
+.pc_right {
 	width: 500px;
 	height: 100%;
 	box-sizing: border-box;
 }
-
 </style>