U 目录结构跳转,压缩主包

master
educoder_weapp 5 years ago
parent c30432d745
commit f25c84d5e8

@ -1,6 +1,10 @@
## v0.16.2
* A 支持选用实践课程
* A 网络请求缓存
## v0.16.1 ## v0.16.1
* A 发送实训至课堂 * A 发送单个实训至课堂
* A 探索实训界面支持多选 * A 探索实训界面支持多选
## v0.16.0 ## v0.16.0
* A 探索界面 * A 探索界面

@ -1,5 +0,0 @@
{
"usingComponents": {
"rich-md": "/components/rich-md/rich-md"
}
}

@ -1,3 +1,4 @@
import apiConfig from "../../apiConfig";
import {accountManager} from "../../../js/utils"; import {accountManager} from "../../../js/utils";
const app = getApp(); const app = getApp();

@ -46,7 +46,7 @@
</form> </form>
</view> </view>
<view class="foot"> <view class="foot">
<navigator class="agreement" hover-class="none" url="/account/pages/agreement/agreement"> <navigator class="agreement" hover-class="none" url="/markdown/account/agreement/agreement">
登录即代表您同意 <text class="color-main agreement">用户协议</text> 登录即代表您同意 <text class="color-main agreement">用户协议</text>
</navigator> </navigator>
</view> </view>

@ -13,21 +13,30 @@
"pages/main/main", "pages/main/main",
"pages/findmore/findmore", "pages/findmore/findmore",
"pages/home/home", "pages/home/home",
"pages/tidings/tidings", "pages/tidings/tidings"
"components/rich-md/rich-md",
"dev/pages/dev/dev"
], ],
"subpackages": [ "subpackages": [
{
"root": "markdown",
"pages": [
"task/task/task",
"components/rich-md/rich-md",
"account/about/about",
"account/agreement/agreement",
"account/pro_authentication/pro_authentication",
"exercise/exercise/exercise",
"path/path/path",
"path/path_send/path_send",
"shixun/shixun/shixun"
]
},
{ {
"root": "account", "root": "account",
"pages": [ "pages": [
"pages/agreement/agreement",
"pages/about/about",
"pages/change_password/change_password", "pages/change_password/change_password",
"pages/profile/profile", "pages/profile/profile",
"pages/account/account", "pages/account/account",
"pages/profile/school_select/school_select", "pages/profile/school_select/school_select",
"pages/pro_authentication/pro_authentication",
"pages/accounts/accounts", "pages/accounts/accounts",
"pages/user_info/user_info" "pages/user_info/user_info"
] ]
@ -76,26 +85,12 @@
"pages/task/task" "pages/task/task"
] ]
}, },
{
"root": "path",
"pages": [
"pages/path/path",
"pages/path_send/path_send"
]
},
{ {
"name": "search", "name": "search",
"root": "pages/search", "root": "pages/search",
"pages": [ "pages": [
"search" "search"
] ]
},
{
"name": "feedback",
"root": "pages/feedback",
"pages": [
"feedback"
]
} }
], ],
"preloadRule": { "preloadRule": {
@ -104,14 +99,15 @@
"packages": [ "packages": [
"shixun", "shixun",
"search", "search",
"path" "markdown"
] ]
}, },
"pages/home/home": { "pages/home/home": {
"network": "all", "network": "all",
"packages": [ "packages": [
"setting", "setting",
"account" "account",
"markdown"
] ]
}, },
"pages/main/main": { "pages/main/main": {
@ -120,20 +116,21 @@
"course", "course",
"account", "account",
"shixun", "shixun",
"path" "markdown"
] ]
}, },
"course/pages/course/course": { "course/pages/course/course": {
"network": "all", "network": "all",
"packages": [ "packages": [
"markdown",
"exercise", "exercise",
"account" "account"
] ]
}, },
"shixun/pages/shixun/shixun": { "account/pages/account/account": {
"network":"all", "network":"all",
"packages":[ "packages":[
"task" "markdown"
] ]
} }
}, },

