├── .gitignore
├── app.js
├── app.json
├── app.wxss
├── components
├── behaviors
│ └── pagination.js
├── book
│ ├── index.js
│ ├── index.json
│ ├── index.wxml
│ └── index.wxss
├── classic
│ ├── classic-beh.js
│ ├── classic.wxss
│ ├── essay
│ │ ├── images
│ │ │ ├── .DS_Store
│ │ │ └── essay@tag.png
│ │ ├── index.js
│ │ ├── index.json
│ │ ├── index.wxml
│ │ └── index.wxss
│ ├── movie
│ │ ├── images
│ │ │ └── movie@tag.png
│ │ ├── index.js
│ │ ├── index.json
│ │ ├── index.wxml
│ │ └── index.wxss
│ └── music
│ │ ├── images
│ │ ├── .DS_Store
│ │ ├── music@tag.png
│ │ ├── player@pause.png
│ │ └── player@play.png
│ │ ├── index.js
│ │ ├── index.json
│ │ ├── index.wxml
│ │ └── index.wxss
├── episode
│ ├── index.js
│ ├── index.json
│ ├── index.wxml
│ └── index.wxss
├── image-button
│ ├── index.js
│ ├── index.json
│ ├── index.wxml
│ └── index.wxss
├── like
│ ├── images
│ │ ├── like.png
│ │ └── like@dis.png
│ ├── index.js
│ ├── index.json
│ ├── index.wxml
│ └── index.wxss
├── loading
│ ├── index.js
│ ├── index.json
│ ├── index.wxml
│ └── index.wxss
├── mask
│ ├── index.js
│ ├── index.json
│ ├── index.wxml
│ └── index.wxss
├── navi
│ ├── images
│ │ ├── .DS_Store
│ │ ├── triangle.dis@left.png
│ │ ├── triangle.dis@right.png
│ │ ├── triangle@left.png
│ │ └── triangle@right.png
│ ├── index.js
│ ├── index.json
│ ├── index.wxml
│ └── index.wxss
├── preview
│ ├── index.js
│ ├── index.json
│ ├── index.wxml
│ └── index.wxss
├── search
│ ├── images
│ │ ├── cancel.png
│ │ └── search.png
│ ├── index.js
│ ├── index.json
│ ├── index.wxml
│ └── index.wxss
└── tag
│ ├── index.js
│ ├── index.json
│ ├── index.wxml
│ └── index.wxss
├── config.js
├── images
├── book
│ ├── quality.png
│ └── tip.png
├── icon
│ ├── search.png
│ └── share.png
├── my
│ ├── about.png
│ ├── course.png
│ ├── like.png
│ ├── my.png
│ ├── my@bg.png
│ ├── study.png
│ └── vendor.png
└── tab
│ ├── book.png
│ ├── book@highlight.png
│ ├── classic.png
│ ├── classic@highlight.png
│ ├── my.png
│ └── my@highlight.png
├── models
├── book.js
├── classic.js
├── keyword.js
└── like.js
├── pages
├── about
│ ├── about.js
│ ├── about.json
│ ├── about.wxml
│ └── about.wxss
├── book
│ ├── book.js
│ ├── book.json
│ ├── book.wxml
│ └── book.wxss
├── classic-detail
│ ├── index.js
│ ├── index.json
│ ├── index.wxml
│ └── index.wxss
├── classic
│ ├── classic.js
│ ├── classic.json
│ ├── classic.wxml
│ └── classic.wxss
├── course
│ ├── course.js
│ ├── course.json
│ ├── course.wxml
│ └── course.wxss
├── detail
│ ├── detail.js
│ ├── detail.json
│ ├── detail.wxml
│ └── detail.wxss
└── my
│ ├── my.js
│ ├── my.json
│ ├── my.wxml
│ └── my.wxss
├── reademeimages
├── aixin.jpg
└── navi.jpg
├── readme.md
├── sitemap.json
└── utils
├── common.js
├── filter.wxs
├── http-promise.js
├── http.js
└── util.js
/.gitignore:
--------------------------------------------------------------------------------
1 | *.pyc
2 | .vscode
3 | .idea/
4 | Pipfile.lock
5 | project.config.json
6 | node_modules
7 |
--------------------------------------------------------------------------------
/app.js:
--------------------------------------------------------------------------------
1 | //app.js
2 | App({
3 | onLaunch: function () {
4 | // 展示本地存储能力
5 | var logs = wx.getStorageSync('logs') || []
6 | logs.unshift(Date.now())
7 | wx.setStorageSync('logs', logs)
8 |
9 | // 登录
10 | wx.login({
11 | success: res => {
12 | // 发送 res.code 到后台换取 openId, sessionKey, unionId
13 | }
14 | })
15 | // 获取用户信息
16 | wx.getSetting({
17 | success: res => {
18 | if (res.authSetting['scope.userInfo']) {
19 | // 已经授权,可以直接调用 getUserInfo 获取头像昵称,不会弹框
20 | wx.getUserInfo({
21 | success: res => {
22 | // 可以将 res 发送给后台解码出 unionId
23 | this.globalData.userInfo = res.userInfo
24 |
25 | // 由于 getUserInfo 是网络请求,可能会在 Page.onLoad 之后才返回
26 | // 所以此处加入 callback 以防止这种情况
27 | if (this.userInfoReadyCallback) {
28 | this.userInfoReadyCallback(res)
29 | }
30 | }
31 | })
32 | }
33 | }
34 | })
35 | },
36 | globalData: {
37 | userInfo: null
38 | }
39 | })
--------------------------------------------------------------------------------
/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "pages": [
3 | "pages/classic/classic",
4 | "pages/book/book",
5 | "pages/my/my",
6 | "pages/detail/detail",
7 | "pages/classic-detail/index",
8 | "pages/about/about",
9 | "pages/course/course"
10 |
11 | ],
12 | "requiredBackgroundModes": [
13 | "audio"
14 | ],
15 | "window": {
16 | "backgroundTextStyle": "light",
17 | "navigationBarBackgroundColor": "#fff",
18 | "navigationBarTitleText": "海洋",
19 | "navigationBarTextStyle": "black",
20 | "navigationStyle": "default",
21 | "enablePullDownRefresh": false
22 | },
23 | "tabBar": {
24 | "selectedColor": "#000000",
25 | "backgroundColor": "#ffffff",
26 | "color": "#c7c7c7",
27 | "list": [
28 | {
29 | "pagePath": "pages/classic/classic",
30 | "text": "流行",
31 | "selectedIconPath": "/images/tab/classic@highlight.png",
32 | "iconPath": "/images/tab/classic.png"
33 | },
34 | {
35 | "pagePath": "pages/book/book",
36 | "selectedIconPath": "/images/tab/book@highlight.png",
37 | "text": "书单",
38 | "iconPath": "/images/tab/book.png"
39 | },
40 | {
41 | "pagePath": "pages/my/my",
42 | "selectedIconPath": "/images/tab/my@highlight.png",
43 | "text": "喜欢",
44 | "iconPath": "/images/tab/my.png"
45 | }
46 | ]
47 | },
48 | "sitemapLocation": "sitemap.json"
49 | }
--------------------------------------------------------------------------------
/app.wxss:
--------------------------------------------------------------------------------
1 | /**app.wxss**/
2 |
3 | page {
4 | font-family: "PingFangSC-Thin";
5 | font-size: 32rpx;
6 | }
7 |
--------------------------------------------------------------------------------
/components/behaviors/pagination.js:
--------------------------------------------------------------------------------
1 | const paginationBev = Behavior({
2 | data: {
3 | dataArray: [], //请求返回的数组
4 | total: null, //数据的总数
5 | noneResult: false, //没有得到想要的搜索结果
6 | loading: false, //表示是否正在发送请求,默认是没有发送请求
7 | },
8 |
9 | methods: {
10 | // 加载更多拼接更多数据到数组中
11 | setMoreData(dataArray) {
12 | const tempArray = this.data.dataArray.concat(dataArray)
13 | this.setData({
14 | dataArray: tempArray
15 | })
16 | },
17 |
18 | // 获取得到的数据的数组的长度
19 | getCurrentStart() {
20 | return this.data.dataArray.length
21 | },
22 |
23 | // 获取设置数据的 总长度
24 | // 如果返回的结果为0,就说明没有得到搜索结果
25 | setTotal(total) {
26 | this.data.total = total
27 | if (total === 0) {
28 | this.setData({
29 | noneResult: true
30 | })
31 | }
32 | },
33 |
34 | // 如果得到数据的长度大于服务器返回的总长度,代表没有更多数据了,就停止发请求
35 | hasMore() {
36 | if (this.data.dataArray.length >= this.data.total) {
37 | return false
38 | } else {
39 | return true
40 | }
41 | },
42 |
43 | // 清空数据,设置初始值
44 | initialize() {
45 | this.setData({
46 | dataArray: [],
47 | noneResult: false,
48 | loading: false,
49 | })
50 | this.data.total = null
51 | },
52 |
53 | // 事件节流机制,判断是否加锁
54 | isLocked() {
55 | return this.data.loading ? true : false
56 | },
57 |
58 | // 加锁
59 | addLocked() {
60 | this.setData({
61 | loading: true
62 | })
63 |
64 | },
65 |
66 | // 解锁
67 | unLocked() {
68 | this.setData({
69 | loading: false
70 | })
71 | },
72 | }
73 |
74 | })
75 |
76 | export {
77 | paginationBev
78 | }
79 |
--------------------------------------------------------------------------------
/components/book/index.js:
--------------------------------------------------------------------------------
1 | // components/book/index.js
2 | Component({
3 | /**
4 | * 组件的属性列表
5 | */
6 | properties: {
7 | book: Object,
8 | showLike: { //每本书下面有个喜欢字样
9 | type: Boolean,
10 | value: true
11 | }
12 | },
13 |
14 | /**
15 | * 组件的初始数据
16 | */
17 | data: {
18 |
19 | },
20 |
21 | /**
22 | * 组件的方法列表
23 | */
24 | methods: {
25 | onTap(event) {
26 | const bid = this.properties.book.id
27 | // 尽量不要在组件的内部写业务逻辑,会降低组件的通用性,他只服务于当前的项目,属于项目组件
28 | wx.navigateTo({
29 | url: `/pages/detail/detail?bid=${bid}`
30 | })
31 | }
32 | }
33 | })
34 |
--------------------------------------------------------------------------------
/components/book/index.json:
--------------------------------------------------------------------------------
1 | {
2 | "component": true,
3 | "usingComponents": {}
4 | }
--------------------------------------------------------------------------------
/components/book/index.wxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{book.title}}
5 | {{book.author}}
6 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/components/book/index.wxss:
--------------------------------------------------------------------------------
1 | .container {
2 | margin-top: 30rpx;
3 | display: flex;
4 | position: relative;
5 | box-shadow: 2px 2px 3px #e3e3e3;
6 | flex-direction: column;
7 | width: 240rpx;
8 | height: 360rpx;
9 | }
10 |
11 | .container image {
12 | width: 100%;
13 | height: 100%;
14 | border-radius: 2px;
15 | }
16 |
17 | .description {
18 | width: 100%;
19 | box-sizing: border-box;
20 | position: absolute;
21 | bottom: 0;
22 | background-color: #fff;
23 | padding: 5rpx 10rpx 8rpx 15rpx;
24 | font-size: 24rpx;
25 | flex-direction: column;
26 | display: flex;
27 | border-bottom-right-radius: 2px;
28 | border-bottom-left-radius: 2px;
29 | }
30 |
31 | .title {
32 | margin-top: 10rpx;
33 | text-overflow: ellipsis;
34 | white-space: nowrap;
35 | overflow: hidden;
36 | }
37 |
38 | .auther {
39 | font-size: 20rpx;
40 | color: #999;
41 | margin-bottom: 10rpx;
42 | text-overflow: ellipsis;
43 | white-space: nowrap;
44 | overflow: hidden;
45 | }
46 |
47 | .foot {
48 | font-size: 20rpx;
49 | display: flex;
50 | flex-direction: row;
51 | justify-content: flex-end;
52 | }
53 |
54 | .footer {
55 | color: #666;
56 | }
57 |
--------------------------------------------------------------------------------
/components/classic/classic-beh.js:
--------------------------------------------------------------------------------
1 | let classicBeh = Behavior({ //创建公用行为
2 | properties: {
3 | img: String,
4 | content: String,
5 | hidden: Boolean
6 | },
7 | data: {
8 |
9 | },
10 | methods: {
11 |
12 | },
13 | attached: function() {
14 |
15 | }
16 | })
17 |
18 | export {
19 | classicBeh
20 | }
21 |
--------------------------------------------------------------------------------
/components/classic/classic.wxss:
--------------------------------------------------------------------------------
1 | /* components/classic/movie/index.wxss */
2 |
3 | .classic-container {
4 | display: flex;
5 | flex-direction: column;
6 | align-items: center;
7 | }
8 |
9 | .classic-img {
10 | width: 750rpx;
11 | height: 500rpx;
12 | }
13 |
14 | .tag {
15 | width: 46rpx;
16 | height: 142rpx;
17 | position: relative;
18 | right: 310rpx;
19 | bottom: 58rpx;
20 | }
21 |
22 | .content {
23 | font-size: 36rpx;
24 | max-width: 550rpx;
25 | /* 文字多少,都能让其居中 */
26 | }
27 |
--------------------------------------------------------------------------------
/components/classic/essay/images/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wudiufo/WeChat-Mini-Program/1e715c852b7ae555f45e613f8412fd43d013ec3f/components/classic/essay/images/.DS_Store
--------------------------------------------------------------------------------
/components/classic/essay/images/essay@tag.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wudiufo/WeChat-Mini-Program/1e715c852b7ae555f45e613f8412fd43d013ec3f/components/classic/essay/images/essay@tag.png
--------------------------------------------------------------------------------
/components/classic/essay/index.js:
--------------------------------------------------------------------------------
1 | import {
2 | classicBeh
3 | } from '../classic-beh'
4 |
5 | Component({
6 |
7 | behaviors: [classicBeh],
8 | /**
9 | * 组件的属性列表
10 | */
11 | properties: {
12 |
13 | },
14 |
15 | /**
16 | * 组件的初始数据
17 | */
18 | data: {
19 |
20 | },
21 |
22 | /**
23 | * 组件的方法列表
24 | */
25 | methods: {
26 |
27 | }
28 | })
29 |
--------------------------------------------------------------------------------
/components/classic/essay/index.json:
--------------------------------------------------------------------------------
1 | {
2 | "component": true,
3 | "usingComponents": {}
4 | }
--------------------------------------------------------------------------------
/components/classic/essay/index.wxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{content}}
5 |
6 |
7 |
--------------------------------------------------------------------------------
/components/classic/essay/index.wxss:
--------------------------------------------------------------------------------
1 | @import "../classic.wxss"
2 |
--------------------------------------------------------------------------------
/components/classic/movie/images/movie@tag.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wudiufo/WeChat-Mini-Program/1e715c852b7ae555f45e613f8412fd43d013ec3f/components/classic/movie/images/movie@tag.png
--------------------------------------------------------------------------------
/components/classic/movie/index.js:
--------------------------------------------------------------------------------
1 | import {
2 | classicBeh
3 | } from '../classic-beh'
4 |
5 | Component({
6 |
7 | behaviors: [classicBeh],
8 | /**
9 | * 组件的属性列表
10 | */
11 | properties: {
12 | // img: String,
13 | // content: String
14 | // hidden: Boolean
15 | },
16 |
17 | /**
18 | * 组件的初始数据
19 | */
20 | data: {
21 |
22 | },
23 |
24 | /**
25 | * 组件的方法列表
26 | */
27 | methods: {
28 |
29 | }
30 | })
31 |
--------------------------------------------------------------------------------
/components/classic/movie/index.json:
--------------------------------------------------------------------------------
1 | {
2 | "component": true,
3 | "usingComponents": {}
4 | }
--------------------------------------------------------------------------------
/components/classic/movie/index.wxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{content}}
5 |
6 |
7 |
--------------------------------------------------------------------------------
/components/classic/movie/index.wxss:
--------------------------------------------------------------------------------
1 | @import "../classic.wxss"
2 |
--------------------------------------------------------------------------------
/components/classic/music/images/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wudiufo/WeChat-Mini-Program/1e715c852b7ae555f45e613f8412fd43d013ec3f/components/classic/music/images/.DS_Store
--------------------------------------------------------------------------------
/components/classic/music/images/music@tag.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wudiufo/WeChat-Mini-Program/1e715c852b7ae555f45e613f8412fd43d013ec3f/components/classic/music/images/music@tag.png
--------------------------------------------------------------------------------
/components/classic/music/images/player@pause.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wudiufo/WeChat-Mini-Program/1e715c852b7ae555f45e613f8412fd43d013ec3f/components/classic/music/images/player@pause.png
--------------------------------------------------------------------------------
/components/classic/music/images/player@play.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wudiufo/WeChat-Mini-Program/1e715c852b7ae555f45e613f8412fd43d013ec3f/components/classic/music/images/player@play.png
--------------------------------------------------------------------------------
/components/classic/music/index.js:
--------------------------------------------------------------------------------
1 | import {
2 | classicBeh
3 | } from '../classic-beh'
4 |
5 | // 获取小程序音乐管理对象
6 | const mMgr = wx.getBackgroundAudioManager()
7 |
8 | Component({
9 | behaviors: [classicBeh], // 组件的属性列表
10 | /**
11 | * 组件的属性列表,动画API CSS3
12 | */
13 | properties: {
14 | // img: String,
15 | // content: String
16 | src: String,
17 | title: String
18 |
19 | },
20 |
21 | /**
22 | * 组件的初始数据
23 | */
24 | data: {
25 | pauseSrc: 'images/player@pause.png',
26 | playSrc: 'images/player@play.png',
27 | playing: false, //当前的音乐默认是不播放的
28 | },
29 |
30 | // 在组件实例进入页面节点树时执行
31 | // hidden,ready,created都触发不了生命周期函数
32 | attached: function(event) {
33 | console.log('attach实例进入页面', '触发1')
34 | this._monitorSwitch()
35 | this._recoverStatus()
36 |
37 |
38 | },
39 |
40 | // hidden 不会触发完整生命周期, 适用于频繁切换; wx:if 会触发完整生命周期, 不大可能改变
41 | // 组件卸载的生命周期函数,微信生命周期, 组件退出界面节点树时执行
42 | // 组件卸载音乐停止播放,但这时不生效是因为,在classic.wxml中用的是hidden,应改为if
43 | detached: function(event) {
44 | // mMgr.stop() //为了保证背景音乐的持续播放就不能加stop
45 | },
46 |
47 | /**
48 | * 组件的方法列表
49 | */
50 | methods: {
51 | // 为图片绑定触摸播放事件
52 | onPlay: function(evevt) {
53 | console.log('onPlay为图片绑定触摸', '触发4')
54 | //如果没有播放就改为播放,如果播放就改为暂停
55 | if (!this.data.playing) {
56 | console.log('onPlay为图片绑定触摸!this.data.playing', '触发5', this.data.playing)
57 | //图片切换
58 | this.setData({
59 | playing: true
60 | })
61 | // 如果当前播放的地址和当前的组件的地址一样,就播放
62 | // 如果当前播放的地址和当前的组件的地址不一样,就把当前组件地址赋值给要播放的地址
63 |
64 | //播放音乐
65 | // mMgr.play()
66 | mMgr.src = this.properties.src
67 | mMgr.title = this.properties.title
68 | } else {
69 | console.log('onPlay为图片绑定触摸', '触发4', this.data.playing)
70 | this.setData({
71 | playing: false
72 | })
73 | mMgr.pause() //音乐暂停
74 | }
75 |
76 | },
77 |
78 | // 监听音乐的播放状态,如果当前组件没有播放的音乐,就设置playing为false。如果播放的地址和当前正在播放的音乐的地址一样,就让播放状态为true
79 | _recoverStatus: function() {
80 | console.log('recoverStatus播放状态', '触发2', this.data.playing)
81 | if (mMgr.paused) {
82 | this.setData({
83 | playing: false
84 | })
85 | console.log('recoverStatus播放状态mMgr.paused', '触发6', this.data.playing)
86 | return
87 |
88 | }
89 | if (this.properties.src === mMgr.src) {
90 |
91 | this.setData({
92 | playing: true
93 | })
94 |
95 | console.log('recoverStatus播放状态mMgr.src === this.properties.src', '触发5', this.data.playing)
96 | }
97 | },
98 |
99 | // 监听播放状态,总控开关就可以控制播放状态,结局总控开关和页面不同步问题
100 | _monitorSwitch: function() {
101 | console.log('monitorSwitch背景音频', '触发3')
102 | // 监听背景音频播放事件
103 | mMgr.onPlay(() => {
104 | this._recoverStatus()
105 | console.log('onPlay ' + this.data.playing)
106 | })
107 | // 监听背景音频暂停事件
108 | mMgr.onPause(() => {
109 | this._recoverStatus()
110 | console.log('onPause ' + this.data.playing)
111 | })
112 | // 关闭音乐控制台,监听背景音频停止事件
113 | mMgr.onStop(() => {
114 | this._recoverStatus()
115 | console.log('onStop ' + this.data.playing)
116 | })
117 | // 监听背景音频自然播放结束事件
118 | mMgr.onEnded(() => {
119 | this._recoverStatus()
120 | console.log('onEnded ' + this.data.playing)
121 | })
122 | }
123 |
124 |
125 |
126 | }
127 | })
128 |
--------------------------------------------------------------------------------
/components/classic/music/index.json:
--------------------------------------------------------------------------------
1 | {
2 | "component": true,
3 | "usingComponents": {}
4 | }
--------------------------------------------------------------------------------
/components/classic/music/index.wxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | {{content}}
7 |
--------------------------------------------------------------------------------
/components/classic/music/index.wxss:
--------------------------------------------------------------------------------
1 | .container {
2 | display: flex;
3 | flex-direction: column;
4 | align-items: center;
5 | }
6 |
7 | .classic-img {
8 | width: 422rpx;
9 | height: 422rpx;
10 | margin-top: 60rpx;
11 | border-radius: 50%;
12 | }
13 |
14 | .player-img {
15 | width: 120rpx;
16 | height: 120rpx;
17 | position: relative;
18 | bottom: 270rpx;
19 | }
20 |
21 | .tag {
22 | width: 44rpx;
23 | height: 128rpx;
24 | position: relative;
25 | bottom: 160rpx;
26 | right: 310rpx;
27 | }
28 |
29 | .rotation {
30 | -webkit-transform: rotate(360deg);
31 | animation: rotation 12s linear infinite;
32 | -moz-animation: rotation 12s linear infinite;
33 | -webkit-animation: rotation 12s linear infinite;
34 | -o-animation: rotation 12s linear infinite;
35 | }
36 |
37 | @-webkit-keyframes rotation {
38 | from {
39 | -webkit-transform: rotate(0deg);
40 | }
41 | to {
42 | -webkit-transform: rotate(360deg);
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/components/episode/index.js:
--------------------------------------------------------------------------------
1 | // components/epsoide/index.js
2 | Component({
3 | /**
4 | * 组件的属性列表
5 | */
6 | properties: {
7 | index: { //默认显示为0
8 | type: String,
9 | observer: function(newVal, oldVal, changedPath) {
10 | let val = newVal < 10 ? '0' + newVal : newVal
11 | this.setData({
12 | _index: val
13 | })
14 | }
15 | }
16 | },
17 |
18 | /**
19 | * 组件的初始数据
20 | */
21 | data: {
22 | months: [
23 | '一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月',
24 | '十二月'
25 | ],
26 | year: 0,
27 | month: '',
28 | _index: ''
29 | },
30 |
31 | // 生命周期函数
32 | attached: function() {
33 | let date = new Date()
34 | let year = date.getFullYear()
35 | let month = date.getMonth()
36 |
37 | this.setData({
38 | year: year,
39 | month: this.data.months[month]
40 | })
41 | },
42 |
43 | /**
44 | * 组件的方法列表
45 | */
46 | methods: {
47 |
48 | }
49 | })
50 |
--------------------------------------------------------------------------------
/components/episode/index.json:
--------------------------------------------------------------------------------
1 | {
2 | "component": true,
3 | "usingComponents": {}
4 | }
--------------------------------------------------------------------------------
/components/episode/index.wxml:
--------------------------------------------------------------------------------
1 |
2 |
3 | No.
4 | {{_index}}
5 |
6 |
7 |
8 | {{month}}
9 | {{year}}
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/components/episode/index.wxss:
--------------------------------------------------------------------------------
1 | .container {
2 | height: 60rpx;
3 | /* 让其变为行内元素,宽度自适应 */
4 | display: inline-flex;
5 | flex-direction: row;
6 | }
7 |
8 | .plain {
9 | font-size: 32rpx;
10 | }
11 |
12 | .index {
13 | font-size: 60rpx;
14 | line-height: 60rpx;
15 | font-weight: 800;
16 | margin-right: 14rpx;
17 | }
18 |
19 | .line {
20 | height: 44rpx;
21 | margin-right: 14rpx;
22 | border-left: 1px solid black;
23 | }
24 |
25 | .index-container {
26 | display: flex;
27 | flex-direction: row;
28 | align-items: baseline;
29 | }
30 |
31 | .data-container {
32 | height: 60rpx;
33 | display: flex;
34 | flex-direction: column;
35 | margin-top: 5rpx;
36 | }
37 |
38 | .month {
39 | font-size: 24rpx;
40 | line-height: 24rpx;
41 | margin-right: 2rpx;
42 | }
43 |
44 | .year {
45 | font-size: 20rpx;
46 | }
47 |
--------------------------------------------------------------------------------
/components/image-button/index.js:
--------------------------------------------------------------------------------
1 | // components/image-button/index.html.js
2 | Component({
3 | /**
4 | * 组件的属性列表
5 | */
6 | options: {
7 | multipleSlots: true // 在组件定义时的选项中启用多slot支持
8 | },
9 | // externalClasses: ['ex-btn-class'],
10 | properties: {
11 | openType: {
12 | type: String
13 | },
14 | imageSrc: {
15 | type: String
16 | },
17 | bindgetuserinfo: {
18 | type: String
19 | }
20 | },
21 |
22 | /**
23 | * 组件的初始数据
24 | */
25 | data: {
26 |
27 | },
28 |
29 |
30 |
31 | /**
32 | * 组件的方法列表
33 | */
34 | methods: {
35 | onGetUserInfo(event) {
36 | this.triggerEvent('getuserinfo', event.detail, {})
37 | },
38 | }
39 | })
--------------------------------------------------------------------------------
/components/image-button/index.json:
--------------------------------------------------------------------------------
1 | {
2 | "component": true,
3 | "usingComponents": {}
4 | }
--------------------------------------------------------------------------------
/components/image-button/index.wxml:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/components/image-button/index.wxss:
--------------------------------------------------------------------------------
1 | .container {
2 | /* display: flex;
3 | flex-direction: row;
4 | justify-content: center; */
5 | /* display: flex; */
6 | padding: 0 !important;
7 | border: none !important;
8 | /* display: inline !important; */
9 | }
10 |
11 |
12 | /* .img{
13 | width:100%;
14 | height:100%;
15 | } */
16 |
--------------------------------------------------------------------------------
/components/like/images/like.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wudiufo/WeChat-Mini-Program/1e715c852b7ae555f45e613f8412fd43d013ec3f/components/like/images/like.png
--------------------------------------------------------------------------------
/components/like/images/like@dis.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wudiufo/WeChat-Mini-Program/1e715c852b7ae555f45e613f8412fd43d013ec3f/components/like/images/like@dis.png
--------------------------------------------------------------------------------
/components/like/index.js:
--------------------------------------------------------------------------------
1 | // components/like/like-cmp.js
2 | Component({
3 | /**
4 | * 组件的属性列表
5 | */
6 | properties: {
7 | like: Boolean,
8 | count: Number,
9 | readOnly: Boolean
10 | },
11 |
12 | data: {
13 | yesSrc: 'images/like.png',
14 | noSrc: 'images/like@dis.png'
15 | },
16 |
17 | methods: {
18 | onLike(event) { //自定义事件
19 | if (this.properties.readOnly) { //如果是只读的就不会执行后面的操作
20 | return
21 | }
22 | let count = this.properties.count
23 | let like = this.properties.like
24 | count = like ? count - 1 : count + 1
25 | this.setData({ //是异步的,先执行count+1,在执行!like
26 | count: count,
27 | like: !like
28 | })
29 | let behavior = like ? 'like' : 'cancel'
30 | //自定义事件
31 | this.triggerEvent('like', {
32 | behavior: behavior
33 | }, {})
34 | }
35 | }
36 | })
37 |
--------------------------------------------------------------------------------
/components/like/index.json:
--------------------------------------------------------------------------------
1 | {
2 | "usingComponents": {}
3 | }
--------------------------------------------------------------------------------
/components/like/index.wxml:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{count}}
4 |
--------------------------------------------------------------------------------
/components/like/index.wxss:
--------------------------------------------------------------------------------
1 | /* components/like/like-cmp.wxss */
2 |
3 | .container {
4 | /* 变成行内元素,自适应内容宽度 */
5 | display: inline-flex;
6 | flex-direction: row;
7 | /* 必须指定宽度,否则会出现移动 */
8 | width: 90rpx;
9 | padding: 10rpx;
10 | }
11 |
12 | .container text {
13 | font-size: 24rpx;
14 | color: #bbb;
15 | /* 消除数字上下空白2px */
16 | line-height: 24rpx;
17 | position: relative;
18 | bottom: 10rpx;
19 | left: 6rpx;
20 | }
21 |
22 | .container image {
23 | width: 32rpx;
24 | height: 28rpx;
25 | }
26 |
--------------------------------------------------------------------------------
/components/loading/index.js:
--------------------------------------------------------------------------------
1 | // components/loading/index.js
2 | Component({
3 | /**
4 | * 组件的属性列表
5 | */
6 | properties: {
7 |
8 | },
9 |
10 | /**
11 | * 组件的初始数据
12 | */
13 | data: {
14 |
15 | },
16 |
17 | /**
18 | * 组件的方法列表
19 | */
20 | methods: {
21 |
22 | }
23 | })
24 |
--------------------------------------------------------------------------------
/components/loading/index.json:
--------------------------------------------------------------------------------
1 | {
2 | "component": true,
3 | "usingComponents": {}
4 | }
--------------------------------------------------------------------------------
/components/loading/index.wxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/components/loading/index.wxss:
--------------------------------------------------------------------------------
1 | .spinner {
2 | width: 40rpx;
3 | height: 40rpx;
4 | position: relative;
5 | /* margin: 100px auto; */
6 | }
7 |
8 | .double-bounce1,
9 | .double-bounce2 {
10 | width: 100%;
11 | height: 100%;
12 | border-radius: 50%;
13 | background-color: #3063b2;
14 | opacity: 0.6;
15 | position: absolute;
16 | top: 0;
17 | left: 0;
18 | -webkit-animation: bounce 2.0s infinite ease-in-out;
19 | animation: bounce 2.0s infinite ease-in-out;
20 | }
21 |
22 | .double-bounce2 {
23 | -webkit-animation-delay: -1.0s;
24 | animation-delay: -1.0s;
25 | }
26 |
27 | @-webkit-keyframes bounce {
28 | 0%,
29 | 100% {
30 | -webkit-transform: scale(0.0)
31 | }
32 | 50% {
33 | -webkit-transform: scale(1.0)
34 | }
35 | }
36 |
37 | @keyframes bounce {
38 | 0%,
39 | 100% {
40 | transform: scale(0.0);
41 | -webkit-transform: scale(0.0);
42 | }
43 | 50% {
44 | transform: scale(1.0);
45 | -webkit-transform: scale(1.0);
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/components/mask/index.js:
--------------------------------------------------------------------------------
1 | // components/mask/index.js
2 | Component({
3 | /**
4 | * 组件的属性列表
5 | */
6 | properties: {
7 |
8 | },
9 |
10 | /**
11 | * 组件的初始数据
12 | */
13 | data: {
14 |
15 | },
16 |
17 | /**
18 | * 组件的方法列表
19 | */
20 | methods: {
21 |
22 | }
23 | })
24 |
--------------------------------------------------------------------------------
/components/mask/index.json:
--------------------------------------------------------------------------------
1 | {
2 | "component": true,
3 | "usingComponents": {}
4 | }
--------------------------------------------------------------------------------
/components/mask/index.wxml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/components/mask/index.wxss:
--------------------------------------------------------------------------------
1 | /* components/mask/index.wxss */
2 |
3 |
4 | /* 弹出评论时添加背景蒙层 */
5 |
6 | .container {
7 | background-color: #000000;
8 | position: fixed;
9 | top: 0;
10 | opacity: 0.6;
11 | width: 100%;
12 | height: 100%;
13 | z-index: 99;
14 | }
15 |
--------------------------------------------------------------------------------
/components/navi/images/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wudiufo/WeChat-Mini-Program/1e715c852b7ae555f45e613f8412fd43d013ec3f/components/navi/images/.DS_Store
--------------------------------------------------------------------------------
/components/navi/images/triangle.dis@left.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wudiufo/WeChat-Mini-Program/1e715c852b7ae555f45e613f8412fd43d013ec3f/components/navi/images/triangle.dis@left.png
--------------------------------------------------------------------------------
/components/navi/images/triangle.dis@right.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wudiufo/WeChat-Mini-Program/1e715c852b7ae555f45e613f8412fd43d013ec3f/components/navi/images/triangle.dis@right.png
--------------------------------------------------------------------------------
/components/navi/images/triangle@left.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wudiufo/WeChat-Mini-Program/1e715c852b7ae555f45e613f8412fd43d013ec3f/components/navi/images/triangle@left.png
--------------------------------------------------------------------------------
/components/navi/images/triangle@right.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wudiufo/WeChat-Mini-Program/1e715c852b7ae555f45e613f8412fd43d013ec3f/components/navi/images/triangle@right.png
--------------------------------------------------------------------------------
/components/navi/index.js:
--------------------------------------------------------------------------------
1 | // components/navi/navi.js
2 | Component({
3 | /**
4 | * 组件的属性列表
5 | */
6 | properties: {
7 | title: String,
8 | first: Boolean, //如果是第一期向右的箭头就禁用
9 | latest: Boolean //如果是最新一期向左的箭头就禁用
10 | },
11 |
12 | /**
13 | * 组件的初始数据
14 | */
15 | data: {
16 | disLeftSrc: './images/triangle.dis@left.png',
17 | leftSrc: './images/triangle@left.png',
18 | disRightSrc: './images/triangle.dis@right.png',
19 | rightSrc: './images/triangle@right.png'
20 | },
21 |
22 | /**
23 | * 组件的方法列表
24 | */
25 | methods: {
26 | onLeft: function(event) { //不是最新一期
27 | if (!this.properties.latest) {
28 | this.triggerEvent('left', {}, {})
29 | }
30 |
31 | },
32 | onRight: function(event) { //不是第一期
33 | if (!this.properties.first) {
34 | this.triggerEvent('right', {}, {})
35 | }
36 |
37 | }
38 | }
39 | })
40 |
--------------------------------------------------------------------------------
/components/navi/index.json:
--------------------------------------------------------------------------------
1 | {
2 | "component": true,
3 | "usingComponents": {}
4 | }
--------------------------------------------------------------------------------
/components/navi/index.wxml:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{title}}
4 |
5 |
6 |
--------------------------------------------------------------------------------
/components/navi/index.wxss:
--------------------------------------------------------------------------------
1 | .container {
2 | width: 600rpx;
3 | height: 80rpx;
4 | background-color: #f7f7f7;
5 | border-radius: 2px;
6 | display: inline-flex;
7 | flex-direction: row;
8 | justify-content: space-between;
9 | align-items: center;
10 | }
11 |
12 | .title {
13 | font-size: 28rpx;
14 | }
15 |
16 | .icon {
17 | height: 80rpx;
18 | width: 80rpx;
19 | }
20 |
--------------------------------------------------------------------------------
/components/preview/index.js:
--------------------------------------------------------------------------------
1 | // components/preview/index.js
2 | Component({
3 | /**
4 | * 组件的属性列表
5 | */
6 | properties: {
7 | classic: {
8 | type: Object,
9 | observer: function(newVal) {
10 | if (newVal) {
11 | var typeText = {
12 | 100: "电影",
13 | 200: "音乐",
14 | 300: "句子"
15 | }[newVal.type]
16 | }
17 | this.setData({
18 | typeText: typeText
19 | })
20 | }
21 | }
22 | },
23 |
24 | /**
25 | * 组件的初始数据
26 | */
27 | data: {
28 | typeText:String
29 | },
30 |
31 | /**
32 | * 组件的方法列表
33 | */
34 | methods: {
35 | onTap:function(event){
36 | // 注意catchtap与bindtap的区别
37 | this.triggerEvent('tap',{
38 | cid:this.properties.classic.id,
39 | type:this.properties.classic.type
40 | },{})
41 | }
42 | }
43 | })
--------------------------------------------------------------------------------
/components/preview/index.json:
--------------------------------------------------------------------------------
1 | {
2 | "component": true,
3 | "usingComponents": {
4 | "v-tag": "/components/tag/index",
5 | "v-like": "/components/like/index"
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/components/preview/index.wxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | {{classic.content}}
8 |
9 |
--------------------------------------------------------------------------------
/components/preview/index.wxss:
--------------------------------------------------------------------------------
1 | .container{
2 | display: flex;
3 | flex-direction: column;
4 | align-items: center;
5 | width:330rpx;
6 | background-color: #ffffff;
7 | }
8 |
9 | .head{
10 | display: flex;
11 | width:100%;
12 | flex-direction: row;
13 | align-items: center;
14 | justify-content: space-between;
15 | height:80rpx;
16 | }
17 |
18 | .tag{
19 | margin-left:20rpx;
20 | margin-top:-2rpx;
21 | height:40rpx;
22 | width:72rpx ;
23 | font-size:24rpx;
24 | background-color:#f7f7f7 !important;
25 | }
26 |
27 | .like{
28 | margin-top:4rpx;
29 | margin-right:4rpx;
30 | }
31 |
32 | .other-img{
33 | width:100%;
34 | height:240rpx;
35 | }
36 |
37 | .music-img{
38 | border-radius: 50%;
39 | width:240rpx;
40 | height:240rpx;
41 | }
42 |
43 | .text{
44 | padding:30rpx;
45 | font-size:28rpx;
46 | height:130rpx;
47 | color:#666666;
48 | overflow: hidden;
49 | }
--------------------------------------------------------------------------------
/components/search/images/cancel.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wudiufo/WeChat-Mini-Program/1e715c852b7ae555f45e613f8412fd43d013ec3f/components/search/images/cancel.png
--------------------------------------------------------------------------------
/components/search/images/search.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wudiufo/WeChat-Mini-Program/1e715c852b7ae555f45e613f8412fd43d013ec3f/components/search/images/search.png
--------------------------------------------------------------------------------
/components/search/index.js:
--------------------------------------------------------------------------------
1 | import {
2 | KeywordModel
3 | } from '../../models/keyword'
4 | import {
5 | BookModel
6 | } from '../../models/book'
7 | import {
8 | paginationBev
9 | } from '../behaviors/pagination'
10 |
11 | const Keywordmodel = new KeywordModel()
12 | const bookmodel = new BookModel()
13 |
14 | // components/search/index.js
15 | Component({
16 | // 组件使用行为需加
17 | behaviors: [paginationBev],
18 |
19 |
20 | /**
21 | * 组件的属性列表
22 | */
23 | properties: {
24 | more: {
25 | type: String,
26 | observer: 'loadMore'
27 | } //从pages/book/book.js 传来的属性,监听滑到底步操作.只要外界传来的属性改变就会触发observer函数执行
28 | },
29 |
30 | /**
31 | * 组件的初始数据
32 | */
33 | data: {
34 | historyWords: [], //历史搜索关键字
35 | hotWords: [], //热门搜索关键字
36 | // dataArray: [], //搜索图书当summary=1,返回概要数据
37 | searching: false, //控制搜索到的图书数据的显隐,默认不显示
38 | q: '', //输入框中要显示的内容
39 | // loading: false, //表示是否正在发送请求,默认是没有发送请求
40 | loadingCenter: false //控制loading加载效果是否显示在中间
41 | },
42 |
43 | // 组件初始化时,小程序默认调用的生命周期函数
44 | attached() {
45 | // const historywords = Keywordmodel.getHistory()
46 | // const hotword = Keywordmodel.getHot()
47 | this.setData({
48 | historyWords: Keywordmodel.getHistory()
49 | })
50 |
51 | Keywordmodel.getHot().then(res => {
52 | this.setData({
53 | hotWords: res.hot
54 | })
55 | })
56 | },
57 |
58 | /**
59 | * 组件的方法列表
60 | */
61 | methods: {
62 | // 只要外界传来的属性改变就会触发observer函数执行
63 | loadMore() {
64 | console.log('监听函数触发到底了')
65 | // 和onConfirm一样都需要调取search方法返回当summary=1,返回概要数据:并更新数据到dataArray中
66 | // 先判断已得到的搜索数据的长度,在调用search方法将最新获取的数据和原来的数据拼接到一起更新数据然后呈现到页面中
67 | if (!this.data.q) {
68 | return
69 | }
70 | // 如果是正在发送请求就什么也不做
71 | if (this.isLocked()) {
72 | return
73 | }
74 | // const length = this.data.dataArray.length
75 |
76 | if (this.hasMore()) {
77 | this.addLocked()
78 | bookmodel.search(this.getCurrentStart(), this.data.q).then(res => {
79 | this.setMoreData(res.books)
80 | this.unLocked()
81 | }, () => {
82 | this.unLocked()
83 | })
84 | }
85 |
86 |
87 | },
88 |
89 |
90 |
91 | // 点击取消将搜索组件关掉,有两种方法:一是,在自己的内部创建一个变量控制显隐,不推荐,因为万一还有其他操作扩展性不好。二是,创建一个自定义事件,将自定义事件传给父级,让父级触发
92 | onCancel(event) {
93 | this.initialize()
94 | this.triggerEvent('cancel', {}, {})
95 | },
96 |
97 | // 触摸搜索图片里的x回到原来输入搜索的页面
98 | onDelete(event) {
99 | this.initialize()
100 | this._hideResult()
101 | },
102 |
103 | // 在input输入框输入完成将输入的内容加到缓存中
104 | onConfirm(event) {
105 | // 为了用户体验好,应该点击完立即显示搜索页面
106 | this._showResult()
107 | // 显示loading效果
108 | this._showLoadingCenter()
109 | // 先清空上一次搜索的数据在加载
110 | // this.initialize()
111 | // 获取搜索的关键词q,调取search方法返回当summary=1,返回概要数据:并更新数据到dataArray中
112 | const q = event.detail.value || event.detail.text
113 | // 不用等数据加载完,输入完成后就把输入的内容显示在输入框中。
114 | this.setData({
115 | q: q
116 | })
117 |
118 | bookmodel.search(0, q).then(res => {
119 | this.setMoreData(res.books)
120 | this.setTotal(res.total)
121 |
122 |
123 | // 不能用户输入什么都保存在缓存中,只有用户搜索到有效的关键字时才保存到缓存中
124 | Keywordmodel.addToHistory(q)
125 |
126 | // 数据加载完成,取消显示loading效果
127 | this._hideLoadingCenter()
128 |
129 | })
130 |
131 | },
132 |
133 | // 更新变量的状态,显示搜索框
134 | _showResult() {
135 | this.setData({
136 | searching: true
137 | })
138 | },
139 |
140 | // 更新变量的状态,隐藏搜索框
141 | _hideResult() {
142 | this.setData({
143 | searching: false,
144 | q: ''
145 | })
146 | },
147 |
148 |
149 |
150 | // 改变loadingCenter的值
151 | _showLoadingCenter() {
152 | this.setData({
153 | loadingCenter: true
154 | })
155 | },
156 |
157 | // 改变loadingCenter的值
158 | _hideLoadingCenter() {
159 | this.setData({
160 | loadingCenter: false
161 | })
162 | }
163 |
164 |
165 |
166 | }
167 | })
168 |
--------------------------------------------------------------------------------
/components/search/index.json:
--------------------------------------------------------------------------------
1 | {
2 | "component": true,
3 | "usingComponents": {
4 | "v-tag": "/components/tag/index",
5 | "v-book": "/components/book/index",
6 | "v-loading": "/components/loading/index"
7 |
8 |
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/components/search/index.wxml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
12 |
13 |
14 |
15 | 历史搜索
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | 热门搜索
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 | 没有搜索到书籍
48 |
49 |
50 |
--------------------------------------------------------------------------------
/components/search/index.wxss:
--------------------------------------------------------------------------------
1 | .container {
2 | display: flex;
3 | flex-direction: column;
4 | align-items: center;
5 | width: 100%;
6 | /* padding: 0 15px; */
7 | }
8 |
9 | .header {
10 | background-color: #fff;
11 | position: fixed;
12 | z-index: 99;
13 | height: 100rpx;
14 | border-top: 1px solid #f5f5ff;
15 | border-bottom: 1px solid #f5f5ff;
16 | display: flex;
17 | flex-direction: row;
18 | width: 750rpx;
19 | /* justify-content: space-between; */
20 | align-items: center;
21 | /* padding: 0 15px; */
22 | }
23 |
24 | .search-container {
25 | display: inline-flex;
26 | flex-direction: row;
27 | /* justify-content: space-between; */
28 | align-items: center;
29 | background-color: #f5f5f5;
30 | border-radius: 50px;
31 | margin-left: 20rpx;
32 | }
33 |
34 | .icon {
35 | width: 14px;
36 | height: 14px;
37 | margin: 0 8px 0 12px
38 | }
39 |
40 | .bar {
41 | border-top-right-radius: 15px;
42 | border-bottom-right-radius: 15px;
43 | display: inline-block;
44 | height: 34px;
45 | width: 500rpx;
46 | font-size: 14px;
47 | }
48 |
49 | .cancel-img {
50 | width: 14px;
51 | height: 14px;
52 | margin-right: 10px;
53 | }
54 |
55 | .cancel {
56 | line-height: 34px;
57 | width: 60px;
58 | text-align: center;
59 | display: inline-block;
60 | border: none;
61 | }
62 |
63 | .history {
64 | width: 690rpx;
65 | margin: 40rpx 0 20rpx 0;
66 | display: flex;
67 | font-size: 14px;
68 | margin-top: 160rpx;
69 | flex-direction: column;
70 | }
71 |
72 | .title {
73 | line-height: 15px;
74 | display: flex;
75 | flex-direction: row;
76 | align-items: center;
77 | /* margin-left:100px; */
78 | }
79 |
80 | .chunk {
81 | height: 15px;
82 | width: 5px;
83 | background-color: #000;
84 | display: inline-block;
85 | margin-right: 10px;
86 | }
87 |
88 | .hot-search {
89 | margin-top: 70rpx;
90 | }
91 |
92 | .tags {
93 | /* padding-left:15px; */
94 | display: flex;
95 | flex-direction: row;
96 | flex-wrap: wrap;
97 | /* justify-content: flex-start; */
98 | margin-top: 24rpx;
99 | padding-left: 15px;
100 | width: 630rpx;
101 | }
102 |
103 | .tags v-tag {
104 | margin-right: 10px;
105 | margin-bottom: 10px;
106 | /* padding-bottom: 10px; */
107 | /* margin-right:6px; */
108 | }
109 |
110 | .books-container {
111 | width: 570rpx;
112 | margin-top: 100rpx;
113 | display: flex;
114 | flex-direction: row;
115 | flex-wrap: wrap;
116 | padding: 0 90rpx 0 90rpx;
117 | justify-content: space-between;
118 | }
119 |
120 | .books-container v-book {
121 | margin-bottom: 25rpx;
122 | }
123 |
124 | .loading-center {
125 | position: absolute;
126 | top: 50%;
127 | left: 50%;
128 | }
129 |
130 | .loading {
131 | margin: 50rpx 0 50rpx 0;
132 | }
133 |
134 | .empty-tip {
135 | display: inline-block;
136 | width: 100%;
137 | text-align: center;
138 | position: absolute;
139 | top: 50%;
140 | /* left: 275rpx; */
141 | }
142 |
--------------------------------------------------------------------------------
/components/tag/index.js:
--------------------------------------------------------------------------------
1 | // components/tag/index.js
2 | Component({
3 |
4 | // 启用slot
5 | options: {
6 | multipleSlots: true
7 | },
8 |
9 | // 外部传进来的css,样式
10 | externalClasses: ['tag-class'],
11 |
12 |
13 | /**
14 | * 组件的属性列表
15 | */
16 | properties: {
17 | text: String
18 | },
19 |
20 | /**
21 | * 组件的初始数据
22 | */
23 | data: {
24 |
25 | },
26 |
27 | /**
28 | * 组件的方法列表
29 | */
30 | methods: {
31 | // 触摸短评小标签时,触发的事件,触发一个自定义事件
32 | onTap(event) {
33 | this.triggerEvent('tapping', {
34 | text: this.properties.text
35 | })
36 | }
37 | }
38 | })
39 |
--------------------------------------------------------------------------------
/components/tag/index.json:
--------------------------------------------------------------------------------
1 | {
2 | "component": true,
3 | "usingComponents": {}
4 | }
--------------------------------------------------------------------------------
/components/tag/index.wxml:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{text}}
4 |
5 |
6 |
--------------------------------------------------------------------------------
/components/tag/index.wxss:
--------------------------------------------------------------------------------
1 | .container {
2 | padding: 4rpx 12rpx;
3 | background-color: #f5f5f5;
4 | color: #666;
5 | border-radius: 2px;
6 | display: inline-flex;
7 | flex-direction: row;
8 | align-items: center;
9 | justify-content: center;
10 | font-size: 28rpx;
11 | }
12 |
--------------------------------------------------------------------------------
/config.js:
--------------------------------------------------------------------------------
1 | const config = {
2 | api_base_url: 'http://bl.7yue.pro/v1/',
3 | appkey: 'RdshydjBvcYZhMZC'
4 | // appkey: 'GgRhTjUNUYn1fHke'
5 | }
6 |
7 | export {
8 | config
9 | }
10 |
--------------------------------------------------------------------------------
/images/book/quality.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wudiufo/WeChat-Mini-Program/1e715c852b7ae555f45e613f8412fd43d013ec3f/images/book/quality.png
--------------------------------------------------------------------------------
/images/book/tip.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wudiufo/WeChat-Mini-Program/1e715c852b7ae555f45e613f8412fd43d013ec3f/images/book/tip.png
--------------------------------------------------------------------------------
/images/icon/search.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wudiufo/WeChat-Mini-Program/1e715c852b7ae555f45e613f8412fd43d013ec3f/images/icon/search.png
--------------------------------------------------------------------------------
/images/icon/share.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wudiufo/WeChat-Mini-Program/1e715c852b7ae555f45e613f8412fd43d013ec3f/images/icon/share.png
--------------------------------------------------------------------------------
/images/my/about.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wudiufo/WeChat-Mini-Program/1e715c852b7ae555f45e613f8412fd43d013ec3f/images/my/about.png
--------------------------------------------------------------------------------
/images/my/course.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wudiufo/WeChat-Mini-Program/1e715c852b7ae555f45e613f8412fd43d013ec3f/images/my/course.png
--------------------------------------------------------------------------------
/images/my/like.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wudiufo/WeChat-Mini-Program/1e715c852b7ae555f45e613f8412fd43d013ec3f/images/my/like.png
--------------------------------------------------------------------------------
/images/my/my.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wudiufo/WeChat-Mini-Program/1e715c852b7ae555f45e613f8412fd43d013ec3f/images/my/my.png
--------------------------------------------------------------------------------
/images/my/my@bg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wudiufo/WeChat-Mini-Program/1e715c852b7ae555f45e613f8412fd43d013ec3f/images/my/my@bg.png
--------------------------------------------------------------------------------
/images/my/study.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wudiufo/WeChat-Mini-Program/1e715c852b7ae555f45e613f8412fd43d013ec3f/images/my/study.png
--------------------------------------------------------------------------------
/images/my/vendor.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wudiufo/WeChat-Mini-Program/1e715c852b7ae555f45e613f8412fd43d013ec3f/images/my/vendor.png
--------------------------------------------------------------------------------
/images/tab/book.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wudiufo/WeChat-Mini-Program/1e715c852b7ae555f45e613f8412fd43d013ec3f/images/tab/book.png
--------------------------------------------------------------------------------
/images/tab/book@highlight.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wudiufo/WeChat-Mini-Program/1e715c852b7ae555f45e613f8412fd43d013ec3f/images/tab/book@highlight.png
--------------------------------------------------------------------------------
/images/tab/classic.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wudiufo/WeChat-Mini-Program/1e715c852b7ae555f45e613f8412fd43d013ec3f/images/tab/classic.png
--------------------------------------------------------------------------------
/images/tab/classic@highlight.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wudiufo/WeChat-Mini-Program/1e715c852b7ae555f45e613f8412fd43d013ec3f/images/tab/classic@highlight.png
--------------------------------------------------------------------------------
/images/tab/my.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wudiufo/WeChat-Mini-Program/1e715c852b7ae555f45e613f8412fd43d013ec3f/images/tab/my.png
--------------------------------------------------------------------------------
/images/tab/my@highlight.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wudiufo/WeChat-Mini-Program/1e715c852b7ae555f45e613f8412fd43d013ec3f/images/tab/my@highlight.png
--------------------------------------------------------------------------------
/models/book.js:
--------------------------------------------------------------------------------
1 | import {
2 | HTTP
3 | } from '../utils/http-promise'
4 |
5 | class BookModel extends HTTP {
6 | // 获取热门书籍
7 | getHotList() {
8 | return this.request({
9 | url: '/book/hot_list'
10 | })
11 | }
12 |
13 | // 获取喜欢书籍数量
14 | getMyBookCount() {
15 | return this.request({
16 | url: '/book/favor/count'
17 | })
18 | }
19 |
20 | // 获取书籍详细信息
21 | getDetail(bid) {
22 | return this.request({
23 | url: `/book/${bid}/detail`
24 | })
25 | }
26 |
27 | // 获取书籍点赞情况
28 | getLikeStatus(bid) {
29 | return this.request({
30 | url: `/book/${bid}/favor`
31 | })
32 | }
33 |
34 | // 获取书籍短评
35 | getComments(bid) {
36 | return this.request({
37 | url: `/book/${bid}/short_comment`
38 | })
39 | }
40 |
41 | // 新增短评
42 | postComment(bid, comment) {
43 | return this.request({
44 | url: '/book/add/short_comment',
45 | method: 'POST',
46 | data: {
47 | book_id: bid,
48 | content: comment
49 | }
50 | })
51 | }
52 |
53 | // 书籍搜索
54 | search(start, q) {
55 | return this.request({
56 | url: '/book/search?summary=1',
57 | data: {
58 | q: q,
59 | start: start
60 | }
61 | })
62 | }
63 |
64 |
65 |
66 | }
67 |
68 | export {
69 | BookModel
70 | }
71 |
--------------------------------------------------------------------------------
/models/classic.js:
--------------------------------------------------------------------------------
1 | import {
2 | HTTP
3 | } from '../utils/http.js'
4 |
5 | class ClassicModel extends HTTP {
6 | constructor() {
7 | super()
8 | }
9 |
10 | //获取最新的期刊
11 | getLatest(cb) {
12 | this.request({
13 | url: 'classic/latest',
14 | success: (res) => {
15 |
16 | this._setLatestIndex(res.index)
17 | cb(res)
18 |
19 | // 将最新的期刊设置到缓存中
20 | let key = this._getKey(res.index)
21 | wx.setStorageSync(key, res)
22 | }
23 | })
24 | }
25 |
26 | // // 获取当前一期的上一期
27 | // getPrevious(index, cb) {
28 | // this.request({
29 | // url: `classic/${index}/previous`,
30 | // success: (res) => {
31 | // cb(res)
32 | // }
33 | // })
34 | // }
35 |
36 | // // 获取当前一期的下一期
37 | // getNext() {
38 | // this.request({
39 | // url: `classic/${index}/next`,
40 | // success: (res) => {
41 | // cb(res)
42 | // }
43 | // })
44 | // }
45 |
46 | // 因为getPrevious,getNext实现代码相似,所以为了简化代码可以合并为一个函数
47 | // 缓存思路:在缓存中寻找key,找不到就发送请求 API,将key写入到缓存中。解决每次都调用Api向服务器发请求,耗费性能
48 | // 在缓存中,确定key
49 | getClassic(index, nextOrPrevious, sCallback) {
50 | // 是next,获取下一期,否则获取上一期
51 | let key = nextOrPrevious === 'next' ? this._getKey(index + 1) : this._getKey(index - 1)
52 | let classic = wx.getStorageSync(key)
53 | if (!classic) {
54 | this.request({
55 | url: `classic/${index}/${nextOrPrevious}`,
56 | success: (res) => {
57 | wx.setStorageSync(this._getKey(res.index), res)
58 | sCallback(res)
59 | }
60 | })
61 | } else { //如果在缓存中有找到key,将返回的内容res保存到缓存中
62 | sCallback(classic)
63 | }
64 |
65 | }
66 |
67 |
68 |
69 | // 当前的期刊是否为第一期
70 | isFirst(index) {
71 | return index === 1 ? true : false
72 | }
73 |
74 | // 当前的期刊是否为最新的一期
75 | isLatest(index) {
76 | // lastestIndex get from storage
77 | let latestIndex = this._getLatestIndex()
78 | return latestIndex === index ? true : false
79 | }
80 |
81 | // 将最新的期刊index存入缓存
82 | _setLatestIndex(index) {
83 | wx.setStorageSync('latest', index)
84 | }
85 |
86 | // 在缓存中获取最新期刊的index
87 | _getLatestIndex() {
88 | let index = wx.getStorageSync('latest')
89 | return index
90 | }
91 |
92 | // 设置缓存中的key
93 | _getKey(index) {
94 | let key = `classic-${index}`
95 | return key
96 | }
97 |
98 | // 获取我喜欢期刊的所有信息
99 | getMyFavor(success) {
100 | const params = {
101 | url: 'classic/favor',
102 | success: success
103 | }
104 | this.request(params)
105 | }
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 | }
129 |
130 | export {
131 | ClassicModel
132 | }
133 |
--------------------------------------------------------------------------------
/models/keyword.js:
--------------------------------------------------------------------------------
1 | import {
2 | HTTP
3 | } from '../utils/http-promise'
4 |
5 | class KeywordModel extends HTTP {
6 | constructor() {
7 | super()
8 | // 把key属性挂载到当前实例上,供实例调取使用
9 | this.key = 'q',
10 | this.maxLength = 10 //搜索关键字的数组最大长度为10
11 | }
12 |
13 | //从缓存中,获取历史搜索关键字,如果缓存中没有直接返回什么也不做
14 | getHistory() {
15 | const words = wx.getStorageSync(this.key)
16 | if (!words) {
17 | return []
18 | }
19 | return words
20 | }
21 |
22 | // 将历史搜索关键字写入缓存中。先从缓存中获取历史关键字的数组,判断是否已经有此关键字。如果没有,获取数组的长度大于最大长度,就将数组最后一项删除。获取数组的长度小于最大长度就将此次输入的关键字加到数组第一位,并且设置到缓存中;
23 | addToHistory(keyword) {
24 | let words = this.getHistory()
25 | const has = words.includes(keyword)
26 | if (!has && keyword.trim() !== '') {
27 | const length = words.length
28 | if (length >= this.maxLength) {
29 | words.pop()
30 | }
31 | words.unshift(keyword)
32 | wx.setStorageSync(this.key, words)
33 | }
34 |
35 | }
36 |
37 | // 获取热门搜素搜关键字
38 | getHot() {
39 | return this.request({
40 | url: '/book/hot_keyword'
41 | })
42 | }
43 | }
44 |
45 | export {
46 | KeywordModel
47 | }
48 |
--------------------------------------------------------------------------------
/models/like.js:
--------------------------------------------------------------------------------
1 | import {
2 | HTTP
3 | } from '../utils/http'
4 |
5 | // 点赞组件
6 | class LikeModel extends HTTP {
7 | // 发送取消点赞,点赞API
8 | like(behavior, artID, category) {
9 | let url = behavior === 'like' ? 'like/cancel' : 'like'
10 | this.request({
11 | url: url,
12 | method: 'POST',
13 | data: {
14 | art_id: artID,
15 | type: category
16 | }
17 | })
18 | }
19 |
20 | // 获取点赞信息
21 | getClassicLikeStatus(artID, category, cb) {
22 | this.request({
23 | url: `classic/${category}/${artID}/favor`,
24 | success: cb
25 | })
26 | }
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 | }
46 | export {
47 | LikeModel
48 | }
49 |
--------------------------------------------------------------------------------
/pages/about/about.js:
--------------------------------------------------------------------------------
1 | // pages/about/about.js
2 | Page({
3 |
4 | /**
5 | * 页面的初始数据
6 | */
7 | data: {
8 |
9 | },
10 |
11 | /**
12 | * 生命周期函数--监听页面加载
13 | */
14 | onLoad: function (options) {
15 |
16 | },
17 |
18 | /**
19 | * 生命周期函数--监听页面初次渲染完成
20 | */
21 | onReady: function () {
22 |
23 | },
24 |
25 | /**
26 | * 生命周期函数--监听页面显示
27 | */
28 | onShow: function () {
29 |
30 | },
31 |
32 | /**
33 | * 生命周期函数--监听页面隐藏
34 | */
35 | onHide: function () {
36 |
37 | },
38 |
39 | /**
40 | * 生命周期函数--监听页面卸载
41 | */
42 | onUnload: function () {
43 |
44 | },
45 |
46 | /**
47 | * 页面相关事件处理函数--监听用户下拉动作
48 | */
49 | onPullDownRefresh: function () {
50 |
51 | },
52 |
53 | /**
54 | * 页面上拉触底事件的处理函数
55 | */
56 | onReachBottom: function () {
57 |
58 | },
59 |
60 | /**
61 | * 用户点击右上角分享
62 | */
63 | onShareAppMessage: function () {
64 |
65 | }
66 | })
--------------------------------------------------------------------------------
/pages/about/about.json:
--------------------------------------------------------------------------------
1 | {
2 | "disableScroll":true
3 | }
--------------------------------------------------------------------------------
/pages/about/about.wxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/pages/book/book.js:
--------------------------------------------------------------------------------
1 | import {
2 | BookModel
3 | } from '../../models/book'
4 | import {
5 | random
6 | } from '../../utils/common'
7 |
8 | const bookModel = new BookModel()
9 |
10 |
11 | // pages/book/book.js
12 | Page({
13 |
14 | /**
15 | * 页面的初始数据
16 | */
17 | data: {
18 | books: [],
19 | searching: false, //控制搜索框组件search显隐,默认不显示
20 | more: '' //是否需要加载更多数据,默认是不加载
21 | },
22 |
23 | /**
24 | * 生命周期函数--监听页面加载
25 | */
26 | onLoad: function(options) {
27 | // 获取热门书籍
28 | const hotList = bookModel.getHotList()
29 | hotList.then(res => {
30 | console.log(res)
31 | this.setData({
32 | books: res
33 | })
34 | })
35 | },
36 |
37 | // 触摸搜索触发onSearching事件,改变搜索组件searing的显隐
38 | onSearching(event) {
39 | this.setData({
40 | searching: true
41 | })
42 | },
43 |
44 | // 调用子组件传递进来的自定义事件cancel,为其绑定事件onCancel,控制搜索组件searing的显隐
45 | onCancel(event) {
46 | this.setData({
47 | searching: false
48 | })
49 | },
50 |
51 | /**
52 | * 生命周期函数--监听页面初次渲染完成
53 | */
54 | onReady: function() {
55 |
56 | },
57 |
58 | /**
59 | * 生命周期函数--监听页面显示
60 | */
61 | onShow: function() {
62 |
63 | },
64 |
65 | /**
66 | * 生命周期函数--监听页面隐藏
67 | */
68 | onHide: function() {
69 |
70 | },
71 |
72 | /**
73 | * 生命周期函数--监听页面卸载
74 | */
75 | onUnload: function() {
76 |
77 | },
78 |
79 | /**
80 | * 页面相关事件处理函数--监听用户下拉动作
81 | */
82 | onPullDownRefresh: function() {
83 |
84 | },
85 |
86 | /**
87 | * 页面上拉触底事件的处理函数
88 | */
89 | onReachBottom: function() {
90 | console.log('到底了')
91 | this.setData({
92 | more: random(16)
93 | })
94 | },
95 |
96 | /**
97 | * 用户点击右上角分享
98 | */
99 | onShareAppMessage: function() {
100 |
101 | }
102 | })
103 |
--------------------------------------------------------------------------------
/pages/book/book.json:
--------------------------------------------------------------------------------
1 | {
2 | "usingComponents": {
3 | "v-book": "/components/book/index",
4 | "v-search": "/components/search/index"
5 |
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/pages/book/book.wxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/pages/book/book.wxss:
--------------------------------------------------------------------------------
1 | .container {
2 | display: flex;
3 | flex-direction: column;
4 | align-items: center;
5 | width: 100%;
6 | }
7 |
8 | .sub-container {
9 | display: flex;
10 | flex-direction: column;
11 | align-items: center;
12 | background-color: #f5f5f5;
13 | margin-top: 100rpx;
14 | }
15 |
16 | .box {
17 | display: flex;
18 | flex-direction: row;
19 | justify-content: center;
20 | align-items: center;
21 | border-radius: 50px;
22 | background-color: #f5f5f5;
23 | height: 68rpx;
24 | width: 700rpx;
25 | color: #999;
26 | }
27 |
28 | .header {
29 | position: fixed;
30 | background-color: #fff;
31 | height: 100rpx;
32 | width: 100%;
33 | border-top: 1px solid #f5f5f5;
34 | display: flex;
35 | flex-direction: row;
36 | align-items: center;
37 | justify-content: center;
38 | box-shadow: 0 0 3px 0 #e3e3e3;
39 | z-index: 99;
40 | }
41 |
42 | .head-img {
43 | width: 106rpx;
44 | height: 34rpx;
45 | margin-top: 40rpx;
46 | }
47 |
48 | .box image {
49 | margin-right: 10px;
50 | width: 14px;
51 | height: 14px;
52 | margin-bottom: -2px;
53 | }
54 |
55 | .books-container {
56 | display: flex;
57 | flex-direction: row;
58 | margin-top: 10rpx;
59 | flex-wrap: wrap;
60 | padding: 0 90rpx 0 90rpx;
61 | justify-content: space-between;
62 | }
63 |
64 | .books-container v-book {
65 | margin-bottom: 10rpx;
66 | }
67 |
--------------------------------------------------------------------------------
/pages/classic-detail/index.js:
--------------------------------------------------------------------------------
1 | // pages/classic-detail/index.js
2 | Page({
3 |
4 | /**
5 | * 页面的初始数据
6 | */
7 | data: {
8 |
9 | },
10 |
11 | /**
12 | * 生命周期函数--监听页面加载
13 | */
14 | onLoad: function (options) {
15 |
16 | },
17 |
18 | /**
19 | * 生命周期函数--监听页面初次渲染完成
20 | */
21 | onReady: function () {
22 |
23 | },
24 |
25 | /**
26 | * 生命周期函数--监听页面显示
27 | */
28 | onShow: function () {
29 |
30 | },
31 |
32 | /**
33 | * 生命周期函数--监听页面隐藏
34 | */
35 | onHide: function () {
36 |
37 | },
38 |
39 | /**
40 | * 生命周期函数--监听页面卸载
41 | */
42 | onUnload: function () {
43 |
44 | },
45 |
46 | /**
47 | * 页面相关事件处理函数--监听用户下拉动作
48 | */
49 | onPullDownRefresh: function () {
50 |
51 | },
52 |
53 | /**
54 | * 页面上拉触底事件的处理函数
55 | */
56 | onReachBottom: function () {
57 |
58 | },
59 |
60 | /**
61 | * 用户点击右上角分享
62 | */
63 | onShareAppMessage: function () {
64 |
65 | }
66 | })
--------------------------------------------------------------------------------
/pages/classic-detail/index.json:
--------------------------------------------------------------------------------
1 | {
2 | "usingComponents": {}
3 | }
--------------------------------------------------------------------------------
/pages/classic-detail/index.wxml:
--------------------------------------------------------------------------------
1 |
2 | pages/classic-detail/index.wxml
3 |
--------------------------------------------------------------------------------
/pages/classic-detail/index.wxss:
--------------------------------------------------------------------------------
1 | /* pages/classic-detail/index.wxss */
--------------------------------------------------------------------------------
/pages/classic/classic.js:
--------------------------------------------------------------------------------
1 | import {
2 | ClassicModel
3 | } from '../../models/classic.js'
4 | import {
5 | LikeModel
6 | } from '../../models/like.js'
7 |
8 | let classicModel = new ClassicModel()
9 | let likeModel = new LikeModel()
10 | Page({
11 |
12 | /**
13 | * 页面的初始数据
14 | */
15 | data: {
16 | classic: null,
17 | latest: true,
18 | first: false,
19 | likeCount: 0,
20 | likeStatus: false
21 |
22 | },
23 | // 监听是否点赞
24 | onLike: function(event) {
25 | console.log(event)
26 | // 获取点赞还是取消的行为
27 | let behavior = event.detail.behavior
28 | likeModel.like(behavior, this.data.classic.id, this.data.classic.type)
29 | },
30 |
31 | // 自定义事件
32 | // 获取当前一期的下一期
33 | onNext: function(evevt) {
34 | // let index = this.data.classic.index
35 | // classicModel.getNext(index, (res) => {
36 | // // console.log(res)
37 | // this.setData({
38 | // classic: res,
39 | // latest: classicModel.isLatest(res.index),
40 | // first: classicModel.isFirst(res.index)
41 | // })
42 | // })
43 |
44 | this._updateClassic('next')
45 | },
46 | // 获取当前一期的上一期
47 | onPrevious: function(evevt) {
48 | // let index = this.data.classic.index
49 | // classicModel.getPrevious(index, (res) => {
50 | // // console.log(res)
51 | // this.setData({
52 | // classic: res,
53 | // latest: classicModel.isLatest(res.index),
54 | // first: classicModel.isFirst(res.index)
55 | // })
56 | // })
57 |
58 | this._updateClassic('previous')
59 |
60 | },
61 |
62 | // 重复代码过多,利用函数封装的思想,新建一个函数抽取公共代码
63 | _updateClassic: function(nextOrPrevious) {
64 | let index = this.data.classic.index
65 | classicModel.getClassic(index, nextOrPrevious, (res) => {
66 | // console.log(res)
67 | this._getLikeStatus(res.id, res.type)
68 | this.setData({
69 | classic: res,
70 | latest: classicModel.isLatest(res.index),
71 | first: classicModel.isFirst(res.index)
72 | })
73 | })
74 | },
75 |
76 | // 获取点赞信息
77 | _getLikeStatus: function(artID, category) {
78 | likeModel.getClassicLikeStatus(artID, category, (res) => {
79 | this.setData({
80 | likeCount: res.fav_nums,
81 | likeStatus: res.like_status
82 | })
83 | })
84 | },
85 |
86 |
87 | /**
88 | * 生命周期函数--监听页面加载
89 | */
90 | onLoad: function(options) {
91 | classicModel.getLatest((res) => {
92 | console.log(res)
93 | // this._getLikeStatus(res.id, res.type) //不能这样写,会多发一次favor请求,消耗性能
94 | this.setData({
95 | classic: res,
96 | likeCount: res.fav_nums,
97 | likeStatus: res.like_status
98 | })
99 | })
100 |
101 |
102 |
103 | // http.request({
104 | // url: 'classic/latest',
105 | // success: (res) => {
106 | // console.log(res)
107 | // }
108 | // })
109 | // wx.request({//比较麻烦的做法,不用
110 | // url: 'http://bl.7yue.pro/v1/classic/latest',
111 | // header: {
112 | // appkey: "RdshydjBvcYZhMZC"
113 | // }
114 | // })
115 | },
116 |
117 | /**
118 | * 生命周期函数--监听页面初次渲染完成
119 | */
120 | onReady: function() {
121 |
122 | },
123 |
124 | /**
125 | * 生命周期函数--监听页面显示
126 | */
127 | onShow: function() {
128 |
129 | },
130 |
131 | /**
132 | * 生命周期函数--监听页面隐藏
133 | */
134 | onHide: function() {
135 |
136 | },
137 |
138 | /**
139 | * 生命周期函数--监听页面卸载
140 | */
141 | onUnload: function() {
142 |
143 | },
144 |
145 | /**
146 | * 页面相关事件处理函数--监听用户下拉动作
147 | */
148 | onPullDownRefresh: function() {
149 |
150 | },
151 |
152 | /**
153 | * 页面上拉触底事件的处理函数
154 | */
155 | onReachBottom: function() {
156 |
157 | },
158 |
159 | /**
160 | * 用户点击右上角分享
161 | */
162 | onShareAppMessage: function() {
163 |
164 | }
165 | })
166 |
--------------------------------------------------------------------------------
/pages/classic/classic.json:
--------------------------------------------------------------------------------
1 | {
2 | "component": true,
3 | "usingComponents": {
4 | "v-like": "/components/like/index",
5 | "v-movie": "/components/classic/movie/index",
6 | "v-music": "/components/classic/music/index",
7 | "v-essay": "/components/classic/essay/index",
8 | "v-episode": "/components/episode/index",
9 | "v-navi": "/components/navi/index",
10 | "v-button": "/components/image-button/index"
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/pages/classic/classic.wxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/pages/classic/classic.wxss:
--------------------------------------------------------------------------------
1 | .container {
2 | /* 尽量都设宽度为100%,防止出错 */
3 | width: 100%;
4 | display: flex;
5 | flex-direction: column;
6 | align-items: center;
7 | }
8 |
9 | .header {
10 | width: 100%;
11 | display: flex;
12 | flex-direction: row;
13 | height: 100rpx;
14 | align-items: center;
15 | justify-content: space-between;
16 | border-top: 1px solid #f5f5f5;
17 | border-bottom: 1px solid #f5f5f5;
18 | }
19 |
20 | .epsoide {
21 | margin-left: 20rpx;
22 | margin-top: 4rpx;
23 | }
24 |
25 | .like {
26 | margin-top: 6rpx;
27 | }
28 |
29 | .nav {
30 | position: absolute;
31 | bottom: 40rpx;
32 | }
33 |
34 | .like-container {
35 | display: flex;
36 | flex-direction: row;
37 | justify-content: space-between;
38 | align-items: center;
39 | margin-right: 30rpx;
40 | }
41 |
42 | .share-btn {
43 | margin-top: 28rpx;
44 | margin-right: 10rpx;
45 | }
46 |
47 | .share {
48 | width: 40rpx;
49 | height: 40rpx;
50 | }
51 |
--------------------------------------------------------------------------------
/pages/course/course.js:
--------------------------------------------------------------------------------
1 | // pages/course/course.js
2 | Page({
3 |
4 | /**
5 | * 页面的初始数据
6 | */
7 | data: {
8 |
9 | },
10 | onShareAppMessage: function () {
11 |
12 | }
13 | })
--------------------------------------------------------------------------------
/pages/course/course.json:
--------------------------------------------------------------------------------
1 | {
2 | "backgroundColor": "#f5f5f5"
3 | }
--------------------------------------------------------------------------------
/pages/course/course.wxml:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/pages/course/course.wxss:
--------------------------------------------------------------------------------
1 | .img{
2 | width:100%;
3 | height:1984rpx;
4 | }
--------------------------------------------------------------------------------
/pages/detail/detail.js:
--------------------------------------------------------------------------------
1 | import {
2 | BookModel
3 | } from '../../models/book'
4 |
5 | import {
6 | LikeModel
7 | } from '../../models/like'
8 |
9 | const bookModel = new BookModel()
10 | const likeModel = new LikeModel()
11 |
12 |
13 |
14 |
15 | // pages/detail/detail.js
16 | Page({
17 |
18 | /**
19 | * 页面的初始数据
20 | */
21 | data: {
22 | comments: [],
23 | book: null,
24 | likeStatus: false,
25 | likeCount: 0,
26 | posting: false //默认不显示评论输入框
27 | },
28 |
29 |
30 | /**
31 | * 生命周期函数--监听页面加载
32 | */
33 | onLoad: function(options) {
34 | // 数据加载时显示loading效果
35 | wx.showLoading()
36 | const bid = options.bid
37 | console.log(bid)
38 | // 获取书籍详细信息
39 | const detail = bookModel.getDetail(bid)
40 | // 获取书籍点赞情况
41 | const likeStatus = bookModel.getLikeStatus(bid)
42 | // 获取书籍短评
43 | const comments = bookModel.getComments(bid)
44 |
45 | // 数据加载完成时取消显示loading效果
46 | Promise.all([detail, comments, likeStatus]).then(res => {
47 | console.log(res)
48 | this.setData({
49 | book: res[0],
50 | comments: res[1].comments,
51 | likeStatus: res[2].like_status,
52 | likeCount: res[2].fav_nums
53 | })
54 | wx.hideLoading()
55 | })
56 |
57 | // detail.then(res => {
58 | // console.log(res)
59 | // this.setData({
60 | // book: res
61 | // })
62 | // })
63 |
64 | // likeStatus.then(res => {
65 | // console.log(res)
66 | // this.setData({
67 | // likeStatus: res.like_status,
68 | // likeCount: res.fav_nums
69 | // })
70 | // })
71 |
72 | // comments.then(res => {
73 | // console.log(res)
74 | // this.setData({
75 | // comments: res.comments
76 | // })
77 | // })
78 | },
79 |
80 | // 触摸点赞组件事件
81 | onLike(event) {
82 | const like_or_cancel = event.detail.behavior
83 | likeModel.like(like_or_cancel, this.data.book.id, 400)
84 | },
85 |
86 | // 触摸评论输入框弹出评论窗口
87 | onFakePost(enent) {
88 | this.setData({
89 | posting: true
90 | })
91 | },
92 |
93 | // 在弹出的评论窗口中点取消,让评论窗口消失
94 | onCancl(event) {
95 | this.setData({
96 | posting: false
97 | })
98 | },
99 |
100 | // 触摸tag组件会触发,input输入框也会触发事件onPost
101 | onPost(event) {
102 | // 获取触发tag里的内容或获取用户input输入的内容
103 | const comment = event.detail.text || event.detail.value
104 | // 获取用户input输入的内容
105 | // const commentInput = event.detail.value
106 |
107 | console.log('comment' + comment)
108 | console.log('commentInput' + comment)
109 |
110 |
111 |
112 | // 对tag里的内容或对用户输入的评论做校验
113 | if (comment.length > 12 || !comment) {
114 | wx.showToast({
115 | title: '短评最多12个字',
116 | icon: 'none'
117 | })
118 | return
119 | }
120 |
121 | // 调用新增短评接口并将最新的评论插到comments数组的第一位,并且把蒙层mask关闭
122 | bookModel.postComment(this.data.book.id, comment).then(res => {
123 | wx.showToast({
124 | title: '+1',
125 | icon: 'none'
126 | })
127 | this.data.comments.unshift({
128 | content: comment,
129 | nums: 1 //这是后面的数量显示
130 | })
131 | this.setData({
132 | comments: this.data.comments,
133 | posting: false
134 | })
135 | })
136 | },
137 |
138 | /**
139 | * 生命周期函数--监听页面初次渲染完成
140 | */
141 | onReady: function() {
142 |
143 | },
144 |
145 | /**
146 | * 生命周期函数--监听页面显示
147 | */
148 | onShow: function() {
149 |
150 | },
151 |
152 | /**
153 | * 生命周期函数--监听页面隐藏
154 | */
155 | onHide: function() {
156 |
157 | },
158 |
159 | /**
160 | * 生命周期函数--监听页面卸载
161 | */
162 | onUnload: function() {
163 |
164 | },
165 |
166 | /**
167 | * 页面相关事件处理函数--监听用户下拉动作
168 | */
169 | onPullDownRefresh: function() {
170 |
171 | },
172 |
173 | /**
174 | * 页面上拉触底事件的处理函数
175 | */
176 | onReachBottom: function() {
177 |
178 | },
179 |
180 | /**
181 | * 用户点击右上角分享
182 | */
183 | onShareAppMessage: function() {
184 |
185 | }
186 | })
187 |
--------------------------------------------------------------------------------
/pages/detail/detail.json:
--------------------------------------------------------------------------------
1 | {
2 | "usingComponents": {
3 | "v-tag": "/components/tag/index",
4 | "v-like": "/components/like/index",
5 | "v-mask": "/components/mask/index",
6 | "v-button": "/components/image-button/index"
7 |
8 |
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/pages/detail/detail.wxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | {{book.title}}
10 | {{book.author}}
11 |
12 |
13 |
14 | 短评
15 | 还没有短评
16 |
17 |
24 |
25 |
26 |
27 | 内容简介
28 | {{util.format(book.summary)}}
29 |
30 |
31 |
32 | 书本信息
33 |
34 |
35 | 出版社
36 | 出版年
37 | 页数
38 | 定价
39 | 装帧
40 |
41 |
42 | {{book.publisher}}
43 | {{book.pubdate}}
44 | {{book.pages}}
45 | {{book.price}}
46 | {{book.binding}}
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 | 输入短评
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
72 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 | var highlight = function(index){
88 | if(index===0){
89 | return 'ex-tag1'
90 | }
91 | else if(index===1){
92 | return 'ex-tag2'
93 | }
94 | return ''
95 | }
96 | module.exports={
97 | highlight:highlight
98 | }
99 |
--------------------------------------------------------------------------------
/pages/detail/detail.wxss:
--------------------------------------------------------------------------------
1 | .container {
2 | background-color: #f5f5f5;
3 | width: 100%;
4 | }
5 |
6 | .head {
7 | background-color: #fff;
8 | padding-top: 40rpx;
9 | padding-bottom: 40rpx;
10 | display: flex;
11 | flex-direction: column;
12 | align-items: center;
13 | }
14 |
15 | .title {
16 | color: #2f2f2f;
17 | margin-top: 20rpx;
18 | font-size: 38rpx;
19 | font-weight: 600;
20 | }
21 |
22 | .author {
23 | font-size: 28rpx;
24 | color: #999;
25 | }
26 |
27 | .head image {
28 | width: 200rpx;
29 | height: 300rpx;
30 | box-shadow: 2px 2px 3px #e3e3e3;
31 | }
32 |
33 | .sub-container {
34 | width: 690rpx;
35 | display: flex;
36 | flex-direction: column;
37 | align-items: center;
38 | margin-top: 30rpx;
39 | background-color: #fff;
40 | padding: 30rpx;
41 | }
42 |
43 | .headline {
44 | font-size: 30rpx;
45 | font-weight: 600;
46 | color: #2f2f2f;
47 | margin-bottom: 20rpx;
48 | }
49 |
50 | .comment-container {
51 | display: flex;
52 | flex-direction: row;
53 | flex-wrap: wrap;
54 | }
55 |
56 | .comment-container v-tag {
57 | margin-right: 15rpx;
58 | margin-bottom: 10rpx;
59 | }
60 |
61 | .num {
62 | margin-left: 10px;
63 | font-size: 22rpx;
64 | color: #aaa;
65 | }
66 |
67 | .content {
68 | text-indent: 58rpx;
69 | font-weight: 500;
70 | }
71 |
72 |
73 | /* v-tag是自定义组件,不能使用css3,在微信小程序中,只有内置组件才可以用css3 */
74 |
75 |
76 | /* .comment-container>v-tag:nth-child(1)>view {
77 | background-color: #fffbdd;
78 | }
79 |
80 | .comment-container>v-tag:nth-child(2)>view {
81 | background-color: #eefbff;
82 | } */
83 |
84 |
85 | /* 定义外部样式 */
86 |
87 | .ex-tag1 {
88 | background-color: #fffbdd !important;
89 | }
90 |
91 | .ex-tag2 {
92 | background-color: #eefbff !important;
93 | }
94 |
95 | .detail-container {
96 | width: 100%;
97 | display: flex;
98 | flex-direction: row;
99 | justify-content: flex-start;
100 | margin-bottom: 100rpx;
101 | font-size: 28rpx;
102 | color: #666;
103 | }
104 |
105 | .vertical {
106 | display: flex;
107 | flex-direction: column;
108 | }
109 |
110 | .description {
111 | color: #999;
112 | margin-right: 30rpx;
113 | }
114 |
115 | .post-container {
116 | height: 100rpx;
117 | box-shadow: 1px -1px 1px #e3e3e3;
118 | position: fixed;
119 | width: 690rpx;
120 | background-color: #fff;
121 | bottom: 0;
122 | display: flex;
123 | flex-direction: row;
124 | align-items: center;
125 | padding: 0 30rpx;
126 | justify-content: space-between;
127 | }
128 |
129 | .post-fake {
130 | display: flex;
131 | flex-direction: row;
132 | align-items: center;
133 | height: 60rpx;
134 | width: 460rpx;
135 | border: 1px solid #999;
136 | border-radius: 15px;
137 | font-size: 22rpx;
138 | padding-left: 20rpx;
139 | }
140 |
141 | .like {
142 | margin-right: 30rpx;
143 | margin-top: 10rpx;
144 | }
145 |
146 | .like-container {
147 | display: flex;
148 | flex-direction: row;
149 | justify-content: space-between;
150 | align-items: center;
151 | }
152 |
153 | .posting-container {
154 | bottom: 0;
155 | position: fixed;
156 | display: flex;
157 | flex-direction: column;
158 | align-items: center;
159 | background-color: #fff;
160 | width: 100%;
161 | z-index: 999;
162 | }
163 |
164 | .post-header {
165 | width: 100%;
166 | height: 100rpx;
167 | border-bottom: 1px solid #f5f5f5;
168 | border-top: 1px solid #f5f5f5;
169 | display: flex;
170 | flex-direction: row;
171 | align-items: center;
172 | justify-content: space-between;
173 | }
174 |
175 | .cancel {
176 | color: #666;
177 | }
178 |
179 | .post-header text {
180 | padding: 25rpx;
181 | }
182 |
183 | .post-header>text:first-child {
184 | font-size: 28rpx;
185 | color: #bbb;
186 | }
187 |
188 | .posting-container .comment-container {
189 | width: 690rpx;
190 | padding: 40rpx 30rpx 0 30rpx;
191 | }
192 |
193 | .post {
194 | width: 690rpx;
195 | height: 56rpx;
196 | margin: 30rpx auto;
197 | background-color: #f5f5f5;
198 | border-radius: 15px;
199 | padding-left: 25rpx;
200 | }
201 |
202 | .shadow {
203 | color: #999;
204 | }
205 |
206 | .like-container {
207 | display: flex;
208 | flex-direction: row;
209 | justify-content: space-between;
210 | align-items: center;
211 | margin-right: 30rpx;
212 | }
213 |
214 | .share-btn {
215 | margin-top: 28rpx;
216 | margin-right: 10rpx;
217 | }
218 |
219 | .share {
220 | width: 40rpx;
221 | height: 40rpx;
222 | }
223 |
--------------------------------------------------------------------------------
/pages/my/my.js:
--------------------------------------------------------------------------------
1 | import {
2 | ClassicModel
3 | } from '../../models/classic'
4 |
5 | import {
6 | BookModel
7 | } from '../../models/book'
8 |
9 | const classicModel = new ClassicModel()
10 | const bookModel = new BookModel()
11 |
12 |
13 |
14 |
15 | Page({
16 | data: {
17 | authorized: false, //切换用户头像显示
18 | userInfo: null, //用户信息
19 | bookCount: 0, //喜欢的书
20 | classics: null //获取我喜欢期刊的所有信息
21 | },
22 | onLoad() {
23 | // 查看是否授权
24 | this.userAuthorized()
25 | // 获取喜欢的书的数量
26 | this.getMyBookCount()
27 | // 获取我喜欢期刊的所有信息
28 | this.getMyFavor()
29 | },
30 |
31 | // 从服务器获取喜欢的书的数量
32 | getMyBookCount() {
33 | bookModel.getMyBookCount().then(res => {
34 | this.setData({
35 | bookCount: res.count
36 | })
37 | })
38 | },
39 |
40 | // 获取我喜欢期刊的所有信息
41 | getMyFavor() {
42 | classicModel.getMyFavor(res => {
43 | this.setData({
44 | classics: res
45 | })
46 | })
47 | },
48 |
49 | // 查看是否授权, 已经授权,可以直接调用 getUserInfo 获取头像昵称
50 | userAuthorized() {
51 | wx.getSetting({
52 | success: data => {
53 | if (data.authSetting['scope.userInfo']) {
54 | wx.getUserInfo({
55 | success: data => {
56 | console.log(data)
57 | this.setData({
58 | authorized: true,
59 | userInfo: data.userInfo
60 | })
61 | }
62 | })
63 | } else {
64 | console.log('err')
65 | }
66 | }
67 | })
68 | },
69 |
70 |
71 | onGetUserInfo(e) {
72 | const userInfo = e.detail.userInfo
73 | console.log(userInfo)
74 | if (userInfo) {
75 | this.setData({
76 | userInfo,
77 | authorized: true
78 | })
79 | }
80 |
81 | },
82 |
83 | // 触摸跳转到另一个页面
84 | onJumpToAbout(event) {
85 | wx.navigateTo({
86 | url: '/pages/about/about'
87 | })
88 | },
89 |
90 | onStudy(event) {
91 | wx.navigateTo({
92 | url: '/pages/course/course'
93 | })
94 | }
95 |
96 |
97 |
98 |
99 |
100 | })
101 |
--------------------------------------------------------------------------------
/pages/my/my.json:
--------------------------------------------------------------------------------
1 | {
2 | "usingComponents": {
3 | "v-button": "/components/image-button/index",
4 | "v-preview": "/components/preview/index"
5 |
6 | },
7 | "backgroundColor": "#f5f5f5"
8 | }
9 |
--------------------------------------------------------------------------------
/pages/my/my.wxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | {{userInfo.nickName}}
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | 关于我们
20 |
21 |
22 | {{bookCount}}
23 | 喜欢的书
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/pages/my/my.wxss:
--------------------------------------------------------------------------------
1 | .container {
2 | display: flex;
3 | flex-direction: column;
4 | align-items: center;
5 | }
6 |
7 | .bg {
8 | width: 750rpx;
9 | height: 574rpx;
10 | }
11 |
12 | .about-container {
13 | padding: 0 100rpx;
14 | width: 550rpx;
15 | display: flex;
16 | flex-direction: row;
17 | justify-content: space-between;
18 | position: absolute;
19 | top: 440rpx;
20 | }
21 |
22 | .about-us {
23 | display: flex;
24 | flex-direction: column;
25 | align-items: center;
26 | justify-content: space-between;
27 | }
28 |
29 | .about-us image {
30 | width: 34rpx;
31 | height: 34rpx;
32 | }
33 |
34 | .description {
35 | font-size: 24rpx;
36 | color: #999999;
37 | }
38 |
39 | .about-container>view:nth-child(2) {
40 | margin-top: -5rpx;
41 | }
42 |
43 | .avatar {
44 | width: 120rpx;
45 | height: 120rpx;
46 | border-radius: 50%;
47 | /* 加上overflow:hidden才会显示圆 */
48 | overflow: hidden;
49 | }
50 |
51 | .avatar-position {
52 | position: absolute;
53 | top: 255rpx;
54 | }
55 |
56 | .avatar-container {
57 | display: flex;
58 | flex-direction: column;
59 | align-items: center;
60 | }
61 |
62 | .study {
63 | width: 88rpx;
64 | height: 88rpx;
65 | position: absolute;
66 | top: 40rpx;
67 | right: 45rpx;
68 | }
69 |
70 | .book-num {
71 | font-size: 36rpx;
72 | color: #000000;
73 | }
74 |
75 | .like-container {
76 | /* margin-top:30rpx; */
77 | width: 100%;
78 | margin-top: -13rpx;
79 | display: flex;
80 | flex-direction: column;
81 | align-items: center;
82 | background-color: #f5f5f5;
83 | }
84 |
85 | .headline {
86 | margin-top: 30rpx;
87 | width: 97rpx;
88 | height: 42rpx;
89 | }
90 |
91 | .preview-container {
92 | margin-top: 30rpx;
93 | display: flex;
94 | flex-direction: row;
95 | padding: 0 30rpx;
96 | flex-wrap: wrap;
97 | justify-content: space-between;
98 | }
99 |
100 | .preview {
101 | margin-bottom: 30rpx;
102 | }
103 |
--------------------------------------------------------------------------------
/reademeimages/aixin.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wudiufo/WeChat-Mini-Program/1e715c852b7ae555f45e613f8412fd43d013ec3f/reademeimages/aixin.jpg
--------------------------------------------------------------------------------
/reademeimages/navi.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wudiufo/WeChat-Mini-Program/1e715c852b7ae555f45e613f8412fd43d013ec3f/reademeimages/navi.jpg
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | ### 小爱心是否点赞组件 `components/like`
2 |
3 | 
4 |
5 | 思路:
6 |
7 | like 默认为 false,显示空心小爱心
8 |
9 | 触摸执行`tap:onLike` 方法,因为 `this.setData({count:count,like:!like})`是异步的,先执行`count = like ? count - 1 : count + 1`,这时like还是false,执行count+1。然后在执行this.setData()方法,将like变为true,显示实心小爱心。
10 |
11 | ```js
12 | let behavior = like ? 'like' : 'cancel'
13 | //自定义事件
14 | this.triggerEvent('like', {
15 | behavior: behavior
16 | }, {})
17 | ```
18 |
19 | 自定义事件like,当like为真时,behavior为like,在`models/like.js`中,`let url = behavior === 'like' ? 'like/cancel' : 'like'`,因为`behavior === 'like'`为真,就调用服务器接口'like/cancel',相反就调用like接口。
20 |
21 | 刚开始实心就调用'like/cancel'接口,空心就调用'like'接口
22 |
23 | ---------------------
24 |
25 | ### 底部左右切换组件 ` components/navi`
26 |
27 | 
28 |
29 | 思路:
30 |
31 | ##### 在`navi/index.js`中:
32 |
33 | ```js
34 | 先定义哪些数据是外部传来的数据,哪些数据是私有数据
35 | properties: {//外部传来的数据
36 | title: String,
37 | first: Boolean, //如果是第一期向右的箭头就禁用,默认是false
38 | latest: Boolean //如果是最新的一期向左的箭头就禁用,默认是false
39 | },
40 | data: {//私有数据
41 | disLeftSrc: './images/triangle.dis@left.png',
42 | leftSrc: './images/triangle@left.png',
43 | disRightSrc: './images/triangle.dis@right.png',
44 | rightSrc: './images/triangle@right.png'
45 | },
46 |
47 | ```
48 |
49 |
50 |
51 | #### 左箭头:
52 |
53 | >在navi/index.wxml中``
54 | >
55 | >src显示图片规则:如果是最新的期刊,就显示向左禁用状态disLeftSrc箭头;如果不是最新一期的期刊,就显示向左可用状态leftSrc箭头
56 | >
57 | >为图片绑定触摸事件onLeft,在`navi/index.js`中:
58 | >
59 | >```js
60 | >在 methods 中:如果不是最新的期刊,就继续绑定自定义事件left
61 | >onLeft: function(event) { //不是最新一期
62 | > if (!this.properties.latest) {
63 | > this.triggerEvent('left', {}, {})
64 | > }
65 | >
66 | > },
67 | >```
68 | >
69 | >
70 |
71 | #### 右箭头:
72 |
73 | >在navi/index.wxml中``
74 | >
75 | >src显示图片规则:如果是第一期的期刊,就显示向右禁用状态disRightSrc箭头;如果不是第一期的期刊,就显示向右可用状态rightSrc箭头
76 | >
77 | >为图片绑定触摸事件onRight,在`navi/index.js`中:
78 | >
79 | >```js
80 | >在 methods 中:如果不是第一期的期刊,就继续绑定自定义事件right
81 | >onRight: function(event) { //不是第一期
82 | > if (!this.properties.first) {
83 | > this.triggerEvent('right', {}, {})
84 | > }
85 | >
86 | > }
87 | >```
88 |
89 | ##### 在`pages/classic`中:
90 |
91 | >
92 | >
93 | >```js
94 | >1:在 classic.json 中,注册使用navi自定义组件
95 | >{
96 | > "usingComponents": {
97 | > "v-like": "/components/like/index",
98 | > "v-movie": "/components/classic/movie/index",
99 | > "v-episode": "/components/episode/index",
100 | > "v-navi": "/components/navi/index"
101 | > }
102 | >}
103 | >2:在 classic.wxml 中:绑定自定义事件left, 获取当前一期的下一期;绑定自定义事件right,获取当前一期的上一期
104 | >
105 | >
106 | >3:在 classic.js 中:
107 | >// 获取当前一期的下一期,左箭头
108 | >onNext: function(evevt) {this._updateClassic('next')
109 | > },
110 | >// 获取当前一期的上一期,右箭头
111 | > onPrevious: function(evevt) { this._updateClassic('previous')
112 | > },
113 | >// 重复代码过多,利用函数封装的思想,新建一个函数抽取公共代码
114 | >// 发送请求,获取当前页的索引,更新数据
115 | > _updateClassic: function(nextOrPrevious) {
116 | > let index = this.data.classic.index
117 | > classicModel.getClassic(index, nextOrPrevious, (res) => {
118 | > // console.log(res)
119 | > this.setData({
120 | > classic: res,
121 | > latest: classicModel.isLatest(res.index),
122 | > first: classicModel.isFirst(res.index)
123 | > })
124 | > })
125 | > },
126 | >
127 | > 4:在 models/classic.js 中:
128 | > // 当前的期刊是否为第一期,first就变为true,右箭头就显示禁用
129 | > isFirst(index) {
130 | > return index === 1 ? true : false
131 | > }
132 | >// 当前的期刊是否为最新的一期,latest就变为TRUE,左箭头就显示禁用
133 | >// 由于服务器数据还会更新,确定不了最新期刊的索引,所以就要利用缓存机制,将最新期刊的索引存入到缓存中,如果外界传进来的索引和缓存的最新期刊的索一样,latest就变为TRUE,左箭头就显示禁用
134 | > isLatest(index) {
135 | > let latestIndex = this._getLatestIndex()
136 | > return latestIndex === index ? true : false
137 | > }
138 | >// 将最新的期刊index存入缓存
139 | > _setLatestIndex(index) {
140 | > wx.setStorageSync('latest', index)
141 | > }
142 | >
143 | > // 在缓存中获取最新期刊的index
144 | > _getLatestIndex() {
145 | > let index = wx.getStorageSync('latest')
146 | > return index
147 | > }
148 | >```
149 |
150 |
151 |
152 | ### 优化缓存。解决每次触摸左右箭头都会频繁向服务器发送请求,这样非常耗性能,用户体验极差。解决方法,就是把第一次发送请求的数据都缓存到本地,再次触摸箭头时,会先查找本地缓存是否有数据,有就直接从缓存中读取数据,没有就在向服务器发送请求,这样利用缓存机制大大的提高了用户的体验。(但也有一部分是需要实时更新的,比如是否点赞的小爱心组件,需要每次都向服务器发送请求获取最新数据)
153 |
154 | ##### 在 models/classic.js 中:
155 |
156 | ```js
157 | 1:
158 | // 设置缓存中的key 的样式,classic-1这种样式
159 | _getKey(index) {
160 | let key = `classic-${index}`
161 | return key
162 | }
163 | 2:
164 | // 因为getPrevious,getNext实现代码相似,所以为了简化代码可以合并为一个函数
165 | // 缓存思路:在缓存中寻找key,找不到就发送请求 API,将key写入到缓存中。解决每次都调用Api向服务器发请求,耗费性能
166 | // 在缓存中,确定key
167 | getClassic(index, nextOrPrevious, sCallback) {
168 | //0: 是next,触摸向左箭头获取下一期,触摸向右箭头否则获取上一期
169 | let key = nextOrPrevious === 'next' ? this._getKey(index + 1) : this._getKey(index - 1)
170 | //1:在缓存中寻找key
171 | let classic = wx.getStorageSync(key)
172 | //2:如果缓存中找不到key,就调用服务器API发送请求获取数据
173 | if (!classic) {
174 | this.request({
175 | url: `classic/${index}/${nextOrPrevious}`,
176 | success: (res) => {
177 | //将获取到的数据设置到缓存中
178 | wx.setStorageSync(this._getKey(res.index), res)
179 | //再把获取到的数据返回,供用户调取使用
180 | sCallback(res)
181 | }
182 | })
183 | } else { //3:如果在缓存中有找到key,将缓存中key对应的value值,返回给用户,供用户调取使用
184 | sCallback(classic)
185 | }
186 |
187 | }
188 | --------------------------------------------------------------------------------
189 |
190 | // 获取最新的期刊利用缓存机制进一步优化
191 | //获取最新的期刊
192 | getLatest(cb) {
193 | this.request({
194 | url: 'classic/latest',
195 | success: (res) => {
196 | //将最新的期刊index存入缓存,防止触摸向左箭头时,没有设置latest的值,左箭头会一直触发发送请求找不到最新的期刊报错
197 | this._setLatestIndex(res.index)
198 | //再把获取到的数据返回,供用户调取使用
199 | cb(res)
200 | // 将最新的期刊设置到缓存中,先调取 this._getKey() 方法,为最新获取的期刊设置key值,调用微信设置缓存方法将key,和对应的value值res存进去
201 | let key = this._getKey(res.index)
202 | wx.setStorageSync(key, res)
203 |
204 | }
205 | })
206 | }
207 | ```
208 |
209 | 处理是否点赞小爱心组件的缓存问题:他不需要缓存,需要实时获取最新数据
210 |
211 | ##### 在 models/like.js 中:
212 |
213 | ```js
214 | //编写一个获取点赞信息的方法,从服务器获取最新点赞信息的数据
215 | // 获取点赞信息
216 | getClassicLikeStatus(artID, category, cb) {
217 | this.request({
218 | url: `classic/${category}/${artID}/favor`,
219 | success: cb
220 | })
221 | }
222 |
223 | ```
224 |
225 | ##### 在 pages/classic/classic.js 中:
226 |
227 | ```js
228 | //设置私有数据初始值
229 | data: {
230 | classic: null,
231 | latest: true,
232 | first: false,
233 | likeCount: 0,//点赞的数量
234 | likeStatus: false //点赞的状态
235 |
236 | },
237 |
238 | // 在classic.wxml中:
239 |
240 | // 编写一个私有方法获取点赞信息
241 | // 获取点赞信息
242 | _getLikeStatus: function(artID, category) {
243 | likeModel.getClassicLikeStatus(artID, category, (res) => {
244 | this.setData({
245 | likeCount: res.fav_nums,
246 | likeStatus: res.like_status
247 | })
248 | })
249 | },
250 |
251 | //生命周期函数--监听页面加载
252 | onLoad: function(options) {
253 | classicModel.getLatest((res) => {
254 | console.log(res)
255 | // this._getLikeStatus(res.id, res.type) //不能这样写,会多发一次favor请求,消耗性能
256 | this.setData({
257 | classic: res,
258 | likeCount: res.fav_nums,
259 | likeStatus: res.like_status
260 | })
261 | })
262 | ```
263 |
264 | -----------------------------------
265 |
266 | ##### 在 classic/music/index.js 中:
267 |
268 | 解决切换期刊时,其他期刊也都是播放状态的问题。应该是,切换期刊时音乐就停止播放,回到默认不播放状态
269 |
270 | 利用组件事件的通信机制,小程序中只有父子组件
271 |
272 | 在 components/classic/music/inddex.js 中:
273 |
274 | ##### 方案一:
275 |
276 | ```js
277 |
278 | //利用组件生命周期,只有 wx:if 才可以从头掉起组件生命周期
279 | // 组件卸载的生命周期函数
280 | // 组件卸载音乐停止播放,但这时不生效是因为,在classic.wxml中用的是hidden,应改为if
281 | detached: function(event) {
282 | mMgr.stop()
283 | },
284 | // 在 pages/classic/classic.wxml 中
285 | //
286 |
287 | ```
288 |
289 | #### 知识点补充:
290 |
291 | > wx:if vs hidden,和Vue框架的v-if和v-show 指令一样:
292 | > wx:if 》他是惰性的,如果初始值为false框架什么也不做,如果初始值为true框架才会局部渲染。true或false的切换就是从页面中局部加入或移除的过程。wx:if 有更高的切换消耗,如果在运行时条件不大可能改变则 wx:if 较好。生命周期会重新执行。
293 | > hidden 》组件始终会被渲染,只是简单的控制显示与隐藏。hidden 有更高的初始渲染消耗。如果需要频繁切换的情景下,用 hidden 更好。生命周期不会重新执行。
294 |
295 | ##### 方案二:(推荐使用)
296 |
297 | 解决切换期刊时音乐可以当做背景音乐一直播放,而其他的期刊是默认是不播放状态
298 |
299 | ##### 在 components/classic/music/inddex.js 中:
300 |
301 | ```js
302 | //为了保证期刊在切换时,背景音乐可以一直播放,就要去除掉 mMgr.stop() 事件方法
303 | detached: function(event) {
304 | // mMgr.stop() //为了保证背景音乐的持续播放就不能加stop
305 | },
306 |
307 | // 监听音乐的播放状态,如果当前页面没有播放的音乐,就设置playing为false。如果当前页面的音乐地址classic.url和当前正在播放的音乐的地址一样,就让播放状态为true
308 | _recoverStatus: function() {
309 | if (mMgr.paused) {
310 | this.setData({
311 | playing: false
312 | })
313 | return
314 | }
315 | if (mMgr.src === this.properties.src) {
316 |
317 | this.setData({
318 | playing: true
319 | })
320 |
321 |
322 | }
323 | },
324 |
325 | // 监听播放状态,总控开关就可以控制播放状态,结局总控开关和页面不同步问题
326 | _monitorSwitch: function() {
327 | console.log('monitorSwitch背景音频', '触发3')
328 | // 监听背景音频播放事件
329 | mMgr.onPlay(() => {
330 | this._recoverStatus()
331 | console.log('onPlay ' + this.data.playing)
332 | })
333 | // 监听背景音频暂停事件
334 | mMgr.onPause(() => {
335 | this._recoverStatus()
336 | console.log('onPause ' + this.data.playing)
337 | })
338 | // 关闭音乐控制台,监听背景音频停止事件
339 | mMgr.onStop(() => {
340 | this._recoverStatus()
341 | console.log('onStop ' + this.data.playing)
342 | })
343 | // 监听背景音频自然播放结束事件
344 | mMgr.onEnded(() => {
345 | this._recoverStatus()
346 | console.log('onEnded ' + this.data.playing)
347 | })
348 | },
349 |
350 | //调用生命周期函数,每次切换都会触发attached生命周期
351 | // 在组件实例进入页面节点树时执行
352 | // hidden,ready,created都触发不了生命周期函数
353 | attached: function(event) {
354 | console.log('attach实例进入页面', '触发1')
355 | this._monitorSwitch()
356 | this._recoverStatus()
357 |
358 |
359 | },
360 | ```
361 |
362 |
363 |
364 | ### 播放动画旋转效果制作:
365 |
366 | ##### ##### 在 components/classic/music/index.wxss 中:
367 |
368 | ```js
369 | //定义帧动画用CSS3
370 | .rotation {
371 | -webkit-transform: rotate(360deg);
372 | animation: rotation 12s linear infinite;
373 | -moz-animation: rotation 12s linear infinite;
374 | -webkit-animation: rotation 12s linear infinite;
375 | -o-animation: rotation 12s linear infinite;
376 | }
377 |
378 | @-webkit-keyframes rotation {
379 | from {
380 | -webkit-transform: rotate(0deg);
381 | }
382 | to {
383 | -webkit-transform: rotate(360deg);
384 | }
385 | }
386 |
387 | ```
388 |
389 | ### 补充css3知识点:
390 |
391 | > 》使用CSS3开启GPU硬件加速提升网站动画渲染性能:
392 | > 为动画DOM元素添加CSS3样式-webkit-transform:transition3d(0,0,0)或-webkit-transform:translateZ(0);,这两个属性都会开启GPU硬件加速模式,从而让浏览器在渲染动画时从CPU转向GPU,其实说白了这是一个小伎俩,也可以算是一个Hack,-webkit-transform:transition3d和-webkit-transform:translateZ其实是为了渲染3D样式,但我们设置值为0后,并没有真正使用3D效果,但浏览器却因此开启了GPU硬件加速模式。
393 | > 》这种GPU硬件加速在当今PC机及移动设备上都已普及,在移动端的性能提升是相当显著地,所以建议大家在做动画时可以尝试一下开启GPU硬件加速。
394 | >
395 | > 》适用情况
396 | > 通过-webkit-transform:transition3d/translateZ开启GPU硬件加速的适用范围:
397 | >
398 | > 使用很多大尺寸图片(尤其是PNG24图)进行动画的页面。
399 | > 页面有很多大尺寸图片并且进行了css缩放处理,页面可以滚动时。
400 | > 使用background-size:cover设置大尺寸背景图,并且页面可以滚动时。(详见:https://coderwall.com/p/j5udlw)
401 | > 编写大量DOM元素进行CSS3动画时(transition/transform/keyframes/absTop&Left)
402 | > 使用很多PNG图片拼接成CSS Sprite时
403 | > 》总结
404 | > 通过开启GPU硬件加速虽然可以提升动画渲染性能或解决一些棘手问题,但使用仍需谨慎,使用前一定要进行严谨的测试,否则它反而会大量占用浏览网页用户的系统资源,尤其是在移动端,肆无忌惮的开启GPU硬件加速会导致大量消耗设备电量,降低电池寿命等问题。
405 |
406 | ##### 在 components/classic/music/index.wxml 中:
407 |
408 | ```js
409 | //为图片加上播放就旋转的类,不播放 就就为空字符串
410 |
411 |
412 | ```
413 |
414 | ---------------------------------
415 |
416 | 用 slot 插槽,解决在公用组件中可以加入其他修饰内容问题。其实就是,在定义公用组件时,用 slot 命名插槽占位,在父组件调用时可以传递需要的内容补位。和Vue的指令 v-slot 相似。
417 |
418 | ##### 在 components/tag/index.js 中:
419 |
420 | ```js
421 | //在 Component 中加入
422 | // 启用slot
423 | options: {
424 | multipleSlots: true
425 | },
426 | ```
427 |
428 |
429 |
430 | ##### 在定义的公共组件 components/tag/index.wxml 中:
431 |
432 | ```html
433 | //定义几个命名插槽,供父元素占位使用
434 |
435 |
436 | {{text}}
437 |
438 |
439 | ```
440 |
441 | ##### 在 pages/detail/detail.json 中:
442 |
443 | ```json
444 | //注册并使用组件
445 | {
446 | "usingComponents": {
447 | "v-tag": "/components/tag/index"
448 | }
449 | }
450 | ```
451 |
452 | ##### 在 pages/detail/detail.wxml 中:
453 |
454 | ```html
455 | //使用组件v-tag,补位命名插槽
456 |
457 | {{'+'+item.nums}}
458 |
459 | ```
460 |
461 |
462 |
463 |
464 |
465 | ------------------------------------------
466 |
467 | ##### 在 `pages/detail/detail` 中,解决评论内容自定义组件 v-tag 评论前两条显示两种颜色的做法:
468 |
469 | ###### 第一种方法:(推荐使用)
470 |
471 | ##### 在 pages/detail/detail.wxss 中:
472 |
473 | ```css
474 | /* v-tag是自定义组件,不能使用css3,在微信小程序中,只有内置组件才可以用css3 */
475 | /*用CSS hack方式给自定义组件加样式*/
476 | .comment-container>v-tag:nth-child(1)>view {
477 | background-color: #fffbdd;
478 | }
479 |
480 | .comment-container>v-tag:nth-child(2)>view {
481 | background-color: #eefbff;
482 | }
483 | ```
484 |
485 | ###### 第二种方法:
486 |
487 | 定义外部样式方法,像父子组件传递属性一样,传递样式类
488 |
489 | ##### 在 detail.wxss 中:
490 |
491 | ```css
492 | /* 定义外部样式 */
493 |
494 | .ex-tag1 {
495 | background-color: #fffbdd !important;
496 | }
497 |
498 | .ex-tag2 {
499 | background-color: #eefbff !important;
500 | }
501 | ```
502 |
503 | ##### 在 detail.wxml 中:
504 |
505 | ```html
506 | /*将自定义的样式类通过属性传值的方式传递给自定义子组件v-tag */
507 |
508 | {{'+'+item.nums}}
509 |
510 | ```
511 |
512 | ##### 在 components/tag/index.js 中:
513 |
514 | ```js
515 | //将外部传进来的样式写在Component中,声明一下
516 | // 外部传进来的css,样式
517 | externalClasses: ['tag-class'],
518 |
519 | ```
520 |
521 |
522 |
523 | ##### 在 components/tag/index.wxml 中:
524 |
525 | ```html
526 | // 把父组件传递过来的类 tag-calss 写在 class 类上
527 |
528 |
529 | {{text}}
530 |
531 |
532 | ```
533 |
534 | ----------------------------------------
535 |
536 | ### 解决服务器返回的内容简介有 \n 换行符的问题:
537 |
538 | 原因:
539 |
540 | > 是因为服务器返回的原始数据 是 `\\n` ,经过转义就变成 `\n`
541 | >
542 | > 而 `\n` 在text文本标签中默认转义为换行
543 |
544 | 解决方法:
545 |
546 | WXS:WXS(WeiXin Script)是小程序的一套脚本语言,结合 `WXML`,可以构建出页面的结构。和Vue 中的 Vue.filter(过滤器名,过滤器方法) 很相似。WXS 与 JavaScript 是不同的语言,有自己的语法,并不和 JavaScript 一致。由于运行环境的差异,在 iOS 设备上小程序内的 WXS 会比 JavaScript 代码快 2 ~ 20 倍。在 android 设备上二者运行效率无差异。
547 |
548 | ##### 在 utils/filter.wxs 中:
549 |
550 | ```js
551 | // 定义过滤器函数,处理服务器返回的数据,将 \\n 变成 \n
552 | // 会打印两次,undefined和请求得到的数据,因为第一次初始时text为null,发送请求得到数据后调用setData更新数据一次
553 | var format = function(text) {
554 | console.log(text)
555 |
556 | if (!text) {
557 | return
558 | }
559 | var reg = getRegExp('\\\\n', 'g')
560 | return text.replace(reg, '\n')
561 | }
562 |
563 | module.exports.format = format
564 | ```
565 |
566 | ##### 在 pages/detail/detail.wxml 中:
567 |
568 | ```html
569 | //引入
570 |
571 | //在需要过滤的数据中使用
572 | {{util.format(book.summary)}}
573 | ```
574 |
575 | -----------------
576 |
577 | ### 解决解决服务器返回的内容简介首行缩进的问题:
578 |
579 | ##### 在 pages/detail/detail.wxss 中:
580 |
581 | ```css
582 | //对需要缩进的段落前加以下的类,但这时只有第一段缩进
583 | .content {
584 | text-indent: 58rpx;
585 | font-weight: 500;
586 | }
587 | ```
588 |
589 | ##### 在 utils/filter.wxs 中:
590 |
591 | ```js
592 | //用转义字符 作为空格,但这时小程序会以 样式输出,不是我们想要的效果
593 | var format = function(text) {
594 | if (!text) {
595 | return
596 | }
597 | var reg = getRegExp('\\\\n', 'g')
598 | return text.replace(reg, '\n ')
599 | }
600 |
601 | module.exports.format = format
602 | ```
603 |
604 | ##### 在 pages/detail/detail.wxml 中:
605 |
606 | ```html
607 | //加入属性 decode="{{true}}",首行缩进问题解决
608 | {{util.format(book.summary)}}
609 | ```
610 |
611 | ------------------------------------
612 |
613 | ### 解决短评过多让其只显示一部分的问题:
614 |
615 | ##### 在 utils/filter.wxs 中:
616 |
617 | ```js
618 | //添加一个限制短评长度的过滤器,并导出
619 | // 限制短评的长度的过滤器
620 | var limit = function(array, length) {
621 | return array.slice(0, length)
622 | }
623 |
624 | module.exports = {
625 | format: format,
626 | limit: limit
627 | };
628 | ```
629 |
630 | ##### 在 pages/detail/detail.wxml 中:
631 |
632 | ```html
633 |
634 |
635 |
636 | 短评
637 |
644 |
645 |
646 |
647 | ```
648 |
649 | ##### 在 pages/detail/detail.wxml 中:进一步优化
650 |
651 | ```html
652 | // 由于 过于乱,改写成wxs形式:
653 |
654 | //先定义wxs过滤器
655 |
656 | var highlight = function(index){
657 | if(index===0){
658 | return 'ex-tag1'
659 | }
660 | else if(index===1){
661 | return 'ex-tag2'
662 | }
663 | return ''
664 | }
665 | module.exports={
666 | highlight:highlight
667 | }
668 |
669 |
670 | //改写为:
671 |
672 |
673 | ```
674 |
675 |
676 |
677 | ---------------------------
678 |
679 | ## 详情最底部短评的实现:
680 |
681 | 用户提交评论内容:
682 |
683 | 点击标签向服务器提交评论内容:
684 |
685 | ##### 在 components\tag\index.wxml 中:
686 |
687 | ```html
688 | //为短评组件绑定出没事件 onTap
689 |
690 |
691 | {{text}}
692 |
693 |
694 | ```
695 |
696 | ##### 在 components\tag\index.js 中:
697 |
698 | ```js
699 | // 当触摸短评小标签时,触发一个自定义事件,将短评内容传进去,公父组件调用自定义事件tapping
700 | methods: {
701 | // 触摸短评小标签时,触发的事件,触发一个自定义事件
702 | onTap(event) {
703 | this.triggerEvent('tapping', {
704 | text: this.properties.text
705 | })
706 | }
707 | }
708 | ```
709 |
710 | ##### 在 pages\detail\detail.wxml 中:
711 |
712 | ```html
713 | //在父组件中调用子组件的自定义tapping事件,并且触发事件onPost
714 |
715 | {{'+'+item.nums}}
716 |
717 | ```
718 |
719 | ##### 在 models\book.js 中:
720 |
721 | ```js
722 | //调取新增短评的接口
723 | // 新增短评
724 | postComment(bid, comment) {
725 | return this.request({
726 | url: '/book/add/short_comment',
727 | method: 'POST',
728 | data: {
729 | book_id: bid,
730 | content: comment
731 | }
732 | })
733 | }
734 | ```
735 |
736 | ##### 在 pages\detail\detail.js 中:
737 |
738 | ```js
739 | // 触摸tag组件会触发,input输入框也会触发事件onPost
740 | // 获取用户的输入内容或触发tag里的内容,并且对用户输入的评论做校验,如果评论的内容长度大于12就弹出警告不向服务器发送请求
741 | //如果评论内容符合规范,就调用新增短评接口并将最新的评论插到comments数组的第一位,更新数据,并且把蒙层mask关闭
742 | onPost(event) {
743 | // 获取触发tag里的内容
744 | const comment = event.detail.text
745 | // 对用户输入的评论做校验
746 | if (comment.length > 12) {
747 | wx.showToast({
748 | title: '短评最多12个字',
749 | icon: 'none'
750 | })
751 | return
752 | }
753 |
754 | // 调用新增短评接口并将最新的评论插到comments数组的第一位
755 | bookModel.postComment(this.data.book.id, comment).then(res => {
756 | wx.showToast({
757 | title: '+1',
758 | icon: 'none'
759 | })
760 | this.data.comments.unshift({
761 | content: comment,
762 | nums: 1 //这是后面的数量显示
763 | })
764 | this.setData({
765 | comments: this.data.comments,
766 | posting: false
767 | })
768 | })
769 | },
770 |
771 | ```
772 |
773 | ##### 在 pages\detail\detail.wxml 中:
774 |
775 | ```html
776 | // input有自己的绑定事件bindconfirm,会调用手机键盘完成按键
777 |
778 | ```
779 |
780 | ##### 在 pages\detail\detail.js 中:
781 |
782 | 点击标签向服务器提交评论内容完成:
783 |
784 | ```js
785 | // 触摸tag组件会触发,input输入框也会触发事件onPost事件;然后获取触发tag里的内容或获取用户input输入的内容;对tag里的内容或对用户输入的评论做校验并且输入的内容不能为空;最后调用新增短评接口并将最新的评论插到comments数组的第一位,并且把蒙层mask关闭
786 |
787 | onPost(event) {
788 | const comment = event.detail.text||event.detail.value
789 | console.log('comment'+comment)
790 | console.log('commentInput'+comment)
791 | if (comment.length > 12|| !comment) {
792 | wx.showToast({
793 | title: '短评最多12个字',
794 | icon: 'none'
795 | })
796 | return
797 | }
798 | bookModel.postComment(this.data.book.id, comment).then(res => {
799 | wx.showToast({
800 | title: '+1',
801 | icon: 'none'
802 | })
803 | this.data.comments.unshift({
804 | content: comment,
805 | nums: 1 //这是后面的数量显示
806 | })
807 | this.setData({
808 | comments: this.data.comments,
809 | posting: false
810 | })
811 | })
812 | },
813 | ```
814 |
815 | ### 细节处理:
816 |
817 | 如果没有短评显示问题:
818 |
819 | ##### 在 pages\detail\detail.wxml 中:
820 |
821 | ```html
822 | //在短评后加上还没有短评标签,如果没有comments短评就不显示还没有短评标签
823 | 短评
824 | 还没有短评
825 |
826 | //在尽可点击标签+1后加上暂无评论标签,如果没有comments短评就不显示暂无评论标签
827 | 尽可点击标签+1
828 | 暂无短评
829 | ```
830 |
831 | -------------------------------------------------
832 |
833 | 由于需要加载的数据较多,为了提高用户体验,需要加一个loading提示数据正在加载中,数据加载完成后就消失;
834 |
835 | 由于都是利用promise异步加载数据,这时取消loading显示应该加到每个promise后,显然不符合需求。如果利用回调函数机制,先加载1在一的回调函数里在加载2依次顺序加载,在最后一个回调函数中写取消loading操作,这样的方式虽然可以实现,但非常耗时间,请求是串行的,假如一个请求需要花费2s中,发三个请求就要花费6秒,非常耗时,而且还会出现回调地狱的现象,不推荐使用。
836 |
837 | 解决方法:在Promise中,有一个Promise.all()方法就可以解决。
838 |
839 | ### 补充知识点:
840 |
841 | > **Promise.all(iterable)** 方法返回一个 [`Promise`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise) 实例,此实例在 `iterable` 参数内所有的 `promise` 都“完成(resolved)”或参数中不包含 `promise` 时回调完成(resolve);如果参数中 `promise` 有一个失败(rejected),此实例回调失败(reject),失败原因的是第一个失败 `promise` 的结果。简单来说就是:只要有一个数组里的promise获取失败就调用reject回调,只有全部数组里的promise都成功才调用resolve回调。
842 | >
843 | > **Promise.race(iterable)** 方法返回一个 promise,一旦迭代器中的某个promise解决或拒绝,返回的 promise就会解决或拒绝。`race` 函数返回一个 `Promise`,它将与第一个传递的 promise 相同的完成方式被完成。它可以是完成( resolves),也可以是失败(rejects),这要取决于第一个完成的方式是两个中的哪个。如果传的迭代是空的,则返回的 promise 将永远等待。如果迭代包含一个或多个非承诺值和/或已解决/拒绝的承诺,则` Promise.race` 将解析为迭代中找到的第一个值。简单来说就是:不论成功还是失败的回调,哪一个快就执行哪个。
844 |
845 |
846 |
847 | ##### 在 pages\detail\detail.js 中:
848 |
849 | ```js
850 | //用了 Promise.all(iterable) 方法就不用写三个Promise方法分别来更新数据了,可以简写成一个all方法再返回的成功的promise中调用setData(),更新请求回的数据
851 | onLoad: function(options) {
852 | // 数据加载时显示loading效果
853 | wx.showLoading()
854 | const bid = options.bid
855 | console.log(bid)
856 | // 获取书籍详细信息
857 | const detail = bookModel.getDetail(bid)
858 | // 获取书籍点赞情况
859 | const likeStatus = bookModel.getLikeStatus(bid)
860 | // 获取书籍短评
861 | const comments = bookModel.getComments(bid)
862 |
863 | // 数据加载完成时取消显示loading效果
864 | Promise.all([detail, comments, likeStatus]).then(res => {
865 | console.log(res)
866 | this.setData({
867 | book: res[0],
868 | comments: res[1].comments,
869 | likeStatus: res[2].like_status,
870 | likeCount: res[2].fav_nums
871 | })
872 | wx.hideLoading()
873 | })
874 |
875 |
876 | },
877 | ```
878 |
879 | ------------------------------------
880 |
881 | ## 图书的搜索:
882 |
883 | 高阶组件:如果一个组件里面的内容比较复杂,包含大量的业务
884 |
885 | ### 知识点补充:
886 |
887 | >工作中我们通常把业务处理逻辑写在models中:
888 | >
889 | >可以写在单个公用组件里,只供自己写业务逻辑调取使用;
890 | >
891 | >可以写在components中,只供components内的组件调取使用,如果想把components发布出去给其他项目用或者提供给其他开发者使用;
892 | >
893 | >可以写在项目根目录models下,供整个项目调取使用写业务逻辑,如果只是做个项目建议写在这里不会乱。
894 | >
895 | >
896 |
897 | ##### 在 components\search\index 中:
898 |
899 | ### 处理历史搜索和热门搜索:
900 |
901 | > 历史搜索:将历史搜索关键字写入缓存中,在从缓存中获取历史搜索关键字。
902 | >
903 | > 热门搜索:调取服务器API
904 | >
905 | > GET /book/hot_list
906 |
907 | ##### 将业务逻辑写在 models\keyword.js 中:
908 |
909 | ```js
910 | //首先从缓存中获取历史搜索关键字数组,判断获取的数组是否为空,如果为空,为了防止报错就返回空数组;如果不为空就直接返回获取的数组。
911 | //将搜索关键字写入缓存中,先从缓存中获取历史关键字的数组,判断是否包含此次输入的关键字,如果没有此关键字,如果获取的长度大于最大长度,就将数组的最后一项删除;如果获取数组的长度小于最大长度,就将此次输入的关键字加到数组的第一位,并且设置到缓存中。
912 | class KeywordModel {
913 | constructor() {
914 | // 把key属性挂载到当前实例上,供实例调取使用
915 | this.key = 'q',
916 | this.maxLength = 10 //搜索关键字的数组最大长度为10
917 | }
918 |
919 | //从缓存中,获取历史搜索关键字数组,如果缓存中没有直接返回空数组
920 | getHistory() {
921 | const words = wx.getStorageSync(this.key)
922 | if (!words) {
923 | return []
924 | }
925 | return words
926 | }
927 |
928 | // 将历史搜索关键字写入缓存中。先从缓存中获取历史关键字的数组,判断数组中是否已经有此关键字。如果没有,并且获取数组的长度大于最大长度,就将数组最后一项删除。获取数组的长度小于最大长度就将此次输入的关键字加到数组第一位,并且设置到缓存中;
929 | addToHistory(keyword) {
930 | let words = this.getHistory()
931 | const has = words.includes(keyword)
932 | if (!has) {
933 | const length = words.length
934 | if (length >= this.maxLength) {
935 | words.pop()
936 | }
937 | words.unshift(keyword)
938 | wx.setStorageSync(this.key, words)
939 | }
940 |
941 | }
942 |
943 | // 获取热门搜素搜关键字
944 | getHot() {
945 |
946 | }
947 | }
948 |
949 | export {KeywordModel}
950 | ```
951 |
952 | ##### 在 components\search\index.wxml 中:
953 |
954 | ```html
955 | //为input输入框绑定onConfirm事件
956 |
957 | ```
958 |
959 | ##### 在 components\search\index.js 中:
960 |
961 | ```js
962 | // onConfirm事件执行,调用将输入的内容添加到缓存中的方法Keywordmodel.addToHistory(word),就可以将历史关键字添加到缓存中
963 | methods: {
964 | // 点击取消将搜索组件关掉,有两种方法:一是,在自己的内部创建一个变量控制显隐,不推荐,因为万一还有其他操作扩展性不好。二是,创建一个自定义事件,将自定义事件传给父级,让父级触发
965 | onCancel(event) {
966 | this.triggerEvent('cancel', {}, {})
967 | },
968 |
969 | // 在input输入框输入完成将输入的内容加到缓存中
970 | onConfirm(event) {
971 | const word = event.detail.value
972 | Keywordmodel.addToHistory(word)
973 | }
974 | }
975 | ```
976 |
977 | ##### 在 components\search\index.js 中:
978 |
979 | ```js
980 | //将历史搜索的内容从缓存中取出来
981 | data: {
982 | historyWords: [] //历史搜索关键字
983 | },
984 |
985 | // 组件初始化时,小程序默认调用的生命周期函数
986 | attached() {
987 | const historywords = Keywordmodel.getHistory()
988 | this.setData({
989 | historyWords: historywords
990 | })
991 | },
992 | ```
993 |
994 | ##### 在 components\search\index.json 中:
995 |
996 | ```json
997 | //注册引用小标签 tag 组件,组件中也可以引入其他组件
998 | "usingComponents": {
999 | "v-tag": "/components/tag/index"
1000 | }
1001 | ```
1002 |
1003 | ##### 在 components\search\index.wxml 中:
1004 |
1005 | ```html
1006 | // 遍历historyWords数组中的每一项,呈现在页面中
1007 |
1008 |
1009 |
1010 | 历史搜索
1011 |
1012 |
1013 |
1014 |
1015 |
1016 |
1017 |
1018 | ```
1019 |
1020 | ### 热门搜索:
1021 |
1022 | ##### 在 models\keyword.js 中:
1023 |
1024 | ```js
1025 | // 引入自己封装的API请求方法
1026 | import {
1027 | HTTP
1028 | } from '../utils/http-promise'
1029 |
1030 | // 获取热门搜素搜关键字
1031 | getHot() {
1032 | return this.request({
1033 | url: '/book/hot_keyword'
1034 | })
1035 | }
1036 | ```
1037 |
1038 | ##### 在 components\search\index.js 中:
1039 |
1040 | ```js
1041 | //定义组件初始值,通过调用传进来的getHot方法获取热门搜索关键字,并更新到初始值hotWords中
1042 | data: {
1043 | historyWords: [], //历史搜索关键字
1044 | hotWords: [] //热门搜索关键字
1045 | },
1046 | // 组件初始化时,小程序默认调用的生命周期函数
1047 | attached() {
1048 | const historywords = Keywordmodel.getHistory()
1049 | const hotword = Keywordmodel.getHot()
1050 | this.setData({
1051 | historyWords: historywords
1052 | })
1053 |
1054 | hotword.then(res => {
1055 | this.setData({
1056 | hotWords: res.hot
1057 | })
1058 | })
1059 | },
1060 | ```
1061 |
1062 | ##### 在 components\search\index.wxml 中:
1063 |
1064 | ```html
1065 | //将从服务器获取到的hotWords数组遍历,呈现到页面中
1066 |
1067 |
1068 |
1069 | 热门搜索
1070 |
1071 |
1072 |
1073 |
1074 |
1075 |
1076 |
1077 | ```
1078 |
1079 | ### 注意点:
1080 |
1081 | >由于在 components\search\index.js 调用了 Keywordmodel.getHot()方法,这个方法是和服务器相关联的,这样做,会使组件复用性降低。
1082 | >
1083 | >如果要想让search组件复用性变高,应该在 components\search\index.js 的 properties 中开放一个属性,然后再引用search组件的pages页面里调用models里的方法,再把数据通过属性传递给search组件,然后再做数据绑定,这样就让search组件具备了复用性
1084 | >
1085 | >
1086 |
1087 | ##### 在 models\book.js 中:
1088 |
1089 | ```js
1090 | //定义search函数,封装向服务器发送请求功能
1091 | // 书籍搜索
1092 | search(start, q) {
1093 | return this.request({
1094 | url: '/book/search?summary=1',
1095 | data: {
1096 | q: q,
1097 | start: start
1098 | }
1099 | })
1100 | }
1101 | ```
1102 |
1103 | ##### 在 components\search\index.js 中:
1104 |
1105 | ```js
1106 | // 导入并实例化BookModel类,负责向服务器发送搜索图书的请求;在data中声明私有变量 dataArray 数组,为搜索图书当summary=1,返回概要数据。在用户输入完成点击完成时,调用bookmodel.search方法,并更新数据到dataArray中。
1107 | //注意点:不能用户输入什么都保存在缓存中,只有用户搜索到有效的关键字时才保存到缓存中
1108 | import {
1109 | BookModel
1110 | } from '../../models/book'
1111 | const bookmodel = new BookModel()
1112 |
1113 | data: {
1114 | historyWords: [], //历史搜索关键字
1115 | hotWords: [], //热门搜索关键字
1116 | dataArray: [] //搜索图书当summary=1,返回概要数据
1117 | },
1118 |
1119 | // 在input输入框输入完成将输入的内容加到缓存中
1120 | onConfirm(event) {
1121 | const word = event.detail.value
1122 |
1123 | // 获取搜索的关键词q,调取search方法返回当summary=1,返回概要数据:并更新数据到dataArray中
1124 | const q = event.detail.value
1125 | bookmodel.search(0, q).then(res => {
1126 | this.setData({
1127 | dataArray: res.books
1128 | })
1129 |
1130 | // 不能用户输入什么都保存在缓存中,只有用户搜索到有效的关键字时才保存到缓存中
1131 | Keywordmodel.addToHistory(word)
1132 |
1133 | })
1134 |
1135 | }
1136 | ```
1137 |
1138 | ##### 在 components\search\index.wxml 中:
1139 |
1140 | 解析得到的搜索数据,并遍历呈现到页面中:
1141 |
1142 | ```html
1143 | //搜索得到的数据和热门搜索,历史搜索是不能一起显示的,一个显示,另一个就得隐藏,搜索得到的结果页面是默认不显示的,需要定义searching一个变量来控制显隐
1144 |
1145 |
1146 |
1147 |
1148 |
1149 |
1150 | ```
1151 |
1152 | ##### 在 components\search\index.js 中:
1153 |
1154 | ```js
1155 | //在data私有属性中定义searching变量来控制显隐,默认为false;在触发onConfirm事件中, 为了用户体验好,应该点击完立即显示搜索页面,并将searching改为true,让其搜索的内容显示到页面上
1156 | data: {
1157 | historyWords: [], //历史搜索关键字
1158 | hotWords: [], //热门搜索关键字
1159 | dataArray: [], //搜索图书当summary=1,返回概要数据
1160 | searching: false //控制搜索到的图书数据的显隐,默认不显示
1161 | },
1162 |
1163 | // 在input输入框输入完成将输入的内容加到缓存中
1164 | onConfirm(event) {
1165 | // 为了用户体验好,应该点击完立即显示搜索页面
1166 | this.setData({
1167 | searching: true
1168 | })
1169 |
1170 | // 获取搜索的关键词q,调取search方法返回当summary=1,返回概要数据:并更新数据到dataArray中
1171 | const q = event.detail.value
1172 | bookmodel.search(0, q).then(res => {
1173 | this.setData({
1174 | dataArray: res.books
1175 | })
1176 |
1177 | // 不能用户输入什么都保存在缓存中,只有用户搜索到有效的关键字时才保存到缓存中
1178 | Keywordmodel.addToHistory(q)
1179 |
1180 | })
1181 |
1182 | }
1183 | ```
1184 |
1185 | ### 实现 搜索框里的 x 按钮功能:
1186 |
1187 | ##### 在 components\search\index.js 中:
1188 |
1189 | ```js
1190 | // 在 components\search\index.wxml 中,为 x 图片绑定触摸时触发的 onDelete 事件
1191 |
1192 | // 触摸搜索图片里的x回到原来输入搜索的页面
1193 | onDelete(event){
1194 | this.setData({
1195 | searching: false
1196 | })
1197 | },
1198 |
1199 |
1200 | ```
1201 |
1202 | ### 实现 用户点击历史搜索和热门搜索里的标签也能跳转到相应的搜索到的结果显示页面:只要监听到用户点击标签的事件就可以实现
1203 |
1204 | ##### 在 components\search\index.js 中:
1205 |
1206 | ```js
1207 | // 在 components\search\index.wxml 中:绑定v-tag组件自定事件tapping触发onConfirm事件:``
1208 |
1209 |
1210 | // 在input输入框输入完成将输入的内容加到缓存中
1211 | onConfirm(event) {
1212 | // 为了用户体验好,应该点击完立即显示搜索页面
1213 | this.setData({
1214 | searching: true
1215 | })
1216 |
1217 | // 获取搜索的关键词q:一种是用户输入的内容或是通过调用tag组件的自定义事件tapping,里面有携带的text文本;调取search方法返回当summary=1,返回概要数据:并更新数据到dataArray中
1218 | const q = event.detail.value || event.detail.text
1219 | bookmodel.search(0, q).then(res => {
1220 | this.setData({
1221 | dataArray: res.books
1222 | })
1223 |
1224 | // 不能用户输入什么都保存在缓存中,只有用户搜索到有效的关键字时才保存到缓存中
1225 | Keywordmodel.addToHistory(q)
1226 |
1227 | })
1228 |
1229 | }
1230 | ```
1231 |
1232 | ### 解决再点击tag标签搜索时应该在input输入框中显示书名的问题:
1233 |
1234 | ##### 在 components\search\index.js 中:
1235 |
1236 | ```js
1237 | //通过数据绑定给input输入框绑定value="{{q}}"
1238 | //``
1239 |
1240 | //先在data中定义私有数据 q: '' 代表输入框中要显示的内容,当数据请求完成后把点击标签的内容q赋值给私有数据q并更新
1241 | data: {
1242 | historyWords: [], //历史搜索关键字
1243 | hotWords: [], //热门搜索关键字
1244 | dataArray: [], //搜索图书当summary=1,返回概要数据
1245 | searching: false, //控制搜索到的图书数据的显隐,默认不显示
1246 | q: '' //输入框中要显示的内容
1247 | },
1248 | // 在input输入框输入完成将输入的内容加到缓存中
1249 | onConfirm(event) {
1250 | // 为了用户体验好,应该点击完立即显示搜索页面
1251 | this.setData({
1252 | searching: true
1253 | })
1254 |
1255 | // 获取搜索的关键词q,调取search方法返回当summary=1,返回概要数据:并更新数据到dataArray中
1256 | const q = event.detail.value || event.detail.text
1257 | bookmodel.search(0, q).then(res => {
1258 | this.setData({
1259 | dataArray: res.books,
1260 | q: q
1261 | })
1262 |
1263 | // 不能用户输入什么都保存在缓存中,只有用户搜索到有效的关键字时才保存到缓存中
1264 | Keywordmodel.addToHistory(q)
1265 |
1266 | })
1267 |
1268 | }
1269 | ```
1270 |
1271 | ### 实现数据分页加载功能:
1272 |
1273 | 第一种方法:用微信小程序提供的内置组件 `scroll-view` 。
1274 |
1275 | 第二种方法:用 pages 里的 页面上拉触底事件的处理函数 onReachBottom。:
1276 |
1277 | ##### 在 pages\book\book.js 中:
1278 |
1279 | ```js
1280 | //在 data里设置私有变量more为false,代表的是是否需要加载更多数据,默认是不加载
1281 | data: {
1282 | books: [],
1283 | searching: false, //控制搜索框组件search显隐,默认不显示
1284 | more: false //是否需要加载更多数据,默认是不加载
1285 | },
1286 |
1287 | //用pages里自带的 页面上拉触底事件的处理函数 onReachBottom 监听页面是否到底了,如果到底了就会就会将more改变为true,就可以实现加载更多数据方法
1288 | onReachBottom: function() {
1289 | console.log('到底了')
1290 | this.setData({
1291 | more: true
1292 | })
1293 | },
1294 |
1295 | //由于 search 组件不是页面级组件,没有 onReachBottom 函数,就需要通过属性传值的方式将more私有变量控制是否加载更多数据传给子组件search
1296 | // 在pages\book\book.wxml中: ``
1297 |
1298 | //然后在search组件里接收父级传递过来的属性more,并利用监听函数observer,只要外界传来的数据改变就会触发此函数执行
1299 | properties: {
1300 | more: {
1301 | type: String,
1302 | observer: '_load_more'
1303 | } //从pages/book/book.js 传来的属性,监听滑到底步操作.只要外界传来的属性改变就会触发observer函数执行
1304 | },
1305 |
1306 | methods: {
1307 | // 只要外界传来的属性改变就会触发observer函数执行
1308 | _load_more() {
1309 | console.log('监听函数触发到底了')
1310 | },
1311 | }
1312 |
1313 | ```
1314 |
1315 | > 但现在存在一个问题就是:
1316 | >
1317 | > observer只会触发一次,因为下拉到底会把more变为true,之后就都是true不会再发生变化了,就不会再触发监听函数observer执行。
1318 | >
1319 | > 解决方法:用随机字符串触发observer函数,因为observer函数的执行必须是监听的数据发生改变才会执行此函数。和Vue中的watch很相似。
1320 |
1321 | ##### 在 pages\book\book.js 中:
1322 |
1323 | ```js
1324 | //将私有数据data中的more改为空字符串
1325 | data: {
1326 | books: [],
1327 | searching: false, //控制搜索框组件search显隐,默认不显示
1328 | more: '' //是否需要加载更多数据,默认是不加载
1329 | },
1330 |
1331 | //触发 页面上拉触底事件的处理函数,将more变为随机数,导入random自定义随机处理函数,问题解决
1332 | onReachBottom: function() {
1333 | console.log('到底了')
1334 | this.setData({
1335 | more: random(16)
1336 | })
1337 | },
1338 |
1339 | // 在 utils\common.js 中:
1340 | // 定义随机生成字符串处理函数,n是生成随机字符串的位数
1341 | const charts = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z']
1342 |
1343 | const random = function generateMixed(n) {
1344 | var res = ''
1345 | for (var i = 0; i < n; i++) {
1346 | var id = Math.ceil(Math.random() * 35)
1347 | res += charts[id]
1348 | }
1349 | return res
1350 | }
1351 |
1352 | export {
1353 | random
1354 | }
1355 |
1356 | ```
1357 |
1358 | ##### 在 components\search\index.js 中:
1359 |
1360 | ### 实现加载更多数据:
1361 |
1362 | ```js
1363 | // 和onConfirm一样都需要调取search方法返回当summary=1,返回概要数据:并更新数据到dataArray中
1364 | // 先判断已得到的搜索数据的长度,在调用search方法将最新获取的数据和原来的数据拼接到一起更新数据然后呈现到页面中
1365 | _load_more() {
1366 | console.log('监听函数触发到底了')
1367 | const length = this.data.dataArray.length
1368 | bookmodel.search(length, this.data.q).then(res => {
1369 | const tempArray = this.data.dataArray.concat(res.books)
1370 | this.setData({
1371 | dataArray: tempArray
1372 | })
1373 | })
1374 |
1375 | },
1376 |
1377 |
1378 | ```
1379 |
1380 | ### 细节完善:
1381 |
1382 | ```js
1383 | //如果关键字q初始没有值就直接返回什么也不做
1384 | _load_more() {
1385 | console.log('监听函数触发到底了')
1386 | if (!this.data.q) {
1387 | return
1388 | }
1389 | const length = this.data.dataArray.length
1390 | bookmodel.search(length, this.data.q).then(res => {
1391 | const tempArray = this.data.dataArray.concat(res.books)
1392 | this.setData({
1393 | dataArray: tempArray
1394 | })
1395 | })
1396 |
1397 | },
1398 |
1399 | //问题:当下拉刷新没有更多数据时,还会继续向服务器发送请求非常耗性能;还有就是用户操作过快没等第一次请求的结果回来,就又发送一次相同的请求,会加载重复的数据,非常耗性能
1400 |
1401 | //解决:使用锁的概念解决重复加载数据的问题
1402 | //其实就是事件节流操作,在data中定义一个loading:false,表示是否正在发送请求,默认是没有发送请求,在_load_more中,判断如果正在发送请求就什么也不做,如果没有正在发送请求就将loading变为true,调用search方法向服务器发送请求,待请求完毕并返回结果时将loading变为false。
1403 | data:{
1404 | loading: false //表示是否正在发送请求,默认是没有发送请求
1405 | },
1406 | _load_more() {
1407 | console.log('监听函数触发到底了')
1408 | if (!this.data.q) {
1409 | return
1410 | }
1411 | // 如果是正在发送请求就什么也不做
1412 | if (this.data.loading) {
1413 | return
1414 | }
1415 | const length = this.data.dataArray.length
1416 | bookmodel.search(length, this.data.q).then(res => {
1417 | const tempArray = this.data.dataArray.concat(res.books)
1418 | this.setData({
1419 | dataArray: tempArray,
1420 | loading: false
1421 | })
1422 | })
1423 |
1424 | },
1425 |
1426 | ```
1427 |
1428 | ### 进一步封装优化,组件行为逻辑抽象分页行为,顺便解决 是否还有更多数据的问题:
1429 |
1430 | 在 components中,创建并封装一个公用行为和方法的组件pagination:
1431 |
1432 | #####在 components\behaviors\pagination.js 中:
1433 |
1434 | ```js
1435 | //封装一个公用行为和方法的类paginationBev
1436 | const paginationBev = Behavior({
1437 | data: {
1438 | dataArray: [], //分页不断加载的数据
1439 | total: 0 //数据的总数
1440 | },
1441 |
1442 | methods: {
1443 | // 加载更多拼接更多数据到数组中;新加载的数据合并到dataArray中
1444 | setMoreData(dataArray) {
1445 | const tempArray = this.data.dataArray.concat(dataArray)
1446 | this.setData({
1447 | dataArray: tempArray
1448 | })
1449 | },
1450 |
1451 | // 调用search方法时返回起始的记录数
1452 | getCurrentStart() {
1453 | return this.data.dataArray.length
1454 | },
1455 |
1456 | // 获取设置从服务器得到数据的 总长度
1457 | setTotal(total) {
1458 | this.data.total = total
1459 | },
1460 |
1461 | // 是否还有更多的数据需要加载。如果得到数据的长度大于服务器返回的总长度,代表没有更多数据了,就停止发请求
1462 | hasMore() {
1463 | if (this.data.dataArray.length >= this.data.total) {
1464 | return false
1465 | } else {
1466 | return true
1467 | }
1468 | }
1469 | }
1470 |
1471 | })
1472 |
1473 | export {
1474 | paginationBev
1475 | }
1476 |
1477 | ```
1478 |
1479 | ##### 在 components\search\index.js 中:
1480 |
1481 | ```js
1482 | // 先导入封装的公用行为方法,再进一步改写_load_more和onConfirm方法,将写好的公用方法用上
1483 | import {
1484 | paginationBev
1485 | } from '../behaviors/pagination'
1486 |
1487 | _load_more() {
1488 | console.log('监听函数触发到底了')
1489 |
1490 | if (!this.data.q) {
1491 | return
1492 | }
1493 | // 如果是正在发送请求就什么也不做
1494 | if (this.data.loading) {
1495 | return
1496 | }
1497 |
1498 |
1499 | if (this.hasMore()) {
1500 | this.data.loading = true//必须放在hasMore()里
1501 | bookmodel.search(this.getCurrentStart(), this.data.q).then(res => {
1502 | this.setMoreData(res.books)
1503 | this.setData({
1504 | loading: false
1505 | })
1506 | })
1507 | }
1508 |
1509 |
1510 | },
1511 | onConfirm(event) {
1512 | // 为了用户体验好,应该点击完立即显示搜索页面
1513 | this.setData({
1514 | searching: true
1515 | })
1516 |
1517 | // 获取搜索的关键词q,调取search方法返回当summary=1,返回概要数据:并更新数据到dataArray中
1518 | const q = event.detail.value || event.detail.text
1519 |
1520 | bookmodel.search(0, q).then(res => {
1521 | this.setMoreData(res.books)
1522 | this.setTotal(res.total)
1523 | this.setData({
1524 |
1525 | q: q
1526 | })
1527 |
1528 | // 不能用户输入什么都保存在缓存中,只有用户搜索到有效的关键字时才保存到缓存中
1529 | Keywordmodel.addToHistory(q)
1530 |
1531 | })
1532 |
1533 | }
1534 | ```
1535 |
1536 | ### 但这时又会出现一个小问题:就是每次点x回退到搜索页面时,再次搜索同样的书籍时,会存在以前请求的数据没有清空又会重新向服务器发送请求,就会出现更多的重复数据
1537 |
1538 | 解决方法:就是在每次点x时,清空本次搜索的数据也就是Behavior里面的数据状态 ,上一次搜索的数据才不会影响本次搜索
1539 |
1540 | ##### 在 components\behaviors\pagination.js 中:
1541 |
1542 | ```js
1543 | //加入清空数据,设置初始值的方法
1544 | initialize() {
1545 | this.data.dataArray = []
1546 | this.data.total = null
1547 | }
1548 | ```
1549 |
1550 | ##### 在 components\search\index.js 中:
1551 |
1552 | ```js
1553 | //在触发onConfirm函数时调用this.initialize()方法先清空上一次搜索的数据在加载
1554 | onConfirm(event) {
1555 | // 为了用户体验好,应该点击完立即显示搜索页面
1556 | this.setData({
1557 | searching: true
1558 | })
1559 | // 先清空上一次搜索的数据在加载
1560 | this.initialize()
1561 | // 获取搜索的关键词q,调取search方法返回当summary=1,返回概要数据:并更新数据到dataArray中
1562 | const q = event.detail.value || event.detail.text
1563 |
1564 | bookmodel.search(0, q).then(res => {
1565 | this.setMoreData(res.books)
1566 | this.setTotal(res.total)
1567 | this.setData({
1568 |
1569 | q: q
1570 | })
1571 |
1572 | // 不能用户输入什么都保存在缓存中,只有用户搜索到有效的关键字时才保存到缓存中
1573 | Keywordmodel.addToHistory(q)
1574 |
1575 | })
1576 |
1577 | }
1578 | ```
1579 |
1580 | ##### 搜索代码重构:增强代码可阅读性:
1581 |
1582 | ##### 在 components\search\index.js 中:
1583 |
1584 | ```js
1585 | //多封装一些小的函数
1586 | import {
1587 | KeywordModel
1588 | } from '../../models/keyword'
1589 | import {
1590 | BookModel
1591 | } from '../../models/book'
1592 | import {
1593 | paginationBev
1594 | } from '../behaviors/pagination'
1595 |
1596 | const Keywordmodel = new KeywordModel()
1597 | const bookmodel = new BookModel()
1598 |
1599 | // components/search/index.js
1600 | Component({
1601 | // 组件使用行为需加
1602 | behaviors: [paginationBev],
1603 |
1604 |
1605 | /**
1606 | * 组件的属性列表
1607 | */
1608 | properties: {
1609 | more: {
1610 | type: String,
1611 | observer: 'loadMore'
1612 | } //从pages/book/book.js 传来的属性,监听滑到底步操作.只要外界传来的属性改变就会触发observer函数执行
1613 | },
1614 |
1615 | /**
1616 | * 组件的初始数据
1617 | */
1618 | data: {
1619 | historyWords: [], //历史搜索关键字
1620 | hotWords: [], //热门搜索关键字
1621 | // dataArray: [], //搜索图书当summary=1,返回概要数据
1622 | searching: false, //控制搜索到的图书数据的显隐,默认不显示
1623 | q: '', //输入框中要显示的内容
1624 | loading: false //表示是否正在发送请求,默认是没有发送请求
1625 | },
1626 |
1627 | // 组件初始化时,小程序默认调用的生命周期函数
1628 | attached() {
1629 | // const historywords = Keywordmodel.getHistory()
1630 | // const hotword = Keywordmodel.getHot()
1631 | this.setData({
1632 | historyWords: Keywordmodel.getHistory()
1633 | })
1634 |
1635 | Keywordmodel.getHot().then(res => {
1636 | this.setData({
1637 | hotWords: res.hot
1638 | })
1639 | })
1640 | },
1641 |
1642 | /**
1643 | * 组件的方法列表
1644 | */
1645 | methods: {
1646 | // 只要外界传来的属性改变就会触发observer函数执行
1647 | loadMore() {
1648 | console.log('监听函数触发到底了')
1649 | // 和onConfirm一样都需要调取search方法返回当summary=1,返回概要数据:并更新数据到dataArray中
1650 | // 先判断已得到的搜索数据的长度,在调用search方法将最新获取的数据和原来的数据拼接到一起更新数据然后呈现到页面中
1651 | if (!this.data.q) {
1652 | return
1653 | }
1654 | // 如果是正在发送请求就什么也不做
1655 | if (this._isLocked()) {
1656 | return
1657 | }
1658 | // const length = this.data.dataArray.length
1659 |
1660 | if (this.hasMore()) {
1661 | this._addLocked()
1662 | bookmodel.search(this.getCurrentStart(), this.data.q).then(res => {
1663 | this.setMoreData(res.books)
1664 | this._unLocked()
1665 | })
1666 | }
1667 |
1668 |
1669 | },
1670 |
1671 |
1672 |
1673 | // 点击取消将搜索组件关掉,有两种方法:一是,在自己的内部创建一个变量控制显隐,不推荐,因为万一还有其他操作扩展性不好。二是,创建一个自定义事件,将自定义事件传给父级,让父级触发
1674 | onCancel(event) {
1675 | this.triggerEvent('cancel', {}, {})
1676 | },
1677 |
1678 | // 触摸搜索图片里的x回到原来输入搜索的页面
1679 | onDelete(event) {
1680 | this._hideResult()
1681 | },
1682 |
1683 | // 在input输入框输入完成将输入的内容加到缓存中
1684 | onConfirm(event) {
1685 | // 为了用户体验好,应该点击完立即显示搜索页面
1686 | this._showResult()
1687 | // 先清空上一次搜索的数据在加载
1688 | this.initialize()
1689 | // 获取搜索的关键词q,调取search方法返回当summary=1,返回概要数据:并更新数据到dataArray中
1690 | const q = event.detail.value || event.detail.text
1691 |
1692 | bookmodel.search(0, q).then(res => {
1693 | this.setMoreData(res.books)
1694 | this.setTotal(res.total)
1695 | this.setData({
1696 |
1697 | q: q
1698 | })
1699 |
1700 | // 不能用户输入什么都保存在缓存中,只有用户搜索到有效的关键字时才保存到缓存中
1701 | Keywordmodel.addToHistory(q)
1702 |
1703 | })
1704 |
1705 | },
1706 |
1707 | // 更新变量的状态,显示搜索框
1708 | _showResult() {
1709 | this.setData({
1710 | searching: true
1711 | })
1712 | },
1713 |
1714 | // 更新变量的状态,隐藏搜索框
1715 | _hideResult() {
1716 | this.setData({
1717 | searching: false
1718 | })
1719 | },
1720 |
1721 | // 事件节流机制,判断是否加锁
1722 | _isLocked() {
1723 | return this.data.loading ? true : false
1724 | },
1725 |
1726 | // 加锁
1727 | _addLocked() {
1728 | this.data.loading = true
1729 | },
1730 |
1731 | // 解锁
1732 | _unLocked() {
1733 | this.data.loading = false
1734 | },
1735 |
1736 |
1737 | }
1738 | })
1739 |
1740 | ```
1741 |
1742 | ### 小问题:当加载的时候突然断网,数据还没加载完,等在恢复网络的时候,就不能继续向服务器发送请求了。问题存在的原因在于出现死锁,只有请求成功才会解锁继续发送请求,如果请求失败,就不会解锁什么也做不了。
1743 |
1744 | 解决方法:
1745 |
1746 | ##### 在 components\search\index.js 中:
1747 |
1748 | ```js
1749 | //只要在请求失败的回调函数里加上解锁就可以了
1750 |
1751 | if (this.hasMore()) {
1752 | this._addLocked()
1753 | bookmodel.search(this.getCurrentStart(), this.data.q).then(res => {
1754 | this.setMoreData(res.books)
1755 | this._unLocked()
1756 | }, () => {
1757 | this._unLocked()
1758 | })
1759 | }
1760 | ```
1761 |
1762 | ### 加入loading效果,提升用户体验:
1763 |
1764 | 先创建一个loading公共组件,只需写简单的样式效果就行,在search组件中注册并使用。
1765 |
1766 | ##### 在 components\search\index.js 中:
1767 |
1768 | ```js
1769 | // 在 components\search\index.wxml 中:加入两个loading组件。 第一个在中间显示,获取搜获数据中;第二个在底部显示,数据加载更多时显示
1770 | //
1771 | //
1772 |
1773 | //在data中添加一个loadingCenter变量控制loading效果是否在中间显示,并且加两个私有函数控制loading的显隐。在onConfirm函数中调用this._showLoadingCenter()函数,显示loading效果,在 数据加载完成,调取this._hideLoadingCenter(),取消显示loading效果,
1774 | data: {loadingCenter: false},
1775 | // 改变loadingCenter的值
1776 | _showLoadingCenter() {
1777 | this.setData({
1778 | loadingCenter: true
1779 | })
1780 | },
1781 |
1782 | // 改变loadingCenter的值
1783 | _hideLoadingCenter() {
1784 | this.setData({
1785 | loadingCenter: false
1786 | })
1787 | }
1788 |
1789 | onConfirm(event) {
1790 | // 为了用户体验好,应该点击完立即显示搜索页面
1791 | this._showResult()
1792 | // 显示loading效果
1793 | this._showLoadingCenter()
1794 | this.initialize()
1795 | const q = event.detail.value || event.detail.text
1796 | bookmodel.search(0, q).then(res => {
1797 | this.setMoreData(res.books)
1798 | this.setTotal(res.total)
1799 | this.setData({
1800 | q: q
1801 | })
1802 | Keywordmodel.addToHistory(q)
1803 | // 数据加载完成,取消显示loading效果
1804 | this._hideLoadingCenter()
1805 | })
1806 |
1807 | },
1808 | ```
1809 |
1810 | ### 知识点补充:
1811 |
1812 | >特别注意setData与直接赋值的区别:
1813 | >
1814 | >setData:调用setData函数更新的数据会触发页面重新渲染,和REACT里的setState相似。
1815 | >
1816 | >而直接赋值,只是在内存中改变的状态,并没有更新到页面中
1817 | >
1818 | >
1819 |
1820 | ### 空搜索结果的处理:
1821 |
1822 | ##### 在 components\behaviors\pagination.js 中:
1823 |
1824 | ```js
1825 | //在公共行为中加入noneResult:false,控制是否显示没有得到想要的搜索结果,在setTotal方法中,如果返回的结果为0,就是没有得到想要的搜索结果。将noneResult:true显示出来。在initialize设置初始值并清空数据函数,再将noneResult:false,取消显示。
1826 |
1827 | data: {
1828 | dataArray: [], //请求返回的数组
1829 | total: null, //数据的总数
1830 | noneResult: false //没有得到想要的搜索结果
1831 | },
1832 |
1833 | // 获取设置数据的 总长度
1834 | // 如果返回的结果为0,就说明没有得到搜索结果,将提示内容显示出来
1835 | setTotal(total) {
1836 | this.data.total = total
1837 | if (total === 0) {
1838 | this.setData({
1839 | noneResult: true
1840 | })
1841 | }
1842 | },
1843 |
1844 | // 清空数据,设置初始值,将提示隐藏
1845 | initialize() {
1846 | this.setData({
1847 | dataArray: [],
1848 | noneResult: false
1849 | })
1850 | this.data.total = null
1851 | }
1852 |
1853 | ```
1854 |
1855 | ##### 在 components\search\index.wxml 中:
1856 |
1857 | ```html
1858 | //加入空搜索显示的结果结构
1859 | 没有搜索到书籍
1860 | ```
1861 |
1862 | ##### 在 components\search\index.js 中:
1863 |
1864 | ```js
1865 | // 触摸搜索图片里的x回到原来输入搜索的页面,先回到初始值,再将搜索组件隐藏。在onConfirm中,不用等数据加载完,输入完成后就把输入的内容显示在输入框中。
1866 | onDelete(event) {
1867 | this.initialize()
1868 | this._hideResult()
1869 | },
1870 |
1871 | onConfirm(event) {
1872 | this._showResult()
1873 | this._showLoadingCenter()
1874 | const q = event.detail.value || event.detail.text
1875 | // 不用等数据加载完,输入完成后就把输入的内容显示在输入框中。
1876 | this.setData({
1877 | q: q
1878 | })
1879 |
1880 | bookmodel.search(0, q).then(res => {
1881 | this.setMoreData(res.books)
1882 | this.setTotal(res.total)
1883 | Keywordmodel.addToHistory(q)
1884 | this._hideLoadingCenter()
1885 |
1886 | })
1887 |
1888 | },
1889 |
1890 | ```
1891 |
1892 | ### 处理一个小问题:就是在热门搜索里搜索王小波,返回的搜索结果页面每本书里会显示有喜欢字样,去掉喜欢字样。
1893 |
1894 | ##### 在 components\book\index.js 中:
1895 |
1896 | ```js
1897 | //在 components\search\index.wxml 中:
1898 | //通过搜索组件搜索显示的书籍都不显示喜欢字样,通过属性传值的方式将喜欢字样去掉,把false传递给子组件,子组件通过showLike变量接收,通过数据控制显隐将喜欢字样去掉。
1899 |
1900 |
1901 | //添加一个showLike属性,代表每本书里面的喜欢字样是否显示
1902 | properties: {
1903 | book: Object,
1904 | showLike: { //控制每本书下面有个喜欢字样的显示与隐藏
1905 | type: Boolean,
1906 | value: true
1907 | }
1908 | },
1909 |
1910 | //在 components\book\index.wxml 中:
1911 | //showLike属性的显示和隐藏控制喜欢字样的显示和隐藏
1912 |
1915 | ```
1916 |
1917 | ### 对 search 组件进一步优化,将锁提取到分页行为中:
1918 |
1919 | ##### 在 components\behaviors\pagination.js 中:
1920 |
1921 | ```js
1922 | //把在components\search\index.js中的三个锁方法提取到公用行为方法中,在公用行为方法中,在data里添加loading:false属性。在initialize函数中,把loading:false也加进去即可
1923 |
1924 | // 事件节流机制,判断是否加锁
1925 | isLocked() {
1926 | return this.data.loading ? true : false
1927 | },
1928 |
1929 | // 加锁
1930 | addLocked() {
1931 | this.setData({
1932 | loading: true
1933 | })
1934 |
1935 | },
1936 |
1937 | // 解锁
1938 | unLocked() {
1939 | this.setData({
1940 | loading: false
1941 | })
1942 | },
1943 | ```
1944 |
1945 | ### 两种方法监听移动端触底的操作:
1946 |
1947 | scroll-view 或 Pages 里的 onReachBottom。如果要想用scroll-view把view组件换成scroll-view就可以。
1948 |
1949 | ------
1950 |
1951 | 微信open-data显示用户信息:`https://developers.weixin.qq.com/miniprogram/dev/api/wx.getUserInfo.html`
1952 |
1953 | 用户授权,需要使用 button 来授权登录。
1954 |
1955 | 很多时候我们需要把得到信息保存到我们自己的服务器上去,需要通过js代码中操作用户头像等信息。封装一个image-button通用组件就可以,改变他的图片,并且可以在不同的方式中调用,只需要改变open-type属性就可以。
1956 |
1957 | ----------
1958 |
1959 | 分享的实现:自定义分享button:
1960 |
1961 | ---------------
1962 |
1963 | 小程序之间的跳转:这两个小程序都必须关联同一个公众号
1964 |
1965 | ------------------
1966 |
1967 | ==============================================================
1968 |
1969 | ------------------------
1970 |
1971 |
1972 |
1973 | ## bug解决
1974 |
1975 | 在`components/episode/index.js`中
1976 |
1977 | 报错`RangeError: Maximum call stack size exceeded`:
1978 |
1979 | 原因:
1980 |
1981 | ```js
1982 | //错误写法
1983 | properties: {
1984 | index: { //默认显示为0
1985 | type: String,
1986 | observer: function(newVal, oldVal, changedPath) {
1987 | let val = newVal < 10 ? '0' + newVal : newVal
1988 | this.setData({
1989 | index: val
1990 | })
1991 | }
1992 | }
1993 | },
1994 | //小程序的observer,相当于vue中的watch监听函数,只要监听的index数据改变,就会调用observer方法,会形成死循环,就会报错RangeError: Maximum call stack size exceeded
1995 | ```
1996 |
1997 |
1998 |
1999 | 解决:
2000 |
2001 | ```js
2002 | //第一种解决方法
2003 | this.setData({
2004 | _index: val
2005 | })
2006 |
2007 | data: {
2008 | year: 0,
2009 | month: '',
2010 | _index: ''
2011 | },
2012 | ```
2013 |
2014 | ```js
2015 | //第二种解决方法 (推荐)
2016 |
2017 | ```
2018 |
2019 | -------------------------------------------
2020 |
2021 | 在`components/music/index.js`中:
2022 |
2023 | 报错:`setBackgroundAudioState:fail title is nil!;at pages/classic/classic onReady function;at api setBackgroundAudioState fail callback function`
2024 |
2025 | `Error: setBackgroundAudioState:fail title is nil!`
2026 |
2027 | 原因:
2028 |
2029 | 少 title 外传属性
2030 |
2031 | 解决:
2032 |
2033 | ```js
2034 | //在`components/music/index.js`中:
2035 | properties: {
2036 | src: String,
2037 | title: String
2038 | },
2039 | methods: {
2040 | // 为图片绑定触摸播放事件
2041 | onPlay: function() {
2042 | //图片切换
2043 | this.setData({
2044 | playing: true
2045 | })
2046 | mMgr.src = this.properties.src
2047 | mMgr.title = this.properties.title
2048 | }
2049 | }
2050 | -----------------------------------------------------------------------------
2051 | //在 app.json 中加上:
2052 | "requiredBackgroundModes": [
2053 | "audio"
2054 | ],
2055 | ```
2056 |
2057 | -------------
2058 |
2059 |
2060 |
2061 |
2062 |
2063 |
2064 |
2065 |
2066 |
2067 |
2068 |
2069 |
2070 |
2071 |
2072 |
2073 |
2074 |
2075 | --------------------------------------------------------------------------
2076 |
2077 | ============================================================================
2078 |
2079 | --------------------------------------------------------------------------------------------------------------------------------
2080 |
2081 | ### 移动端增加用户体验优化
2082 |
2083 | #### 在`components/navi`中:
2084 |
2085 | > 点击的左右小三角要足够大,用户触摸时才能点击到。两种方法,第一种是再切图时,切得大一些;第二种是,自己编写代码控制操作区域
2086 |
2087 |
2088 |
2089 |
2090 |
2091 | -----------------------
2092 |
2093 | -------------------------------------------
2094 |
2095 | 完成效果展示:
2096 |
2097 | [视频地址](https://www.bilibili.com/video/av48887843)
--------------------------------------------------------------------------------
/sitemap.json:
--------------------------------------------------------------------------------
1 | {
2 | "desc": "关于本文件的更多信息,请参考文档 https://developers.weixin.qq.com/miniprogram/dev/framework/sitemap.html",
3 | "rules": [{
4 | "action": "allow",
5 | "page": "*"
6 | }]
7 | }
--------------------------------------------------------------------------------
/utils/common.js:
--------------------------------------------------------------------------------
1 | // 定义随机生成字符串处理函数,n是生成随机字符串的位数
2 | const charts = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z']
3 |
4 | const random = function generateMixed(n) {
5 | var res = ''
6 | for (var i = 0; i < n; i++) {
7 | var id = Math.ceil(Math.random() * 35)
8 | res += charts[id]
9 | }
10 | return res
11 | }
12 |
13 | export {
14 | random
15 | }
16 |
--------------------------------------------------------------------------------
/utils/filter.wxs:
--------------------------------------------------------------------------------
1 | // WXS(WeiXin Script)是小程序的一套脚本语言,结合 WXML,可以构建出页面的结构。WXS 与 JavaScript 是不同的语言,有自己的语法,并不和 JavaScript 一致。由于运行环境的差异,在 iOS 设备上小程序内的 WXS 会比 JavaScript 代码快 2 ~ 20 倍。在 android 设备上二者运行效率无差异。
2 |
3 |
4 | // 定义过滤器函数,处理服务器返回的数据,将 \\n 变成 \n
5 | var format = function(text) {
6 | console.log(text)
7 | // 会打印两次,undefined和请求得到的数据,因为第一次初始时text为null,发送请求得到数据后调用setData更新数据一次
8 | if (!text) {
9 | return
10 | }
11 | var reg = getRegExp('\\\\n', 'g')
12 | return text.replace(reg, '\n ')
13 | }
14 |
15 | // 限制短评的长度的过滤器
16 | var limit = function(array, length) {
17 | return array.slice(0, length)
18 | }
19 |
20 |
21 |
22 | module.exports = {
23 | format: format,
24 | limit: limit
25 | };
26 |
--------------------------------------------------------------------------------
/utils/http-promise.js:
--------------------------------------------------------------------------------
1 | import {
2 | config
3 | } from '../config.js'
4 |
5 | // 错误码提示
6 | const tips = {
7 | 1: '抱歉出现了一个错误', //默认错误
8 | 0: 'OK, 成功',
9 | 1000: ' 输入参数错误',
10 | 1001: ' 输入的json格式不正确',
11 | 1002: ' 找不到资源',
12 | 1003: ' 未知错误',
13 | 1004: ' 禁止访问',
14 | 1005: ' 不正确的开发者key',
15 | 1006: ' 服务器内部错误',
16 | 1007: '请求地址错误',
17 | 2000: '你已经点过赞了',
18 | 2001: '你还没点过赞',
19 | 3000: '该期内容不存在'
20 |
21 | }
22 |
23 | class HTTP {
24 |
25 | request({
26 | url,
27 | data = {},
28 | method = "GET"
29 | }) {
30 | return new Promise((resolve, reject) => {
31 | this._request(url, resolve, reject, data, method)
32 | })
33 | }
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 | //http 请求类, 当noRefech为true时,不做未授权重试机制
45 | _request(url, resolve, reject, data = {}, method = "GET") {
46 |
47 | wx.request({
48 | url: config.api_base_url + url,
49 | data: data,
50 | method: method,
51 | header: {
52 | 'content-type': 'application/json',
53 | 'appkey': config.appkey
54 | },
55 | success: (res) => {
56 | // 判断以2(2xx)开头的状态码为正确
57 | // 异常不要返回到回调中,就在request中处理,记录日志并showToast一个统一的错误即可
58 | const code = res.statusCode.toString()
59 | if (code.startsWith('2')) {
60 | resolve(res.data)
61 | } else { //服务器错误
62 | reject()
63 | const error_code = res.data.error_code
64 | this._show_error(error_code)
65 | }
66 | },
67 | fail: (err) => { //请求接口失败
68 | reject()
69 | this._show_error(1)
70 | }
71 | })
72 | }
73 |
74 | // 错误弹窗处理
75 | _show_error(error_code) {
76 | if (!error_code) {
77 | error_code = 1
78 | }
79 | const tip = tips[err_code]
80 | wx.showToast({
81 | title: tips ? tip : tips[1],
82 | icon: 'none',
83 | duration: 2000
84 | })
85 | }
86 | }
87 |
88 | export {
89 | HTTP
90 | }
91 |
--------------------------------------------------------------------------------
/utils/http.js:
--------------------------------------------------------------------------------
1 | import {
2 | config
3 | } from '../config.js'
4 |
5 | // 错误码提示
6 | const tips = {
7 | 1: '抱歉出现了一个错误', //默认错误
8 | 0: 'OK, 成功',
9 | 1000: ' 输入参数错误',
10 | 1001: ' 输入的json格式不正确',
11 | 1002: ' 找不到资源',
12 | 1003: ' 未知错误',
13 | 1004: ' 禁止访问',
14 | 1005: ' 不正确的开发者key',
15 | 1006: ' 服务器内部错误',
16 | 1007: '请求地址错误',
17 | 2000: '你已经点过赞了',
18 | 2001: '你还没点过赞',
19 | 3000: '该期内容不存在'
20 |
21 | }
22 |
23 | class HTTP {
24 | //http 请求类, 当noRefech为true时,不做未授权重试机制
25 | request(params) {
26 | if (!params.method) { //没传方法默认为get,容错处理
27 | params.method = 'GET'
28 | }
29 | wx.request({
30 | url: config.api_base_url + params.url,
31 | data: params.data,
32 | method: params.method,
33 | header: {
34 | 'content-type': 'application/json',
35 | 'appkey': config.appkey
36 | },
37 | success: (res) => {
38 | // 判断以2(2xx)开头的状态码为正确
39 | // 异常不要返回到回调中,就在request中处理,记录日志并showToast一个统一的错误即可
40 | let code = res.statusCode.toString()
41 | if (code.startsWith('2')) {
42 | params.success && params.success(res.data)
43 | } else { //服务器错误
44 | let error_code = res.data.error_code
45 | this._show_error(error_code)
46 | }
47 | },
48 | fail: (err) => { //请求接口失败
49 | this._show_error(1)
50 | }
51 | })
52 | }
53 |
54 | // 错误弹窗处理
55 | _show_error(error_code) {
56 | if (!error_code) {
57 | error_code = 1
58 | }
59 | const tip = tips[error_code]
60 | wx.showToast({
61 | title: tip ? tip : tips[1],
62 | icon: 'none',
63 | duration: 2000
64 | })
65 | }
66 | }
67 |
68 | export {
69 | HTTP
70 | }
71 |
--------------------------------------------------------------------------------
/utils/util.js:
--------------------------------------------------------------------------------
1 | const formatTime = date => {
2 | const year = date.getFullYear()
3 | const month = date.getMonth() + 1
4 | const day = date.getDate()
5 | const hour = date.getHours()
6 | const minute = date.getMinutes()
7 | const second = date.getSeconds()
8 |
9 | return [year, month, day].map(formatNumber).join('/') + ' ' + [hour, minute, second].map(formatNumber).join(':')
10 | }
11 |
12 | const formatNumber = n => {
13 | n = n.toString()
14 | return n[1] ? n : '0' + n
15 | }
16 |
17 | module.exports = {
18 | formatTime: formatTime
19 | }
20 |
--------------------------------------------------------------------------------