├── pages ├── detail │ ├── detail.json │ ├── detail.js │ ├── detail.wxml │ └── detail.wxss └── index │ ├── index.json │ ├── index.js │ ├── index.wxml │ └── index.wxss ├── images ├── home.png ├── love.png ├── medal.png ├── speech.png ├── trophy.png └── favorite.png ├── app.json ├── app.wxss ├── README.md ├── LICENSE └── app.js /pages/detail/detail.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /pages/index/index.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /images/home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HackSwjtu/weapp-clawjtu/HEAD/images/home.png -------------------------------------------------------------------------------- /images/love.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HackSwjtu/weapp-clawjtu/HEAD/images/love.png -------------------------------------------------------------------------------- /images/medal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HackSwjtu/weapp-clawjtu/HEAD/images/medal.png -------------------------------------------------------------------------------- /images/speech.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HackSwjtu/weapp-clawjtu/HEAD/images/speech.png -------------------------------------------------------------------------------- /images/trophy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HackSwjtu/weapp-clawjtu/HEAD/images/trophy.png -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "pages":[ 3 | "pages/index/index", 4 | "pages/detail/detail" 5 | ] 6 | } -------------------------------------------------------------------------------- /images/favorite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HackSwjtu/weapp-clawjtu/HEAD/images/favorite.png -------------------------------------------------------------------------------- /pages/detail/detail.js: -------------------------------------------------------------------------------- 1 | let app = getApp(); 2 | Page({ 3 | data: { 4 | post: null 5 | }, 6 | onLoad: function(_options) { 7 | let _type = _options.id.slice(0, _options.id.indexOf('_')); 8 | this.setData({ 9 | post: app.globalData[_type + 's'].get(_options.id) 10 | }); 11 | } 12 | }); -------------------------------------------------------------------------------- /pages/detail/detail.wxml: -------------------------------------------------------------------------------- 1 | {{post.title}} 2 | 3 | 4 | {{description}} 5 | 6 | 7 | {{post.detail}} -------------------------------------------------------------------------------- /app.wxss: -------------------------------------------------------------------------------- 1 | page { 2 | position: relative; 3 | height: 100%; 4 | width: 100%; 5 | --tablet-height: 90rpx; 6 | --banner-height: 152rpx; 7 | font-family: 等线, 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; 8 | background-color: #FAFAFA; 9 | color: rgb(88, 89, 91); 10 | } 11 | .icon { 12 | height: 50rpx; 13 | width: 50rpx; 14 | } 15 | .icon.active { 16 | filter: brightness(2); 17 | } 18 | .hr { 19 | display: block; 20 | height: 1px; 21 | width: 100%; 22 | background-color: rgb(198, 186, 186); 23 | } -------------------------------------------------------------------------------- /pages/detail/detail.wxss: -------------------------------------------------------------------------------- 1 | #title { 2 | font-size: 48rpx; 3 | font-weight: 700; 4 | } 5 | .medal { 6 | display: inline-block; 7 | background-image: url(../../images/medal.png); 8 | background-position: center center; 9 | background-size: contain; 10 | } 11 | .post-description { 12 | display: block; 13 | font-size: 32rpx; 14 | font-weight: 700; 15 | } 16 | #detail { 17 | font-size: 30rpx; 18 | line-height: 125%; 19 | } 20 | .detail-block { 21 | --vmargin: 30rpx; 22 | --hmargin: 60rpx; 23 | margin: var(--vmargin) var(--hmargin); 24 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # weapp-clawjtu 2 | 3 | ![](https://img.shields.io/badge/Wechat-app-green.svg) 4 | ![](https://img.shields.io/badge/HackSwjtu-weapp-red.svg) 5 | ![](https://img.shields.io/badge/license-MIT-green.svg?style=flat) 6 | 7 | ![](http://of7whelxn.bkt.clouddn.com/post.png) 8 | 9 | > Clawjtu - 查询交大最新讲座信息和学术竞赛信息的微信小程序 10 | 11 | ## Data source 12 | 13 | + [pyspider-clawswjtu](https://github.com/HackSwjtu/pyspider-clawswjtu) 14 | 15 | ## MIT License 16 | 17 | Copyright (c) 2017 Hack Swjtu 18 | 19 | Permission is hereby granted, free of charge, to any person obtaining a copy 20 | of this software and associated documentation files (the "Software"), to deal 21 | in the Software without restriction, including without limitation the rights 22 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 23 | copies of the Software, and to permit persons to whom the Software is 24 | furnished to do so, subject to the following conditions: 25 | 26 | The above copyright notice and this permission notice shall be included in all 27 | copies or substantial portions of the Software. 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Hack Swjtu 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 | -------------------------------------------------------------------------------- /pages/index/index.js: -------------------------------------------------------------------------------- 1 | let app = getApp(); 2 | Page({ 3 | data: { 4 | userInfo: null, 5 | posts: null, 6 | status: 'all', 7 | scroll_top: 0 8 | }, 9 | backToTop() { 10 | this.setData({scroll_top: 0}); 11 | }, 12 | toggleFav(_event) { 13 | let _id = _event.currentTarget.id.slice(4); 14 | app.toggleFav({ 15 | id: _id, 16 | success: () => this.getCurrent() 17 | }); 18 | }, 19 | setPosts(_posts) { 20 | this.setData({posts: _posts}); 21 | }, 22 | getPosts(_type) { 23 | if(this.data.status !== _type) 24 | this.backToTop(); 25 | this.setData({status: _type}); 26 | if(_type == 'fav') 27 | return app.getPosts({ 28 | success: _posts => this.setPosts(_posts.filter(_post => _post.favorited)) 29 | }); 30 | if(_type == 'all') 31 | return app.getPosts({ 32 | success: this.setPosts 33 | }); 34 | app.getPostsByType({ 35 | type: _type, 36 | success: this.setPosts 37 | }); 38 | }, 39 | getPostsByEvent(_event) { 40 | this.getPosts(_event.currentTarget.id); 41 | }, 42 | refreshPosts() { 43 | app.refreshPosts({success: this.setPosts}); 44 | }, 45 | getCurrent() { 46 | this.getPosts(this.data.status); 47 | }, 48 | onLoad() { 49 | this.getPosts('all'); 50 | app.getUserInfo({success: _userInfo => this.setData({userInfo: _userInfo})}); 51 | } 52 | }); -------------------------------------------------------------------------------- /pages/index/index.wxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | {{post.detail}} 12 | 13 | 14 | 15 | 16 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /pages/index/index.wxss: -------------------------------------------------------------------------------- 1 | #banner { 2 | position: absolute; 3 | top: 0; 4 | height: var(--banner-height); 5 | width: 100%; 6 | display: flex; 7 | align-items: center; 8 | box-shadow: 0 0 16rpx rgba(0, 0, 0, 0.5); 9 | background-color: #F6F6F6; 10 | } 11 | #avatar { 12 | display: block; 13 | margin: 20rpx 40rpx 20rpx 60rpx; 14 | height: 112rpx; 15 | width: 112rpx; 16 | border-radius: 50%; 17 | } 18 | #post-container { 19 | --margin: 30rpx; 20 | position: absolute; 21 | padding-bottom: var(--margin); 22 | width: 100%; 23 | } 24 | .post { 25 | position: relative; 26 | --padding: 30rpx; 27 | margin: var(--margin); 28 | border-radius: 8rpx; 29 | background-color: white; 30 | box-shadow: 0 4rpx 8rpx 2rpx rgba(0, 0, 0, 0.25); 31 | } 32 | .post-info { 33 | position: relative; 34 | padding: var(--padding); 35 | } 36 | .post-title { 37 | display: block; 38 | width: calc(100% - var(--padding) - 50rpx); 39 | font-size: 32rpx; 40 | color: rgb(88, 89, 91); 41 | font-weight: 700; 42 | margin-bottom: 10px; 43 | } 44 | .post-description text { 45 | display: block; 46 | font-size: 24rpx; 47 | color: rgb(187, 187, 188); 48 | } 49 | .post-preview { 50 | padding: var(--padding); 51 | font-size: 28rpx; 52 | color: rgb(88, 89, 91); 53 | text-overflow: ellipsis; 54 | white-space: nowrap; 55 | overflow: hidden; 56 | } 57 | .post-love { 58 | position: absolute; 59 | right: var(--padding); 60 | top: var(--padding); 61 | filter: grayscale(); 62 | } 63 | .post-love.loved { 64 | filter: grayscale(0); 65 | } 66 | #tablet { 67 | display: flex; 68 | width: 100%; 69 | align-items: center; 70 | justify-content: space-between; 71 | position: absolute; 72 | bottom: 0; 73 | height: var(--tablet-height); 74 | box-shadow: 0 0 16rpx rgba(0, 0, 0, 0.5); 75 | background-color: white; 76 | } 77 | .tablet { 78 | flex-grow: 1; 79 | text-align: center; 80 | background-color: white; 81 | } 82 | #fav { 83 | margin-right: 60rpx; 84 | height: 50rpx; 85 | width: 50rpx; 86 | } 87 | #post-container { 88 | height: calc(100% - var(--banner-height) - var(--tablet-height)); 89 | top: var(--banner-height); 90 | } -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | let description_keywords = { 2 | competition: { 3 | time: 'publishdate' 4 | }, 5 | lecture: { 6 | content: 'place', 7 | time: 'time', 8 | speaker: 'speaker' 9 | } 10 | }; 11 | let parseDescriptions = (_post, _type) => { 12 | let _result = {}; 13 | Object.keys(description_keywords[_type]).forEach(_key => _result[_key] = _post[description_keywords[_type][_key]]); 14 | return _result; 15 | }; 16 | let parsePost = (_post, _favorited, _type) => ({ 17 | id: _type + '_' + _post.id, 18 | title: _post.title, 19 | descriptions: parseDescriptions(_post, _type), 20 | detail: _post.detail, 21 | favorited: _favorited 22 | }); 23 | let getFavsFromStorage = _type => { 24 | let _favorites; 25 | try{ 26 | _favorites = JSON.parse(wx.getStorageSync('favorited_' + _type + '_id')); 27 | } catch(e) { _favorites = []; } 28 | return new Map(_favorites); 29 | }; 30 | let parsePosts = (_posts, _type) => { 31 | let _favorites = getFavsFromStorage(_type); 32 | return new Map( 33 | _posts.map(_post => [ 34 | _type + '_' + _post.id, 35 | parsePost(_post, !!_favorites.get(_type + '_' + _post.id), _type), 36 | ]) 37 | ); 38 | }; 39 | let mapToArray = _map => { 40 | let _arr = []; 41 | _map.forEach(_element => _arr.push(_element)); 42 | return _arr; 43 | }; 44 | let compareTime = (_p0, _p1) => _p0.descriptions.time > _p1.descriptions.time; 45 | App({ 46 | globalData: { 47 | userInfo: null, 48 | lectures: null, 49 | competitions: null, 50 | spider_apis: { 51 | lecture: 'https://api.hackswjtu.com/lecture/lastweek', 52 | competition: 'https://api.hackswjtu.com/competition/recent' 53 | } 54 | }, 55 | refreshUserInfo(_options) { 56 | let _fail = () => { 57 | console.log(1); 58 | this.globalData.userInfo = null; 59 | _options.fail && _options.fail(); 60 | }; 61 | wx.login({ 62 | success: () => { 63 | wx.getUserInfo({ 64 | success: _res => { 65 | this.globalData.userInfo = _res.userInfo; 66 | _options.success(this.globalData.userInfo); 67 | }, 68 | fail: _fail 69 | }); 70 | }, 71 | fail: _fail 72 | }); 73 | }, 74 | getUserInfo(_options) { 75 | let _fail = () => this.refreshUserInfo(_options); 76 | if(!this.globalData.userInfo) 77 | !this.refreshUserInfo(_options); 78 | }, 79 | refreshPostsByType(_options) { 80 | wx.request({ 81 | url: this.globalData.spider_apis[_options.type], 82 | success: _res => { 83 | this.globalData[_options.type + 's'] = parsePosts(_res.data[_options.type], _options.type); 84 | _options.success(mapToArray(this.globalData[_options.type + 's']).sort(compareTime)); 85 | }, 86 | fail: () => { 87 | this.globalData[_options.type + 's'] = null; 88 | _options.fail && _options.fail(); 89 | } 90 | }); 91 | }, 92 | getPostsByType(_options) { 93 | if(this.globalData[_options.type + 's'] !== null) 94 | _options.success(mapToArray(this.globalData[_options.type + 's']).sort(compareTime)); 95 | else 96 | this.refreshPostsByType(_options); 97 | }, 98 | refreshPosts(_options) { 99 | let _result; 100 | this.refreshPostsByType({ 101 | type: 'lecture', 102 | success: _lectures => { 103 | this.refreshPostsByType({ 104 | type: 'competition', 105 | success: _competitions => _options.success(_lectures.concat(_competitions).sort(compareTime)), 106 | fail: _options.fail 107 | }); 108 | }, 109 | fail: () => _options.fail && _options.fail() 110 | }); 111 | }, 112 | getEachTypesOfPosts(_options, _types, _results = []) { 113 | let _current_type = _types.shift(); 114 | let _current_options = {fail: _options.fail}; 115 | let _success = _res => { 116 | _results = _results.concat(_res); 117 | _types.length ? this.getEachTypesOfPosts(_options, _types, _results) : _options.success(_results.sort(compareTime)); 118 | }; 119 | this.getPostsByType({ 120 | type: _current_type, 121 | success: _success, 122 | fail: _options.fail 123 | }); 124 | }, 125 | getPosts(_options) { 126 | this.getEachTypesOfPosts(_options, [ 127 | 'lecture', 128 | 'competition' 129 | ]); 130 | }, 131 | toggleFav(_options) { 132 | let _type = _options.id.slice(0, _options.id.indexOf('_')); 133 | let _map = this.globalData[_type + 's']; 134 | let _copy = _map.get(_options.id); 135 | _copy.favorited = !(_copy.favorited); 136 | _map.set(_options.id, _copy); 137 | wx.setStorage({ 138 | key: 'favorited_' + _type + '_id', 139 | data: JSON.stringify( 140 | mapToArray(_map).map(_post => [_post.id, _post.favorited]) 141 | ), 142 | success: _options.success 143 | }); 144 | }, 145 | onLaunch() { 146 | wx.setNavigationBarTitle({title: 'Clawjtu'}); 147 | } 148 | }); --------------------------------------------------------------------------------