@ -5,11 +5,10 @@ const developUrl = "https://test-newweb.educoder.net";
const trialUrl = "https://pre-newweb.educoder.net"; const trialUrl = "https://pre-newweb.educoder.net";
const releaseUrl = "https://www.educoder.net"; const releaseUrl = "https://www.educoder.net";
let _version = "0.16.2"; let _version = "0.16.3";
var eduUrl = releaseUrl; var eduUrl = releaseUrl;
/** /**
* A 支持选用实践课程 * U 优化项目结构主包压缩一半
* A 网络请求缓存
*/ */
export function switchEnv(env) { export function switchEnv(env) {

@ -1,72 +0,0 @@
// miniprogram/dev/pages/dev/dev.js
Page({
/**
* 页面的初始数据
*/
data: {
scrollTop:0,
re:false,
type:"secondary"
},
onScanCode(e){
},
/**
* 生命周期函数--监听页面加载
*/
onLoad: function (options) {
var p =this.selectComponent("#main")
p.onLoad();
p.onShow();
},
/**
* 生命周期函数--监听页面初次渲染完成
*/
onReady: function () {
},
/**
* 生命周期函数--监听页面显示
*/
onShow: function () {
},
/**
* 生命周期函数--监听页面隐藏
*/
onHide: function () {
},
/**
* 生命周期函数--监听页面卸载
*/
onUnload: function () {
},
/**
* 页面相关事件处理函数--监听用户下拉动作
*/
onPullDownRefresh: function () {
},
/**
* 页面上拉触底事件的处理函数
*/
onReachBottom: function () {
},
/**
* 用户点击右上角分享
*/
onShareAppMessage: function () {
}
})

@ -1,5 +0,0 @@
{
"usingComponents": {
"main":"/pages/home/home"
}
}

@ -1,3 +0,0 @@
page{
background: white;
}

@ -1,6 +0,0 @@
{
"component": true,
"usingComponents": {
"rich-md": "/components/rich-md/rich-md"
}
}

@ -1,6 +0,0 @@
{
"component": true,
"usingComponents": {
"rich-md": "/components/rich-md/rich-md"
}
}

@ -1,6 +0,0 @@
{
"component": true,
"usingComponents": {
"rich-md": "/components/rich-md/rich-md"
}
}

@ -1,6 +0,0 @@
{
"component": true,
"usingComponents": {
"rich-md": "/components/rich-md/rich-md"
}
}

@ -1,192 +1,15 @@
import {throttle} from "../../../js/utils";
const app = getApp();
Page({ Page({
data: { data: {
exercise_questions: [],
loading: true,
exercise: {},
count_down:'试卷不限时'
},
scrollToAnswerSheet(){
wx.pageScrollTo({
selector:".exercise-status"
})
},
scrollToQues(e){
let {target: {dataset:{ques_id}}} = e;
;
if(!ques_id) return;
wx.pageScrollTo({
selector:"#q-"+ques_id
});
},
onAnswer(e){
;
let {detail:{q_position,answered}} = e;
let key = "question_status[" + (q_position - 1) +"].ques_status"
this.setData({[key]: answered});
this.refreshAnsweredCount();
},
startCountDown({left_time}){
var left = left_time;
var setCountDown = (time)=>{
let s = time%60;
let m = parseInt(time/60)%60;
let h = parseInt(time/3600);
if(s<10)
s = '0' +s;
if(m<10)
m = '0' + m;
if(h<10)
h = '0' + h;
let count_down = h + ":" + m + ":" + s;
this.setData({count_down});
}
setCountDown(--left);
var id = setInterval(()=>{
if(left<=1){
return this.stopCountDown(id);
}
setCountDown(--left);
},1000);
},
stopCountDown(id){
clearInterval(id);
var showToast = throttle(wx.showToast, 800, {});//延时Toast
showToast({
title: '时间已到,系统自动为你提交中',icon:"none"
})
app.api("exercises.commit_exercise")({exercise_id:this.exercise_id, commit_method:2})
.then(res=>{
showToast({
title: "试卷提交成功!"
});
//this.pull_questions();
}).catch(e=>{
//app.showError(e);
var title = '提交失败';
//if(e.code==-2)
// title = '您未作答任何题';
showToast({
title,
icon:"none"
});
let db = wx.cloud.database();
db.collection("data").add({
data: {
name: "exercises.commit_exercise",
e,
createdAt: db.serverDate()
}
});
})
this.setData({count_down: '考试时间已截止'});
},
refreshAnsweredCount(){
if(!this.data.question_status)
return;
let answered_count = 0;
this.data.question_status.map(i=>{
answered_count += i.ques_status;
});
this.setData({answered_count});
},
pull_questions: function(){
app.api("exercises.start_answer")({exercise_id: this.exercise_id})
.then(res=>{
;
this.setData(res);
this.setData({loading: false});
if(this.data.question_status)
this.refreshAnsweredCount();
if(res.exercise.left_time)
this.startCountDown({left_time: res.exercise.left_time});
}).catch(e => {
this.setData({status:e.code});
app.showError(e);
});
},
save_exercise: function({show_loading=true}={}){
if(show_loading){
wx.showLoading({
title: '请稍候',
});
}
app.callApi({ name:"exercises.begin_commit", data:{exercise_id: this.exercise_id}, complete: wx.hideLoading})
.then(res=>{
if(show_loading){
wx.showToast({
title: "保存成功",
});
}
}).catch(e=>{
app.realTimeLog.error(e, "save exercise fail");
})
},
commit_exercise: function(){
//wx.showLoading({title: '检查作答情况中'});
app.api("exercises.begin_commit")({ exercise_id: this.exercise_id})
.then(resp=>{
let {question_undo, shixun_undo} = resp;
if(question_undo==0)
var content = "交卷后无法更改作答,是否确认?"
else {
var content = "您还有"+question_undo+"题未作答";
if(shixun_undo>0)
content += ",包含"+shixun_undo+"道实训题";
content +=",是否现在交卷呢?";
}
//wx.hideLoading();
wx.showModal({
title:"提示",
content,
success:res=>{
if(res.confirm){
app.api("exercises.commit_exercise")({exercise_id: this.exercise_id})
.then(res=>{
;
;
app.showMsg(res);
setTimeout(()=>{
wx.navigateBack({
delta:1
})
}, 800);
})
.catch(e=>{
app.showError(e);
});
}
}
})
})
.catch(e=>{
app.showError(e);
});
}, },
onLoad: function (options) { onLoad: function (options) {
this.exercise_id = options.exercise_id; if(options)
this.course_name = options.exercise_name; var query = "?" + Object.keys(options).map(i=>`${i}=${options[i]}`).join("&");
this.pull_questions(); else
}, var query = ""
wx.redirectTo({url: "/markdown/exercise/exercise/exercise" + query});
onShow: function () { global.realTimeLog.error("unexpected page");
},
onHide: function () {
this.save_exercise({show_loading: false});
},
onUnload: function () {
this.save_exercise({ show_loading: false });
},
onPullDownRefresh: function () {
} }
}) })

@ -1,9 +1,3 @@
{ {
"usingComponents": { "usingComponents": {}
"choice-question":"/exercise/components/choice-question/choice-question",
"main-question": "/exercise/components/main-question/main-question",
"null-question": "/exercise/components/null-question/null-question",
"shixun-question":"/exercise/components/shixun-question/shixun-question"
},
"navigationBarTitleText": "试卷"
} }

@ -1,62 +1,3 @@
<page-meta> <view>
<navigation-bar title="{{exercise.exercise_name}}" /> 跳转中...
</page-meta>
<!--exercise-score wx:if="{{exercise.exercise_scores}}" data="{{exercise.exercise_scores}}"/-->
<block wx:if="{{question_status&&exercise.user_exercise_status!=1&&exercise.user_exercise_status!=4}}">
<view class="header">
<view class="left-time">倒计时:{{count_down}}</view>
<view class="count-down"></view>
<view bindtap="scrollToAnswerSheet" class="answer-count">
<view>答题卡</view>
<view class="answer-count-num">{{answered_count}}/{{question_status.length}}</view>
</view> </view>
</view>
<view class="header-place-holder"></view>
</block>
<view class="exercise-score" wx:if="{{exercise_scores}}">
<view class="user-score">总分
<text class="color-main total-score">{{user_score}}</text>/{{exercise_types.q_scores}}</view>
<block wx:if="{{exercise_scores.objective_scores.length>0}}">
<view>客观题</view>
<view class="scores" bindtap="scrollToQues">
<view class="score-item" data-ques_id="{{item.ques_id}}" wx:for="{{exercise_scores.objective_scores}}">
{{item.ques_position}}
<view class="score {{item.answer_status==1?'right':'wrong'}}">{{item.user_score||'未评分'}}</view>
</view>
</view>
</block>
<block wx:if="{{exercise_scores.subjective_scores.length>0}}">
<view>主观题</view>
<view class="scores" bindtap="scrollToQues">
<view class="score-item" data-ques_id="{{item.ques_id}}" wx:for="{{exercise_scores.subjective_scores}}">
{{item.ques_position}}
<view class="score {{item.answer_status==1?'right':'wrong'}}">{{item.user_score||'未评分'}}</view>
</view>
</view>
</block>
</view>
<view bindanswer="onAnswer">
<view class="question-wrap" wx:for="{{exercise_questions}}" wx:key="question_id">
<choice-question id="q-{{item.question_id}}" is_md="{{exercise.is_md}}" wx:if="{{item.question_type<3}}" data="{{item}}" exercise_status="{{exercise.exercise_status}}" user_exercise_status="{{exercise.user_exercise_status}}"/>
<null-question id="q-{{item.question_id}}" is_md="{{exercise.is_md}}" wx:elif="{{item.question_type==3}}" data="{{item}}" exercise_status="{{exercise.exercise_status}}" user_exercise_status="{{exercise.user_exercise_status}}"/>
<main-question id="q-{{item.question_id}}" is_md="{{exercise.is_md}}" wx:elif="{{item.question_type==4}}" data="{{item}}" exercise_status="{{exercise.exercise_status}}" user_exercise_status="{{exercise.user_exercise_status}}"/>
<shixun-question id="q-{{item.question_id}}" is_md="{{exercise.is_md}}" wx:elif="{{item.question_type==5}}" data="{{item}}" exercise_status="{{exercise.exercise_status}}" user_exercise_status="{{exercise.user_exercise_status}}"/>
</view>
</view>
<view class="end" wx:if="{{!loading}}">---- END ----</view>
<!--exercise-status wx:if="{{exercise.exercise_stauts}}" data="{{exercise.exercise_status}}"/-->
<block wx:if="{{question_status}}">
<view class="exercise-status">
<view>作答情况</view>
<view class="statuses" bindtap="scrollToQues">
<view data-ques_id="{{item.ques_id}}" class="status-item {{item.ques_status==1?'answered':''}}" wx:for="{{question_status}}">
{{item.ques_number}}
</view>
</view>
</view>
<view class="operations" wx:if="{{exercise.user_exercise_status!=1&&exercise.user_exercise_status!=4}}">
<button id="save-exercise" type="main" plain="1" catchtap="save_exercise">保存</button>
<button id="submit-exercise" type="main" catchtap="commit_exercise">交卷</button>
</view>
</block>

@ -1,118 +1 @@
page{ /* miniprogram/exercise/pages/exercise/exercise.wxss */
overflow-x: hidden;
}
.header{
position: fixed;
top: 0;
background: white;
display: flex;
align-items: center;
height: 40px;
width: 100%;
z-index: 1000;
justify-content: space-evenly;
font-size: 15px;
box-shadow: 2px 2px 2px #dddddd;
}
.left-time{
padding: 0 8px 0 12px;
}
.count-down{
flex:auto;
text-align: center;
}
.answer-count{
padding: 0 12px 0 8px;
font-size: 13px;
color: #0095f0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.answer-count-num{
font-size: 11px;
}
.header-place-holder{
height: 40px;
}
.user-score{
text-align: center;
font-size: 18px;
padding-bottom: 12px;
}
.total-score{
font-weight: bolder;
}
.exercise-status,
.exercise-score{
background: white;
padding: 12px;
}
.statuses,
.scores{
display: flex;
flex-wrap: wrap;
}
.status-item,
.score-item{
border-radius: 4px;
margin: 8px 0 8px 10px;
width: 44px;
height: 44px;
display: flex;
justify-content: center;
align-items: center;
flex: none;
}
.status-item{
background: #cbcbcb;
color: white;
}
.status-item.answered{
background: #00b0f0;
}
.score-item{
color: #00b0f0;
border: 1px #00b0f0 solid;
position: relative;
}
.score{
position: absolute;
bottom: -8px;
font-size: 10px;
color: white;
border-radius: 32px;
padding: 1.8px 5px;
white-space: nowrap;
}
.score.right{
background: #00b0f0;
}
.score.wrong{
background: #fa5151;
}
.question-wrap{
margin: 8px 10px;
}
.end{
text-align: center;
margin: 10px;
color: dimgray;
}
.operations{
display: flex;
padding: 2px;
}
.operations>button{
flex: 1;
}

@ -1,5 +1,5 @@
<view class="navigation"> <view class="navigation">
<view style="height:{{statusBarHeight}}px"></view> <view class="status-bar" style="height:{{statusBarHeight}}px"></view>
<view class="navigation-bar"> <view class="navigation-bar">
<view class="navigation-left"> <view class="navigation-left">
<icon wx:if="{{showSearch}}" class="search" type="search" size="25" color="white" bindtap="enterSearch" /> <icon wx:if="{{showSearch}}" class="search" type="search" size="25" color="white" bindtap="enterSearch" />

@ -3,6 +3,9 @@
background: #00b0f0; background: #00b0f0;
flex: none; flex: none;
} }
.status-bar{
transition: all ease-out 0.6s
}
.navigation-bar{ .navigation-bar{
height: 44px; height: 44px;
padding-right: 105px; padding-right: 105px;

@ -1,8 +1,180 @@
class Record{ import apiConfig from "./apiConfig";
export class Session{
cookies = ""
constructor(){
}
saveCookies(cookies){
this.cookies = cookies.map(item => item.split(";")[0]).join("; ");
}
splitCookies(string){
let cookies = string.split(/,\s?(?=[^=,\;]*?\=)/ig);
return cookies;
}
request({ url, header, success,...options}) {
return wx.request({
...options,
url,
header: {...header, "Cookie": this.cookies},
success: (res) => {
if (res.cookies && res.cookies.length>0)
this.saveCookies(res.cookies);
else if (res.header["Set-Cookie"])
this.saveCookies(this.splitCookies(res.header["Set-Cookie"]));
else if(res.header["set-cookie"])
this.saveCookies(this.splitCookies(res.header["set-Cookie"]));
success(res);
if(url.indexOf("login.json")!=-1&&!res.data.status&&!this.cookies){
wx.reportMonitor("0", 2);
global.realTimeLog.error(res, "login fail: fail parse cookies!!", res);
}
}
});
}
uploadFile({url, filePath, name, header, formData, success, fail, complete}){
return wx.uploadFile({
url,
filePath,
name,
header: {"Cookie": this.cookies, ...header},
formData,
success:res=>{
try{
res.data = JSON.parse(res.data);
}catch(e){
//@todo
}
;
if(success)
success(res)
},
fail,
complete
})
}
downloadFile({url, header, filePath, success, fail, complete}){
return wx.downloadFile({
url,
header: {...header, "Cookie": this.cookies},
filePath,
success,
fail,
complete
})
}
} }
export default function Api(options){
function handler({success, fail, complete, resolve, reject}){
return {
success: res => {
if (res.data.status && res.data.status > 100 || res.data.status<0) {
switch (res.data.status) {
case 401:
var message = "(●'◡'●)\n请先登录哦";
var code = res.data.status;
break;
default:
var message = res.data.message;
var code = res.data.status;
}
let e = new Error(message);
e.code = code;
if (fail) fail(e, res.data);
reject(e, res.data);
return complete&&complete(e, res.data);
}
success&&success(res.data);
resolve(res.data);
complete&&complete(res.data);
},
fail: e => {
if ("errMsg" in e)
(e = new Error("(⋟﹏⋞)\n网络连接失败了")).code = -2;
if (!("message" in e))
(e = new Error("哎呀,出错了\n(_)")).code = -1;
fail&& fail(e, {});
reject(e, {});
complete&&complete(e, {});
}
}
}
export function triggerApi({session, name, data:_data = {}, success, fail, complete, config:{method,header={}}={}}) {
let api = apiConfig;
for (var key of name.split(".")) api=api[key];
let { url, query={}, form = {}, config:{method:_method,_header={},...config}={}} = api;
method = method || _method;
if(url){
if(url._)
url = url[method||"GET"]||url._;
url = name.replace(/\.?[^\.]+$|\./g, "/") + url.replace(/\{(.*?)\}/, function (match, k) {
return _data[k];
}).replace(/\*/g,key);
}else
url = name.replace(/\./g, "/");
url = global.config.apiRoot + url + ".json";
header = {..._header,...header};
var data={},param={};
if(form._)
form={...form._,...form[method||"GET"]};
if(query._)
query={...query._,...query[method||"GET"]};
for(var key in form){
if(key in _data)
data[key]=_data[key];
else{
let value = form[key];
if (value!=null)
data[key] = value;
else if (typeof value == "object")
console.error(`${key} was not given in data`, _data);
}
}
for(var key in query){
if (key in _data)
param[key] = _data[key];
else {
let value = query[key];
if (value!=null)
param[key] = value;
else if (typeof value == "object")
console.error(`${key} was not given in data`, _data);
}
}
let queryStr = Object.keys(param).map(key => `${key}=${param[key]}`).join("&");
if(queryStr)
url = url+"?"+queryStr;
if(method=="uploadFile"){
var formData = data;
var {name:_name, timeout} = config;
if(!_name)
_name = Object.keys(formData)[0];
var filePath = formData[_name];
delete formData[_name];
return new Promise((resolve, reject)=>{
session.uploadFile({
url, filePath, name:_name, header, timeout, formData, ...handler({success, fail, complete, resolve, reject})
})
});
}else if(method=="downloadFile"){
return new Promise((resolve,reject)=>{
session.downloadFile({
url, header, success:res=>{
resolve(res);
success&&success(res);
}, fail:e=>{
reject(e);
fail&&fail(e)
},complete
})
})
}else
return new Promise((resolve, reject) => {
session.request({
url, method, data, header,...handler({success, fail, complete, resolve, reject})
});
})
} }

@ -201,17 +201,4 @@ weapps:{
export default apiConfig; export default apiConfig;
export function setApi(obj){
for(var kstr in obj){
var value = obj[kstr];
var ref = apiConfig
for(var k of kstr.split(".")){
if(!(k in ref))
ref[k]={}
ref = ref[k];
}
Object.assign(ref, value);
}
}
global.setApi = setApi;
global.apiConfig = apiConfig; global.apiConfig = apiConfig;

@ -1,10 +1,9 @@
import apiConfig from "./apiConfig";
import edu from "./edu"; import {triggerApi, Session} from "./api";
import md5 from "./md5"; import md5 from "./md5";
import Session from "./requests"; import { accountManager} from "./utils";
import { getResConstruction, accountManager} from "./utils";
export default class Client{ class Client{
constructor({session} = {}) { constructor({session} = {}) {
this.session = session || new Session(); this.session = session || new Session();
this.user = {}; this.user = {};
@ -134,6 +133,7 @@ export default class Client{
return {randomcode:this.randomcode,client_key:this.client_key}; return {randomcode:this.randomcode,client_key:this.client_key};
} }
api(name,config={},data={}){ api(name,config={},data={}){
global.realTimeLog.error("call desprated method!!!");
return ({success, fail, complete,..._data}={})=>{ return ({success, fail, complete,..._data}={})=>{
return this.callApi({name,config, data:{...data,..._data}, success, fail, complete}); return this.callApi({name,config, data:{...data,..._data}, success, fail, complete});
} }
@ -142,7 +142,7 @@ export default class Client{
let session = this.session; let session = this.session;
let _data = this.trigger("before",name,data)||{}; let _data = this.trigger("before",name,data)||{};
data = {..._data, ...data}; data = {..._data, ...data};
return edu({ return triggerApi({
session, name, config, data: {...this.refresh_key(),...data}, session, name, config, data: {...this.refresh_key(),...data},
success:res=>{ success:res=>{
this.trigger("success",name,res); this.trigger("success",name,res);

@ -1,115 +0,0 @@
import apiConfig from "./apiConfig";
function handler({success, fail, complete, resolve, reject}){
return {
success: res => {
if (res.data.status && res.data.status > 100 || res.data.status<0) {
switch (res.data.status) {
case 401:
var message = "(●'◡'●)\n请先登录哦";
var code = res.data.status;
break;
default:
var message = res.data.message;
var code = res.data.status;
}
let e = new Error(message);
e.code = code;
if (fail) fail(e, res.data);
reject(e, res.data);
return complete&&complete(e, res.data);
}
success&&success(res.data);
resolve(res.data);
complete&&complete(res.data);
},
fail: e => {
if ("errMsg" in e)
(e = new Error("(⋟﹏⋞)\n网络连接失败了")).code = -2;
if (!("message" in e))
(e = new Error("哎呀,出错了\n(_)")).code = -1;
fail&& fail(e, {});
reject(e, {});
complete&&complete(e, {});
}
}
}
export default function ({ name, data:_data = {}, session, success, fail, complete, config:{method,header={}}={}}) {
let api = apiConfig;
for (var key of name.split(".")) api=api[key];
let { url, query={}, form = {}, config:{method:_method,_header={},...config}={}} = api;
method = method || _method;
if(url){
if(url._)
url = url[method||"GET"]||url._;
url = name.replace(/\.?[^\.]+$|\./g, "/") + url.replace(/\{(.*?)\}/, function (match, k) {
return _data[k];
}).replace(/\*/g,key);
}else
url = name.replace(/\./g, "/");
url = global.config.apiRoot + url + ".json";
header = {..._header,...header};
var data={},param={};
if(form._)
form={...form._,...form[method||"GET"]};
if(query._)
query={...query._,...query[method||"GET"]};
for(var key in form){
if(key in _data)
data[key]=_data[key];
else{
let value = form[key];
if (value!=null)
data[key] = value;
else if (typeof value == "object")
console.error(`${key} was not given in data`, _data);
}
}
for(var key in query){
if (key in _data)
param[key] = _data[key];
else {
let value = query[key];
if (value!=null)
param[key] = value;
else if (typeof value == "object")
console.error(`${key} was not given in data`, _data);
}
}
let queryStr = Object.keys(param).map(key => `${key}=${param[key]}`).join("&");
if(queryStr)
url = url+"?"+queryStr;
if(method=="uploadFile"){
var formData = data;
var {name:_name, timeout} = config;
if(!_name)
_name = Object.keys(formData)[0];
var filePath = formData[_name];
delete formData[_name];
return new Promise((resolve, reject)=>{
session.uploadFile({
url, filePath, name:_name, header, timeout, formData, ...handler({success, fail, complete, resolve, reject})
})
});
}else if(method=="downloadFile"){
return new Promise((resolve,reject)=>{
session.downloadFile({
url, header, success:res=>{
resolve(res);
success&&success(res);
}, fail:e=>{
reject(e);
fail&&fail(e)
},complete
})
})
}else
return new Promise((resolve, reject) => {
session.request({
url, method, data, header,...handler({success, fail, complete, resolve, reject})
});
})
}

@ -1,75 +0,0 @@
export default class{
cookies = ""
constructor(){
}
saveCookies(cookies){
this.cookies = cookies.map(item => item.split(";")[0]).join("; ");
}
splitCookies(string){
let cookies = string.split(/,\s?(?=[^=,\;]*?\=)/ig);
return cookies;
}
request({ url, header, success,...options}) {
return wx.request({
...options,
url,
header: {...header, "Cookie": this.cookies},
success: (res) => {
if (res.cookies && res.cookies.length>0)
this.saveCookies(res.cookies);
else if (res.header["Set-Cookie"])
this.saveCookies(this.splitCookies(res.header["Set-Cookie"]));
else if(res.header["set-cookie"])
this.saveCookies(this.splitCookies(res.header["set-Cookie"]));
success(res);
if(url.indexOf("login.json")!=-1&&!res.data.status&&!this.cookies){
wx.reportMonitor("0",2);
global.realTimeLog.error(res, "login fail: fail parse cookies!!");
var db = wx.cloud.database();
db.collection("failCookies").add({
data:{
createdAt: db.serverDate(),
res,
header:res.header,
"Set-Cookies": res.header["Set-Cookie"],
"set-cookie":res.header["set-cookie"],
cookies:res.cookies
}
})
}
}
});
}
uploadFile({url, filePath, name, header, formData, success, fail, complete}){
return wx.uploadFile({
url,
filePath,
name,
header: {"Cookie": this.cookies, ...header},
formData,
success:res=>{
try{
res.data = JSON.parse(res.data);
}catch(e){
//@todo
}
;
if(success)
success(res)
},
fail,
complete
})
}
downloadFile({url, header, filePath, success, fail, complete}){
return wx.downloadFile({
url,
header: {...header, "Cookie": this.cookies},
filePath,
success,
fail,
complete
})
}
}

@ -0,0 +1,5 @@
{
"usingComponents": {
"rich-md": "/markdown/components/rich-md/rich-md"
}
}

@ -1,6 +1,6 @@
{ {
"usingComponents": { "usingComponents": {
"rich-md": "/components/rich-md/rich-md" "rich-md": "/markdown/components/rich-md/rich-md"
}, },
"navigationBarTitleText": "服务协议" "navigationBarTitleText": "服务协议"
} }

@ -1,6 +1,6 @@
{ {
"usingComponents": { "usingComponents": {
"rich-md":"/components/rich-md/rich-md", "rich-md":"/markdown/components/rich-md/rich-md",
"iconfont":"/components/iconfont/iconfont" "iconfont":"/components/iconfont/iconfont"
}, },
"navigationBarTitleText": "实名认证" "navigationBarTitleText": "实名认证"

@ -0,0 +1,27 @@
.form-item {
background: #fefefe;
display: flex;
justify-content: space-between;
align-items: center;
padding: 14px 12px;
margin-bottom: 1px;
}
.form-item>.value {
text-align: right;
flex: auto;
}
.form-item>.key {
display: inline-block;
flex: none;
}
.form-item .tip {
font-size: 12px;
color: dimgray;
}
.form-item>switch {
transform: scale(0.8);
}

@ -1,6 +1,6 @@
{ {
"usingComponents": { "usingComponents": {
"rich-md": "/components/rich-md/rich-md", "rich-md": "/markdown/components/rich-md/rich-md",
"iconfont": "/components/iconfont/iconfont" "iconfont": "/components/iconfont/iconfont"
}, },
"navigationBarTitleText": "职业认证" "navigationBarTitleText": "职业认证"

@ -1,6 +1,6 @@
if (!global.towxml) if (!global.towxml)
global.towxml = require('../../towxml/index'); global.towxml = require('../../towxml/index');
import {navigateToUrl} from "../../js/utils"; import {navigateToUrl} from "../../../js/utils";
Component({ Component({
externalClasses: ['my-class',"class"], externalClasses: ['my-class',"class"],
@ -14,11 +14,9 @@ Component({
datakey:{ datakey:{
type:String, type:String,
observer:function(key){ observer:function(key){
;
wx.getStorage({ wx.getStorage({
key, key,
success: res=>{ success: res=>{
;
this.setData({_nodes:res.data}); this.setData({_nodes:res.data});
}, },
}); });
@ -49,11 +47,7 @@ Component({
methods: { methods: {
handleTap(e){ handleTap(e){
;
;
var {target:{dataset:{data}},currentTarget:{dataset:{data:_data}}} = e; var {target:{dataset:{data}},currentTarget:{dataset:{data:_data}}} = e;
var key = "RICHMDKEY";
;
let {tag, attr} = _data; let {tag, attr} = _data;
if(tag=='navigator'&&attr.href){ if(tag=='navigator'&&attr.href){
if(!navigateToUrl({url: attr.href})) if(!navigateToUrl({url: attr.href}))
@ -73,7 +67,7 @@ Component({
wx.setStorage({ wx.setStorage({
key,data,success:res=>{ key,data,success:res=>{
wx.navigateTo({ wx.navigateTo({
url: `/components/rich-md/rich-md?datakey=${key}&type=html` url: `/markdown/components/rich-md/rich-md?datakey=${key}&type=html`
}) })
} }
}) })

@ -1,6 +1,6 @@
{ {
"component": true, "component": true,
"usingComponents": { "usingComponents": {
"towxml":"/towxml/towxml" "towxml":"/markdown/towxml/towxml"
} }
} }

@ -0,0 +1,6 @@
{
"component": true,
"usingComponents": {
"rich-md": "/markdown/components/rich-md/rich-md"
}
}

@ -0,0 +1,192 @@
import {throttle} from "../../../js/utils";
const app = getApp();
Page({
data: {
exercise_questions: [],
loading: true,
exercise: {},
count_down:'试卷不限时'
},
scrollToAnswerSheet(){
wx.pageScrollTo({
selector:".exercise-status"
})
},
scrollToQues(e){
let {target: {dataset:{ques_id}}} = e;
;
if(!ques_id) return;
wx.pageScrollTo({
selector:"#q-"+ques_id
});
},
onAnswer(e){
;
let {detail:{q_position,answered}} = e;
let key = "question_status[" + (q_position - 1) +"].ques_status"
this.setData({[key]: answered});
this.refreshAnsweredCount();
},
startCountDown({left_time}){
var left = left_time;
var setCountDown = (time)=>{
let s = time%60;
let m = parseInt(time/60)%60;
let h = parseInt(time/3600);
if(s<10)
s = '0' +s;
if(m<10)
m = '0' + m;
if(h<10)
h = '0' + h;
let count_down = h + ":" + m + ":" + s;
this.setData({count_down});
}
setCountDown(--left);
var id = setInterval(()=>{
if(left<=1){
return this.stopCountDown(id);
}
setCountDown(--left);
},1000);
},
stopCountDown(id){
clearInterval(id);
var showToast = throttle(wx.showToast, 800, {});//延时Toast
showToast({
title: '时间已到,系统自动为你提交中',icon:"none"
})
app.api("exercises.commit_exercise")({exercise_id:this.exercise_id, commit_method:2})
.then(res=>{
showToast({
title: "试卷提交成功!"
});
//this.pull_questions();
}).catch(e=>{
//app.showError(e);
var title = '提交失败';
//if(e.code==-2)
// title = '您未作答任何题';
showToast({
title,
icon:"none"
});
let db = wx.cloud.database();
db.collection("data").add({
data: {
name: "exercises.commit_exercise",
e,
createdAt: db.serverDate()
}
});
})
this.setData({count_down: '考试时间已截止'});
},
refreshAnsweredCount(){
if(!this.data.question_status)
return;
let answered_count = 0;
this.data.question_status.map(i=>{
answered_count += i.ques_status;
});
this.setData({answered_count});
},
pull_questions: function(){
app.api("exercises.start_answer")({exercise_id: this.exercise_id})
.then(res=>{
;
this.setData(res);
this.setData({loading: false});
if(this.data.question_status)
this.refreshAnsweredCount();
if(res.exercise.left_time)
this.startCountDown({left_time: res.exercise.left_time});
}).catch(e => {
this.setData({status:e.code});
app.showError(e);
});
},
save_exercise: function({show_loading=true}={}){
if(show_loading){
wx.showLoading({
title: '请稍候',
});
}
app.callApi({ name:"exercises.begin_commit", data:{exercise_id: this.exercise_id}, complete: wx.hideLoading})
.then(res=>{
if(show_loading){
wx.showToast({
title: "保存成功",
});
}
}).catch(e=>{
app.realTimeLog.error(e, "save exercise fail");
})
},
commit_exercise: function(){
//wx.showLoading({title: '检查作答情况中'});
app.api("exercises.begin_commit")({ exercise_id: this.exercise_id})
.then(resp=>{
let {question_undo, shixun_undo} = resp;
if(question_undo==0)
var content = "交卷后无法更改作答,是否确认?"
else {
var content = "您还有"+question_undo+"题未作答";
if(shixun_undo>0)
content += ",包含"+shixun_undo+"道实训题";
content +=",是否现在交卷呢?";
}
//wx.hideLoading();
wx.showModal({
title:"提示",
content,
success:res=>{
if(res.confirm){
app.api("exercises.commit_exercise")({exercise_id: this.exercise_id})
.then(res=>{
;
;
app.showMsg(res);
setTimeout(()=>{
wx.navigateBack({
delta:1
})
}, 800);
})
.catch(e=>{
app.showError(e);
});
}
}
})
})
.catch(e=>{
app.showError(e);
});
},
onLoad: function (options) {
this.exercise_id = options.exercise_id;
this.course_name = options.exercise_name;
this.pull_questions();
},
onShow: function () {
},
onHide: function () {
this.save_exercise({show_loading: false});
},
onUnload: function () {
this.save_exercise({ show_loading: false });
},
onPullDownRefresh: function () {
}
})

@ -0,0 +1,9 @@
{
"usingComponents": {
"choice-question":"./choice-question/choice-question",
"main-question": "./main-question/main-question",
"null-question": "./null-question/null-question",
"shixun-question":"./shixun-question/shixun-question"
},
"navigationBarTitleText": "试卷"
}

@ -0,0 +1,62 @@
<page-meta>
<navigation-bar title="{{exercise.exercise_name}}" />
</page-meta>
<!--exercise-score wx:if="{{exercise.exercise_scores}}" data="{{exercise.exercise_scores}}"/-->
<block wx:if="{{question_status&&exercise.user_exercise_status!=1&&exercise.user_exercise_status!=4}}">
<view class="header">
<view class="left-time">倒计时:{{count_down}}</view>
<view class="count-down"></view>
<view bindtap="scrollToAnswerSheet" class="answer-count">
<view>答题卡</view>
<view class="answer-count-num">{{answered_count}}/{{question_status.length}}</view>
</view>
</view>
<view class="header-place-holder"></view>
</block>
<view class="exercise-score" wx:if="{{exercise_scores}}">
<view class="user-score">总分
<text class="color-main total-score">{{user_score}}</text>/{{exercise_types.q_scores}}</view>
<block wx:if="{{exercise_scores.objective_scores.length>0}}">
<view>客观题</view>
<view class="scores" bindtap="scrollToQues">
<view class="score-item" data-ques_id="{{item.ques_id}}" wx:for="{{exercise_scores.objective_scores}}">
{{item.ques_position}}
<view class="score {{item.answer_status==1?'right':'wrong'}}">{{item.user_score||'未评分'}}</view>
</view>
</view>
</block>
<block wx:if="{{exercise_scores.subjective_scores.length>0}}">
<view>主观题</view>
<view class="scores" bindtap="scrollToQues">
<view class="score-item" data-ques_id="{{item.ques_id}}" wx:for="{{exercise_scores.subjective_scores}}">
{{item.ques_position}}
<view class="score {{item.answer_status==1?'right':'wrong'}}">{{item.user_score||'未评分'}}</view>
</view>
</view>
</block>
</view>
<view bindanswer="onAnswer">
<view class="question-wrap" wx:for="{{exercise_questions}}" wx:key="question_id">
<choice-question id="q-{{item.question_id}}" is_md="{{exercise.is_md}}" wx:if="{{item.question_type<3}}" data="{{item}}" exercise_status="{{exercise.exercise_status}}" user_exercise_status="{{exercise.user_exercise_status}}"/>
<null-question id="q-{{item.question_id}}" is_md="{{exercise.is_md}}" wx:elif="{{item.question_type==3}}" data="{{item}}" exercise_status="{{exercise.exercise_status}}" user_exercise_status="{{exercise.user_exercise_status}}"/>
<main-question id="q-{{item.question_id}}" is_md="{{exercise.is_md}}" wx:elif="{{item.question_type==4}}" data="{{item}}" exercise_status="{{exercise.exercise_status}}" user_exercise_status="{{exercise.user_exercise_status}}"/>
<shixun-question id="q-{{item.question_id}}" is_md="{{exercise.is_md}}" wx:elif="{{item.question_type==5}}" data="{{item}}" exercise_status="{{exercise.exercise_status}}" user_exercise_status="{{exercise.user_exercise_status}}"/>
</view>
</view>
<view class="end" wx:if="{{!loading}}">---- END ----</view>
<!--exercise-status wx:if="{{exercise.exercise_stauts}}" data="{{exercise.exercise_status}}"/-->
<block wx:if="{{question_status}}">
<view class="exercise-status">
<view>作答情况</view>
<view class="statuses" bindtap="scrollToQues">
<view data-ques_id="{{item.ques_id}}" class="status-item {{item.ques_status==1?'answered':''}}" wx:for="{{question_status}}">
{{item.ques_number}}
</view>
</view>
</view>
<view class="operations" wx:if="{{exercise.user_exercise_status!=1&&exercise.user_exercise_status!=4}}">
<button id="save-exercise" type="main" plain="1" catchtap="save_exercise">保存</button>
<button id="submit-exercise" type="main" catchtap="commit_exercise">交卷</button>
</view>
</block>

@ -0,0 +1,118 @@
page{
overflow-x: hidden;
}
.header{
position: fixed;
top: 0;
background: white;
display: flex;
align-items: center;
height: 40px;
width: 100%;
z-index: 1000;
justify-content: space-evenly;
font-size: 15px;
box-shadow: 2px 2px 2px #dddddd;
}
.left-time{
padding: 0 8px 0 12px;
}
.count-down{
flex:auto;
text-align: center;
}
.answer-count{
padding: 0 12px 0 8px;
font-size: 13px;
color: #0095f0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.answer-count-num{
font-size: 11px;
}
.header-place-holder{
height: 40px;
}
.user-score{
text-align: center;
font-size: 18px;
padding-bottom: 12px;
}
.total-score{
font-weight: bolder;
}
.exercise-status,
.exercise-score{
background: white;
padding: 12px;
}
.statuses,
.scores{
display: flex;
flex-wrap: wrap;
}
.status-item,
.score-item{
border-radius: 4px;
margin: 8px 0 8px 10px;
width: 44px;
height: 44px;
display: flex;
justify-content: center;
align-items: center;
flex: none;
}
.status-item{
background: #cbcbcb;
color: white;
}
.status-item.answered{
background: #00b0f0;
}
.score-item{
color: #00b0f0;
border: 1px #00b0f0 solid;
position: relative;
}
.score{
position: absolute;
bottom: -8px;
font-size: 10px;
color: white;
border-radius: 32px;
padding: 1.8px 5px;
white-space: nowrap;
}
.score.right{
background: #00b0f0;
}
.score.wrong{
background: #fa5151;
}
.question-wrap{
margin: 8px 10px;
}
.end{
text-align: center;
margin: 10px;
color: dimgray;
}
.operations{
display: flex;
padding: 2px;
}
.operations>button{
flex: 1;
}

@ -0,0 +1,6 @@
{
"component": true,
"usingComponents": {
"rich-md": "/markdown/components/rich-md/rich-md"
}
}

@ -0,0 +1,6 @@
{
"component": true,
"usingComponents": {
"rich-md": "/markdown/components/rich-md/rich-md"
}
}

@ -0,0 +1,6 @@
{
"component": true,
"usingComponents": {
"rich-md": "/markdown/components/rich-md/rich-md"
}
}

@ -1,7 +1,7 @@
{ {
"usingComponents": { "usingComponents": {
"nav-bar":"/components/nav-bar/nav-bar", "nav-bar":"/components/nav-bar/nav-bar",
"rich-md":"/components/rich-md/rich-md", "rich-md":"/markdown/components/rich-md/rich-md",
"mp-icon":"/weui-miniprogram/icon/icon" "mp-icon":"/weui-miniprogram/icon/icon"
}, },
"navigationBarTitleText": "实践课程" "navigationBarTitleText": "实践课程"

@ -0,0 +1,103 @@
const app = getApp();
//status:[];
const cateTypes={
description:0,
task:1
}
Page({
data:{
shixun:{},
challenges:[],
list:[
{text:"简介"},{text:"任务"}
],
description:""
},
sendToCourse(){
let {id} = this.data.shixun;
this.setData({shixun_ids: [id], showSendDialog: true});
},
collect(){
let {id, is_collect} = this.data.shixun;
let api_name = is_collect?"collections.cancel":"collections";
app.api(api_name)({container_type:"Shixun", container_id:id})
.then(res=>{
;
this.pullShixun({showLoading: 0});
if(is_collect){
res.message = "已取消收藏";
var icon = "success";
var duration = 1500;
}else{
var icon = "none";
var duration = 3000;
}
wx.showToast({
title: res.message,
icon,
duration
})
}).catch(e=>{
;
app.showError(e);
})
},
enterChallenge(){
wx.showLoading({
title: '开启中',
});
this.setData({loading: 1});
app.api("shixuns.shixun_exec")({ identifier:this.data.identifier})
.then(res=>{
app.navigateTo({ url: "{task}?identifier=" + res.game_identifier});
}).catch(e=>{
app.showError(e);
}).finally(e=>{
wx.hideLoading();
this.setData({loading:false})
});
},
scrollTo({scrollTop}){
wx.pageScrollTo({scrollTop,duration:200})
},
switchNav({detail:{current,source}}){
if(source=="touch")
this.setData({current});
},
apiChallenges: app.api("shixuns.challenges"),
apiShixun:app.api("shixuns"),
async pullChallenges(){
let {description, challenge_list:challenges} = await this.apiChallenges({identifier:this.data.identifier});
this.setData({description, challenges});
},
async pullShixun({showLoading=1}={}){
if(showLoading)
wx.showLoading({
title: '努力加载中'
})
let {identifier} = this.data;
let shixun = await this.apiShixun({identifier});
this.setData({shixun}, res=>{
if(showLoading)
wx.hideLoading()
});
},
onLoad: function (options) {
let {identifier,cate_type} = options;
let current = cateTypes[cate_type];
this.setData({identifier, current});
this.pullChallenges().catch(e=>{
});
this.pullShixun().catch(e=>{
app.showError(e);
});
},
onShareAppMessage: function () {
return app.shareApp({
title:this.data.shixun.name,
imageUrl: global.config.eduImgDir+"avatars/Shixun/"+this.data.shixun.id
})
}
})

@ -0,0 +1,12 @@
{
"usingComponents": {
"rich-md":"/markdown/components/rich-md/rich-md",
"require-login":"/components/require-login/require-login",
"challenge-item":"./challenge-item/challenge-item",
"nav-bar":"/components/nav-bar/nav-bar",
"send-to-course":"/components/modal/send-to-course/send-to-course"
},
"navigationBarBackgroundColor": "#0080f0",
"navigationBarTextStyle": "white",
"navigationBarTitleText": "实训项目"
}

@ -0,0 +1,51 @@
<!--page-meta>
<navigation-bar title="{{shixun.name}}"/>
</page-meta-->
<wxs src="./shixun.wxs" module="handler"/>
<view class="header">
<view class="shixun-title">{{shixun.name}}</view>
<view class="shixun-detail">
<view>
<view class="detail-key">学习人数</view>
<view class="detail-value">{{shixun.stu_num}}</view>
</view>
<view>
<view class="detail-key">难度级别</view>
<view class="detail-value">{{shixun.diffcult}}</view>
</view>
<view>
<view class="detail-key">经验值</view>
<view class="detail-value">{{shixun.experience}}</view>
</view>
</view>
</view>
<require-login bg="#00b0f0" bindchange="pullShixun" message="点击登陆,可以获取更多内容哦"/>
<nav-bar list="{{list}}" current="{{current}}" type="line" bindchange="switchNav"/>
<swiper class="body" current="{{current}}" bindchange="switchNav" circular="1">
<swiper-item>
<scroll-view class="scroll-body" scroll-y="{{1}}" bindscroll="{{handler.scroll}}">
<rich-md my-class="desp" nodes="{{description}}" type="markdown"/>
</scroll-view>
</swiper-item>
<swiper-item>
<scroll-view class="scroll-body" scroll-y="1" bindscroll="{{handler.scroll}}">
<view class="cate">
<view class="cate-header">
<text class="square"/>
<text class="cate-name">全部任务</text>
</view>
<view class="challenges">
<view class="challenge-wrap" wx:for="{{challenges}}" wx:key="challenge_id">
<challenge-item data="{{item}}"/>
</view>
</view>
</view>
</scroll-view>
</swiper-item>
</swiper>
<view wx:if="{{shixun.task_operation[0]}}" class="operations">
<button class="collect" bindtap="collect" type="main" plain>{{shixun.is_collect?'已收藏':'收藏'}}</button>
<button bindtap="enterChallenge" disabled="{{loading}}" type="main">{{shixun.task_operation[0]}}</button>
<button class="send" type="main" bindtap="sendToCourse" plain>发送至</button>
</view>
<send-to-course show="{{showSendDialog}}" shixun_ids="{{shixun_ids}}"/>

@ -0,0 +1,89 @@
.header{
text-align: center;
position: relative;
display: flex;
flex-direction: column;
align-items: center;
height: 138px;
background: white;
}
.shixun-title{
background: #0080f0;
font-weight: bolder;
padding: 2px 10px;
font-size: 20px;
color: white;
height: 84px;
width: 100%;
}
.shixun-detail{
position: absolute;
top: 60px;
height: 70px;
background:white;
box-shadow: 3px 3px 10px #e7e7e7;
display: flex;
border-radius: 6px;
width: 600rpx;
}
.shixun-detail>view{
flex:auto;
display: flex;
flex-direction: column;
}
.shixun-detail>view>view{
flex: auto;
display: flex;
justify-content: center;
flex-direction: column;
}
.detail-key{
color: dimgrey;
font-size: 13px;
}
.cate{
background: white;
overflow: hidden;
}
.cate-header{
padding: 8px 8px 0px 8px;
}
.square{
width: 0;
height: 0px;
border-radius: 10px;
border-left: 5px solid #00b0f0;
}
.cate-name{
padding: 0 10px;
}
.body{
height: calc(100vh - 76px);
}
.scroll-body{
height: 100%;
background: white;
}
.operations{
position: sticky;
bottom: 0;
left:0;
right:0;
display: flex;
background: white;
}
.operations>button{
flex: 1;
display: flex;
justify-content: center;
align-content: center;
white-space: nowrap;
border-radius: 0;
}
button.collect, button.send{
background: white!important;
}
/* tmp solution*/
.weui-half-screen-dialog__hd__side .weui-icon-btn.weui-icon-btn_more{
display: none;
}

@ -0,0 +1,274 @@
const app = getApp();
import {navigateToUrl} from '../../../js/utils';
Page({
data: {
theme: 'light',
current: 0,
content:"加载中...",
attachDir: global.config.attachDir,
titles: ["任务描述", "代码文件", "测评结果"],
can_use_editor:wx.canIUse("editor")
},
/*onTap(e){
;
let {detail:{tag, attr}} = e;
if(tag=='navigator'&&attr.href)
navigateToUrl({url: attr.href});
;
},*/
checkTime(){
if(this.timeChecked)
return;
let date = new Date();
let hours = date.getHours();
let min = date.getMinutes();
if(hours>=0&&hours<=3||hours>=23&&min>=30){
this.timeChecked = true;
let y = date.getFullYear();
let m = date.getMonth();
let d = date.getDate();
if(hours>=23)
d += 1;
let t = y+'-'+m+'-'+d;
let key = 'task-show-go-to-sleep-last-time'
let last = wx.getStorageSync(key);
if(last==t)
return
this.setData({showGoToSleep:true});
wx.reportAnalytics('show_go_to_sleep_in_task', {});
wx.setStorageSync(key, t);
}else if(hours<=21){
this.timeChecked = true;
}
},
onTapTopTip(){
this.setData({showGoToSleep:false});
wx.reportAnalytics('tap_go_to_sleep', {});
},
setNavigationTheme(){
let {theme} = this.data;
if(theme=='dark'){
wx.setNavigationBarColor({backgroundColor:"#303030",frontColor:"#ffffff",animation:'linear'});
}else{
wx.setNavigationBarColor({backgroundColor:"#fbfbfb",frontColor:"#000000",animation:"linear"});
}
},
switchTheme({detail}){
;
let {value} = detail;
let theme = value?'dark':'light';
if(theme==this.data.theme)
return;
wx.showNavigationBarLoading()
this.setData({theme},wx.hideNavigationBarLoading);
this.setNavigationTheme();
wx.setStorageSync('config-task-theme', theme);
},
enterTask(e){
var {target:{dataset:{identifier}}} = e;
;
if(identifier)
wx.redirectTo({url:"./task?identifier="+identifier});
},
setCurrent({current}){
this.setData({current});
},
enterChallenge(){
this.setData({current:0})
},
enterOutcome(){
this.setData({current:2});
},
onTextAreaBlur({detail:{value}}){
if (!this.modified) {
this.modified = this.content != value;
}
if(this.modified){
this.content = value;
this.updateFile();
}
},
onTextAreaInput({detail:{value}}){
this.modified = this.content!=value;
if(this.modified)
this.content = value;
},
onEditorInput(e){
let {text} = e.detail;
;
this.modified = this.content!=text;
if(this.modified){
this.content = text;
// 自动缩进适配┭┮﹏┭┮,小程序限制太多了
this.oldLines = this.lines;
this.lines = this.content.split(/\n/g);
if(this.oldLines.length+1==this.lines.length){
for(var i=this.oldLines.length-1;i>=0;i--){
if(this.lines[i+1]!=this.oldLines[i]){
let match = this.lines[i].match(/^\s+/);
if(match)
this.editor.insertText({text:match[0]});
break;
}
}
}
}
},
onEditorBlur({detail}){
let {text} = detail;
if(!this.modified){
this.modified = this.content.trimEnd()!=text.trimEnd();
}
if(this.modified){
this.content = text;
this.updateFile();
}
},
processPath(path){
return path.replace(/[;]$/,"");
},
async updateFile({ evaluate=0}={}){
var {content} = this;
var {path} = this.data.challenge;
path = this.processPath(path);
var {identifier} = this.data.myshixun;
var {id:game_id} = this.data.game;
if(this.modified||evaluate)
this.modified = false;
try{
var res = await app.api("myshixuns.update_file")({path,identifier,game_id,content,evaluate});
return res;
}catch(e){
this.modified = true;
global.realTimeLog.error(e, "fail: update code file fail");
throw new Error(e.message);
}
},
buildGame({detail:{value}}){
wx.showLoading({
title: '代码上传中',
});
this.setData({ building: 1 });
var {identifier} = this.data;
if(!this.data.can_use_editor)
this.content = value.content;
this.updateFile({evaluate:1})
.then(res=>{
var {sec_key, resubmit=""} = res;
app.api("tasks.game_build")({ identifier, resubmit, sec_key, content_modified:1})
.then(res => {
this.getBuildStatus({sec_key, resubmit});
})
.catch(e=>{
wx.hideLoading();
this.setData({ building: 0 })
global.realTimeLog.error(e, "fail: build game fail");
})
}).catch(e=>{
wx.hideLoading();
this.setData({building:0});
})
},
getBuildStatus({resubmit="", sec_key=""}){
var { identifier } = this.data;
var timer = setInterval(()=>{
app.api("tasks.game_status")({identifier,resubmit,sec_key})
.then(res=>{
if("status" in res){
wx.hideLoading();
this.setData({ current: 2, building: 0 });
this.setData(res);
clearInterval(timer);
}else if(res.running_code_message){
wx.showLoading({
title: res.running_code_message
})
}
})
}, 1000);
},
onSwiperChange(e){
;
let {detail:{current,source}} = e;
if(source=="touch"){
this.setData({current});
}
if(current==1&&!this.content){
this.pullContent();
}
this.checkTime();
},
async pullTask(){
wx.showLoading({
title: '努力加载中',
})
wx.showNavigationBarLoading();
let {identifier} = this.data;
let res = await app.api("tasks")({identifier});
res.challenge.task_pass = res.challenge.task_pass.replace(/\[TOC\]\s*-+\s*/,"")
this.setData(res,()=>{
wx.hideLoading();
wx.hideNavigationBarLoading();
});
},
async pullContent(){
;
let {path} = this.data.challenge;
path = this.processPath(path);
let {identifier} = this.data;
let {content} = await app.api("tasks.rep_content")({identifier,path});
this.content = content;
if(this.data.can_use_editor){
if(!this.editor)
this.onEditorReady({pullContent:1});
else{
//let delta = {ops:[{insert:content}]};
//this.editor.setContents({delta});
this.editor.clear();
this.lines = content.split(/\n/g);
this.editor.insertText({text:content});
}
}else
this.setData({content});
;
},
onEditorReady({pullContent=0}={}) {
const that = this
wx.createSelectorQuery().select('#code-editor').context(function (res) {
that.editor = res.context
if(pullContent&&that.editor)
that.pullContent();
//that.editor.insertText({text:that.content});
;
}).exec();
},
onLoad: function (options) {
let theme = wx.getStorageSync('config-task-theme');
this.setData({theme});
this.setNavigationTheme();
let {identifier} = options;
this.setData({identifier});
this.pullTask();
},
onReady(){
this.checkTime();
},
onHide: function () {
this.updateFile();
},
onShareAppMessage: function () {
let {challenge, shixun} = this.data;
return app.shareApp({
title:`${challenge.position}关:${challenge.subject}`,
imageUrl: global.config.eduImgDir + "avatars/Shixun/" + shixun.id,
path:`/shixun/pages/shixun/shixun?identifier=${shixun.identifier}&cate_type=task`
})
}
})

@ -0,0 +1,8 @@
{
"usingComponents": {
"rich-md":"/markdown/components/rich-md/rich-md",
"test-set":"./test_set/test_set",
"mp-toptips":"/weui-miniprogram/toptips/toptips"
},
"navigationBarTitleText": "实训关卡"
}

@ -0,0 +1,64 @@
<page-meta>
<navigation-bar title="{{titles[current]}}"/>
</page-meta>
<mp-toptips bindtap="onTapTopTip" msg="夜深了,注意休息" type="info" show="{{showGoToSleep}}" delay="0"></mp-toptips>
<swiper class="body" circular="1" current="{{current}}" bindchange="onSwiperChange">
<swiper-item>
<scroll-view class="challenge-body {{theme}}" scroll-y="1">
<block wx:if="{{challenge}}">
<view class="subject {{theme}}">第{{challenge.position}}关:{{challenge.subject}}</view>
<view class="sub-header {{theme}}">
<!--path="pagesB/shixuntasks/shixuntasks?game_identifier={{identifier}}"
path="pagesB/trainingdetails/trainingdetails?identifier={{shixun.identifier}}&shixunid={{shixun.id}}"
-->
<navigator target="miniProgram" hover-class="none" app-id="wx2402d86a6b534f77"
path="pages/shiyan/shixun?shixunDetailsid={{shixun.id}}&shixunidentifier={{shixun.identifier}}"
class="nav-educoder">
<image src="{{attachDir}}888038" class="icon"></image>
用EduCoder云网实训(推荐)
</navigator>
<view class="switch-wrap">黑暗模式<switch class="theme-switch" bindchange="switchTheme" color="dimgrey" checked="{{theme=='dark'}}"></switch></view>
</view>
</block>
<rich-md nodes="{{challenge.task_pass}}" type="markdown" theme="{{theme}}"/>
</scroll-view>
</swiper-item>
<swiper-item>
<view class="editor-body">
<form class="form-body" bindsubmit="buildGame">
<editor wx:if="{{can_use_editor}}" class="editor"
id="code-editor"
bindready="onEditorReady"
bindinput="onEditorInput"
bindblur="onEditorBlur"/>
<textarea wx:else class="editor" name="content" maxlength="-1"
value="{{content}}"
bindblur="onTextAreaBlur"
show-confirm-bar="{{false}}"
bindinput="onTextAreaInput">
</textarea>
<view class="operations">
<button class="button-challenge" plain="1" type="main" bindtap="enterChallenge">任务</button>
<button id="task-build" class="button-build" loading="{{building}}" disabled="{{building}}" type="main" form-type="submit">测评</button>
<button class="button-outcome" plain="1" type="main" bindtap="enterOutcome">测试集</button>
</view>
</form>
</view>
</swiper-item>
<swiper-item class="outcome-swiper">
<scroll-view class="outcome-body" scroll-y="1">
<view class="compile-output">
<rich-text wx:if="{{sets_error_count}}" nodes="{{last_compile_output}}"></rich-text>
<view wx:elif="{{test_sets_count}}" class="success">成功通过 {{test_sets_count-sets_error_count}}/{{test_sets_count}}</view>
</view>
<view class="test-set-wrap" wx:for="{{test_sets}}">
<test-set data="{{item}}" index="{{index}}"/>
</view>
</scroll-view>
<view class="operations" bindtap="enterTask">
<button wx:if="{{prev_game}}" data-identifier="{{prev_game}}" type="main" plain>上一关</button>
<button wx:if="{{next_game}}" data-identifier="{{next_game}}" type="main">下一关</button>
</view>
</swiper-item>
</swiper>

@ -0,0 +1,89 @@
.body{
height: 100vh;
}
.sub-header{
display: flex;
padding-top: 14px;
margin-bottom: -10px;
justify-content: space-between;
align-items: center;
}
.nav-educoder{
font-size: 12px;
padding-left: 12px;
}
.nav-educoder>.icon{
height: 20px;
width: 20px;
}
.dark{
background: black!important;
color: white;
opacity: 0.8;
}
.switch-wrap{
text-align: right;
font-size: 12px;
}
.theme-switch{
transform: scale(0.6);
}
.challenge-body{
height: 100%;
background: white;
}
.subject{
padding-left: 20px;
font-size: 18px;
font-weight: bolder;
}
.editor-body{
height: 100%;
display: flex;
flex-direction: column;
}
.form-body{
height: 100%;
display: flex;
flex-direction: column;
}
.editor{
width: auto;
background: #002B36;
color: #93A1A1;
font: 14px/normal 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', monospace;
white-space: pre;
padding: 0px 10px;
height: calc(100vh - 42px);
flex: auto;
}
.operations{
flex: none;
display: flex;
}
.operations>button{
flex: auto;
border-radius: 0;
}
.outcome-swiper{
display: flex;
flex-direction: column;
}
.outcome-body{
flex: 1 1 1px;
height: 1px;
background: #111c24;
}
.compile-output{
max-height: 36vh;
overflow-y: scroll;
padding: 6px 10px;
color: #ff6545;
}
.success{
color: #12af25;
}
.test-set-wrap{
margin: 7px 8px;
}

@ -0,0 +1,9 @@
{
"component": true,
"usingComponents": {
"decode": "./decode",
"latex": "./latex/latex",
"table": "./table/table",
"img": "./img/img"
}
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save