删除多余文件,升级UI, 增加文件删除功能

smart_class
educoder_weapp 5 years ago
parent a8b22d1300
commit c7b1177c8d

@ -1,3 +0,0 @@
{
"directory": "vendors"
}

1
.gitignore vendored

@ -11,6 +11,7 @@ $RECYCLE.BIN/
.Trashes .Trashes
lib_refer lib_refer
towxml
project.config.json project.config.json
# Node.js # Node.js

@ -1,4 +1,19 @@
使用说明 # 功能介绍
========
使用代码前,请先修改 `app.js` 里面的域名配置。 - 学员可以在点击右下角”加入课堂”选择课程加入,教员可以在“加入课堂中”新建课程
- 长按相应课程可以退出学习
- 进入课堂界面会显示在位,头像为彩色,若退出课堂界面则会显示灰色头像
- 教员在课堂界面中可以直观地看到学员在位情况,可以选择学员让其起立回答问题,并且对学员可以进行加分、减分操作
- 学员可以收到教员让其起立提问、回答的提示,还可以点击“我要提问、回答”
- 在课堂界面右下角进入“更多”可以查看本课堂的课程文件资源
- 教员可以在课程的设置界面中更新课程的名称,还可以将学员移除本课程的学习
## 其他
- 用户可以在设置中更改姓名、用户名

@ -1,28 +1,38 @@
{ {
"pages": [ "pages": [
"pages/classes/classes", "pages/classes/classes",
"pages/changeuser/changeuser", "pages/changeuser/changeuser",
"pages/index/index",
"pages/config/config",
"pages/https/https",
"pages/session/session",
"pages/websocket/websocket",
"pages/game/game",
"pages/shouquan/shouquan",
"pages/outside/outside",
"pages/setting/setting", "pages/setting/setting",
"pages/user/user", "pages/user/user",
"pages/addclass/addclass", "pages/addclass/addclass",
"pages/classdetail/classdetail", "pages/classdetail/classdetail",
"pages/classroom/classroom", "pages/classroom/classroom",
"pages/classsetting/classsetting" "pages/classsetting/classsetting",
"pages/about/about",
"pages/createclass/createclass"
], ],
"window": { "window": {
"backgroundTextStyle": "light", "backgroundTextStyle": "light",
"navigationBarBackgroundColor": "#fff", "navigationBarBackgroundColor": "#f2f2f2",
"navigationBarTitleText": " ", "navigationBarTitleText": "积分教室",
"navigationBarTextStyle": "black" "navigationBarTextStyle": "black"
}, },
"tabBar": {
"selectedColor": "#00C7B7",
"list": [
{
"pagePath": "pages/classes/classes",
"text": "学习",
"iconPath": "images/tabbar-icon/tabbar_contact_default.png",
"selectedIconPath": "images/tabbar-icon/tabbar_contact_pressed.png"
},
{
"pagePath": "pages/setting/setting",
"iconPath": "images/tabbar-icon/tabbar_settings_default.png",
"selectedIconPath": "images/tabbar-icon/tabbar_settings_pressed.png",
"text": "设置"
}
]
},
"sitemapLocation": "sitemap.json" "sitemapLocation": "sitemap.json"
} }

