├── .gitignore ├── LICENSE ├── README.md ├── app.js ├── app.json ├── app.wxss ├── config.js ├── image ├── 1.png ├── 2.png └── arrowright.png ├── mock ├── achievements.html ├── data.js └── parser.js ├── package.json ├── pages ├── achievement │ ├── achievement.js │ ├── achievement.json │ ├── achievement.wxml │ └── achievement.wxss ├── elective │ ├── elective.js │ └── elective.wxml ├── index │ ├── index.js │ ├── index.wxml │ └── index.wxss ├── introduce │ ├── introduce.js │ └── introduce.wxml └── panel │ ├── panel.js │ ├── panel.wxml │ └── panel.wxss └── utils ├── parser.js ├── simulator.js └── util.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directories 30 | node_modules 31 | jspm_packages 32 | 33 | # Optional npm cache directory 34 | .npm 35 | 36 | # Optional REPL history 37 | .node_repl_history 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 zonghua 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 教务系统 微信小程序 2 | 3 | ## 说明 4 | 5 | 正方教务系统的(微信)小程序,~~成绩数据爬虫~~ 6 | 7 | **准备用服务端做数据抓取,客户端仅作数据显示** 8 | 9 | ~~请见下方局限~~ 10 | 11 | **此项目使用 Applet 名称仅为借古讽今只之用** 12 | 13 | ## 演示 14 | 15 | 1. 从 GitHub 克隆项目 16 | 17 | 2. 启动微信 Web 开发着工具,添加项目 18 | 19 | 3. 选择无 AppID ,填写任意名称,找到项目目录添加 20 | 21 | 4. 点击“查询” 22 | 23 |  24 | 25 |  26 | 27 | 28 | ## ~~局限~~ ## 29 | 30 | **由于 Javascript 跨域限制暂时无法直接通过小程序去抓取目标网站的 Cookie** 31 | 32 | xhr 33 | ``` 34 | Date: Fri, 30 Sep 2016 12:46:33 GMT 35 | Server: Microsoft-IIS/6.0 36 | X-AspNet-Version: 1.1.4322 37 | X-Powered-By: ASP.NET 38 | MicrosoftOfficeWebServer: 5.0_Pub 39 | Content-Type: image/Gif; charset=gb2312 40 | Access-Control-Allow-Origin: * 41 | Cache-Control: private 42 | Access-Control-Allow-Headers: X-Requested-With, Content-Type 43 | Content-Length: 2245 44 | 45 | ``` 46 | chrome 47 | ``` 48 | HTTP/1.1 200 OK 49 | Date: Fri, 30 Sep 2016 12:46:33 GMT 50 | Server: Microsoft-IIS/6.0 51 | MicrosoftOfficeWebServer: 5.0_Pub 52 | X-Powered-By: ASP.NET 53 | X-AspNet-Version: 1.1.4322 54 | Set-Cookie: ASP.NET_SessionId=hw4tpo55f4005ojii14d2e3r; path=/ 55 | Cache-Control: private 56 | Content-Type: image/Gif; charset=gb2312 57 | Content-Length: 2245 58 | ``` 59 | 60 | **小程序暂时使用直接 GET 获取到的 HTML 内容进行解析然后填充视图作为演示** 61 | 62 | ``` 63 | var achievementUrl = isDebug ? mockUrl + 'achievements.html' : sisUrl + '/xscj.aspx?xh=' 64 | ``` 65 | 66 | **如果能够避免跨域访问的限制,小程序可以不依赖服务端完成抓取的任务** 67 | 68 | ``` 69 | // XMLHttpRequest 完备 70 | var test = function (url, callback) { 71 | var xhr = new XMLHttpRequest() 72 | xhr.responseType = 'blob' 73 | xhr.onload = function () { 74 | var reader = new FileReader() 75 | var headers = xhr.getAllResponseHeaders() 76 | reader.onloadend = function () { 77 | callback(reader.result, headers) 78 | } 79 | reader.readAsDataURL(xhr.response) 80 | } 81 | xhr.open('GET', url) 82 | xhr.send() 83 | } 84 | ``` -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | // app.js 2 | App({ 3 | onLaunch: function () { 4 | // 调用API从本地缓存中获取数据 5 | // var logs = wx.getStorageSync( 'logs' ) || [] 6 | // logs.unshift( Date.now() ) 7 | // wx.setStorageSync( 'logs', logs ) 8 | }, 9 | getUserInfo: function (cb) { 10 | var that = this 11 | if (this.globalData.userInfo) { 12 | typeof cb == 'function' && cb(this.globalData.userInfo) 13 | } else { 14 | // 调用登录接口 15 | wx.login({ 16 | success: function () { 17 | wx.getUserInfo({ 18 | success: function (res) { 19 | that.globalData.userInfo = res.userInfo 20 | typeof cb == 'function' && cb(that.globalData.userInfo) 21 | } 22 | }) 23 | } 24 | }) 25 | } 26 | }, 27 | globalData: { 28 | userInfo: null 29 | } 30 | }) 31 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "pages": [ 3 | "pages/index/index", 4 | "pages/achievement/achievement", 5 | "pages/introduce/introduce", 6 | "pages/elective/elective", 7 | "pages/panel/panel" 8 | ], 9 | "window": { 10 | "backgroundTextStyle": "light", 11 | "navigationBarBackgroundColor": "#fff", 12 | "navigationBarTitleText": "教务系统小程序", 13 | "navigationBarTextStyle": "black" 14 | }, 15 | "debug":true 16 | } -------------------------------------------------------------------------------- /app.wxss: -------------------------------------------------------------------------------- 1 | page { 2 | background-color: #fbf9fe; 3 | height: 100%; 4 | } 5 | .container { 6 | display: flex; 7 | flex-direction: column; 8 | min-height: 100%; 9 | justify-content: space-between; 10 | } 11 | .page-header { 12 | display: flex; 13 | font-size: 32rpx; 14 | color: #aaa; 15 | margin-top: 50rpx; 16 | flex-direction: column; 17 | align-items: center; 18 | } 19 | .page-header-text { 20 | padding: 20rpx 40rpx; 21 | } 22 | .page-header-line { 23 | width: 150rpx; 24 | height: 1px; 25 | border-bottom: 1px solid #ccc; 26 | } 27 | 28 | .page-body { 29 | width: 100%; 30 | display: flex; 31 | flex-direction: column; 32 | align-items: center; 33 | flex-grow: 1; 34 | overflow-x: hidden; 35 | } 36 | .page-body-wrapper { 37 | margin-top: 100rpx; 38 | display: flex; 39 | flex-direction: column; 40 | align-items: center; 41 | width: 100%; 42 | } 43 | .page-body-wrapper form { 44 | width: 100%; 45 | } 46 | .page-body-wording { 47 | text-align: center; 48 | padding: 200rpx 100rpx; 49 | } 50 | .page-body-info { 51 | display: flex; 52 | flex-direction: column; 53 | align-items: center; 54 | background-color: #fff; 55 | margin-bottom: 50rpx; 56 | width: 100%; 57 | padding: 50rpx 0 150rpx 0; 58 | } 59 | .page-body-title { 60 | margin-bottom: 100rpx; 61 | font-size: 32rpx; 62 | } 63 | .page-body-text { 64 | font-size: 30rpx; 65 | line-height: 26px; 66 | color: #ccc; 67 | } 68 | .page-body-text-small { 69 | font-size: 24rpx; 70 | color: #000; 71 | margin-bottom: 100rpx; 72 | } 73 | .page-body-form { 74 | width: 100%; 75 | background-color: #fff; 76 | display: flex; 77 | flex-direction: column; 78 | width: 100%; 79 | border: 1px solid #eee; 80 | } 81 | .page-body-form-item { 82 | display: flex; 83 | align-items: center; 84 | margin-left: 30rpx; 85 | border-bottom: 1px solid #eee; 86 | height: 88rpx; 87 | font-size: 34rpx; 88 | } 89 | .page-body-form-key { 90 | width: 180rpx; 91 | color: #000; 92 | } 93 | .page-body-form-value { 94 | flex-grow: 1; 95 | } 96 | .page-body-form-value .input-placeholder { 97 | color: #b2b2b2; 98 | } 99 | 100 | .page-body-form-picker { 101 | display: flex; 102 | justify-content: space-between; 103 | height: 100rpx; 104 | align-items: center; 105 | font-size: 36rpx; 106 | margin-left: 20rpx; 107 | padding-right: 20rpx; 108 | border-bottom: 1px solid #eee; 109 | } 110 | .page-body-form-picker-value { 111 | color: #ccc; 112 | } 113 | 114 | .page-body-buttons { 115 | width: 100%; 116 | } 117 | .page-body-button { 118 | margin: 25rpx; 119 | } 120 | .page-body-button image { 121 | width: 150rpx; 122 | height: 150rpx; 123 | } 124 | .page-footer { 125 | text-align: center; 126 | color: #1aad19; 127 | font-size: 28rpx; 128 | padding: 6px; 129 | bottom: 0px; 130 | position: fixed; 131 | } 132 | 133 | .green{ 134 | color: #09BB07; 135 | } 136 | .red{ 137 | color: #F76260; 138 | } 139 | .blue{ 140 | color: #10AEFF; 141 | } 142 | .yellow{ 143 | color: #FFBE00; 144 | } 145 | .gray{ 146 | color: #C9C9C9; 147 | } 148 | 149 | .strong{ 150 | font-weight: bold; 151 | } 152 | 153 | .bc_green{ 154 | background-color: #09BB07; 155 | } 156 | .bc_red{ 157 | background-color: #F76260; 158 | } 159 | .bc_blue{ 160 | background-color: #10AEFF; 161 | } 162 | .bc_yellow{ 163 | background-color: #FFBE00; 164 | } 165 | .bc_gray{ 166 | background-color: #C9C9C9; 167 | } 168 | 169 | .tc{ 170 | text-align: center; 171 | } 172 | 173 | .page input,checkbox{ 174 | padding: 20rpx 30rpx; 175 | background-color: #fff; 176 | } 177 | checkbox, radio{ 178 | margin-right: 10px; 179 | } 180 | 181 | .btn-area{ 182 | padding: 0 30px; 183 | } 184 | .btn-area button{ 185 | margin-top: 20rpx; 186 | margin-bottom: 20rpx; 187 | } 188 | 189 | .page { 190 | min-height: 100%; 191 | flex: 1; 192 | background-color: #FBF9FE; 193 | font-size: 32rpx; 194 | font-family: -apple-system-font,Helvetica Neue,Helvetica,sans-serif; 195 | overflow: hidden; 196 | } 197 | .page__hd{ 198 | padding: 50rpx 50rpx 50rpx 50rpx; 199 | text-align: center; 200 | } 201 | .page__title{ 202 | display: inline-block; 203 | padding: 20rpx 40rpx; 204 | font-size: 32rpx; 205 | color: #AAAAAA; 206 | border-bottom: 1px solid #CCCCCC; 207 | } 208 | .page__info{ 209 | display: inline-block; 210 | font-size: 38rpx; 211 | color: #AAAAAA; 212 | } 213 | .page__desc{ 214 | display: none; 215 | margin-top: 20rpx; 216 | font-size: 26rpx; 217 | color: #BBBBBB; 218 | } 219 | 220 | .section{ 221 | margin-bottom: 80rpx; 222 | } 223 | .section_gap{ 224 | padding: 0 30rpx; 225 | } 226 | .section__title{ 227 | margin-bottom: 16rpx; 228 | padding-left: 30rpx; 229 | padding-right: 30rpx; 230 | } 231 | .section_gap .section__title{ 232 | padding-left: 0; 233 | padding-right: 0; 234 | } 235 | 236 | .shading{ 237 | background-color: #eee; 238 | background-image: -moz-linear-gradient(45deg,#fff 25%, transparent 25%, transparent 50%,#fff 50%,#fff 75%, transparent 75%, transparent); 239 | background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(25%,rgba(255,255,255,0.2)),color-stop(25%,transparent),color-stop(50%,transparent),color-stop(50%,rgba(255,255,255,0.2)),color-stop(75%,rgba(255,255,255,0.2)),color-stop(75%,transparent)); 240 | background-size: 16px 16px; 241 | 242 | } 243 | -------------------------------------------------------------------------------- /config.js: -------------------------------------------------------------------------------- 1 | var isDebug = true 2 | var sisUrl = 'http://10.50.17.10:80' 3 | var mockUrl = 'https://applehater.cn/mock/' 4 | 5 | // 只能用模拟得数据做演示 6 | var achievementUrl = isDebug ? mockUrl + 'achievements.html' : sisUrl + '/xscj.aspx?xh=' 7 | 8 | module.exports = { 9 | loginUrl: sisUrl + '/default3.aspx', 10 | codeUrl: sisUrl + '/CheckCode.aspx', 11 | infoUrl: sisUrl + '/xsxx.aspx?xh=', 12 | achievementUrl: achievementUrl, 13 | electiveUrl: sisUrl + '/ryxk.aspx?xh=', 14 | mockUrl: mockUrl 15 | } 16 | -------------------------------------------------------------------------------- /image/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zh-h/student-information-system-wechat-applet/026561871eaffe181619e179b86e2f7aba24b652/image/1.png -------------------------------------------------------------------------------- /image/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zh-h/student-information-system-wechat-applet/026561871eaffe181619e179b86e2f7aba24b652/image/2.png -------------------------------------------------------------------------------- /image/arrowright.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zh-h/student-information-system-wechat-applet/026561871eaffe181619e179b86e2f7aba24b652/image/arrowright.png -------------------------------------------------------------------------------- /mock/achievements.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |([\w\W]+?)<\/td>/ig 7 | var szxfPattern = /(.+?)<\/font><\/span>/ 8 | var pjxfjdPattern = /(.+?)<\/font><\/span>/ 9 | var zxfjdPattern = /(.+?)<\/font>/ 10 | 11 | // ajax 访问图片/二进制 12 | var test = function (url, callback) { 13 | var xhr = new XMLHttpRequest() 14 | xhr.responseType = 'blob' 15 | xhr.onload = function () { 16 | var reader = new FileReader() 17 | var headers = xhr.getAllResponseHeaders() 18 | reader.onloadend = function () { 19 | callback(reader.result, headers) 20 | } 21 | reader.readAsDataURL(xhr.response) 22 | } 23 | xhr.open('GET', url) 24 | xhr.send() 25 | } 26 | 27 | var paseAchievement = function (html) { 28 | // match values 29 | var achievement = [] 30 | 31 | // match tables 32 | var tableMatchers = html.match(tablePattern) 33 | var table1 = tableMatchers[2] 34 | var trMatchers1 = table1.match(trPattern) 35 | // match table grids 36 | for (var i in trMatchers1) { 37 | var tr = trMatchers1[i] 38 | var tdMatchers = tr.match(tdPatterb) 39 | var row = [] 40 | for (var j in tdMatchers) { 41 | var value = tdMatchers[j].replace(/( | )/, '').replace(/(<\/td>)/, '') 42 | if (value == ' ') 43 | value = '' 44 | row.push(value) 45 | } 46 | achievement.push(row) 47 | } 48 | 49 | return achievement 50 | } 51 | 52 | var paseViewState = function (html) { 53 | var viewState = '' 54 | var viewStateMatcher = html.match(viewStatePattern) 55 | if (viewStateMatcher != null) 56 | viewState = viewStateMatcher[1] 57 | return viewState 58 | } 59 | 60 | module.exports = { 61 | 'test': test, 62 | 'paseAchievement': paseAchievement, 63 | 'paseViewState': paseViewState 64 | } 65 | -------------------------------------------------------------------------------- /utils/simulator.js: -------------------------------------------------------------------------------- 1 | var config = require('../config.js') 2 | var paser = require('./parser.js') 3 | 4 | var loginUrl = config.loginUrl 5 | var infoUrl = config.infoUrl 6 | var electiveUrl = config.electiveUrl 7 | var achievementUrl = config.achievementUrl 8 | var cookieStr = '' 9 | 10 | login = function (username, password) { 11 | // TODO 12 | } 13 | 14 | getAchievement = function (successFunc, failFunc) { 15 | console.log(achievementUrl) 16 | wx.request({ 17 | url: achievementUrl, 18 | header: { 19 | 'Cookie': cookieStr 20 | }, 21 | success: function (res) { 22 | var data = [] 23 | try { 24 | data = paser.paseAchievement(res.data) 25 | successFunc(data) 26 | } catch (error) { 27 | failFunc('parse error') 28 | } 29 | }, 30 | fail: function (res) { 31 | failFunc('network error') 32 | } 33 | }) 34 | } 35 | 36 | getElective = function () { 37 | // TODO 38 | return [] 39 | } 40 | 41 | getInfo = function () { 42 | // TODO 43 | return [] 44 | } 45 | 46 | module.exports = { 47 | 'getAchievement': getAchievement, 48 | 'login': login, 49 | 'getElective': getElective, 50 | 'getInfo': getInfo 51 | } 52 | -------------------------------------------------------------------------------- /utils/util.js: -------------------------------------------------------------------------------- 1 | function formatTime( date ) { 2 | var year = date.getFullYear() 3 | var month = date.getMonth() + 1 4 | var day = date.getDate() 5 | 6 | var hour = date.getHours() 7 | var minute = date.getMinutes() 8 | var second = date.getSeconds() 9 | 10 | 11 | return [ year, month, day ].map( formatNumber ).join( '/' ) + ' ' + [ hour, minute, second ].map( formatNumber ).join( ':' ) 12 | } 13 | 14 | function formatNumber( n ) { 15 | n = n.toString() 16 | return n[ 1 ] ? n : '0' + n 17 | } 18 | 19 | module.exports = { 20 | formatTime: formatTime 21 | } 22 | -------------------------------------------------------------------------------- |