增加试卷答题统计结果查看功能

smart_class
educoder_weapp 5 years ago
parent f8967fd65f
commit e620d7248b

@ -5,7 +5,7 @@ educoder微信小程序帮助使用[educoder平台](https://www.educoder.net)
[https://github/jinke18/smart_class](https://github.com/jinke18/smart_class)
## 小程序码
![小程序码](./images/weapp_code.png)
![小程序码](/images/weapp_code.png)
# 功能介绍
@ -34,7 +34,7 @@ educoder微信小程序帮助使用[educoder平台](https://www.educoder.net)
- 试卷截止后并且老师选择了公开答案,学生可以看到公布的答案
- 老师可以创建试卷,发布试卷,查看学员作答分数(developing)
- 老师可以创建试卷,发布试卷,查看学员作答分数
## 其他
账号的注册、登陆、找回密码、头像更改等

@ -2,8 +2,7 @@
"pages": [
"pages/my_courses/my_courses",
"pages/findmore/findmore",
"pages/exercise_setting/exercise_setting",
"pages/exercise_setting/exercise_setting",
"pages/login/login",
"pages/course_setting/course_setting",
"pages/exercises/exercises",
@ -19,7 +18,10 @@
"pages/reset_password/reset_password",
"pages/image_crop/image_crop",
"pages/question_setting/question_setting",
"pages/exercise_grade/exercise_grade"
"pages/exercise_grade/exercise_grade",
"pages/course_invite/course_invite",
"pages/exercise_result/exercise_result"
],
"window": {
"backgroundTextStyle": "dark",
@ -37,19 +39,18 @@
"iconPath": "images/tabbar-icon/tabbar_study_default.png",
"selectedIconPath": "images/tabbar-icon/tabbar_study_pressed.png"
},
{
"pagePath": "pages/courses/courses",
"text": "课程列表",
"iconPath": "images/tabbar-icon/tabbar_contact_default.png",
"selectedIconPath": "images/tabbar-icon/tabbar_contact_pressed.png"
},
{
"pagePath": "pages/findmore/findmore",
"text": "发现",
"iconPath": "images/tabbar-icon/tabbar_findmore_default.png",
"selectedIconPath": "images/tabbar-icon/tabbar_findmore_pressed.png"
},
{
"pagePath": "pages/findmore/findmore",
"text": "发现",
"iconPath": "images/tabbar-icon/tabbar_findmore_default.png",
"selectedIconPath": "images/tabbar-icon/tabbar_findmore_pressed.png"
},
{
"pagePath": "pages/setting/setting",
"iconPath": "images/tabbar-icon/tabbar_settings_default.png",

@ -608,6 +608,29 @@ export class Client{
})
})
}
/**
* @todo change limit default 10
*/
get_exercise_result({ exercise_id, sort = "asc", page = 1, limit=50, success, fail, complete}){
return new Promise((resolve, reject)=>{
Exercise.get_result({
session: this.session,
exercise_id, sort, page, limit,
success: res => {
if (typeof success == "function") {
success(res);
}
resolve(res);
},
fail: error => {
if (typeof fail == "function") {
fail(error);
}
reject(error);
}
})
})
}
get_exercise_grade({exercise_id,order="end_at", search="", success, fail, complete}){
return new Promise((resolve, reject) => {
Exercise.get_grade({

@ -420,6 +420,28 @@ export class Exercise{
complete: complete
})
}
/**@todo limit change */
static get_result({session, exercise_id, sort="asc", page=1, limit=50,success, fail, complete}){
let data={sort, page, limit};
session.request({
url: api_base_url +`/exercises/${exercise_id}/exercise_result.json`,
method: "GET",
data,
success(res) {
if ("status" in res.data) {
if (res.data.status < 0) {
fail(new Error(res.data.message));
return;
}
}
if (typeof success == "function") {
success(res);
}
},
fail: fail,
complete: complete
})
}
static publish({session,course_id, exercise_ids, end_time, success, fail, complete}){
let data = {check_ids: exercise_ids, end_time};
session.request({

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

@ -6,7 +6,8 @@ Page({
/**
* 页面的初始数据
*/
md: '# 简介\neducoder微信小程序帮助使用[educoder平台](https://www.educoder.net)的应用方便在手机上使用。利用educoder网站的API搭建\n\n## 源码\n[https://github/jinke18/smart_class](https://github.com/jinke18/smart_class)\n\n## 小程序码\n![小程序码](../../images/weapp_code.png)\n\n\n# 功能介绍\n\n## 教室\n- 学员可以输入邀请码进入课堂\n\n- 进入教室界面会显示在位,头像为彩色,若退出课堂界面则会显示灰色头像\n\n- 教员在教室界面中可以直观地看到学员在位情况,可以选择学员让其起立回答问题,并且对学员可以进行加分、减分操作\n\n- 学员可以收到教员让其起立提问、回答的提示,还可以点击“我要提问、回答”\n\n- 教室内有讨论区,可以交流\n\n## 课程资源\n\n- 在课程界面进入“资源”可以查看本课堂的课程文件资源\n\n- 支持打开ppt doc xls pdf文件\n\n## 试卷作答\n\n- 学员在课程内可以看到老师发布的试卷,并且回答\n\n- 试卷截止后可以学生可以看到公布的答案',
md: '# 简介\neducoder微信小程序帮助使用[educoder平台](https://www.educoder.net)的应用方便在手机上使用。利用educoder网站的API搭建\n\n## 源码\n[https://github/jinke18/smart_class](https://github.com/jinke18/smart_class)\n\n## 小程序码\n![小程序码](/images/weapp_code.png)\n\n# 功能介绍\n\n## 教室\n- 学员可以输入邀请码进入课堂\n\n- 进入教室界面会显示在位,头像为彩色,若退出课堂界面则会显示灰色头像\n\n- 教员在教室界面中可以直观地看到学员在位情况,可以选择学员让其起立回答问题,并且对学员可以进行加分、减分操作\n\n- 在分数列表中可以看到加减分记录(数据在后台可以导出)\n\n- 学员可以收到教员让其起立提问、回答的提示,还可以点击“我要提问、回答”\n\n- 教室内有讨论区,可以交流\n\n## 课程资源\n\n- 在课程界面进入“资源”可以查看本课堂的课程文件资源\n\n- 支持打开ppt doc xls pdf文件\n\n## 试卷作答\n\n- 学员在课程内可以看到老师发布的试卷,并且回答\n\n- 试卷截止后并且老师选择了公开答案,学生可以看到公布的答案\n\n- 老师可以创建试卷,发布试卷,查看学员作答分数(developing)\n\n## 其他\n账号的注册、登陆、找回密码、头像更改等\n\n# 实现\n## educoder平台接入\n使用HTTP与平台的api接口交互\n\n接口列表如下\n- 搜索课堂https://www.educoder.net/api/courses.json\n\n- 查询用户的课堂https://www.educoder.net/api/users/<user_id>/courses.json\n\n- 查询学校https://www.educoder.net/api/schools/school_list.json\n\n- 新建课堂https://www.educoder.net/api/courses.json\n\n- 加入课堂https://www.educoder.net/api/courses/apply_to_join_course.json\n\n- 新建试卷https://www.educoder.net/api/courses/<course_id>/exercises/new.json\n\n- 查询试卷https://www.educoder.net/api/courses/<course_id>/exercises.json\n\n- 班级文件资源https://www.educoder.net/api/files.json\n\n# 教室学员在位情况及分数的同步实现\n使用了[leancloud](https://www.leancloud.cn/)提供的javascript开发包实现数据同步功能, 如学员在位情况、分数的同步, 其底部技术为websocket'
,
data: {
article: {}

@ -1,4 +1,5 @@
// pages/course_detail/course_detail.js
const app = getApp();
Page({
/**
@ -39,7 +40,8 @@ Page({
* 生命周期函数--监听页面加载
*/
onLoad: function (options) {
this.setData({"course.id": options.course_id})
this.course_id = options.course_id;
this.setData({course_id: options.course_id})
if(options.course_name && options.course_name!="undefined"){
wx.setNavigationBarTitle({
title: options.course_name
@ -59,7 +61,11 @@ Page({
* 生命周期函数--监听页面显示
*/
onShow: function () {
app.client.get_course_info({course_id: this.course_id})
.then(res=>{
console.log(res);
this.setData({course: res.data});
});
},
/**
@ -93,7 +99,16 @@ Page({
/**
* 用户点击右上角分享
*/
onShareAppMessage: function () {
onShareAppMessage: function (options) {
let course = this.data.course;
let next_week_time = new Date().getTime()+7*24*3600*1000;
let current_user = app.client.current_user;
if(options.from=="button"){
return{
path: `/pages/course_invite/course_invite?course_id=${this.course_id}&invite_code=${course.invite_code}&deadline=${next_week_time}&course_name=${course.name}&inviter=${current_user.real_name}&avatar_url=https://www.educoder.net/images/${current_user.image_url}`,
imageUrl:"/images/invite.png",
title: "课堂邀请--"+course.name
}
}
}
})

@ -1,5 +1,6 @@
<view class="container">
<view class="top-banner">
<view class="course_name">{{course.name}}</view>
<view class="teacher">
</view>
</view>
@ -7,8 +8,9 @@
<block wx:for="{{course_modules}}" wx:for-item="course_module">
<navigator></navigator>
</block>
<view wxLif="{{show_classroom}}" data-url="../classroom/classroom?id={{course.id}}" bindtap="enter_page" class="nav course_module">进入教室</view>
<navigator url="../exercises/exercises?id={{course.id}}" class="nav course_module">试卷</navigator>
<navigator url="../files/files?id={{course.id}}" class="nav course_module">资源</navigator>
<view wxLif="{{show_classroom}}" data-url="../classroom/classroom?id={{course_id}}" bindtap="enter_page" class="nav course_module">进入教室</view>
<navigator url="../exercises/exercises?id={{course_id}}" class="nav course_module">试卷</navigator>
<navigator url="../files/files?id={{course_id}}" class="nav course_module">资源</navigator>
</view>
<button wx:if="{{course.is_admin}}" open-type="share">发送课堂邀请链接</button>
</view>

@ -0,0 +1,110 @@
// pages/course_join/course_invite.js
const app = getApp();
Page({
/**
* 页面的初始数据
*/
data: {
},
join_course: function(event) {
const { invite_code, identities } = this.data;
let data = { invite_code: invite_code, student: 1};
console.log(data);
console.log({ ...data });
app.client.join_course({ ...data })
.then(res => {
if (res.data.status == 401) {
wx.showToast({
title: "请先登陆",
icon: "none"
});
wx.navigateTo({
url: '../login/login',
})
return;
}
console.log(res);
wx.showToast({
title: res.data.message
})
wx.navigateTo({
url: "../course/course?course_id=" + res.data.course_id
});
})
.catch(error => {
wx.showToast({
title: error.toString(),
icon: "none"
});
console.warn(error);
})
},
/**
* 生命周期函数--监听页面加载
*/
onLoad: function (options) {
console.log(options);
this.invite_code = options.invite_code;
this.deadline = options.deadline;
this.inviter = options.inviter;
this.course_name = options.course_name;
this.setData(options);
let current_time = new Date().getTime();
this.setData({current_time: current_time});
},
/**
* 生命周期函数--监听页面初次渲染完成
*/
onReady: function () {
},
/**
* 生命周期函数--监听页面显示
*/
onShow: function () {
},
/**
* 生命周期函数--监听页面隐藏
*/
onHide: function () {
},
/**
* 生命周期函数--监听页面卸载
*/
onUnload: function () {
},
/**
* 页面相关事件处理函数--监听用户下拉动作
*/
onPullDownRefresh: function () {
},
/**
* 页面上拉触底事件的处理函数
*/
onReachBottom: function () {
},
/**
* 用户点击右上角分享
*/
onShareAppMessage: function () {
}
})

@ -0,0 +1,3 @@
{
"usingComponents": {}
}

@ -0,0 +1,13 @@
<view class="container">
<view class="course_info" style="text-align: center;padding: 18px; margin-bottom: 20px;">
<image style="border-radius: 50%;overflow: hidden;width: 136rpx;height:136rpx;" src="{{avatar_url}}"></image>
<text>\n{{inviter}}\n</text>
<text>邀请你加入课程\n</text>
<text style="font-size:24px;">{{course_name}}\n</text>
</view>
<view style="text-align:center;">
<button type="primary" wx:if="{{current_time<=deadline}}" bindtap="join_course">加入
</button>
<view class="error" disabled="{{current_time>deadline}}">该邀请链接已失效</view>
</view>
</view>

@ -0,0 +1 @@
/* pages/course_join/course_invite.wxss */

@ -0,0 +1,99 @@
// pages/exercise_result/exercise_result.js
const app = getApp();
Page({
/**
* 页面的初始数据
*/
data: {
results: [],
page_status: 0,
error:""
},
/**
* 生命周期函数--监听页面加载
*/
onLoad: function (options) {
this.exercise_id = options.exercise_id;
this.exercise_id = 2996;
},
/**
* 生命周期函数--监听页面初次渲染完成
*/
onReady: function () {
},
/**
* 生命周期函数--监听页面显示
*/
onShow: function () {
app.client.get_exercise_result({ exercise_id: this.exercise_id })
.then(res => {
this.setData({ error: "" })
console.log(res);
if (res.data.commit_results) {
let results = res.data.commit_results;
for (var result of results) {
if (result.ques_type == 4) {
for (var detail of result.ques_details) {
if (detail.choice_text == 1) detail.choice_text = "满分作答";
else if (detail.choice_text == 2) detail.choice_text = "部分得分作答";
else if (detail.choice_text == 3) detail.choice_text = "零分作答";
else if (detail.choice_text == 4) detail.choice_text = "未批";
}
}
if (result.ques_type == 3) {
for (var detail of result.ques_details) {
if (detail.choice_text == "wrong") detail.choice_text = "错误答案";
}
}
}
console.log(results);
this.setData({ results, page_status: 1 })
}
})
.catch(error => {
console.log(error);
this.setData({ error: error.message })
})
},
/**
* 生命周期函数--监听页面隐藏
*/
onHide: function () {
},
/**
* 生命周期函数--监听页面卸载
*/
onUnload: function () {
},
/**
* 页面相关事件处理函数--监听用户下拉动作
*/
onPullDownRefresh: function () {
},
/**
* 页面上拉触底事件的处理函数
*/
onReachBottom: function () {
},
/**
* 用户点击右上角分享
*/
onShareAppMessage: function () {
}
})

@ -0,0 +1,4 @@
{
"usingComponents": {},
"navigationBarTitleText": "试卷结果"
}

@ -0,0 +1,44 @@
<view class="container">
<view class="error" wx:if="{{page_status==1&&results.length==0}}">
结果未公布
</view>
<view class="error" wx:if="{{error}}">
{{error}}
</view>
<view class="result-list" style="margin:0 -12px;">
<block wx:for="{{results}}" wx:for-item="result">
<view class="question" style="margin:7px 2px; padding:4px 12px; background:white; border-radius: 10rpx;">
<view class="question-title">
<text class="hint">第{{result.ques_position}}题</text>
<text>{{result.ques_title}}</text>
<text class="hint">{{result.effictive_counts||0}}人作答</text>
<text class="error">正确率:{{result.right_percent}}%</text>
</view>
<view wx:if="{{result.ques_type==0 || result.ques_type==1 || result.ques_type==2}}" class="choices">
<block wx:for="{{result.ques_details}}" wx:for-item="detail">
<view class="choice">
<radio checked="{{detail.choice_right_boolean}}" disabled>
<text class="choice-text">{{detail.choice_text}}</text>
<text class="error">{{detail.choice_users_count}}人</text>
</radio>
<progress active show-info duration="7" border-radius="2" percent="{{detail.choice_percent*100}}"></progress>
</view>
</block>
</view>
<view wx:elif="{{result.ques_type==3 || result.ques_type==4}}" class="choices">
<block wx:for="{{result.ques_details}}" wx:for-item="detail">
<view class="choice">
<text class="choice-text">{{detail.choice_text}}</text>
<text class="error">{{detail.choice_users_count}}人</text>
<progress active show-info duration="7" border-radius="2" percent="{{detail.choice_percent*100}}"></progress>
</view>
</block>
</view>
<view wx:else class="choices">
暂不支持此类题目
</view>
</view>
</block>
</view>
</view>

@ -0,0 +1 @@
/* pages/exercise_result/exercise_result.wxss */

@ -143,10 +143,5 @@ Page({
},
/**
* 用户点击右上角分享
*/
onShareAppMessage: function () {
}
})

@ -53,6 +53,11 @@ Page({
currentTab: 0,
navScrollLeft: 0
},
exercise_result: function ({ currentTarget: { dataset } }){
wx.navigateTo({
url: `../exercise_result/exercise_result?exercise_id=${dataset.exercise_id}`,
})
},
see_grade: function({currentTarget:{dataset}}){
wx.navigateTo({
url: `../exercise_grade/exercise_grade?exercise_id=${dataset.exercise_id}`,

@ -23,13 +23,15 @@
</navigator>
</view>
<block wx:for="{{exercises}}" wx:for-item="exercise" wx:key="id">
<view class="exercise" wx:if="{{exercise.exercise_status==navItem.exercise_status||navItem.exercise_status=='all'}}" class="exercise" bindtap="see_grade" bindlongpress="enter_exercise" data-exercise_id="{{exercise.id}}">
<view class="exercise" wx:if="{{exercise.exercise_status==navItem.exercise_status||navItem.exercise_status=='all'}}" class="exercise" catchtap="see_grade" bindlongpress="enter_exercise" data-exercise_id="{{exercise.id}}">
<view class="exercise-name">
<text class="exercise-name">{{exercise.exercise_name}}</text>
<text class="exercise-name" data-exercise_id="{{exercise.id}}" bindtap="exercise_result">{{exercise.exercise_name}}</text>
</view>
<view class="exercise-info flex-wrap">
<text class="hint" wx:if="{{exercise.exercise_status!=3&&(exercise.current_status==0 || exercise.current_status==2)}}">还有{{exercise.exercise_left_time||' '}}截止</text>
<text class="exercise-tip">{{exercise.exercise_tips[0]}}</text>
<block wx:for="{{exercise.exercise_tips}}" wx:for-item="exercise_tip">
<text class="exercise-tip">{{exercise_tip}}</text>
</block>
<text wx:if="{{exercise.exercise_status!=3&&(exercise.current_status==0 || exercise.current_status==2)}}" class="start-answer tappable" catchtap="enter_exercise" data-exercise_id="{{exercise.id}}">进入答题</text>
<text wx:if="{{(exercise.current_status==1 || exercise.exercise_status==3)&&exercise.current_status!=3}}" class="start-answer tappable" catchtap="see_exercise" data-exercise_id="{{exercise.id}}">查看答题</text>
<text wx:if="{{exercise.current_status==3&&(exercise.exercise_status==1||exercise.exercise_status==2)}}" class="edit-exercise tappable" data-exercise_id="{{exercise.id}}" catchtap="edit_exercise">编辑</text>

Loading…
Cancel
Save