@ -1,11 +1,10 @@
@font-face {font-family: "iconfont"; @font-face {font-family: "iconfont";
src: url('iconfont.eot?t=1571919948950'); /* IE9 */ src: url('iconfont.eot?t=1572072148156'); /* IE9 */
src: url('iconfont.eot?t=1571919948950#iefix') format('embedded-opentype'), /* IE6-IE8 */ src: url('iconfont.eot?t=1572072148156#iefix') format('embedded-opentype'), /* IE6-IE8 */
url('data:application/x-font-woff2;charset=utf-8;base64,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') format('woff2'), url('data:application/x-font-woff2;charset=utf-8;base64,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') format('woff2'),
url('iconfont.woff?t=1571919948950') format('woff'), url('iconfont.woff?t=1572072148156') format('woff'),
url('iconfont.ttf?t=1571919948950') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+ */ url('iconfont.ttf?t=1572072148156') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+ */
url('iconfont.svg?t=1571919948950#iconfont') format('svg'); /* iOS 4.1- */ url('iconfont.svg?t=1572072148156#iconfont') format('svg'); /* iOS 4.1- */
} }
.iconfont { .iconfont {
@ -27,7 +26,7 @@
content: "\e647"; content: "\e647";
} }
.myicon-question:before { .myicon-question:before {
content: "\e670"; content: "\e648";
} }
.myicon-internet_class:before { .myicon-internet_class:before {
@ -38,6 +37,10 @@
content: "\e637"; content: "\e637";
} }
.myicon-add:before {
content: "\e600";
}
.myicon__info { .myicon__info {
color: #fff; color: #fff;
left: 100%; left: 100%;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1018 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 416 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 222 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 943 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 773 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 719 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 604 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

@ -1,17 +1,6 @@
// pages/shouquan.js // pages/about/about.js
Page({ Page({
bindGetUserInfo: function (e) {
var that = this;
//此处授权得到userInfo
console.log(e.detail.userInfo);
//接下来写业务代码
//最后,记得返回刚才的页面
wx.navigateBack({
delta: 1
})
},
/** /**
* 页面的初始数据 * 页面的初始数据
*/ */

@ -0,0 +1,24 @@
<view class="container">
<text>
# 功能介绍
- 学员可以在点击右下角”加入课堂”选择课程加入,教员可以在“加入课堂中”新建课程
- 长按相应课程可以退出学习
- 进入课堂界面会显示在位,头像为彩色,若退出课堂界面则会显示灰色头像
- 教员在课堂界面中可以直观地看到学员在位情况,可以选择学员让其起立回答问题,并且对学员可以进行加分、减分操作
- 学员可以收到教员让其起立提问、回答的提示,还可以点击“我要提问、回答”
- 在课堂界面右下角进入“更多”可以查看本课堂的课程文件资源
- 教员可以在课程的设置界面中更新课程的名称,还可以将学员移除本课程的学习
## 其他
- 用户可以在设置中更改姓名、用户名
</text>
</view>

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

@ -10,52 +10,12 @@ Page({
* 页面的初始数据 * 页面的初始数据
*/ */
classes: [], classes: [],
current_user: null,
data: { data: {
new_classname:'', loading: true,
new_class_teacher_username: '',
classes: [] classes: []
}, },
updateName: function ({detail: {value}}){
this.setData({new_classname:value});
},
create_class: function(event){
let {new_classname} = this.data;
new_classname = new_classname.trim();
if(new_classname==""){
wx.showToast({
title:"课程名称不能为空",
icon: "none"
})
return;
}
class_ = new Class({
name: new_classname,
teacher: AV.User.current()
})
presence = new Presence({
user: AV.User.current(),
class: class_
});
wx.showLoading({
title: '请稍候',
})
presence.save().then(()=>{
wx.hideLoading();
wx.showToast({
title: '创建成功,可在首页课程列表以教师身份进入课堂',
icon:"none",
duration: 4000
});
this.setData({new_classname: ""});
},
()=>{
wx.hideLoading();
wx.showToast({
title: '操作失败',
icon: "none"
});
});
},
add_class:function(event){ add_class:function(event){
class_id = event.currentTarget.dataset.id; class_id = event.currentTarget.dataset.id;
class_name = event.currentTarget.dataset.name; class_name = event.currentTarget.dataset.name;
@ -101,29 +61,36 @@ Page({
query = new AV.Query("Presence"); query = new AV.Query("Presence");
query.equalTo("user", AV.User.current()); query.equalTo("user", AV.User.current());
query.include("class"); query.include("class");
query.find().then(this.set_presences.bind(this)); return query.find().then(this.set_presences.bind(this));
}, },
set_presences: function(presences){ set_presences: function(presences){
console.log("set_presences"); console.log("set_presences");
console.log(presences); console.log(presences);
present_class_ids = presences.map(presence=>presence.get("class").id); present_class_ids = presences.map(presence=>presence.get("class").id);
classes =this.classes.map(class_=>{ classes = [];
if(present_class_ids.indexOf(class_.id)>-1){ current_user = this.current_user;
this.classes.map(class_=>{
teacher = class_.get("teacher");
if(present_class_ids.indexOf(class_.id)>-1 || current_user.id==teacher.id){
class_.set("present", true); class_.set("present", true);
}else{ }else{
class_.set("present", false); class_.set("present", false);
classes.push(class_);
} }
return class_;
}); });
console.log(classes); console.log(classes);
this.setData(jsonify({classes})); this.setData(jsonify({classes}));
this.setData({loading: false});
}, },
/** /**
* 生命周期函数--监听页面加载 * 生命周期函数--监听页面加载
*/ */
onLoad: function (options) { onLoad: function (options) {
this.pull_classes().then(this.pull_presences); wx.showLoading({
this.setData({new_class_teacher_username: AV.User.current().get("username")}); title: '加载中',
})
this.current_user = AV.User.current();
this.pull_classes().then(this.pull_presences).then(wx.hideLoading);
}, },
/** /**

@ -1,18 +1,9 @@
<!--pages/addclass/addclass.wxml--> <!--pages/addclass/addclass.wxml-->
<view class="container"> <view class="container">
<view class="create-class">
<text>创建我的课程</text>
<view class="flex-wrap input-wrap">
<text class="label">课程名称:</text>
<input value="{{new_classname}}" bindinput="updateName" id="name" class="info-input"/>
</view>
<view class="flex-wrap input-wrap">
<text class="label">教师用户名:</text>
<input value="{{new_class_teacher_username}}" id="teacher_name" class="info-input disabled" disabled="true"/>
</view>
<button bindtap="create_class">创建新课程</button>
<view class="class-list form-wrap"> <view class="class-list form-wrap">
<text>可加入学习:</text> <image wx:if="{{classes.length==0 && !loading}}" src="../../images/none2.png" class="none-content"></image>
<text wx:if="{{classes.length==0 && !loading}}" class="none-content">没有可以加入的课程了</text>
<block wx:for="{{classes}}" wx:for-item="class" wx:key="objectId"> <block wx:for="{{classes}}" wx:for-item="class" wx:key="objectId">
<view hidden="{{class.present}}" bindtap="add_class" class="class-item flex-wrap" data-id="{{class.objectId}}" data-name="{{class.name}}"> <view hidden="{{class.present}}" bindtap="add_class" class="class-item flex-wrap" data-id="{{class.objectId}}" data-name="{{class.name}}">
<myicon class="class-icon" type="internet_class" size="26"></myicon> <myicon class="class-icon" type="internet_class" size="26"></myicon>
@ -20,5 +11,4 @@
</view> </view>
</block> </block>
</view> </view>
</view>
</view> </view>

@ -16,7 +16,7 @@
position: relative; position: relative;
} }
.class-item:after { .class-item:after {
content: '加入学习>'; content: '加入 >';
display: block; display: block;
position: absolute; position: absolute;
right: 3rpx; right: 3rpx;
@ -31,35 +31,16 @@
vertical-align: middle; vertical-align: middle;
} }
.input-wrap { .none-content{
background: #fff; align-self: center;
border-bottom: 1px solid #eee;
margin: 0 -12px;
padding: 0 12px;
height: 46px;
line-height: 46px;
} }
text.none-content{
.info-input { font-size: 16px;
font-size: 18px; margin-top: 32rpx;
line-height: 100%;
height: 100%;
}
.disabled{
color: #999;
}
.label {
color: #999;
display: inline;
}
.create-class{
bottom: 0;
} }
button{ image.none-content{
margin: 4px 26px; margin-top: 120rpx;
font-size: 14px; height: 160rpx;
width: 180rpx;
} }

@ -11,6 +11,7 @@ Page({
class: null, class: null,
pic_urls: [], pic_urls: [],
data: { data: {
loading: true,
files:[] files:[]
}, },
@ -26,7 +27,40 @@ Page({
}) })
}, },
del_file: function(event){ del_file: function(event){
objectid = event.currentTarget.dataset.id;
fileid = event.currentTarget.dataset.fileid;
pull_files = this.pull_files.bind(this);
wx.showModal({
title: '确认',
content: '删除该资源吗?',
success(res){
if(res.confirm){
file = new AV.Object.createWithoutData("File_", objectid);
file.destroy().then(()=>{
wx.cloud.deleteFile({
fileList:[fileid],
success:()=>{
wx.showToast({
title: '删除成功',
});
pull_files();
},
fail:()=>{
wx.showToast({
title: '删除失败',
icon: "none"
})
}
});
},()=>{
wx.showToast({
title: '操作失败',
icon:"none"
})
})}
}
})
}, },
download: function(event){ download: function(event){
console.log(event); console.log(event);
@ -141,6 +175,7 @@ Page({
return file; return file;
}) })
this.setData(jsonify({files})); this.setData(jsonify({files}));
this.setData({loading: false});
console.log(this.data); console.log(this.data);
}, },
/** /**

@ -2,10 +2,13 @@
<!--pages/changeuser/changeuser.wxml--> <!--pages/changeuser/changeuser.wxml-->
<view class="container"> <view class="container">
<text>课堂资源</text> <text>课堂资源</text>
<view class="no-file-view">
<image wx:if="{{files.length==0 && !loading}}" src="../../images/none2.png" class="no-file"></image>
</view>
<view class="file-list form-wrap"> <view class="file-list form-wrap">
<block wx:for="{{files}}" wx:for-item="file" wx:key="objectId"> <block wx:for="{{files}}" wx:for-item="file" wx:key="objectId">
<image wx:if="{{file.ispic}}" src="{{file.fileid}}" bindtap="preview_pic" data-fileid="{{file.fileid}}"></image> <image wx:if="{{file.ispic}}" src="{{file.fileid}}" bindtap="preview_pic" data-fileid="{{file.fileid}}"></image>
<view class="file-item flex-wrap" bindtap="{{file.ispic?'preview_pic':'download'}}" data-fileid="{{file.fileid}}" data-id="{{file.objectId}}"> <view class="file-item flex-wrap" bindtap="{{file.ispic?'preview_pic':'download'}}" data-fileid="{{file.fileid}}" data-id="{{file.objectId}}" bindlongpress="del_file">
<!--image wx:if="{{file.ispic}}" src="{{file.fileid}}"></image--> <!--image wx:if="{{file.ispic}}" src="{{file.fileid}}"></image-->
<text>{{file.filename}}</text> <text>{{file.filename}}</text>
<text class="hint file-uploader">上传者:{{file.uploader_username}}</text> <text class="hint file-uploader">上传者:{{file.uploader_username}}</text>

@ -29,7 +29,19 @@ button{
vertical-align: center; vertical-align: center;
font-size: 14px; font-size: 14px;
} }
.upload-file{
background: white;
.no-file-view{
width: 100%;
}
text.no-file{
font-size: 16px;
margin-top: 32rpx;
vertical-align: center;
}
image.no-file{
margin-top: 120rpx;
height: 160rpx;
width: 180rpx;
vertical-align: center;
} }

@ -18,16 +18,25 @@ Page({
class.name 课程名称 class.name 课程名称
*/ */
data: { data: {
//classes: [{objectId: "232323", "name":"测试"}] classes: [],
classes: [] loading: true,
}, },
presences: [], presences: [],
classes: [], classes: [],
add_class:function(event){
wx.navigateTo({
url: '../addclass/addclass',
})
},
del_class: function(event){ del_class: function(event){
class_id = event.currentTarget.dataset.class_id; class_id = event.currentTarget.dataset.class_id;
class_name = event.currentTarget.dataset.class_name; class_name = event.currentTarget.dataset.class_name;
presence = this.presences.filter(presence=>presence.get("class").id==class_id)[0]; presence = this.presences.filter(presence=>presence.get("class").id==class_id)[0];
pull_classes = this.pull_classes.bind(this); if(presence==null){
console.warn("presence not in list, is it the teacher's classroom?")
return;
}
pull_presences = this.pull_presences.bind(this);
wx.showModal({ wx.showModal({
title: '退出学习', title: '退出学习',
content: '退出学习会清零你该门课程的分数', content: '退出学习会清零你该门课程的分数',
@ -37,7 +46,7 @@ Page({
title: '请稍候', title: '请稍候',
}) })
presence.destroy().then(() => { presence.destroy().then(() => {
pull_classes(); pull_presences();
wx.hideLoading(); wx.hideLoading();
wx.showToast({ wx.showToast({
title: '退出成功', title: '退出成功',
@ -76,16 +85,22 @@ Page({
}, },
pull_classes: function(){ pull_presences: function(){
console.log("classes.pull_classes"); console.log("classes.pull_presences");
var query = new AV.Query("Presence"); var query = new AV.Query("Presence");
query.equalTo("user", AV.User.current()); query.equalTo("user", AV.User.current());
query.include("class"); query.include("class");
query.find().then((presences) => { return query.find().then((presences) => {
this.presences = presences; this.presences = presences;
this.classes = presences.map((presence) => {return presence.get("class")}); classes = presences.map((presence) => {return presence.get("class")});
console.log(this.classes); query = new AV.Query("Class_");
this.setData(jsonify({ classes: this.classes })); query.equalTo("teacher", AV.User.current());
query.include("teacher");
query.find().then(myclasses=>{
this.setData(jsonify({classes: classes.concat(myclasses), loading: false}));
})
console.log(classes);
}) })
}, },
/** /**
@ -93,7 +108,12 @@ Page({
*/ */
onShow: function () { onShow: function () {
console.log("classes.onShow()"); console.log("classes.onShow()");
this.pull_classes(); if(this.data.loading){
wx.showLoading({
title: '加载中',
})
}
this.pull_presences().then(wx.hideLoading);
}, },
/** /**
@ -114,7 +134,7 @@ Page({
* 页面相关事件处理函数--监听用户下拉动作 * 页面相关事件处理函数--监听用户下拉动作
*/ */
onPullDownRefresh: function () { onPullDownRefresh: function () {
this.pull_classes(); this.pull_presences();
}, },
/** /**

@ -6,43 +6,18 @@ classes: 课程列表 type: Array
class.name 课程名称 class.name 课程名称
--> -->
<view class="container"> <view class="container">
<view class="class-list"> <view class="class-list">
<text wx:if="{{classes.length==0}}">你还没有加入任何课堂</text> <image wx:if="{{classes.length==0 && !loading}}" class="none-content" src="../../images/none.png" mode="aspectFit"></image>
<text wx:if="{{classes.length==0 && !loading}}" class="none-content hint">你的课程表空空如也</text>
<block wx:for="{{classes}}" wx:for-item="class" wx:key="objectId"> <block wx:for="{{classes}}" wx:for-item="class" wx:key="objectId">
<view class="classroom flex-wrap" data-class_id="{{class.objectId}}" data-class_name="{{class.name}}" bindtap="enter_class" catchlongtap="del_class"> <view class="classroom flex-wrap" data-class_id="{{class.objectId}}" data-class_name="{{class.name}}" bindtap="enter_class" catchlongpress="del_class">
<!--icon type="success"></icon--> <!--icon type="success"></icon-->
<myicon class="classroom-icon" type="internet_class" size="26" color="#2351e4"></myicon> <myicon class="classroom-icon" type="internet_class" size="26" color="#2351e4"></myicon>
<text>{{class.name}}</text> <text>{{class.name}}</text>
</view> </view>
</block> </block>
</view> </view>
<text class="hint"> </view>
# 项目简介 <myicon class="add-class" type="add" color="#2040ef" size="56" bindtap="add_class"></myicon>
## 学员可以在点击右下角”加入课堂”选择课程加入,教员可以在“加入课堂中”新建课程
## 长按相应课程可以退出学习
## 进入课堂界面会显示在位,头像为彩色,若退出课堂界面则会显示灰色头像
## 教员在课堂界面中可以直观地看到学员在位情况,可以选择学员让其起立回答问题,并且对学员可以进行加分、减分操作
## 学员可以收到教员让其起立提问、回答的提示,还可以点击“我要提问、回答”
## 在课堂界面右下角进入“更多”可以查看本课堂的课程文件资源
## 教员可以在课程的设置界面中更新课程的名称,还可以将学员移除本课程的学习
## 其他
### 用户可以在设置中更改姓名、用户名
### 为方便测试,在设置中可以选择测试用户登陆
</text>
</view>
<view class="footer flex-wrap">
<navigator url="../setting/setting" class="setting tappable">设置</navigator>
<navigator url="../addclass/addclass" class="addclass tappable">加入课堂</navigator>
</view>

@ -33,30 +33,27 @@
.classroom-icon{ .classroom-icon{
margin-left: 2px; margin-left: 2px;
margin-right: 10px; margin-right: 10px;
} }
.footer { .add-class{
position: fixed; position: fixed;
right: 0;
margin-right: 48rpx;
margin-bottom: 36rpx;
bottom: 0; bottom: 0;
height: 44px;
width: 100%;
line-height: 44px;
color: #666;
border-top: 1px solid #eee;
background: #fff;
font-size: 16px;
text-align: center;
} }
.tappable{ .none-content{
color: #1aad19; align-self: center;
padding: 0 12px;
position: absolute;
} }
text.none-content{
.addclass{ font-size: 16px;
right:0;
} }
.setting{
left:0; image.none-content{
margin-top: 120rpx;
height: 240rpx;
width: 220rpx;
} }

@ -22,8 +22,9 @@ Page({
asking_presence: null, asking_presence: null,
idStudentMap: {}, idStudentMap: {},
data: { data: {
//以下数据的同步由该代码文件实现数据的呈现由classroom.wxml实现 loading: true,
students: [], // 该课程所有的学生, 元素类型见上文中的student students: [], // 该课程所有的学生, 元素类型见上文中的student
student_num: 0,
current_user: null, //目前登陆的用户,类型为对象属性有objectIdusername, name current_user: null, //目前登陆的用户,类型为对象属性有objectIdusername, name
teacher: null, teacher: null,
select_stu_id: '', select_stu_id: '',
@ -79,6 +80,8 @@ Page({
}).catch(console.error); }).catch(console.error);
}, },
close_conversation: function(event){ close_conversation: function(event){
console.log("close conversation");
console.log(event);
this.setData({show_conversation: false}); this.setData({show_conversation: false});
this.messageSubscription.unsubscribe(); this.messageSubscription.unsubscribe();
this.messageUnbind(); this.messageUnbind();
@ -159,6 +162,7 @@ Page({
set_presence: function(presences){ set_presence: function(presences){
console.log("set_presence"); console.log("set_presence");
console.log(presences); console.log(presences);
pull_presence = this.pull_presence.bind(this);
this.presences = presences; this.presences = presences;
students = this.presences.map((presence)=>{ students = this.presences.map((presence)=>{
student = presence.get("user"); student = presence.get("user");
@ -167,7 +171,9 @@ Page({
if(students.length==1){ if(students.length==1){
student = students[0]; student = students[0];
}else{ }else{
student.fetch(); console.warn("pull_presence because new student join");
pull_presence();
return;
} }
} }
this.idStudentMap[student.id]=student; this.idStudentMap[student.id]=student;
@ -182,6 +188,7 @@ Page({
}); });
this.students = students; this.students = students;
this.setData(jsonify({students})); this.setData(jsonify({students}));
this.setData({loading:false})
return presences; return presences;
}, },

@ -1,6 +1,6 @@
<view class="classroom" bindtap="select_stu" data-id="none"> <movable-area>
<movable-area> <view class="classroom" bindtap="select_stu" data-id="none">
<movable-view wx:if="{{show_conversation}}" direction="all" y="{{240}}"> <movable-view wx:if="{{show_conversation&&!loading}}" direction="all" y="{{240}}">
<myicon type="close" class="close" bindtap="close_conversation"></myicon> <myicon type="close" class="close" bindtap="close_conversation"></myicon>
<scroll-view scroll-y="{{true}}" class="message-list"> <scroll-view scroll-y="{{true}}" class="message-list">
<block wx:for="{{messages}}" wx:for-item="message" wx:key="objectId"> <block wx:for="{{messages}}" wx:for-item="message" wx:key="objectId">
@ -19,7 +19,12 @@
<text style="display: {{message_text ? 'inline': 'none'}}" class="send-message" ontap="send_message">发送</text> <text style="display: {{message_text ? 'inline': 'none'}}" class="send-message" ontap="send_message">发送</text>
</view> </view>
</movable-view> </movable-view>
<text class="hint">课程名称:{{class_name}} 教员姓名:{{teacher.name}}</text>
<text wx:if="{{!loading}}" class="hint" >课程名称:{{class_name}} 教员姓名:{{teacher.name}}</text>
<view wx:if="{{(students.length==0 || students.length==1 && students[0].objectId==current_user.objectId)&& !loading}}" class="no-student-view">
<image src="../../images/cry.png" class="no-student"></image>
<text class="no-student">还没有学员加入</text>
</view>
<view class="student-list"> <view class="student-list">
<block wx:for="{{students}}" wx:for-item="student" wx:key="objectId"> <block wx:for="{{students}}" wx:for-item="student" wx:key="objectId">
<view class="{{teacher.objectId==current_user.objectId?'student selectable':'student'}}" hidden="{{teacher.objectId==student.objectId}}" catchtap="select_stu" data-id="{{student.objectId}}"> <view class="{{teacher.objectId==current_user.objectId?'student selectable':'student'}}" hidden="{{teacher.objectId==student.objectId}}" catchtap="select_stu" data-id="{{student.objectId}}">
@ -33,23 +38,23 @@
</block> </block>
</view> </view>
<view class="operation-list"> <view class="operation-list">
<view class="teacher-operation" hidden="{{teacher.objectId!=current_user.objectId || select_stu_id==''}}"> <view wx:if="{{!loading}}" class="teacher-operation" hidden="{{teacher.objectId!=current_user.objectId || select_stu_id==''}}">
<button type="primary" catchtap="add_mark" data-id="{{select_stu_id}}" data-mark="{{-1}}">减一分</button> <button type="primary" catchtap="add_mark" data-id="{{select_stu_id}}" data-mark="{{-1}}">减一分</button>
<button type="primary" catchtap="add_mark" data-id="{{select_stu_id}}" data-mark="{{1}}">加一分</button> <button type="primary" catchtap="add_mark" data-id="{{select_stu_id}}" data-mark="{{1}}">加一分</button>
<button type="primary" catchtap="add_mark" data-id="{{select_stu_id}}" data-mark="{{2}}">加两分</button> <button type="primary" catchtap="add_mark" data-id="{{select_stu_id}}" data-mark="{{2}}">加两分</button>
<button type="primary" catchtap="set_isasking" data-id="{{select_stu_id}}" data-status="{{2}}">请起立</button> <button type="primary" catchtap="set_isasking" data-id="{{select_stu_id}}" data-status="{{2}}">请起立</button>
</view> </view>
<view class="student-operation" hidden="{{teacher.objectId==current_user.objectId}}"> <view wx:if="{{!loading}}" class="student-operation" hidden="{{teacher.objectId==current_user.objectId}}">
<button hidden="{{current_user.isasking!=0}}" type="primary" catchtap="set_isasking" data-id="{{current_user.objectId}}" data-status="{{1}}">我要提问/回答</button> <button hidden="{{current_user.isasking!=0}}" type="primary" catchtap="set_isasking" data-id="{{current_user.objectId}}" data-status="{{1}}">我要提问/回答</button>
<button hidden="{{current_user.isasking!=1}}" type="primary" catchtap="set_isasking" data-id="{{current_user.objectId}}" data-status="{{0}}">取消提问</button> <button hidden="{{current_user.isasking!=1}}" type="primary" catchtap="set_isasking" data-id="{{current_user.objectId}}" data-status="{{0}}">取消提问</button>
<text hidden="{{current_user.isasking!=2}}" class="warning">请起立提问/回答</text> <text hidden="{{current_user.isasking!=2}}" class="warning">请起立提问/回答</text>
<button hidden="{{current_user.isasking!=2}}" type="primary" catchtap="set_isasking" data-id="{{current_user.objectId}}" data-status="{{0}}">完成回答</button> <button hidden="{{current_user.isasking!=2}}" type="primary" catchtap="set_isasking" data-id="{{current_user.objectId}}" data-status="{{0}}">完成回答</button>
</view> </view>
</view> </view>
<view class="{{teacher.objectId!=current_user.objectId?'footer white':'footer'}}"> <view wx:if="{{!loading}}" class="footer white">
<text class="tappable class-setting" hidden="{{teacher.objectId!=current_user.objectId}}" bindtap="enter_setting" data-id="{{class_id}}">设置</text> <text class="tappable class-setting" hidden="{{teacher.objectId!=current_user.objectId}}" bindtap="enter_setting" data-id="{{class_id}}">设置</text>
<text hidden="{{teacher.objectId==current_user.objectId}}">我的得分:{{current_user.mark}}分</text> <text hidden="{{teacher.objectId==current_user.objectId}}">我的得分:{{current_user.mark}}分</text>
<text class="tappable class-detail" bindtap="enter_detail">更多</text> <text class="tappable class-detail" bindtap="enter_detail">资源</text>
</view>
</view> </view>
</movable-area> </movable-area>
</view>

@ -38,10 +38,7 @@ movable-view{
.send-message:hover{ .send-message:hover{
opacity: 0.6; opacity: 0.6;
} }
.classroom{
width: 100%;
height: 100%;
}
.student-list{ .student-list{
display: -webkit-flex; display: -webkit-flex;
@ -100,3 +97,20 @@ button{
.white{ .white{
background: white; background: white;
} }
.no-student-view{
vertical-align: center;
align-items: center;
align-self: center;
}
text.no-student{
font-size: 16px;
margin-top: 32rpx;
}
image.no-student{
margin-top: 120rpx;
height: 160rpx;
width: 180rpx;
}

@ -24,7 +24,7 @@ Page({
wx.showToast({ wx.showToast({
title:"更新成功" title:"更新成功"
}); });
this.setData({classname: new_classname}); this.setData({classname: new_classname, new_classname: ""});
},()=>{ },()=>{
wx.showToast({ wx.showToast({
title:"操作失败", title:"操作失败",

@ -48,6 +48,5 @@ button{
.footer{ .footer{
position: fixed; position: fixed;
bottom: 0; bottom: 0;
background: white;
width: 100%; width: 100%;
} }

@ -1,24 +0,0 @@
const app = getApp();
const config = app.config;
const lab = require('../../lib/lab');
const done = config.host != '<请配置访问域名>';
Page({
data: {
done,
status: done ? 'success' : 'waiting',
host: config.host,
hintLine1: done ? '域名已配置' : '请修改小程序源码 app.js',
hintLine2: done ? '小程序实验将使用下面域名进行' : '配置小程序使用的服务器域名'
},
goBack() {
wx.navigateBack();
},
onShow() {
if (done) {
lab.finish('config');
}
}
});

@ -1,13 +0,0 @@
<view class="lab https">
<view class="status">
<icon type="{{status}}" size="70" />
</view>
<view class="hint">
<view class="line">{{hintLine1}}</view>
<view class="line">{{hintLine2}}</view>
</view>
<view class="hint">
<view class="line">{{host}}</view>
</view>
<view class="command"></view>
</view>

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

@ -0,0 +1,108 @@
// pages/createclass/createclass.js
const AV = require("../../lib/av-live-query-weapp-min")
const Presence = require("../../model/presence")
const Class = require("../../model/class")
Page({
/**
* 页面的初始数据
*/
data: {
new_classname: '',
new_class_teacher_username: '',
},
updateName: function ({ detail: { value } }) {
this.setData({ new_classname: value });
},
create_class: function (event) {
let { new_classname } = this.data;
new_classname = new_classname.trim();
if (new_classname == "") {
wx.showToast({
title: "课程名称不能为空",
icon: "none"
})
return;
}
class_ = new Class({
name: new_classname,
teacher: AV.User.current()
})
wx.showLoading({
title: '请稍候',
})
class_.save().then(() => {
wx.hideLoading();
wx.showToast({
title: '创建成功,可在首页课程列表以教师身份进入课堂',
icon: "none",
duration: 4000
});
this.setData({ new_classname: "" });
},
() => {
wx.hideLoading();
wx.showToast({
title: '操作失败',
icon: "none"
});
});
},
/**
* 生命周期函数--监听页面加载
*/
onLoad: function (options) {
this.setData({ new_class_teacher_username: AV.User.current().get("username") });
},
/**
* 生命周期函数--监听页面初次渲染完成
*/
onReady: function () {
},
/**
* 生命周期函数--监听页面显示
*/
onShow: function () {
},
/**
* 生命周期函数--监听页面隐藏
*/
onHide: function () {
},
/**
* 生命周期函数--监听页面卸载
*/
onUnload: function () {
},
/**
* 页面相关事件处理函数--监听用户下拉动作
*/
onPullDownRefresh: function () {
},
/**
* 页面上拉触底事件的处理函数
*/
onReachBottom: function () {
},
/**
* 用户点击右上角分享
*/
onShareAppMessage: function () {
}
})

@ -0,0 +1,13 @@
<!--pages/createclass/createclass.wxml-->
<view class="container">
<text>创建我的课程</text>
<view class="flex-wrap input-wrap">
<text class="label">课程名称:</text>
<input value="{{new_classname}}" bindinput="updateName" id="name" class="info-input"/>
</view>
<view class="flex-wrap input-wrap">
<text class="label">教师用户名:</text>
<input value="{{new_class_teacher_username}}" id="teacher_name" class="info-input disabled" disabled="true"/>
</view>
<button bindtap="create_class">创建新课程</button>
</view>

@ -0,0 +1,30 @@
.input-wrap {
background: #fff;
border-bottom: 1px solid #eee;
margin: 0 -12px;
padding: 0 12px;
height: 46px;
line-height: 46px;
}
.info-input {
font-size: 18px;
line-height: 100%;
height: 100%;
}
.disabled{
color: #999;
}
.label {
color: #999;
display: inline;
}
button{
margin: 4px 26px;
font-size: 16px;
}

@ -1,253 +0,0 @@
"use strict";
require('../../lib/regenerator-runtime');
const regeneratorRuntime = global.regeneratorRuntime;
// 引入 co 和 promisify 帮助我们进行异步处理
const co = require('../../lib/co');
const promisify = require('../../lib/promisify');
// 引入 Wafer 客户端 SDK 支持会话
const wafer = require('../../vendors/wafer-client-sdk/index');
// 简单的小程序 WebSocket 信道封装
const Tunnel = require('../../lib/tunnel');
// 登录接口转成返回 Promise 形式
const login = promisify(wafer.login);
// 获得小程序实例
const app = getApp();
// 用于记录实验成功
const lab = require('../../lib/lab');
// 设置会话登录地址
wafer.setLoginUrl(`https://${app.config.host}/login`);
// 文案
const WIN_TEXTS = ['很棒', '秒杀', '赢了', 'Winner', '胜利', '不要大意', '无敌啊'];
const LOSE_TEXTS = ['失误', '卧槽', '不可能', 'Loser', '行不行啊', '加油', '大侠再来'];
const EQ_TEXTS = ['平局', '平分秋色', '对方学你', '照镜子', '半斤八两', '换一个', '一样的'];
const pickText = texts => texts[Math.floor(texts.length * Math.random())];
// 定义页面
Page({
data: {
// 是否已经和服务器连接
connected: false,
// 游戏是否进行中
playing: false,
// 当前需要展示的游戏信息
gameInfo: "",
// 开始游戏按钮文本
startButtonText: "开始",
//「我」的信息,包括昵称、头像、分数、选择
myName: "",
myAvatar: null,
myScore: 0,
myStreak: 0,
myChoice: Math.floor(Math.random() * 10000) % 3 + 1,
//「你」的信息
youHere: false,
yourName: "",
yourAvatar: null,
yourScore: 0,
yourStreak: 0,
yourChoice: 1,
yourMove: 0,
// 取得胜利的是谁
win: null
},
// 页面显示后,开始连接
onShow: function() {
this.begin();
},
// 进行登录和链接,完成后开始启动游戏服务
begin: co.wrap(function *() {
try {
this.setData({ gameInfo: "正在登陆" });
yield login();
this.setData({ gameInfo: "正在连接"});
yield this.connect();
} catch (error) {
console.error('error on login or connect: ', error);
}
this.serve();
}),
// 链接到服务器后进行身份识别
connect: co.wrap(function *() {
const tunnel = this.tunnel = new Tunnel();
try {
yield tunnel.connect(`wss://${app.config.host}/game`, wafer.buildSessionHeader());
} catch (connectError) {
console.error({ connectError });
this.setData({ gameInfo: "连接错误" });
throw connectError;
}
tunnel.on('close', () => {
this.setData({
connected: false,
gameInfo: "连接已中断"
});
});
this.setData({
gameInfo: "准备",
connected: true,
gameState: 'connected'
});
return new Promise((resolve, reject) => {
// 10 秒后超时
const timeout = setTimeout(() => reject, 10000);
tunnel.on('id', ({ uname, uid, uavatar }) => {
this.uid = uid;
this.setData({
myName: uname,
myAvatar: uavatar
});
resolve(tunnel);
clearTimeout(timeout);
});
});
}),
// 开始进行游戏服务
serve: co.wrap(function *() {
const tunnel = this.tunnel;
// 游戏开始,初始化对方信息,启动计时器
tunnel.on('start', packet => {
const you = packet.players.filter(user => user.uid !== this.uid).pop();
this.setData({
playing: false,
done: false,
finding: true,
gameInfo: '正在寻找玩伴...'
});
setTimeout(() => {
this.setData({
youHere: true,
yourName: you.uname,
yourAvatar: you.uavatar,
finding: false,
playing: true,
gameInfo: "准备"
});
}, 10);
let gameTime = packet.gameTime;
clearInterval(this.countdownId);
this.countdownId = setInterval(() => {
if (gameTime > 0) {
this.setData({ gameInfo: --gameTime });
} else {
clearInterval(this.countdownId);
}
}, 1000);
this.tunnel.emit('choice', { choice: this.data.myChoice });
});
// 对方有动静的时候,触发提醒
let movementTimer = 0;
const movementTimeout = 300;
tunnel.on('movement', packet => {
const lastMove = this.lastMove;
this.setData({ yourMove: lastMove == 1 ? 2 : 1 });
clearTimeout(movementTimer);
movementTimer = setTimeout(() => {
this.lastMove = this.data.yourMove;
this.setData({ yourMove: 0 });
}, 300);
});
// 服务器通知结果
tunnel.on('result', packet => {
// 清除计时器
clearInterval(this.countdownId);
// 双方结果
const myResult = packet.result.find(x => x.uid == this.uid);
const yourResult = packet.result.find(x => x.uid != this.uid);
// 本局结果
let gameInfo, win = 'nobody';
if (myResult.roundScore == 0 && yourResult.roundScore == 0) {
gameInfo = pickText(EQ_TEXTS);
}
else if (myResult.roundScore > 0) {
gameInfo = pickText(WIN_TEXTS);
win = 'me';
}
else {
gameInfo = pickText(LOSE_TEXTS);
win = 'you'
}
// 更新到视图
this.setData({
gameInfo,
myScore: myResult.totalScore,
myStreak: myResult.winStreak,
yourChoice: yourResult.choice,
yourScore: yourResult.totalScore,
yourStreak: yourResult.winStreak,
gameState: 'finish',
win,
startButtonText: win == 'you' ? "不服" : "再来",
done: true
});
lab.finish('game');
setTimeout(() => this.setData({ playing: false }), 1000);
});
}),
requestComputer() {
if (this.tunnel) {
this.tunnel.emit('requestComputer');
}
},
// 点击开始游戏按钮,发送加入游戏请求
startGame: co.wrap(function *() {
if (this.data.playing) return;
if (!this.data.connected) return;
this.setData({
playing: false,
done: false,
finding: true,
gameInfo: '正在寻找玩伴...'
});
this.tunnel.emit('join');
}),
// 点击手势,更新选择是石头、剪刀还是布
switchChoice(e) {
if (!this.data.playing) return;
let myChoice = this.data.myChoice + 1;
if (myChoice == 4) {
myChoice = 1;
}
this.setData({ myChoice });
this.tunnel.emit('choice', { choice: myChoice });
}
});

@ -1,33 +0,0 @@
<view class="root {{connected && 'connected'}} {{finding && 'finding'}} {{playing && 'playing'}} {{done && 'done'}}">
<view class="my-side">
<image class="avatar" src="{{myAvatar}}" mode="aspectFill" />
<image class="hand {{win == 'me' && 'win'}}" src="../../images/{{myChoice}}.png" bindtap="switchChoice"></image>
<text class="score">
{{myName}}
得分 {{myScore}}
</text>
<text class="streak">
连胜 {{myStreak}}
</text>
</view>
<view class="your-side {{youHere && 'here'}} {{yourMove && 'move-' + yourMove}}">
<image class="avatar" src="{{yourAvatar}}" mode="aspectFill" />
<image class="hand {{win == 'you' && 'win'}}" src="../../images/{{yourChoice}}.png"></image>
<text class="score">
{{yourName}}
得分 {{yourScore}}
</text>
<text class="streak">
连胜 {{yourStreak}}
</text>
</view>
<view class="game-info">{{gameInfo}}</view>
<button class="start-game" type="primary" bindtap="startGame">{{startButtonText}}</button>
<button class="request-computer" bindtap="requestComputer">跟机器人玩</button>
</view>

@ -1,191 +0,0 @@
.root {
overflow: hidden;
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
}
.my-side {
width: 1500rpx;
height: 1500rpx;
position: absolute;
bottom: -1125rpx;
left: -375rpx;
border-radius: 100%;
background: lightskyblue;
overflow: visible;
}
.your-side {
width: 1500rpx;
height: 1500rpx;
position: absolute;
top: -1500rpx;
left: -375rpx;
border-radius: 100%;
background: lightpink;
overflow: visible;
transition: top ease 0.6s;
}
.your-side.here {
top: -1125rpx;
}
.avatar {
position: absolute;
width: 172rpx;
height: 172rpx;
border-radius: 100%;
border: 8rpx solid white;
background-color: #f0f0f0;
}
.hand {
position: absolute;
width: 187rpx;
height: 187rpx;
border-radius: 100%;
overflow: visible;
}
.score, .streak {
position: absolute;
color: #334567;
width: 225rpx;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.streak {
text-align: right;
}
.my-side .avatar {
left: 656.25rpx;
top: -93.75rpx;
transition: top ease .2s;
}
.my-side .hand {
left: 656.25rpx;
top: 150rpx;
transition: top ease .2s;
}
.root.playing .my-side .hand:active {
transform: scale(1.3);
}
.root.done .my-side .avatar {
top: 168.75rpx;
}
.root.done .my-side .hand {
top: -112.5rpx;
}
.my-side .score {
left: 412.5rpx;
bottom: 1162.5rpx;
}
.my-side .streak {
right: 412.5rpx;
bottom: 1162.5rpx;
}
.your-side .avatar {
left: 656.25rpx;
bottom: -93.75rpx;
transition: all ease .2s;
opacity: 0;
}
.your-side.here .avatar {
opacity: 1;
}
.your-side.move-1 .avatar {
transform: rotate(15deg);
}
.your-side.move-2 .avatar {
transform: rotate(-15deg);
}
.your-side .hand {
left: 656.25rpx;
bottom: 150rpx;
opacity: 0;
transition: bottom ease .2s;
transform: rotate(180deg);
}
.root.done .your-side .avatar {
bottom: 168.75rpx;
}
.root.done .your-side .hand {
bottom: -93.75rpx;
opacity: 1;
}
.your-side .score {
left: 412.5rpx;
top: 1106.25rpx;
}
.your-side .streak {
right: 412.5rpx;
top: 1106.25rpx;
}
.start-game, .request-computer {
position: absolute;
width: 200rpx;
height: 80rpx;
line-height: 80rpx;
margin-left: -100rpx;
bottom: 500rpx;
left: 50%;
display: none;
}
.request-computer {
width: 300rpx;
margin-left: -150rpx;
}
.root.finding .request-computer {
display: block;
}
.root.connected .start-game {
display: block;
}
.root.connected.playing .start-game,
.root.finding .start-game {
display: none;
}
.game-info {
font-size: 75rpx;
position: absolute;
width: 750rpx;
height: 112.5rpx;
line-height: 112.5rpx;
margin-top: -56.25rpx;
bottom: 600rpx;
left: 0;
text-align: center;
opacity: 1;
}
.root.connected.root.playing .game-info,
.root.connected.root.done .game-info {
opacity: 1;
}

@ -1,54 +0,0 @@
const app = getApp();
const config = app.config;
const lab = require('../../lib/lab');
Page({
data: {
status: 'waiting',
url: 'https://' + config.host + '/hello',
requesting: false,
hintLine1: '完成服务器开发,',
hintLine2: '使得下面的地址可以访问'
},
request() {
this.setData({
requesting: true,
status: 'waiting',
hintLine1: '正在发送',
hintLine2: '...'
});
wx.request({
url: this.data.url,
method: 'GET',
success: (res) => {
if (+res.statusCode == 200) {
this.setData({
status: 'success',
hintLine1: '服务器响应成功',
hintLine2: '返回:' + res.data
});
lab.finish('https');
} else {
this.setData({
status: 'warn',
hintLine1: '响应错误',
hintLine2: '响应码:' + res.statusCode
});
}
},
fail: (res) => {
console.log(res);
this.setData({
status: 'warn',
hintLine1: '请求失败',
hintLine2: res.errMsg
});
},
complete: () => {
this.setData({
requesting: false
});
}
});
}
});

@ -1,15 +0,0 @@
<view class="lab https">
<view class="status">
<icon type="{{status}}" size="70" />
</view>
<view class="hint">
<view class="line">{{hintLine1}}</view>
<view class="line">{{hintLine2}}</view>
</view>
<view class="hint">
<view class="line">{{url}}</view>
</view>
<view class="command">
<button wx:if="{{status != 'success'}}" type="primary" bindtap="request" disabled="{{requesting}}">发送请求</button>
</view>
</view>

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

@ -1,25 +0,0 @@
const lab = require('../../lib/lab');
Page({
data: {
labs: [
{ id: 'config', title: '实验准备:配置请求域名' },
{ id: 'https', title: '实验一HTTPS' },
{ id: 'session', title: '实验二:会话' },
{ id: 'websocket', title: '实验三WebSocket' },
{ id: 'game', title: '实验四:剪刀石头布小游戏' },
{ id: "classes", title: "进入教室"}
],
done: lab.getFinishLabs()
},
onShow() {
console.log({ done: lab.getFinishLabs() });
this.setData({ done: lab.getFinishLabs() });
},
clear() {
lab.clear();
this.setData({ done: lab.getFinishLabs() });
}
});

@ -1,3 +0,0 @@
{
"navigationBarTitleText": "积分教室"
}

@ -1,11 +0,0 @@
<view class="index">
<view class="nav">
<navigator wx:for="{{labs}}" wx:key="{{lab.id}}" wx:for-item="lab" url="/pages/{{lab.id}}/{{lab.id}}">
<icon type="{{ done[lab.id] ? 'success' : 'waiting_circle' }}" size="24"></icon>
<text>{{lab.title}}</text>
</navigator>
</view>
<view class="clear">
<button bindtap="clear">清空任务状态</button>
</view>
</view>

@ -1,41 +0,0 @@
/* pages/index/index.wxss */
.index {
font-family: 'PingFang SC';
}
.nav {
display: flex;
flex-direction: column;
padding: 0 50rpx;
}
.nav navigator {
padding: 30rpx 0;
border-bottom: 1rpx solid #EEE;
position: relative;
}
.nav navigator:after {
content: '>';
display: block;
position: absolute;
right: 3rpx;
top: 50%;
height: 40rpx;
line-height: 40rpx;
margin-top: -20rpx;
color: #999;
}
.nav navigator icon,
.nav navigator text {
vertical-align: middle;
}
.nav navigator icon {
margin-right: 25rpx;
}
.clear {
margin: 100rpx 50rpx;
}

@ -1,74 +0,0 @@
// pages/outside/outside.js
Page({
/**
* 页面的初始数据
*/
data: {
name: 'Hello World',
src: '/images/weixin.jpg'
},
getMyInfo: function (e) {
console.log(e.detail.userInfo)
let info = e.detail.userInfo;
this.setData({
name: info.nickName,//更新名称
src: info.avatarUrl//更新图片来源
})
},
/**
* 生命周期函数--监听页面加载
*/
onLoad: function (options) {
},
/**
* 生命周期函数--监听页面初次渲染完成
*/
onReady: function () {
},
/**
* 生命周期函数--监听页面显示
*/
onShow: function () {
},
/**
* 生命周期函数--监听页面隐藏
*/
onHide: function () {
},
/**
* 生命周期函数--监听页面卸载
*/
onUnload: function () {
},
/**
* 页面相关事件处理函数--监听用户下拉动作
*/
onPullDownRefresh: function () {
},
/**
* 页面上拉触底事件的处理函数
*/
onReachBottom: function () {
},
/**
* 用户点击右上角分享
*/
onShareAppMessage: function () {
}
})

@ -1,7 +0,0 @@
<view class='container'>
<image src='{{src}}' class='abc'></image>
<text>{{name}}</text>
<button open-type='getUserInfo' bindgetuserinfo='getMyInfo'>点击登录积分教室</button>
<!-- open-type='getUserInfo 表示激活获取微信用户信息功能' -->
<!-- bindgetuserinfo='getMyInfo' 表示获得的数据将传递给自定义函数getMyInfo,可以自己取-->
</view>

@ -1,18 +0,0 @@
/* 样式设计 */
.container{
height: 90vh; /*高100视窗这里写100%无效的*/
display: flex; /*flex布局方法*/
flex-direction: column;/*垂直布局*/
align-items: center;/*水平方向居中*/
justify-content: space-around;/*垂直方向分散布局*/
}
.abc{
width: 400rpx;/*图片宽度*/
border-radius: 50%;/*4个角变为圆角形状*/
}
text{
font-size: 50rpx;/*字体大小*/
color: red;
background-color: blue;
border:5rpx solid paleturquoise;
}

@ -1,67 +0,0 @@
const app = getApp();
const config = app.config;
const wafer = require('../../vendors/wafer-client-sdk/index');
const lab = require('../../lib/lab');
wafer.setLoginUrl(`https://` + config.host + '/login');
Page({
data: {
status: 'waiting',
url: 'https://' + config.host + '/me',
requesting: false,
hintLine1: '完成服务器开发,',
hintLine2: '让服务器可以识别小程序会话'
},
request() {
this.setData({
requesting: true,
status: 'waiting',
hintLine1: '正在发送',
hintLine2: '...'
});
wafer.request({
login: true,
url: this.data.url,
method: 'GET',
success: (res) => {
if (+res.statusCode == 200) {
if (res.data.openId) {
this.setData({
status: 'success',
hintLine1: '成功获取会话',
hintLine2: res.data.nickName,
avatarUrl: res.data.avatarUrl
});
lab.finish('session');
} else {
this.setData({
status: 'warn',
hintLine1: '会话获取失败',
hintLine2: '未获取到 openId'
});
console.error('会话获取失败', res.data);
}
} else {
this.setData({
status: 'warn',
hintLine1: '响应错误',
hintLine2: '响应码:' + res.statusCode
});
}
},
fail: (error) => {
this.setData({
status: 'warn',
hintLine1: '获取失败',
hintLine2: error.message
});
},
complete: () => {
this.setData({
requesting: false
});
}
});
}
});

@ -1,16 +0,0 @@
<view class="lab https">
<view class="status">
<icon wx:if="{{status != 'success'}}" type="{{status}}" size="70" />
<image wx:if="{{status == 'success'}}" src="{{avatarUrl}}" mode="aspectFill" />
</view>
<view class="hint">
<view class="line">{{hintLine1}}</view>
<view class="line">{{hintLine2}}</view>
</view>
<view class="hint">
<view class="line">{{url}}</view>
</view>
<view class="command">
<button wx:if="{{status != 'success'}}" type="primary" bindtap="request" disabled="{{requesting}}">获取会话</button>
</view>
</view>

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

@ -1,4 +1,20 @@
const AV = require("../../lib/av-live-query-weapp-min")
Page({ Page({
data:{
current_user: null
},
enter_usersetting: function(event){
wx.navigateTo({
url: '../user/user'
})
},
enter_changeuser: function(event){
console.log(event);
wx.navigateTo({
url: '../changeuser/changeuser'
})
},
onShow: function(){
this.setData({current_user: AV.User.current().toJSON()})
}
}) })

@ -1,4 +1,13 @@
<view class="form-wrap"> <view class="form-wrap">
<navigator url="../user/user" class="navigator user">登陆信息修改</navigator> <view class="user-view">
<navigator url="../changeuser/changeuser" class="navigator user">更换登陆用户</navigator> <image src="../../images/avatar.png" class="user" mode="aspectFit"></image>
<text class="user" bindtap="enter_usersetting">{{current_user.name}} | {{current_user.username}}</text>
</view>
<view class="nav-list">
<navigator url="../user/user" class="nav user" bindlongpress="enter_changeuser">登陆信息修改</navigator>
<navigator hidden="{{true}}" url="../changeuser/changeuser" class="nav user">更换登陆用户</navigator>
<navigator url="../addclass/addclass" class="nav addclass">加入课程</navigator>
<navigator url="../createclass/createclass" class="nav createclass">创建课程</navigator>
<navigator url="../about/about" class="nav about">关于</navigator>
</view>
</view> </view>

@ -1,7 +1,27 @@
.navigator { .nav {
background: white; background: white;
border-bottom: 1px solid #eee; border-bottom: 1px solid #eee;
padding: 0 12px; padding: 0 12px;
height: 46px; height: 46px;
line-height: 46px; line-height: 46px;
} }
.user-view{
display: flex;
flex-direction: column;
}
.user{
align-self: center;
}
text.user{
font-size: 16px;
margin-top: 5px;
margin-bottom: 8px;
}
image.user{
margin-top: 10rpx;
height: 120rpx;
width: 120rpx;
}

@ -1,5 +0,0 @@
<view class="lab https">
<view class="command">
<button open-type="getUserInfo" bindgetuserinfo="bindGetUserInfo">授权登录</button>
</view>
</view>

@ -1,109 +0,0 @@
const app = getApp();
const config = app.config;
const wafer = require('../../vendors/wafer-client-sdk/index');
const lab = require('../../lib/lab');
Page({
data: {
status: 'waiting',
url: 'wss://' + config.host + '/ws',
connecting: false,
hintLine1: '完成服务器开发,',
hintLine2: '让服务器支持 WebSocket 连接'
},
/**
* WebSocket 是否已经连接
*/
socketOpen: false,
/**
* 开始连接 WebSocket
*/
connect() {
this.setData({
status: 'waiting',
connecting: true,
hintLine1: '正在连接',
hintLine2: '...'
});
this.listen();
wafer.setLoginUrl(`https://${config.host}/login`);
wafer.login({
success: () => {
const header = wafer.buildSessionHeader();
const query = Object.keys(header).map(key => `${key}=${encodeURIComponent(header[key])}`).join('&');
wx.connectSocket({
// 小程序 wx.connectSocket() API header 参数无效,把会话信息附加在 URL 上
url: `${this.data.url}?${query}`,
header
});
},
fail: (err) => {
this.setData({
status: 'warn',
connecting: false,
hintLine1: '登录失败',
hintLine2: err.message || err
});
}
});
},
/**
* 监听 WebSocket 事件
*/
listen() {
wx.onSocketOpen(() => {
this.socketOpen = true;
this.setData({
status: 'success',
connecting: false,
hintLine1: '连接成功',
hintLine2: '现在可以通过 WebSocket 发送接收消息了'
});
console.info('WebSocket 已连接');
});
wx.onSocketMessage((message) => {
this.setData({
hintLine2: message.data
});
lab.finish('websocket');
});
wx.onSocketClose(() => {
this.setData({
status: 'waiting',
hintLine1: 'WebSocket 已关闭'
});
console.info('WebSocket 已关闭');
});
wx.onSocketError(() => {
setTimeout(() => {
this.setData({
status: 'warn',
connecting: false,
hintLine1: '发生错误',
hintLine2: 'WebSocket 连接建立失败'
});
});
console.error('WebSocket 错误');
});
},
/**
* 发送一个包含当前时间信息的消息
*/
send() {
wx.sendSocketMessage({
data: new Date().toTimeString().split(' ').shift() + '.' + (new Date().getMilliseconds())
});
},
/**
* 关闭 WebSocket 连接
*/
close() {
this.socketOpen = false;
wx.closeSocket();
}
});

@ -1,17 +0,0 @@
<view class="lab https">
<view class="status">
<icon type="{{status}}" size="70" />
</view>
<view class="hint">
<view class="line">{{hintLine1}}</view>
<view class="line">{{hintLine2}}</view>
</view>
<view class="hint">
<view class="line">{{url}}</view>
</view>
<view class="command">
<button type="primary" bindtap="connect" disabled="{{connecting || status == 'success'}}">连接</button>
<button bindtap="send" disabled="{{ status != 'success' }}">发送消息</button>
<button bindtap="close" disabled="{{ status != 'success' }}">断开</button>
</view>
</view>

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

@ -1,42 +0,0 @@
{
"name": "wafer-client-sdk",
"description": "QCloud 微信小程序客户端 SDK",
"main": "index.js",
"authors": [
"Tencent Cloud"
],
"license": "MIT",
"keywords": [
"qcloud",
"weapp",
"wechat",
"sdk",
"client",
"auth",
"websocket"
],
"homepage": "https://github.com/tencentyun/wafer-client-sdk",
"ignore": [
"**/.*",
"node_modules",
"bower_components",
"test",
"tests",
"typings.json",
"jsconfig.json",
"package.json",
".npmignore",
".travis.yml",
".gitignore"
],
"version": "0.8.4",
"_release": "0.8.4",
"_resolution": {
"type": "version",
"tag": "v0.8.4",
"commit": "3f605d1aa5bd0206bb9368586f4acb104f5f8ae5"
},
"_source": "https://github.com/tencentyun/wafer-client-sdk.git",
"_target": "^0.8.3",
"_originalSource": "wafer-client-sdk"
}

@ -1,24 +0,0 @@
LICENSE - "MIT License"
Copyright (c) 2016 by Tencent Cloud
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

@ -1,247 +0,0 @@
# 微信小程序客户端腾讯云增强 SDK
[![Build Status](https://travis-ci.org/tencentyun/wafer-client-sdk.svg?branch=master)](https://travis-ci.org/tencentyun/wafer-client-sdk)
[![Coverage Status](https://coveralls.io/repos/github/tencentyun/wafer-client-sdk/badge.svg?branch=master)](https://coveralls.io/github/tencentyun/wafer-client-sdk?branch=master)
[![License](https://img.shields.io/github/license/tencentyun/wafer-client-sdk.svg)](LICENSE)
本 项目是 [Wafer](https://github.com/tencentyun/wafer-solution) 的组成部分,为小程序客户端开发提供 SDK 支持会话服务和信道服务。
## SDK 获取与安装
解决方案[客户端 Demo](https://github.com/tencentyun/wafer-client-demo) 已经集成并使用最新版的 SDK需要快速了解的可以从 Demo 开始。
如果需要单独开始,本 SDK 已经发布为 bower 模块,可以直接安装到小程序目录中。
```sh
npm install -g bower
bower install wafer-client-sdk
```
安装之后,就可以使用 `require` 引用 SDK 模块:
```js
var qcloud = require('./bower_components/wafer-client-sdk/index.js');
```
## 会话服务
[会话服务](https://github.com/tencentyun/wafer-solution/wiki/%E4%BC%9A%E8%AF%9D%E6%9C%8D%E5%8A%A1)让小程序拥有会话管理能力。
### 登录
登录可以在小程序和服务器之间建立会话,服务器由此可以获取到用户的标识和信息。
```js
var qcloud = require('./bower_components/qcloud-weapp-client-sdk/index.js');
// 设置登录地址
qcloud.setLoginUrl('https://199447.qcloud.la/login');
qcloud.login({
success: function (userInfo) {
console.log('登录成功', userInfo);
},
fail: function (err) {
console.log('登录失败', err);
}
});
```
本 SDK 需要配合云端 SDK 才能提供完整会话服务。通过 [setLoginUrl](#setLoginUrl) 设置登录地址,云服务器在该地址上使用云端 SDK 处理登录请求。
> `setLoginUrl` 方法设置登录地址之后会一直有效,因此你可以在微信小程序启动时设置。
登录成功后,可以获取到当前微信用户的基本信息。
### 请求
如果希望小程序的网络请求包含会话,登录之后使用 [request](#request) 方法进行网络请求即可。
```js
qcloud.request({
url: 'http://199447.qcloud.la/user',
success: function (response) {
console.log(response);
},
fail: function (err) {
console.log(err);
}
});
```
如果调用 `request` 之前还没有登录,则请求不会带有会话。`request` 方法也支持 `login` 参数支持在请求之前自动登录。
```js
// 使用 login 参数之前,需要设置登录地址
qcloud.setLoginUrl('https://199447.qcloud.la/login');
qcloud.request({
login: true,
url: 'http://199447.qcloud.la/user',
success: function (response) {
console.log(response);
},
fail: function (err) {
console.log(err);
}
});
```
关于会话服务详细技术说明,请参考 [Wiki](https://github.com/tencentyun/wafer-solution/wiki/%E4%BC%9A%E8%AF%9D%E6%9C%8D%E5%8A%A1)。
## 信道服务
[信道服务](https://github.com/tencentyun/wafer-solution/wiki/%E4%BF%A1%E9%81%93%E6%9C%8D%E5%8A%A1)小程序支持利用腾讯云的信道资源使用 WebSocket 服务。
```js
// 创建信道,需要给定后台服务地址
var tunnel = this.tunnel = new qcloud.Tunnel('https://199447.qcloud.la/tunnel');
// 监听信道内置消息,包括 connect/close/reconnecting/reconnect/error
tunnel.on('connect', () => console.log('WebSocket 信道已连接'));
tunnel.on('close', () => console.log('WebSocket 信道已断开'));
tunnel.on('reconnecting', () => console.log('WebSocket 信道正在重连...'));
tunnel.on('reconnect', () => console.log('WebSocket 信道重连成功'));
tunnel.on('error', error => console.error('信道发生错误:', error));
// 监听自定义消息(服务器进行推送)
tunnel.on('speak', speak => console.log('收到 speak 消息:', speak));
// 打开信道
tunnel.open();
// 发送消息
tunnel.emit('speak', { word: "hello", who: { nickName: "techird" }});
// 关闭信道
tunnel.close();
```
信道服务同样需要业务服务器配合云端 SDK 支持,构造信道实例的时候需要提供业务服务器提供的信道服务地址。通过监听信道消息以及自定义消息来通过信道实现业务。
关于信道使用的更完整实例,建议参考客户端 Demo 中的[三木聊天室应用源码](https://github.com/tencentyun/wafer-client-demo/blob/master/pages/chat/chat.js)。
关于信道服务详细技术说明,请参考 [Wiki](https://github.com/tencentyun/wafer-solution/wiki/%E4%BF%A1%E9%81%93%E6%9C%8D%E5%8A%A1)。
## API
### setLoginUrl
设置会话服务登录地址。
#### 语法
```js
qcloud.setLoginUrl(loginUrl);
```
#### 参数
|参数         |类型 |说明
|-------------|---------------|--------------
|loginUrl |string |会话服务登录地址
### login
登录,建立微信小程序会话。
#### 语法
```js
qcloud.login(options);
```
#### 参数
|参数         |类型 |说明
|-------------|---------------|--------------
|options     |PlainObject   |会话服务登录地址
|options.success | () => void | 登录成功的回调
|options.error | (error) => void | 登录失败的回调
### request
进行带会话的请求。
#### 语法
```js
qcloud.request(options);
```
#### 参数
|参数         |类型 |说明
|-------------|---------------|--------------
|options     |PlainObject   | 会话服务登录地址
|options.login | bool         | 是否自动登录以获取会话,默认为 false
|options.url   | string       | 必填,要请求的地址
|options.header | PlainObject | 请求头设置,不允许设置 Referer
|options.method | string     | 请求的方法,默认为 GET
|options.success | (response) => void | 登录成功的回调。<ul><li>`response.statusCode`:请求返回的状态码</li><li>`response.data`:请求返回的数据</li></ul>
|options.error | (error) => void | 登录失败的回调
|options.complete | () => void | 登录完成后回调,无论成功还是失败
### Tunnel
表示一个信道。由于小程序的限制,同一时间只能有一个打开的信道。
#### constructor
##### 语法
```js
var tunnel = new Tunnel(tunnelUrl);
```
#### 参数
|参数         |类型 |说明
|-------------|---------------|--------------
|tunnelUrl   |String   | 会话服务登录地址
#### on
监听信道上的事件。信道上事件包括系统事件和服务器推送消息。
##### 语法
```js
tunnel.on(type, listener);
```
##### 参数
|参数         |类型 |说明
|-------------|---------------|--------------
|type       |string     | 监听的事件类型
|listener     |(message?: any) => void | 监听器,具体类型的事件发生时调用监听器。如果是消息,则会有消息内容。
##### 事件
|事件 |说明
|-------------|-------------------------------
|connect |信道连接成功后回调
|close |信道关闭后回调
|reconnecting |信道发生重连时回调
|reconnected |信道重连成功后回调
|error |信道发生错误后回调
|[message]   |信道服务器推送过来的消息类型,如果消息类型和上面内置的时间类型冲突,需要在监听的时候在消息类型前加 `@`
|\*           |监听所有事件和消息,监听器第一个参数接收到时间或消息类型 
#### open
打开信道,建立连接。由于小程序的限制,同一时间只能有一个打开的信道。
##### 语法
```js
tunnel.open();
```
#### emit
向信道推送消息。
##### 语法
```js
tunnel.emit(type, content);
```
##### 参数
|参数         |类型 |说明
|-------------|---------------|--------------
|type       |string       | 要推送的消息的类型
|content |any | 要推送的消息的内容
#### close
关闭信道
##### 语法
```js
tunnel.close();
```
## LICENSE
[MIT](LICENSE)

@ -1,32 +0,0 @@
{
"name": "wafer-client-sdk",
"description": "QCloud 微信小程序客户端 SDK",
"main": "index.js",
"authors": [
"Tencent Cloud"
],
"license": "MIT",
"keywords": [
"qcloud",
"weapp",
"wechat",
"sdk",
"client",
"auth",
"websocket"
],
"homepage": "",
"ignore": [
"**/.*",
"node_modules",
"bower_components",
"test",
"tests",
"typings.json",
"jsconfig.json",
"package.json",
".npmignore",
".travis.yml",
".gitignore"
]
}

@ -1,26 +0,0 @@
var constants = require('./lib/constants');
var login = require('./lib/login');
var Session = require('./lib/session');
var request = require('./lib/request');
var Tunnel = require('./lib/tunnel');
var exports = module.exports = {
login: login.login,
setLoginUrl: login.setLoginUrl,
LoginError: login.LoginError,
clearSession: Session.clear,
request: request.request,
buildSessionHeader: request.buildSessionHeader,
RequestError: request.RequestError,
Tunnel: Tunnel,
};
// 导出错误类型码
Object.keys(constants).forEach(function (key) {
if (key.indexOf('ERR_') === 0) {
exports[key] = constants[key];
}
});

@ -1,20 +0,0 @@
module.exports = {
WX_HEADER_CODE: 'X-WX-Code',
WX_HEADER_ENCRYPTED_DATA: 'X-WX-Encrypted-Data',
WX_HEADER_IV: 'X-WX-IV',
WX_HEADER_ID: 'X-WX-Id',
WX_HEADER_SKEY: 'X-WX-Skey',
WX_SESSION_MAGIC_ID: 'F2C224D4-2BCE-4C64-AF9F-A6D872000D1A',
ERR_INVALID_PARAMS: 'ERR_INVALID_PARAMS',
ERR_WX_LOGIN_FAILED: 'ERR_WX_LOGIN_FAILED',
ERR_WX_GET_USER_INFO: 'ERR_WX_GET_USER_INFO',
ERR_LOGIN_TIMEOUT: 'ERR_LOGIN_TIMEOUT',
ERR_LOGIN_FAILED: 'ERR_LOGIN_FAILED',
ERR_LOGIN_SESSION_NOT_RECEIVED: 'ERR_LOGIN_MISSING_SESSION',
ERR_INVALID_SESSION: 'ERR_INVALID_SESSION',
ERR_CHECK_LOGIN_FAILED: 'ERR_CHECK_LOGIN_FAILED',
};

@ -1,175 +0,0 @@
var utils = require('./utils');
var constants = require('./constants');
var Session = require('./session');
/***
* @class
* 表示登录过程中发生的异常
*/
var LoginError = (function () {
function LoginError(type, message) {
Error.call(this, message);
this.type = type;
this.message = message;
}
LoginError.prototype = new Error();
LoginError.prototype.constructor = LoginError;
return LoginError;
})();
/**
* 微信登录获取 code encryptData
*/
var getWxLoginResult = function getLoginCode(callback) {
wx.login({
success: function (loginResult) {
wx.getUserInfo({
withCredentials: true,
success: function (res) {
//此处为获取微信信息后的业务方法
callback(null, {
code: loginResult.code,
encryptedData: userResult.encryptedData,
iv: userResult.iv,
userInfo: userResult.userInfo,
});
},
fail: function () {
//获取用户信息失败后。请跳转授权页面
wx.showModal({
title: '警告',
content: '尚未进行授权,请点击确定跳转到授权页面进行授权。',
success: function (res) {
if (res.confirm) {
console.log('用户点击确定')
wx.navigateTo({
url: '../shouquan/shouquan',
})
}
}
})
},
});
},
fail: function (loginError) {
var error = new LoginError(constants.ERR_WX_LOGIN_FAILED, '微信登录失败,请检查网络状态');
error.detail = loginError;
callback(error, null);
},
});
};
var noop = function noop() {};
var defaultOptions = {
method: 'GET',
success: noop,
fail: noop,
loginUrl: null,
};
/**
* @method
* 进行服务器登录以获得登录会话
*
* @param {Object} options 登录配置
* @param {string} options.loginUrl 登录使用的 URL服务器应该在这个 URL 上处理登录请求
* @param {string} [options.method] 请求使用的 HTTP 方法默认为 "GET"
* @param {Function} options.success(userInfo) 登录成功后的回调函数参数 userInfo 微信用户信息
* @param {Function} options.fail(error) 登录失败后的回调函数参数 error 错误信息
*/
var login = function login(options) {
options = utils.extend({}, defaultOptions, options);
if (!defaultOptions.loginUrl) {
options.fail(new LoginError(constants.ERR_INVALID_PARAMS, '登录错误:缺少登录地址,请通过 setLoginUrl() 方法设置登录地址'));
return;
}
var doLogin = () => getWxLoginResult(function (wxLoginError, wxLoginResult) {
if (wxLoginError) {
options.fail(wxLoginError);
return;
}
var userInfo = wxLoginResult.userInfo;
// 构造请求头,包含 code、encryptedData 和 iv
var code = wxLoginResult.code;
var encryptedData = wxLoginResult.encryptedData;
var iv = wxLoginResult.iv;
var header = {};
header[constants.WX_HEADER_CODE] = code;
header[constants.WX_HEADER_ENCRYPTED_DATA] = encryptedData;
header[constants.WX_HEADER_IV] = iv;
// 请求服务器登录地址,获得会话信息
wx.request({
url: options.loginUrl,
header: header,
method: options.method,
data: options.data,
success: function (result) {
var data = result.data;
// 成功地响应会话信息
if (data && data[constants.WX_SESSION_MAGIC_ID]) {
if (data.session) {
data.session.userInfo = userInfo;
Session.set(data.session);
options.success(userInfo);
} else {
var errorMessage = '登录失败(' + data.error + ')' + (data.message || '未知错误');
var noSessionError = new LoginError(constants.ERR_LOGIN_SESSION_NOT_RECEIVED, errorMessage);
options.fail(noSessionError);
}
// 没有正确响应会话信息
} else {
var errorMessage = '登录请求没有包含会话响应,请确保服务器处理 `' + options.loginUrl + '` 的时候正确使用了 SDK 输出登录结果';
var noSessionError = new LoginError(constants.ERR_LOGIN_SESSION_NOT_RECEIVED, errorMessage);
options.fail(noSessionError);
}
},
// 响应错误
fail: function (loginResponseError) {
var error = new LoginError(constants.ERR_LOGIN_FAILED, '登录失败,可能是网络错误或者服务器发生异常');
options.fail(error);
},
});
});
var session = Session.get();
if (session) {
wx.checkSession({
success: function () {
options.success(session.userInfo);
},
fail: function () {
Session.clear();
doLogin();
},
});
} else {
doLogin();
}
};
var setLoginUrl = function (loginUrl) {
defaultOptions.loginUrl = loginUrl;
};
module.exports = {
LoginError: LoginError,
login: login,
setLoginUrl: setLoginUrl,
};

@ -1,125 +0,0 @@
var constants = require('./constants');
var utils = require('./utils');
var Session = require('./session');
var loginLib = require('./login');
var noop = function noop() {};
var buildSessionHeader = function buildSessionHeader() {
var session = Session.get();
var header = {};
if (session && session.id && session.skey) {
header[constants.WX_HEADER_ID] = session.id;
header[constants.WX_HEADER_SKEY] = session.skey;
}
return header;
};
/***
* @class
* 表示请求过程中发生的异常
*/
var RequestError = (function () {
function RequestError(type, message) {
Error.call(this, message);
this.type = type;
this.message = message;
}
RequestError.prototype = new Error();
RequestError.prototype.constructor = RequestError;
return RequestError;
})();
function request(options) {
if (typeof options !== 'object') {
var message = '请求传参应为 object 类型,但实际传了 ' + (typeof options) + ' 类型';
throw new RequestError(constants.ERR_INVALID_PARAMS, message);
}
var requireLogin = options.login;
var success = options.success || noop;
var fail = options.fail || noop;
var complete = options.complete || noop;
var originHeader = options.header || {};
// 成功回调
var callSuccess = function () {
success.apply(null, arguments);
complete.apply(null, arguments);
};
// 失败回调
var callFail = function (error) {
fail.call(null, error);
complete.call(null, error);
};
// 是否已经进行过重试
var hasRetried = false;
if (requireLogin) {
doRequestWithLogin();
} else {
doRequest();
}
// 登录后再请求
function doRequestWithLogin() {
loginLib.login({ success: doRequest, fail: callFail });
}
// 实际进行请求的方法
function doRequest() {
var authHeader = buildSessionHeader();
wx.request(utils.extend({}, options, {
header: utils.extend({}, originHeader, authHeader),
success: function (response) {
var data = response.data;
// 如果响应的数据里面包含 SDK Magic ID表示被服务端 SDK 处理过,此时一定包含登录态失败的信息
if (data && data[constants.WX_SESSION_MAGIC_ID]) {
// 清除登录态
Session.clear();
var error, message;
if (data.error === constants.ERR_INVALID_SESSION) {
// 如果是登录态无效,并且还没重试过,会尝试登录后刷新凭据重新请求
if (!hasRetried) {
hasRetried = true;
doRequestWithLogin();
return;
}
message = '登录态已过期';
error = new RequestError(data.error, message);
} else {
message = '鉴权服务器检查登录态发生错误(' + (data.error || 'OTHER') + ')' + (data.message || '未知错误');
error = new RequestError(constants.ERR_CHECK_LOGIN_FAILED, message);
}
callFail(error);
return;
}
callSuccess.apply(null, arguments);
},
fail: callFail,
complete: noop,
}));
};
};
module.exports = {
RequestError: RequestError,
request: request,
buildSessionHeader: buildSessionHeader
};

@ -1,18 +0,0 @@
var constants = require('./constants');
var SESSION_KEY = 'weapp_session_' + constants.WX_SESSION_MAGIC_ID;
var Session = {
get: function () {
return wx.getStorageSync(SESSION_KEY) || null;
},
set: function (session) {
wx.setStorageSync(SESSION_KEY, session);
},
clear: function () {
wx.removeStorageSync(SESSION_KEY);
},
};
module.exports = Session;

@ -1,528 +0,0 @@
var requestLib = require('./request');
var wxTunnel = require('./wxTunnel');
/**
* 当前打开的信道同一时间只能有一个信道打开
*/
var currentTunnel = null;
// 信道状态枚举
var STATUS_CLOSED = Tunnel.STATUS_CLOSED = 'CLOSED';
var STATUS_CONNECTING = Tunnel.STATUS_CONNECTING = 'CONNECTING';
var STATUS_ACTIVE = Tunnel.STATUS_ACTIVE = 'ACTIVE';
var STATUS_RECONNECTING = Tunnel.STATUS_RECONNECTING = 'RECONNECTING';
// 错误类型枚举
var ERR_CONNECT_SERVICE = Tunnel.ERR_CONNECT_SERVICE = 1001;
var ERR_CONNECT_SOCKET = Tunnel.ERR_CONNECT_SOCKET = 1002;
var ERR_RECONNECT = Tunnel.ERR_RECONNECT = 2001;
var ERR_SOCKET_ERROR = Tunnel.ERR_SOCKET_ERROR = 3001;
// 包类型枚举
var PACKET_TYPE_MESSAGE = 'message';
var PACKET_TYPE_PING = 'ping';
var PACKET_TYPE_PONG = 'pong';
var PACKET_TYPE_TIMEOUT = 'timeout';
var PACKET_TYPE_CLOSE = 'close';
// 断线重连最多尝试 5 次
var DEFAULT_MAX_RECONNECT_TRY_TIMES = 5;
// 每次重连前,等待时间的增量值
var DEFAULT_RECONNECT_TIME_INCREASE = 1000;
function Tunnel(serviceUrl) {
if (currentTunnel && currentTunnel.status !== STATUS_CLOSED) {
throw new Error('当前有未关闭的信道,请先关闭之前的信道,再打开新信道');
}
currentTunnel = this;
// 等确认微信小程序全面支持 ES6 就不用那么麻烦了
var me = this;
//=========================================================================
// 暴露实例状态以及方法
//=========================================================================
this.serviceUrl = serviceUrl;
this.socketUrl = null;
this.status = null;
this.open = openConnect;
this.on = registerEventHandler;
this.emit = emitMessagePacket;
this.close = close;
this.isClosed = isClosed;
this.isConnecting = isConnecting;
this.isActive = isActive;
this.isReconnecting = isReconnecting;
//=========================================================================
// 信道状态处理,状态说明:
// closed - 已关闭
// connecting - 首次连接
// active - 当前信道已经在工作
// reconnecting - 断线重连中
//=========================================================================
function isClosed() { return me.status === STATUS_CLOSED; }
function isConnecting() { return me.status === STATUS_CONNECTING; }
function isActive() { return me.status === STATUS_ACTIVE; }
function isReconnecting() { return me.status === STATUS_RECONNECTING; }
function setStatus(status) {
var lastStatus = me.status;
if (lastStatus !== status) {
me.status = status;
}
}
// 初始为关闭状态
setStatus(STATUS_CLOSED);
//=========================================================================
// 信道事件处理机制
// 信道事件包括:
// connect - 连接已建立
// close - 连接被关闭(包括主动关闭和被动关闭)
// reconnecting - 开始重连
// reconnect - 重连成功
// error - 发生错误,其中包括连接失败、重连失败、解包失败等等
// [message] - 信道服务器发送过来的其它事件类型,如果事件类型和上面内置的事件类型冲突,将在事件类型前面添加前缀 `@`
//=========================================================================
var preservedEventTypes = 'connect,close,reconnecting,reconnect,error'.split(',');
var eventHandlers = [];
/**
* 注册消息处理函数
* @param {string} messageType 支持内置消息类型"connect"|"close"|"reconnecting"|"reconnect"|"error"以及业务消息类型
*/
function registerEventHandler(eventType, eventHandler) {
if (typeof eventHandler === 'function') {
eventHandlers.push([eventType, eventHandler]);
}
}
/**
* 派发事件通知所有处理函数进行处理
*/
function dispatchEvent(eventType, eventPayload) {
eventHandlers.forEach(function (handler) {
var handleType = handler[0];
var handleFn = handler[1];
if (handleType === '*') {
handleFn(eventType, eventPayload);
} else if (handleType === eventType) {
handleFn(eventPayload);
}
});
}
/**
* 派发事件事件类型和系统保留冲突的事件名会自动加上 '@' 前缀
*/
function dispatchEscapedEvent(eventType, eventPayload) {
if (preservedEventTypes.indexOf(eventType) > -1) {
eventType = '@' + eventType;
}
dispatchEvent(eventType, eventPayload);
}
//=========================================================================
// 信道连接控制
//=========================================================================
var isFirstConnection = true;
var isOpening = false;
/**
* 连接信道服务器获取 WebSocket 连接地址获取地址成功后开始进行 WebSocket 连接
*/
function openConnect() {
if (isOpening) return;
isOpening = true;
// 只有关闭状态才会重新进入准备中
setStatus(isFirstConnection ? STATUS_CONNECTING : STATUS_RECONNECTING);
requestLib.request({
url: serviceUrl,
method: 'GET',
success: function (response) {
if (+response.statusCode === 200 && response.data && response.data.url) {
openSocket(me.socketUrl = response.data.url);
} else {
dispatchConnectServiceError(response);
}
},
fail: dispatchConnectServiceError,
complete: () => isOpening = false,
});
function dispatchConnectServiceError(detail) {
if (isFirstConnection) {
setStatus(STATUS_CLOSED);
dispatchEvent('error', {
code: ERR_CONNECT_SERVICE,
message: '连接信道服务失败,网络错误或者信道服务没有正确响应',
detail: detail || null,
});
} else {
startReconnect(detail);
}
}
}
/**
* 打开 WebSocket 连接打开后注册微信的 Socket 处理方法
*/
function openSocket(url) {
wxTunnel.listen({
onOpen: handleSocketOpen,
onMessage: handleSocketMessage,
onClose: handleSocketClose,
onError: handleSocketError,
});
wx.connectSocket({ url: url });
isFirstConnection = false;
}
//=========================================================================
// 处理消息通讯
//
// packet - 数据包,序列化形式为 `${type}` 或者 `${type}:${content}`
// packet.type - 包类型,包括 message, ping, pong, close
// packet.content? - 当包类型为 message 的时候,会附带 message 数据
//
// message - 消息体,会使用 JSON 序列化后作为 packet.content
// message.type - 消息类型,表示业务消息类型
// message.content? - 消息实体,可以为任意类型,表示消息的附带数据,也可以为空
//
// 数据包示例:
// - 'ping' 表示 Ping 数据包
// - 'message:{"type":"speak","content":"hello"}' 表示一个打招呼的数据包
//=========================================================================
// 连接还没成功建立的时候,需要发送的包会先存放到队列里
var queuedPackets = [];
/**
* WebSocket 打开之后更新状态同时发送所有遗留的数据包
*/
function handleSocketOpen() {
/* istanbul ignore else */
if (isConnecting()) {
dispatchEvent('connect');
}
else if (isReconnecting()) {
dispatchEvent('reconnect');
resetReconnectionContext();
}
setStatus(STATUS_ACTIVE);
emitQueuedPackets();
nextPing();
}
/**
* 收到 WebSocket 数据包交给处理函数
*/
function handleSocketMessage(message) {
resolvePacket(message.data);
}
/**
* 发送数据包如果信道没有激活将先存放队列
*/
function emitPacket(packet) {
if (isActive()) {
sendPacket(packet);
} else {
queuedPackets.push(packet);
}
}
/**
* 数据包推送到信道
*/
function sendPacket(packet) {
var encodedPacket = [packet.type];
if (packet.content) {
encodedPacket.push(JSON.stringify(packet.content));
}
wx.sendSocketMessage({
data: encodedPacket.join(':'),
fail: handleSocketError,
});
}
function emitQueuedPackets() {
queuedPackets.forEach(emitPacket);
// empty queued packets
queuedPackets.length = 0;
}
/**
* 发送消息包
*/
function emitMessagePacket(messageType, messageContent) {
var packet = {
type: PACKET_TYPE_MESSAGE,
content: {
type: messageType,
content: messageContent,
},
};
emitPacket(packet);
}
/**
* 发送 Ping
*/
function emitPingPacket() {
emitPacket({ type: PACKET_TYPE_PING });
}
/**
* 发送关闭包
*/
function emitClosePacket() {
emitPacket({ type: PACKET_TYPE_CLOSE });
}
/**
* 解析并处理从信道接收到的包
*/
function resolvePacket(raw) {
var packetParts = raw.split(':');
var packetType = packetParts.shift();
var packetContent = packetParts.join(':') || null;
var packet = { type: packetType };
if (packetContent) {
try {
packet.content = JSON.parse(packetContent);
} catch (e) {}
}
switch (packet.type) {
case PACKET_TYPE_MESSAGE:
handleMessagePacket(packet);
break;
case PACKET_TYPE_PONG:
handlePongPacket(packet);
break;
case PACKET_TYPE_TIMEOUT:
handleTimeoutPacket(packet);
break;
case PACKET_TYPE_CLOSE:
handleClosePacket(packet);
break;
default:
handleUnknownPacket(packet);
break;
}
}
/**
* 收到消息包直接 dispatch 给处理函数
*/
function handleMessagePacket(packet) {
var message = packet.content;
dispatchEscapedEvent(message.type, message.content);
}
//=========================================================================
// 心跳、断开与重连处理
//=========================================================================
/**
* Ping-Pong 心跳检测超时控制这个值有两个作用
* 1. 表示收到服务器的 Pong 相应之后过多久再发下一次 Ping
* 2. 如果 Ping 发送之后超过这个时间还没收到 Pong断开与服务器的连接
* 该值将在与信道服务器建立连接后被更新
*/
let pingPongTimeout = 15000;
let pingTimer = 0;
let pongTimer = 0;
/**
* 信道服务器返回 Ping-Pong 控制超时时间
*/
function handleTimeoutPacket(packet) {
var timeout = packet.content * 1000;
/* istanbul ignore else */
if (!isNaN(timeout)) {
pingPongTimeout = timeout;
ping();
}
}
/**
* 收到服务器 Pong 响应定时发送下一个 Ping
*/
function handlePongPacket(packet) {
nextPing();
}
/**
* 发送下一个 Ping
*/
function nextPing() {
clearTimeout(pingTimer);
clearTimeout(pongTimer);
pingTimer = setTimeout(ping, pingPongTimeout);
}
/**
* 发送 Ping等待 Pong
*/
function ping() {
/* istanbul ignore else */
if (isActive()) {
emitPingPacket();
// 超时没有响应,关闭信道
pongTimer = setTimeout(handlePongTimeout, pingPongTimeout);
}
}
/**
* Pong 超时没有响应信道可能已经不可用需要断开重连
*/
function handlePongTimeout() {
startReconnect('服务器已失去响应');
}
// 已经重连失败的次数
var reconnectTryTimes = 0;
// 最多允许失败次数
var maxReconnectTryTimes = Tunnel.MAX_RECONNECT_TRY_TIMES || DEFAULT_MAX_RECONNECT_TRY_TIMES;
// 重连前等待的时间
var waitBeforeReconnect = 0;
// 重连前等待时间增量
var reconnectTimeIncrease = Tunnel.RECONNECT_TIME_INCREASE || DEFAULT_RECONNECT_TIME_INCREASE;
var reconnectTimer = 0;
function startReconnect(lastError) {
if (reconnectTryTimes >= maxReconnectTryTimes) {
close();
dispatchEvent('error', {
code: ERR_RECONNECT,
message: '重连失败',
detail: lastError,
});
}
else {
wx.closeSocket();
waitBeforeReconnect += reconnectTimeIncrease;
setStatus(STATUS_RECONNECTING);
reconnectTimer = setTimeout(doReconnect, waitBeforeReconnect);
}
if (reconnectTryTimes === 0) {
dispatchEvent('reconnecting');
}
reconnectTryTimes += 1;
}
function doReconnect() {
openConnect();
}
function resetReconnectionContext() {
reconnectTryTimes = 0;
waitBeforeReconnect = 0;
}
/**
* 收到服务器的关闭请求
*/
function handleClosePacket(packet) {
close();
}
function handleUnknownPacket(packet) {
// throw away
}
var isClosing = false;
/**
* 收到 WebSocket 断开的消息处理断开逻辑
*/
function handleSocketClose() {
/* istanbul ignore if */
if (isClosing) return;
/* istanbul ignore else */
if (isActive()) {
// 意外断开的情况,进行重连
startReconnect('链接已断开');
}
}
function close() {
isClosing = true;
closeSocket();
setStatus(STATUS_CLOSED);
resetReconnectionContext();
isFirstConnection = false;
clearTimeout(pingTimer);
clearTimeout(pongTimer);
clearTimeout(reconnectTimer);
dispatchEvent('close');
isClosing = false;
}
function closeSocket(emitClose) {
if (isActive() && emitClose !== false) {
emitClosePacket();
}
wx.closeSocket();
}
//=========================================================================
// 错误处理
//=========================================================================
/**
* 错误处理
*/
function handleSocketError(detail) {
switch (me.status) {
case Tunnel.STATUS_CONNECTING:
dispatchEvent('error', {
code: ERR_SOCKET_ERROR,
message: '连接信道失败,网络错误或者信道服务不可用',
detail: detail,
});
break;
}
}
}
module.exports = Tunnel;

@ -1,18 +0,0 @@
/**
* 拓展对象
*/
exports.extend = function extend(target) {
var sources = Array.prototype.slice.call(arguments, 1);
for (var i = 0; i < sources.length; i += 1) {
var source = sources[i];
for (var key in source) {
if (source.hasOwnProperty(key)) {
target[key] = source[key];
}
}
}
return target;
};

@ -1,32 +0,0 @@
/* istanbul ignore next */
const noop = () => void(0);
let onOpen, onClose, onMessage, onError;
/* istanbul ignore next */
function listen(listener) {
if (listener) {
onOpen = listener.onOpen;
onClose = listener.onClose;
onMessage = listener.onMessage;
onError = listener.onError;
} else {
onOpen = noop;
onClose = noop;
onMessage = noop;
onError = noop;
}
}
/* istanbul ignore next */
function bind() {
wx.onSocketOpen(result => onOpen(result));
wx.onSocketClose(result => onClose(result));
wx.onSocketMessage(result => onMessage(result));
wx.onSocketError(error => onError(error));
}
listen(null);
bind();
module.exports = { listen };

@ -1,15 +0,0 @@
# 后台数据交互部分日志
## 已完成-登录微信公众平台,前往 设置 > 开发设置 > 服务器配置 > 「修改」 链接,增加下述域名为白名单中的域名
request合法域名
fqcxn98z.api.lncldglobal.com
fqcxn98z.engine.lncldglobal.com
fqcxn98z.rtm.lncldglobal.com
fqcxn98z.push.lncldglobal.com
app-router.leancloud.cn
socket合法域名
us-w1-backend2.leancloud.cn
us-w1-backend3.leancloud.cn
us-w1-backend4.leancloud.cn
us-w1-backend5.leancloud.cn
us-w1-backend6.leancloud.cn
Loading…
Cancel
Save