Project Address:Github.com/wudiufo/WeC…

Completed effect display:www.bilibili.com/video/av488…


Small love whether to like the componentcomponents/like

Ideas:

Like defaults to false to display hollow hearts

Touch executes the tap: onLike method because this.setData({count:count,like:! Like}) is asynchronous, count = like? Count -1: count+1, in which case like is false, execute count+1. Then execute the this.setData() method and change like to true to show a solid heart.

let behavior = like ? 'like' : 'cancel'
        // Custom events
      this.triggerEvent('like', {
        behavior: behavior
      }, {})
Copy the code

In models/like.js, let url = behavior === ‘like’? ‘like/cancel’ : ‘like’, because behavior === ‘like’ is true, the server interface ‘like/cancel’ is called, and otherwise the like interface is called.

At the beginning, the ‘like/cancel’ interface is called for solid, and the ‘like’ interface is called for hollow


Bottom toggle components left and rightcomponents/navi

Ideas:

innavi/index.jsIn:
Define which data is external and which is private.// Data from outside
    title: String.first: Boolean.// Disable it for the first right arrow, false by default
    latest: Boolean // The left arrow is disabled if it is the latest issue. The default is false
  },
 data: {// Private data
    disLeftSrc: './images/[email protected]'.leftSrc: './images/[email protected]'.disRightSrc: './images/[email protected]'.rightSrc: './images/[email protected]'
  },

Copy the code

Left arrow:

SRC display image rule: if it is the latest issue, display the left disabled disLeftSrc arrow; If it is not the latest issue, the left available arrow is displayed

Bind the touch event onLeft to the image in navi/index.js:

In methods, if it is not the latest journal, continue to bind the custom event left onLeft:function(event) { // Not the latest issue
     if (!this.properties.latest) {
       this.triggerEvent('left', {}, {})}},Copy the code

Right arrow:

SRC display image rule: if it is the first issue of the journal, display the disRightSrc arrow to the right; If it is not the first issue of the journal, the rightSrc arrow is displayed for the available status to the right

Bind the touch event onRight to the image in navi/index.js:

In methods: if it is not the first issue of the journal, continue to bind the custom event right onRight:function(event) { // Not the first issue
     if (!this.properties.first) {
       this.triggerEvent('right', {}, {})}}Copy the code
inpages/classicIn:
1: In classic.json, register using navi custom components {"usingComponents": {
   "v-like": "/components/like/index"."v-movie": "/components/classic/movie/index"."v-episode": "/components/episode/index"."v-navi": "/components/navi/index"}}2In classic. WXML: bind the custom event left to get the next issue of the current issue; Bind the custom event right to get the previous issue of the current issue < V-navi bind:left="onNext" bind:right="onPrevious" class="nav" title="{{classic.title}}" first="{{first}}" latest="{{latest}}"/>
   
3: In classic.js:// Get the next issue of the current issue, left arrow
onNext: function(evevt) {this._updateClassic('next')},// Get the previous issue of the current issue, right arrow
onPrevious: function(evevt) { this._updateClassic('previous')},// Use the idea of function encapsulation to create a new function to extract common code
// Send a request to get the index of the current page and update the data
 _updateClassic: function(nextOrPrevious) {
   let index = this.data.classic.index
   classicModel.getClassic(index, nextOrPrevious, (res) => {
     // console.log(res)
     this.setData({
       classic: res,
       latest: classicModel.isLatest(res.index),
       first: classicModel.isFirst(res.index)
     })
   })
 },
     
4: In models/classic.js:// If the current issue is the first issue, first becomes true and the right arrow shows disabled
 isFirst(index) {
   return index === 1 ? true : false
 }
// Whether the current journal is the latest issue, latest is TRUE, and the left arrow shows disabled
// If the latest index is the same as the latest index in the cache, the latest index will change to TRUE and the left arrow will display disabled
 isLatest(index) {
   let latestIndex = this._getLatestIndex()
   return latestIndex === index ? true : false
 }
// Cache the latest journal index
 _setLatestIndex(index) {
   wx.setStorageSync('latest', index)
 }

 // Get the index of the latest journal in the cache
 _getLatestIndex() {
   let index = wx.getStorageSync('latest')
   return index
 }
Copy the code

Optimize the cache. Solve each touch left and right arrow will frequently send requests to the server, which is very performance consumption, user experience is poor. The solution is to cache the data sent for the first time to the local cache. When you touch the arrow again, the local cache will first check whether there is data. If there is data, it will directly read data from the cache, and if there is no data, it will send a request to the server. (But there are also parts that need to be updated in real time, such as the “like” widget, which needs to send a request to the server every time to get the latest data.)

In the models/classic. In js:
1:
// Set the style of the key in the cache, classic-1
  _getKey(index) {
    let key = `classic-${index}`
    return key
  }
2:
  Since getPrevious and getNext implement similar code, they can be combined into a single function for simplicity
  // Find the key in the cache, send a request API, write the key to the cache. Solve the problem of calling the Api to send requests to the server every time, which costs performance
  // In the cache, determine the key
  getClassic(index, nextOrPrevious, sCallback) {
    //0: Next, touch the left arrow to get the next issue, touch the right arrow to get the previous issue otherwise
    let key = nextOrPrevious === 'next' ? this._getKey(index + 1) : this._getKey(index - 1)
    //1: find the key in the cache
    let classic = wx.getStorageSync(key)
    //2: If the key is not found in the cache, the server API is called to send a request for data
    if(! classic) {this.request({
        url: `classic/${index}/${nextOrPrevious}`.success: (res) = > {
            // Set the obtained data to the cache
          wx.setStorageSync(this._getKey(res.index), res)
            // Return the obtained data for the user to access
          sCallback(res)
        }
      })
    } else { //3: If a key is found in the cache, the value corresponding to the key in the cache is returned to the user for retrieval
      sCallback(classic)
    }

  }
--------------------------------------------------------------------------------

// Get the latest journals using the caching mechanism for further optimization
 // Get the latest journals
  getLatest(cb) {
    this.request({
      url: 'classic/latest'.success: (res) = > {
// Cache the latest journal index, in case the left arrow is not set to the latest value, the left arrow will always trigger send request can not find the latest journal error
        this._setLatestIndex(res.index)
          // Return the obtained data for the user to access
        cb(res)
        // To set the latest journal in the cache, call this._getKey() to set the key value for the latest journal, and call wechat to set the cache method to store the key and the corresponding value res
        let key = this._getKey(res.index)
        wx.setStorageSync(key, res)
		
      }
    })
  }
Copy the code

Handle the cache issue of whether or not to like the little heart component: it doesn’t need to cache, it needs to get the latest data in real time

In the models/like. In js:
// Write a method to get the latest like information from the server
  // Get the likes
  getClassicLikeStatus(artID, category, cb) {
    this.request({
      url: `classic/${category}/${artID}/favor`.success: cb
    })
  }

Copy the code
In the pages/classic/classic. In js:
// Set the initial value of private data
data: {
    classic: null.latest: true.first: false.likeCount: 0.// The number of likes
    likeStatus: false // Like status

  },
      
 // classic.wxml: 
      
      
 // Write a private method to get the like information
  // Get the likes
  _getLikeStatus: function(artID, category) {
    likeModel.getClassicLikeStatus(artID, category, (res) => {
      this.setData({
        likeCount: res.fav_nums,
        likeStatus: res.like_status
      })
    })
  },
      
  // Lifecycle function -- listens for page loading
  onLoad: function(options) {
    classicModel.getLatest((res) = > {
      console.log(res)
        // this._getLikeStatus(res.id, res.type) // This._getLikeStatus(res.id, res.type
      this.setData({
        classic: res,
        likeCount: res.fav_nums,
        likeStatus: res.like_status
      })
    })
Copy the code

In the classic/music/index. In js:

Solve the problem that other periodicals are also in play state when switching periodicals. The music stops playing when you switch periodicals and returns to the default state of not playing

Using the communication mechanism of component events, there are only parent and child components in applets

The components/classic/music/inddex. Js:

Solution a:

// With the component lifecycle, only Wx :if can drop the component lifecycle from scratch
// Lifecycle function for component uninstallation
  // The component uninstalls the music and stops playing, but this does not work because, in classic.wxml, hidden is used and should be changed to if
  detached: function(event) {
    mMgr.stop()
  },
 / / pages/classic/classic WXML
 // 
      
      
Copy the code

Add:

Wx :if vs hidden, the same as the Vue framework’s V-if and V-show instructions: wx:if it is lazy, the framework does nothing if the initial value is false, and only partially renders if the initial value is true. A true or false switch is a partial addition or removal from a page. Wx :if has higher switching costs and is better if conditions are unlikely to change at run time. The life cycle is re-executed. Hidden components are always rendered, with simple control over show and hide. Hidden has a higher initial rendering cost. If you need to switch frequently, hidden is better. The life cycle is not re-executed.

Option 2 :(recommended)

When switching periodicals, music can be played as background music all the time, while other periodicals are not played by default

The components/classic/music/inddex. Js:
// To keep the background music playing while the periodicals switch, remove the mmgr.stop () event method
detached: function(event) {
    // mmgr.stop () // Stop cannot be added to keep the background music playing
  },
      
// Listen for music playing status. If there is no music playing on the current page, set playing to false. If the music address classic.url on the current page is the same as that of the currently playing music, set the play state to True
_recoverStatus: function() {
      if (mMgr.paused) {
        this.setData({
          playing: false
        })
        return
      }
      if (mMgr.src === this.properties.src) {
        
          this.setData({
            playing: true})}},// Monitor the playback status, the general control switch can control the playback status, the end of the general control switch and page synchronization problem
    _monitorSwitch: function() {
      console.log('monitorSwitch Background Audio '.'trigger 3')
        // Listen for background audio playback events
      mMgr.onPlay((a)= > {
          this._recoverStatus()
          console.log('onPlay ' + this.data.playing)
        })
        // Listen for background audio pause events
      mMgr.onPause((a)= > {
          this._recoverStatus()
          console.log('onPause ' + this.data.playing)
        })
        // Close the music console and listen for the background audio stop event
      mMgr.onStop((a)= > {
          this._recoverStatus()
          console.log('onStop ' + this.data.playing)
        })
        // Listen to the background audio playback end event
      mMgr.onEnded((a)= > {
        this._recoverStatus()
        console.log('onEnded ' + this.data.playing)
      })
    },
        
  // Call the lifecycle function, and each switch triggers the Attached lifecycle
        // Executed when the component instance enters the page node tree
  // Hidden, ready, created cannot trigger lifecycle functions
  attached: function(event) {
    console.log('Attach instance to page'.Triggered '1')
    this._monitorSwitch()
    this._recoverStatus()


  },
Copy the code

Play animation rotation effect production:

The components/classic/music/index. WXSS:
// Define frame animation using CSS3
.rotation {
  -webkit-transform: rotate(360deg);
  animation: rotation 12s linear infinite;
  -moz-animation: rotation 12s linear infinite;
  -webkit-animation: rotation 12s linear infinite;
  -o-animation: rotation 12s linear infinite;
}

@-webkit-keyframes rotation {
  from {
    -webkit-transform: rotate(0deg);
  }
  to {
    -webkit-transform: rotate(360deg); }}Copy the code

Add CSS3 knowledge points:

-webkit-transform: transition3D (0,0,0) or -webkit-transform:translateZ(0); Both of these attributes enable GPU hardware acceleration mode, which allows the browser to switch from THE CPU to the GPU when rendering animations. This is actually a trick and a Hack. -webkit-transform: Transition3D and -webkit-transform:translateZ are used to render 3D styles, but when we set the value to 0, the 3D effect is not used, but the browser has enabled GPU hardware acceleration mode. ” This kind of GPU hardware acceleration has been popular in today’s PC and mobile devices, and the performance improvement in the mobile terminal is quite significant. Therefore, we suggest that you can try to enable GPU hardware acceleration when doing animation.

“Applicable conditions Through its – transform: transition3d/translateZ open GPU hardware acceleration of the scope of application:

A page animated with many large images, especially PNG24 images. The page has a lot of large size images and has CSS zooming, the page can scroll. Use background-size:cover to set large size background images and when the page can be scrolled. (see: coderwall.com/p/j5udlw) write a lot of DOM elements to CSS 3 animation (transition/transform/keyframes/absTop&Left) using a lot of PNG image splicing into CSS Sprite” By opening the GPU hardware acceleration while it is possible to promote animation rendering performance or solve some difficult problems, but use still need to be careful, before use must be rigorous test, otherwise it will load to browse the web user’s system resources, especially in the mobile terminal, unscrupulous open GPU hardware acceleration can lead to large power consumption equipment, Reduced battery life and other issues.

The components/classic/music/index. WXML:
// Add a spin class to the image, or an empty string if it does not play
<image class="classic-img {{playing? 'rotation':''}}"  src="{{img}}"></image>

Copy the code

Slot slots solve the problem of adding other decorators to common components. When defining a common component, slot names the slot placeholder and passes the required content fill when the parent component calls. Similar to Vue’s instruction V-slot.

The components/tag/index. In js:
// Add to Component
/ / enable slot
  options: {
    multipleSlots: true
  },
Copy the code
In the common components of the definition of the components/tag/index. WXML:
// Define a few named slots for the parent element to hold<view class="container tag-class">
  <slot name="before"></slot>
  <text>{{text}}</text>
  <slot name="after"></slot>
</view>
Copy the code
In the pages/detail/detail. In the json:
{"usingComponents": {" V-tag ": "/components/tag/index"}}Copy the code
In the pages/detail/detail. WXML in:
// Use the component v-tag to name the slot<v-tag tag-class="{{index===0? 'ex-tag1':''||index===1? 'ex-tag2':''}}" text="{{item.content}}">
     <text class="num" slot="after">{{'+'+item.nums}}</text>
</v-tag>
Copy the code

inpages/detail/detailThe first two colors of a v-tag comment are displayed in two different colors:
The first method :(recommended)
In the pages/detail/detail. WXSS in:
/* V-tag is a custom component and cannot use CSS3. In wechat applets, only built-in components can use CSS3 */
/* Use CSS hacks to style custom components */
.comment-container>v-tag:nth-child(1)>view {
  background-color: #fffbdd;
}

.comment-container>v-tag:nth-child(2)>view {
  background-color: #eefbff;
}
Copy the code
The second method:

Define external style methods that pass style classes in the same way parent-child components pass properties

In detail. The WXSS:
/* Define external styles */

.ex-tag1 {
  background-color: #fffbdd ! important;
}

.ex-tag2 {
  background-color: #eefbff ! important;
}
Copy the code
In detail. The WXML:
/* Pass the custom style class to the custom child v-tag */ via attribute values<v-tag tag-class="{{index===0? 'ex-tag1':''||index===1? 'ex-tag2':''}}" text="{{item.content}}">
        <text class="num" slot="after">{{'+'+item.nums}}</text>
</v-tag>
Copy the code
The components/tag/index. In js:
// Declare externally passed styles in Component
// External CSS styles passed in
  externalClasses: ['tag-class'].Copy the code
The components/tag/index. In the WXML:
// write the tag-calss class passed by the parent to the class<view class="container tag-class">
  <slot name="before"></slot>
  <text>{{text}}</text>
  <slot name="after"></slot>
</view>
Copy the code

[fixed] server returns contents with \n newline character:

The reason:

This is because the raw data returned by the server is \\n, which is escaped to \n

While \n is escaped to newline by default in text text tags

Solutions:

WXS: WXS (WeiXin Script) is a scripting language for small programs that, combined with WXML, can build the structure of a page. Filter (filter name, filter method) in Vue. WXS is a different language from JavaScript and has its own syntax, which is not consistent with JavaScript. Depending on the operating environment, WXS in applets can be anywhere from 2 to 20 times faster than JavaScript code on iOS devices. There is no difference in efficiency between the two on Android devices.

In the utils/filter. The WXS:
// Define a filter function that processes the data returned by the server to change \\n to \n
// It will print undefined and the requested data twice, because the first time the initial text is null, after sending the request to get the data, call setData to update the data once
var format = function(text) {
  console.log(text)
    
  if(! text) {return
  }
  var reg = getRegExp('\\\\n'.'g')
  return text.replace(reg, '\n')}module.exports.format = format
Copy the code
In the pages/detail/detail. WXML in:
/ / introduction<wxs src=".. /.. /utils/filter.wxs" module="util"/>// Used in data that needs to be filtered<text class="content">{{util.format(book.summary)}}</text>
Copy the code

[bug Mc-108966] – Server returns content summary with indentation:

In the pages/detail/detail. WXSS in:
Content {text-indent: 58rpx; text-indent: 58rpx; font-weight: 500; }Copy the code
In the utils/filter. The WXS:
// Use the escape character   As a space, but then the applet will start with   Style output, not what we want
var format = function(text) { 
  if(! text) {return
  }
  var reg = getRegExp('\\\\n'.'g')
  return text.replace(reg, '\n        ')}module.exports.format = format
Copy the code
In the pages/detail/detail. WXML in:
Decode ="{{true}}"<text class="content" decode="{{true}}">{{util.format(book.summary)}}</text>
Copy the code

Fixed the issue of too many short comments showing only one part:

In the utils/filter. The WXS:
// Add a filter that limits the length of the comment and export it
// Filter to limit the length of the comment
var limit = function(array, length) {
  return array.slice(0, length)
}

module.exports = {
  format: format,
  limit: limit
};
Copy the code
In the pages/detail/detail. WXML in:
<wxs src=".. /.. /utils/filter.wxs" module="util"/>

<view class="sub-container">
    <text class="headline">essay</text>
    <view class="comment-container">
      <block wx:for="{{util.limit(comments,10)}}" wx:key="content">
        <v-tag tag-class="{{index===0? 'ex-tag1':''||index===1? 'ex-tag2':''}}" text="{{item.content}}">
        <text class="num" slot="after">{{'+'+item.nums}}</text>
        </v-tag>
      </block>
    </view>
  </view>


Copy the code
In pages/detail/detail. WXML: further optimized
/ / because<v-tag tag-class="{{index===0? 'ex-tag1':''||index===1? 'ex-tag2':''}}" text="{{item.content}}">// Define a WXS filter first<wxs module="tool">
  var highlight = function(index){
    if(index===0){
      return 'ex-tag1'
    }
    else if(index===1){
      return 'ex-tag2'
    }
    return ''
  }
  module.exports={
    highlight:highlight
  }
</wxs>// Change to:<v-tag tag-class="{{tool.highlight(index)}}" text="{{item.content}}">
    
Copy the code

Implementation of the bottom comment:

Comments submitted by users:

Click the TAB to submit a comment to the server:

The components \ tag \ index WXML:
// Bind the comment component to the occurrence event onTap<view bind:tap="onTap" class="container tag-class">
  <slot name="before"></slot>
  <text>{{text}}</text>
  <slot name="after"></slot>
</view>
Copy the code
The components \ tag \ index in js:
// When tapping the comment widget, a custom event is triggered, the comment content is uploaded, and the parent component calls custom event tapping
 methods: {
    // Touch the comment small TAB when triggering the event, triggering a custom event
    onTap(event) {
      this.triggerEvent('tapping', {
        text: this.properties.text
      })
    }
  }
Copy the code
In the pages, detail, detail. WXML in:
// Invoke the custom tapping event of the child in the parent, and trigger the onPost event<v-tag bind:tapping="onPost" tag-class="{{tool.highlight(index)}}" text="{{item.content}}">
    <text class="num" slot="after">{{'+'+item.nums}}</text>
</v-tag>
Copy the code
In the models \ book. In js:
// Retrieve the interface for the new comment
// Add short comments
  postComment(bid, comment) {
    return this.request({
      url: '/book/add/short_comment'.method: 'POST'.data: {
        book_id: bid,
        content: comment
      }
    })
  }
Copy the code
In the pages, detail, detail. In js:
// The touch tag component fires, and the input field also fires the onPost event
// Get the user's input or trigger the contents of the tag, and verify the user input comments, if the length of the comment is greater than 12, pop-up warning not to send the request to the server
// If the comments conform to the specification, call the new comment interface and insert the latest comment first in the Comments array, update the data, and close the mask
onPost(event) {
    // Get the contents of the trigger tag
    const comment = event.detail.text
      // Validates user input comments
    if (comment.length > 12) {
      wx.showToast({
        title: 'Short comments up to 12 words'.icon: 'none'
      })
      return
    }

    // Call the new comment interface and insert the latest comment first in the Comments array
    bookModel.postComment(this.data.book.id, comment).then(res= > {
      wx.showToast({
        title: '+ 1'.icon: 'none'
      })
      this.data.comments.unshift({
        content: comment,
        nums: 1 // This is the quantity display behind
      })
      this.setData({
        comments: this.data.comments,
        posting: false})})},Copy the code
In the pages, detail, detail. WXML in:
// Input has its own binding event, bindConfirm, which calls the mobile keyboard to complete the key<input bindconfirm="onPost"  type="text" class="post" placeholder="Essays of up to 12 words."/>
Copy the code
In the pages, detail, detail. In js:

Click the TAB to submit a comment to the server complete:

// The touch tag component will fire, and the input field will fire the onPost event; Then get the contents of the trigger tag or the input from the user; Verify tag content or user input comments and input cannot be empty; Finally, the new comment interface is called and the most recent comment is inserted first in the Comments array and the mask is turned off

 onPost(event) {
    const comment = event.detail.text||event.detail.value
    console.log('comment'+comment)
    console.log('commentInput'+comment)
    if (comment.length > 12| |! comment) { wx.showToast({title: 'Short comments up to 12 words'.icon: 'none'
      })
      return
    }
    bookModel.postComment(this.data.book.id, comment).then(res= > {
      wx.showToast({
        title: '+ 1'.icon: 'none'
      })
      this.data.comments.unshift({
        content: comment,
        nums: 1 // This is the quantity display behind
      })
      this.setData({
        comments: this.data.comments,
        posting: false})})},Copy the code

Details:

If no short comments show the problem:

In the pages, detail, detail. WXML in:
// Add a comment tag after the comment. If there is no comment tag, it will not show that there is no comment tag<text class="headline">essay</text>
<text class="shadow" wx:if="{{! comments.length}}">No essays yet</text>// If you can click the tag +1, add the no comment tag. If there are no comments, the no comment tag will not be displayed<text wx:if="{{! comments.length}}">Click the TAB +1</text>
<text wx:else>No essay</text>
Copy the code

Due to the large amount of data to be loaded, to improve user experience, you need to add a loading message indicating that data is being loaded and data will disappear after loading.

Since promises are used to load data asynchronously, the unloading display should be added to each promise, which obviously does not meet the requirements. If using the callback function mechanism, load 1 first in a callback function in load 2 loading in turn order, written in the last a callback function to cancel the loading operation, although this way can be realized, but very time-consuming, the request is serial, if a request takes 2 s, three requests will spend 6 seconds, very time consuming, And there will be a callback hell phenomenon, not recommended.

Solution: In promises, there is a promise.all () method that will do the trick.

Supplementary knowledge:

The promise.all (iterable) method returns an instance of a Promise that is resolved when all promises in iterable arguments are “resolved” or when the arguments do not contain the Promise; If a promise fails (Rejected), the instance calls back (reject) because of the result of the first failed promise. Simply put, the reject callback is called whenever one of the promises in the array fails, and the resolve callback is called only when all the promises in the array succeed.

The promise.race (iterable) method returns a Promise that is resolved or rejected once a Promise in the iterator is resolved or rejected. The race function returns a Promise that will be completed in the same way as the first Promise passed. It may be a high degree of completion or rejection, depending on which of the two is the first. If the passed iteration is empty, the returned promise will wait forever. If the iteration contains one or more non-promise values and/or resolved/rejected promises, promise.race resolves to the first value found in the iteration. Simply put: Execute whichever callback succeeds or fails as soon as possible.

In the pages, detail, detail. In js:
// With promise.all (iterable), instead of writing three Promise methods to update the data, we can abbreviate them to one all method and call setData() on a successful Promise to update the requested data
onLoad: function(options) {
    // The loading effect is displayed during data loading
    wx.showLoading()
    const bid = options.bid
    console.log(bid)
      // Get book details
    const detail = bookModel.getDetail(bid)
      // Get the likes of books
    const likeStatus = bookModel.getLikeStatus(bid)
      // Get short book reviews
    const comments = bookModel.getComments(bid)

    // Cancel the loading effect when the data is loaded
      Promise.all([detail, comments, likeStatus]).then(res= > {
      console.log(res)
      this.setData({
        book: res[0].comments: res[1].comments,
        likeStatus: res[2].like_status,
        likeCount: res[2].fav_nums
      })
      wx.hideLoading()
    })

    
  },
Copy the code

Book search:

Higher-order components: If the content of a component is complex and contains a large number of services

Add:

At work we usually write the business processing logic in models:

Can be written in a single public component, only for their own writing business logic call use;

You can write it in components, only for components within components, if you want to publish components to other projects or to other developers;

You can write it in the project root directory, Models, for the entire project to use the write business logic. If you are just working on a project, you are advised to write it here.

The components, the search and index of:

Handling historical and popular searches:

Historical search: Writes the historical search keyword to the cache, and retrieves the historical search keyword from the cache.

Hot search: call server API

GET /book/hot_list

Write the business logic in models\keyword. Js:
// If the array is empty, return the empty array to prevent errors; Returns the fetched array if it is not empty.
// Write the search keyword to the cache, obtain the array of historical keywords from the cache first, determine whether the input keyword contains the keyword, if there is no keyword, if the obtained length is greater than the maximum length, delete the last item of the array; If the length of the array is less than the maximum length, the input keyword is added to the first of the array and set to the cache.
class KeywordModel {
  constructor() {
    // Mount the key attribute to the current instance for instance fetching
    this.key = 'q'.this.maxLength = 10 // The maximum length of the search keyword array is 10
  }

  // From the cache, get the historical search key word group, if the cache does not directly return an empty array
  getHistory() {
    const words = wx.getStorageSync(this.key)
    if(! words) {return[]}return words
  }

  // Write the historical search keyword to the cache. Retrieves an array of historical keywords from the cache and determines whether the keyword already exists in the array. If not, and the length of the array is greater than the maximum length, the last item of the array is deleted. Get the length of the array is less than the maximum length will be the input keyword to the array first, and set to the cache;
  addToHistory(keyword) {
    let words = this.getHistory()
    const has = words.includes(keyword)
    if(! has) {const length = words.length
      if (length >= this.maxLength) {
        words.pop()
      }
      words.unshift(keyword)
      wx.setStorageSync(this.key, words)
    }

  }

  // Get popular search keywords
  getHot() {

  }
}

export {KeywordModel}
Copy the code
The components, the search and index. In the WXML:
// Bind the onConfirm event to the input field<input bind:confirm="onConfirm" type="text" class="bar" placeholder-class="in-bar" placeholder="Book name" auto-focus="true"/>
Copy the code
The components, the search and index. In js:
/ / onConfirm event, called the input content added to the cache method Keywordmodel. AddToHistory (word), historical keywords can be added to the cache
methods: {
    // Click Cancel to turn off the search component, there are two ways: first, create a variable in your own internal control explicit implicit, not recommended, because there are other operations bad scalability. Second, create a custom event and pass the custom event to the parent for the parent to fire
    onCancel(event) {
      this.triggerEvent('cancel'}, {}, {}),// Add the input to the cache after typing in the input field
    onConfirm(event) {
      const word = event.detail.value
      Keywordmodel.addToHistory(word)
    }
  }
Copy the code
The components, the search and index. In js:
// Fetch the contents of the historical search from the cache
data: {
    historyWords: [] // History search keyword
  },

  // The lifecycle function that the applet calls by default when the component is initialized
  attached() {
    const historywords = Keywordmodel.getHistory()
    this.setData({
      historyWords: historywords
    })
  },
Copy the code
The components, the search and index. In the json:
"UsingComponents ": {"v-tag": "/components/tag/index"}Copy the code
The components, the search and index. In the WXML:
// Iterate over each item in the historyWords array, rendering it on the page<view class="history">
      <view class="title">
        <view class="chunk"></view>
        <text>Search history</text>
      </view>
      <view class="tags">
        <block wx:for="{{historyWords}}" wx:key="item">
          <v-tag text='{{item}}'/>
        </block>
      </view>
    </view>
Copy the code

Top searches:

In the models \ keyword in js:
// Introduce its own wrapped API request methods
import {
  HTTP
} from '.. /utils/http-promise'

// Get popular search keywords
  getHot() {
    return this.request({
      url: '/book/hot_keyword'})}Copy the code
The components, the search and index. In js:
// Define the initial value of the component, call the passed getHot method to get the hot search keyword, and update it to the initial value hotWords
data: {
    historyWords: [].// History search keyword
    hotWords: [] // Popular search keywords
  },
// The lifecycle function that the applet calls by default when the component is initialized
  attached() {
    const historywords = Keywordmodel.getHistory()
    const hotword = Keywordmodel.getHot()
    this.setData({
      historyWords: historywords
    })

    hotword.then(res= > {
      this.setData({
        hotWords: res.hot
      })
    })
  },
Copy the code
The components, the search and index. In the WXML:
// Iterate over the hotWords array obtained from the server and render it to the page<view class="history hot-search">
      <view class="title">
        <view class="chunk"></view>
        <text>Top search</text>
      </view>
      <view class="tags">
        <block wx:for="{{hotWords}}" wx:key="item">
          <v-tag text='{{item}}'/>
        </block>
      </view>
    </view>
Copy the code

Note:

Since the keywordModel.gethot () method is called in components\search\index.js, which is associated with the server, this makes the component less reusable.

To make the search component more reusable, open a property in the components\ Search \index.js properties and invoke the methods in models from the Pages page of the Search component. The data is then passed to the Search component via properties and then bound to make the search component reusable

In the models \ book. In js:
// Define the search function that encapsulates sending requests to the server
// Book search
  search(start, q) {
    return this.request({
      url: '/book/search? summary=1'.data: {
        q: q,
        start: start
      }
    })
  }
Copy the code
The components, the search and index. In js:
// Import and instantiate the BookModel class, which sends requests to the server to search for books; Declare a private variable dataArray array in data that returns summary data for searching books when summary=1. When the user finishes typing and clicks Finish, the bookModel.search method is called and the data is updated to dataArray.
// Note: Not everything the user enters is saved in the cache, but only if the user finds a valid keyword
import {
  BookModel
} from '.. /.. /models/book'
const bookmodel = new BookModel()

data: {
    historyWords: [].// History search keyword
    hotWords: [], // Popular search keywords
    dataArray: [] // Search books when summary=1, return summary data
  },
      
       // Add the input to the cache after typing in the input field
    onConfirm(event) {
      const word = event.detail.value

      When summary=1, return summary data: and update data to dataArray
      const q = event.detail.value
      bookmodel.search(0, q).then(res= > {
        this.setData({
          dataArray: res.books
        })

        // Can't everything the user enters be saved in the cache, only if the user finds a valid keyword
        Keywordmodel.addToHistory(word)

      })

    }
Copy the code
The components, the search and index. In the WXML:

Parse the resulting search data and iterate through the rendering to the page:

// Search data and popular search, search history is not displayed together, one display, another will have to hide, search results page is not displayed by default, need to define a variable to control the explicit hidden<view wx:if="{{searching}}" class="books-container">
    <block wx:for="{{dataArray}}" wx:key="{{item.id}}">
      <v-book book="{{item}}" class="book"></v-book>
    </block>
  </view>

Copy the code
The components, the search and index. In js:
// Define searching variable in data private attribute to control explicit and implicit. Default is false; When the onConfirm event is triggered, for the user's good experience, the search page should be displayed immediately after the click, and the searching should be changed to True, so that the search content is displayed on the page
data: {
    historyWords: [].// History search keyword
    hotWords: [], // Popular search keywords
    dataArray: [], // Search books when summary=1, return summary data
    searching: false // Control the explicit and implicit of the searched book data, not displayed by default
  },

    // Add the input to the cache after typing in the input field
    onConfirm(event) {
      // For good user experience, the search page should be displayed immediately after clicking
      this.setData({
        searching: true
      })

      When summary=1, return summary data: and update data to dataArray
      const q = event.detail.value
      bookmodel.search(0, q).then(res= > {
        this.setData({
          dataArray: res.books
        })

        // Can't everything the user enters be saved in the cache, only if the user finds a valid keyword
        Keywordmodel.addToHistory(q)

      })

    }
Copy the code

X button function in search box:

The components, the search and index. In js:
// In components\search\index. WXML, 

// Touch the x in the search image to return to the page where you entered the search
onDelete(event){
      this.setData({
        searching: false})},Copy the code

Users can click on the label in the history search and popular search can also jump to the corresponding search results display page: as long as the user clicks on the label event can be achieved

The components, the search and index. In js:
// In Components \ Search \index. WXML: Bindings V -tag component self - tuning event triggering onConfirm event: `
      `


  // Add the input to the cache after typing in the input field
    onConfirm(event) {
      // For good user experience, the search page should be displayed immediately after clicking
      this.setData({
        searching: true
      })

      Q: One is content input by user or custom event tapping via calling tag component, with text text attached; Fetching the search method returns when summary=1, returns summary data: and updates the data to dataArray
      const q = event.detail.value || event.detail.text
      bookmodel.search(0, q).then(res= > {
        this.setData({
          dataArray: res.books
        })

        // Can't everything the user enters be saved in the cache, only if the user finds a valid keyword
        Keywordmodel.addToHistory(q)

      })

    }
Copy the code

[Fixed] the title of the book should be displayed in the input field when clicking on the tag:

The components, the search and index. In js:
Value ="{{q}}"
// '`

// Define the private data q in data: "represents the content to be displayed in the input box. When the data request is completed, the content q of the click label is assigned to the private data Q and updated
data: {
    historyWords: [].// History search keyword
    hotWords: [], // Popular search keywords
    dataArray: [], // Search books when summary=1, return summary data
    searching: false.// Control the explicit and implicit of the searched book data, not displayed by default
    q: ' ' // What to display in the input box
  },
   // Add the input to the cache after typing in the input field
    onConfirm(event) {
      // For good user experience, the search page should be displayed immediately after clicking
      this.setData({
        searching: true
      })

      When summary=1, return summary data: and update data to dataArray
      const q = event.detail.value || event.detail.text
      bookmodel.search(0, q).then(res= > {
        this.setData({
          dataArray: res.books,
          q: q
        })

        // Can't everything the user enters be saved in the cache, only if the user finds a valid keyword
        Keywordmodel.addToHistory(q)

      })

    }
Copy the code

Achieve data paging loading function:

The first method: use the built-in component Scrollview provided by wechat applet.

The second method: Use the onReachBottom handler for pull-down events on pages. :

In the pages, the book, the book. In js:
// Set the more private variable in data to false, indicating whether more data needs to be loaded. The default value is not loaded
data: {
    books: [].searching: false.// Controls the implicit display of the search box component, which is not displayed by default
    more: false // Whether to load more data. By default, no data is loaded
  },
      
 // Use pages' onReachBottom handler to check whether the page has reached the bottom. If the page has reached the bottom, more will be changed to true, and more data can be loaded
  onReachBottom: function() {
    console.log('To the end')
    this.setData({
      more: true})},// Since the search component is not a page-level component, there is no onReachBottom function, and the more private variable is used to control whether to load more data to the child component search via attribute passing
 WXML: '
      
      
 // Then the search component receives the property more from the parent and uses the listener function Observer, which is triggered whenever data changes from the outside world
  properties: {
    more: {
      type: String.observer: '_load_more'
    } // The property from pages/book/book.js listens for the slide to the bottom step. The Observer function is triggered whenever a property change comes in from the outside
  },
      
 methods: {
    // The observer function is triggered whenever a property change is received from the outside world
    _load_more() {
      console.log('Listen function fired to the end')}},Copy the code

But there’s a problem:

The observer will only fire once, because the pull-down will change more to true, and then it will never change again, and no longer trigger the observer function.

The workaround: Trigger an Observer function with a random string, since the observer function must be executed only if the data it is listening for has changed. Similar to Watch in Vue.

In the pages, the book, the book. In js:
// Change more in private data to an empty string
data: {
    books: [].searching: false.// Controls the implicit display of the search box component, which is not displayed by default
    more: ' ' // Whether to load more data. By default, no data is loaded
  },
      
// Trigger the page pull-bottom event handler, change more to a random number, import random custom random handler, problem solved
 onReachBottom: function() {
    console.log('To the end')
    this.setData({
      more: random(16)})},// in utils\common.js:
// Define a random-generated string handler where n is the number of bits to generate a random string
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']

const random = function generateMixed(n) {
  var res = ' '
  for (var i = 0; i < n; i++) {
    var id = Math.ceil(Math.random() * 35)
    res += charts[id]
  }
  return res
}

export {
  random
}

Copy the code
The components, the search and index. In js:

Implement loading more data:

// As with onConfirm, the search method returns summary data when summary=1, and updates the data to dataArray
// First judge the length of the obtained search data, and then call the search method to splice the latest obtained data with the original data to update the data and then present the data to the page
 _load_more() {
      console.log('Listen function fired to the end')
      const length = this.data.dataArray.length
      bookmodel.search(length, this.data.q).then(res= > {
        const tempArray = this.data.dataArray.concat(res.books)
        this.setData({
          dataArray: tempArray
        })
      })

    },


Copy the code

Perfect details:

// If the keyword q has no initial value, it returns nothing
_load_more() {
      console.log('Listen function fired to the end')
    if (!this.data.q) {
        return
      }
      const length = this.data.dataArray.length
      bookmodel.search(length, this.data.q).then(res= > {
        const tempArray = this.data.dataArray.concat(res.books)
        this.setData({
          dataArray: tempArray
        })
      })

    },

// Problem: Continue to send requests to the server when there is no more data in the pull-down flush. Another problem is that the user acts too fast and sends the same request again without waiting for the results of the first request, which can load duplicate data and consume very high performance
        
// Solution: Use the lock concept to solve the problem of reloading data
// Define a loading in data: False: indicates whether a request is being sent. By default, no request is being sent. In _load_more, the loading method does nothing if a request is being sent. Change loading to false when the request completes and the result is returned.
        data:{
           loading: false // Indicates whether a request is being sent. The default is no request
        },
        _load_more() {
      console.log('Listen function fired to the end')
    if (!this.data.q) {
        return
      }
    // Do nothing if the request is being sent
      if (this.data.loading) {
        return
      }
      const length = this.data.dataArray.length
      bookmodel.search(length, this.data.q).then(res= > {
        const tempArray = this.data.dataArray.concat(res.books)
        this.setData({
          dataArray: tempArray,
            loading: false})})},Copy the code

Further encapsulation optimizations, component behavior logic abstracts the paging behavior, incidentally addressing the question of whether there is more data:

In components, create and encapsulate a component pagination for common behavior and methods:

##### in Components \ Behaviors \pagination.js:

// encapsulates a common behavior and method class paginationBev
const paginationBev = Behavior({
  data: {
    dataArray: [].// paging continuously loaded data
    total: 0 // Total number of data
  },

  methods: {
    // Load more concatenation more data into the array; The newly loaded data is merged into dataArray
    setMoreData(dataArray) {
      const tempArray = this.data.dataArray.concat(dataArray)
      this.setData({
        dataArray: tempArray
      })
    },

    // Returns the starting number of records when the search method is called
    getCurrentStart() {
      return this.data.dataArray.length
    },

    // Get sets the total length of data received from the server
    setTotal(total) {
      this.data.total = total
    },

    // If there is more data to load. If the length of the returned data is greater than the total length returned by the server, there is no more data and the request is stopped
    hasMore() {
      if (this.data.dataArray.length >= this.data.total) {
        return false
      } else {
        return true}}}})export {
  paginationBev
}

Copy the code
The components, the search and index. In js:
// Import the encapsulated public behavior method, then overwrite the _load_more and onConfirm methods to use the written public method
import {
  paginationBev
} from '.. /behaviors/pagination'

 _load_more() {
      console.log('Listen function fired to the end')
       
      if (!this.data.q) {
        return
      }
      // Do nothing if the request is being sent
      if (this.data.loading) {
        return
      }
     
     
      if (this.hasMore()) {
        this.data.loading = true// must be placed in hasMore()
        bookmodel.search(this.getCurrentStart(), this.data.q).then(res= > {
          this.setMoreData(res.books)
          this.setData({
			loading: false
          })
        })
      }


    },
          onConfirm(event) {
      // For good user experience, the search page should be displayed immediately after clicking
      this.setData({
        searching: true
      })

      When summary=1, return summary data: and update data to dataArray
      const q = event.detail.value || event.detail.text

      bookmodel.search(0, q).then(res= > {
        this.setMoreData(res.books)
        this.setTotal(res.total)
        this.setData({

          q: q
        })

        // Can't everything the user enters be saved in the cache, only if the user finds a valid keyword
        Keywordmodel.addToHistory(q)

      })

    }
Copy the code

But at this time, there will be a small problem: that is, every time you click X to return to the search page and search the same book again, there will be data that has not been cleared before and the request will be sent to the server again, so there will be more repeated data

Solution: When clicking x each time, clear the data of the current search, that is, the data state in Behavior, so that the data of the last search will not affect the current search

The components, behaviors, pagination. In js:
// Add a method to clear data and set the initial value
initialize() {
      this.data.dataArray = []
      this.data.total = null
    }
Copy the code
The components, the search and index. In js:
// The this.initialize() method is called when onConfirm is triggered to clear the data from the previous search before loading
onConfirm(event) {
      // For good user experience, the search page should be displayed immediately after clicking
      this.setData({
          searching: true
        })
        // Clear the data from the previous search before loading
      this.initialize()
        When summary=1, return summary data: and update data to dataArray
      const q = event.detail.value || event.detail.text

      bookmodel.search(0, q).then(res= > {
        this.setMoreData(res.books)
        this.setTotal(res.total)
        this.setData({

          q: q
        })

        // Can't everything the user enters be saved in the cache, only if the user finds a valid keyword
        Keywordmodel.addToHistory(q)

      })

    }
Copy the code
Search code refactoring: Enhanced code readability:
The components, the search and index. In js:
// Encapsulate more small functions
import {
  KeywordModel
} from '.. /.. /models/keyword'
import {
  BookModel
} from '.. /.. /models/book'
import {
  paginationBev
} from '.. /behaviors/pagination'

const Keywordmodel = new KeywordModel()
const bookmodel = new BookModel()

// components/search/index.js
Component({
  // Component usage behavior needs to be added
  behaviors: [paginationBev],


  /** * Component property list */
  properties: {
    more: {
      type: String.observer: 'loadMore'
    } // The property from pages/book/book.js listens for the slide to the bottom step. The Observer function is triggered whenever a property change comes in from the outside
  },

  /** * The initial data of the component */
  data: {
    historyWords: [].// History search keyword
    hotWords: [], // Popular search keywords
    DataArray: [], // Search for books when summary=1, return summary data
    searching: false.// Control the explicit and implicit of the searched book data, not displayed by default
    q: ' '.// What to display in the input box
    loading: false // Indicates whether a request is being sent. The default is no request
  },

  // The lifecycle function that the applet calls by default when the component is initialized
  attached() {
    // const historywords = Keywordmodel.getHistory()
    // const hotword = Keywordmodel.getHot()
    this.setData({
      historyWords: Keywordmodel.getHistory()
    })

    Keywordmodel.getHot().then(res= > {
      this.setData({
        hotWords: res.hot
      })
    })
  },

  /** * list of component methods */
  methods: {
    // The observer function is triggered whenever a property change is received from the outside world
    loadMore() {
      console.log('Listen function fired to the end')
        // As with onConfirm, the search method returns summary data when summary=1, and updates the data to dataArray
        // First judge the length of the obtained search data, and then call the search method to splice the latest obtained data with the original data to update the data and then present the data to the page
      if (!this.data.q) {
        return
      }
      // Do nothing if the request is being sent
      if (this._isLocked()) {
        return
      }
      // const length = this.data.dataArray.length

      if (this.hasMore()) {
        this._addLocked()
        bookmodel.search(this.getCurrentStart(), this.data.q).then(res= > {
          this.setMoreData(res.books)
          this._unLocked()
        })
      }


    },



    // Click Cancel to turn off the search component, there are two ways: first, create a variable in your own internal control explicit implicit, not recommended, because there are other operations bad scalability. Second, create a custom event and pass the custom event to the parent for the parent to fire
    onCancel(event) {
      this.triggerEvent('cancel'}, {}, {}),// Touch the x in the search image to return to the page where you entered the search
    onDelete(event) {
      this._hideResult()
    },

    // Add the input to the cache after typing in the input field
    onConfirm(event) {
      // For good user experience, the search page should be displayed immediately after clicking
      this._showResult()
        // Clear the data from the previous search before loading
      this.initialize()
        When summary=1, return summary data: and update data to dataArray
      const q = event.detail.value || event.detail.text

      bookmodel.search(0, q).then(res= > {
        this.setMoreData(res.books)
        this.setTotal(res.total)
        this.setData({

          q: q
        })

        // Can't everything the user enters be saved in the cache, only if the user finds a valid keyword
        Keywordmodel.addToHistory(q)

      })

    },

    // Update the state of the variable to display the search box
    _showResult() {
      this.setData({
        searching: true})},// Update variable status, hide the search box
    _hideResult() {
      this.setData({
        searching: false})},// Event throttling mechanism to determine whether to lock
    _isLocked() {
      return this.data.loading ? true : false
    },

    / / lock
    _addLocked() {
      this.data.loading = true
    },

    / / unlock
    _unLocked() {
      this.data.loading = false}}})Copy the code

Minor problem: When the network is suddenly disconnected during loading, the data is not loaded. When the network is restored, the server cannot continue to send requests. The reason for the problem is that a deadlock occurs, and the request will only be unlocked if it succeeds and the request will continue to be sent. If the request fails, the request will not be unlocked and nothing can be done.

Solutions:

The components, the search and index. In js:
// Just add unlock to the failed callback

      if (this.hasMore()) {
        this._addLocked()
        bookmodel.search(this.getCurrentStart(), this.data.q).then(res= > {
          this.setMoreData(res.books)
          this._unLocked()
        }, () => {
          this._unLocked()
        })
      }
Copy the code

Loading effect is added to improve user experience:

Create a loading public component, just write a simple style effect, register it in the Search component and use it.

The components, the search and index. In js:
// In components\search\index.wxml: add two loading components. The first one is shown in the middle, in the retrieval of the retrieved data; The second one is shown at the bottom, with more data loaded
//<v-loading class="loading-center" wx:if="{{loadingCenter}}"/>
// 
      

// Add a loadingCenter variable to data to control whether loading is displayed in the middle, and add two private functions to control whether loading is displayed or not. Call this._showloadingCenter () in onConfirm to display the loading effect. After data loading is complete, call this._hideloadingCenter () to cancel displaying the loading effect.
data: {loadingCenter: false},
 // Change the value of loadingCenter
    _showLoadingCenter() {
      this.setData({
        loadingCenter: true})},// Change the value of loadingCenter
    _hideLoadingCenter() {
      this.setData({
        loadingCenter: false
      })
    }

onConfirm(event) {
      // For good user experience, the search page should be displayed immediately after clicking
      this._showResult()
        // Displays the loading effect
      this._showLoadingCenter()
      this.initialize()   
      const q = event.detail.value || event.detail.text
      bookmodel.search(0, q).then(res= > {
        this.setMoreData(res.books)
        this.setTotal(res.total)
        this.setData({
          q: q
        })
        Keywordmodel.addToHistory(q)
        // When data is loaded, the loading effect is disabled
        this._hideLoadingCenter()
      })

    },
Copy the code

Add:

Note in particular the difference between setData and direct assignment:

SetData: A call to setData will trigger a page re-rendering, similar to setState in REACT.

Direct assignment, on the other hand, is just a state that changes in memory, not updated to the page

Empty search results processing:

The components, behaviors, pagination. In js:
// Add noneResult: false to the public behavior to control whether the desired search result is not displayed. In the setTotal method, if the result returned is 0, the desired search result is not displayed. Display noneResult: True. Set the initial value in Initialize and empty the data function, then set noneResult: false and cancel the display.

 data: {
    dataArray: [].// Request the array returned
    total: null.// Total number of data
    noneResult: false // Did not get the desired search results
  },
  
  // Get the total length of the set data
    // If the result is 0, the search result is not found, and the prompt content is displayed
    setTotal(total) {
      this.data.total = total
      if (total === 0) {
        this.setData({
          noneResult: true})}},// Clear the data, set the initial value, will prompt to hide
    initialize() {
      this.setData({
        dataArray: [].noneResult: false
      })
      this.data.total = null
    }
  
Copy the code
The components, the search and index. In the WXML:
// Add an empty search result structure<text wx:if="{{ noneResult}}" class="empty-tip">No books found</text>
Copy the code
The components, the search and index. In js:
// Touch the x in the search image to return to the original search page, return to the initial value, and then hide the search component. In onConfirm, you don't have to wait for the data to load. Once the input is complete, the input is displayed in the input box.
onDelete(event) {
      this.initialize()
      this._hideResult()
    },
        
    onConfirm(event) {
      this._showResult()
      this._showLoadingCenter()
      const q = event.detail.value || event.detail.text
        // Do not wait for the data to load, after the completion of the input content is displayed in the input box.
      this.setData({
        q: q
      })

      bookmodel.search(0, q).then(res= > {
        this.setMoreData(res.books)
        this.setTotal(res.total)
        Keywordmodel.addToHistory(q)
        this._hideLoadingCenter()

      })

    },        
  
Copy the code

Deal with a small problem: is in the popular search search Wang Xiaobo, return to the search results page each book will show like the word, remove like the word.

The components/book/index. In js:
// In components\search\index. WXML:
// All books searched by the search component do not display the "like" word. The "like" word is removed by attribute passing, and false is passed to the child component. The child component receives the "like" variable through the showLike variable.
<v-book book="{{item}}" class="book" show-like="{{false}}"></v-book>Properties: {book: Object, showLike: {// Control the display and hiding of the likes in each book. Type: Boolean, value: True}}, // in components\book\index. WXML: //show and hide the showLike attribute controls the show and hide of the like words<view class="foot" wx:if="{{showLike}}">
      <text class="footer">{{book. Fav_nums}}</text>
  </view>
Copy the code

The Search component is further optimized to extract the lock into the paging behavior:

The components, behaviors, pagination. In js:
// Extract the three lock methods in components\search\index.js into the common behavior method. In the common behavior method, add loading: false property to data. In the initialize function, add Loading: false

// Event throttling mechanism to determine whether to lock
    isLocked() {
      return this.data.loading ? true : false
    },

    / / lock
    addLocked() {
      this.setData({
        loading: true})},/ / unlock
    unLocked() {
      this.setData({
        loading: false})},Copy the code

There are two ways to monitor the bottom of the mobile terminal:

OnReachBottom in Scroll View or Pages. If you want to use an scroll view you can just replace the view component with an scroll view.


WeChat open – data display user information: https://developers.weixin.qq.com/miniprogram/dev/api/wx.getUserInfo.html

User authorization, need to use button to authorize login.

Most of the time, we need to save the information to our own server, need to operate the user profile picture and other information through THE JS code. Encapsulate an image-button generic component that changes its image and can be called in different ways by changing the open-type property.


Button:


Jump between applets: both applets must be associated with the same public number


= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =


Bug to solve

The components/episode came/index. In js

RangeError: Maximum Call stack size exceeded:

The reason:

// Error
properties: {
    index: { // Displays 0 by default
      type: String.observer: function(newVal, oldVal, changedPath) {
        let val = newVal < 10 ? '0' + newVal : newVal
        this.setData({
          index: val
        })
      }
    }
  },
// The observer of the applet is equivalent to the watch listener in vue. If the index data monitored changes, the observer method will be called, which will cause an infinite loop, and the RangeError: Maximum call stack size exceeded will be reported
Copy the code

Solution:

  // The first solution
this.setData({
          _index: val
        })

  data: {
    year: 0.month: ' '._index: ' '
  },
Copy the code
// Second solution (recommended)

Copy the code

The components/music/index. In js:

Error: setBackgroundAudioState: fail the title is nil. ; at pages/classic/classic onReady function; at api setBackgroundAudioState fail callback function

Error: setBackgroundAudioState:fail title is nil!

The reason:

Fewer title external attributes

Solution:

/ / in ` components/music/index in js ` :
properties: {
    src: String.title: String
  },
methods: {
    // Bind the touch play event to the image
    onPlay: function() {
      // Image toggle
      this.setData({
        playing: true
      })
      mMgr.src = this.properties.src
      mMgr.title = this.properties.title
    }
  }
-----------------------------------------------------------------------------
// Add to app.json:
"requiredBackgroundModes": [
    "audio"].Copy the code


= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =


Mobile added user experience optimization

incomponents/naviIn:

The left and right triangles should be large enough for the user to touch. There are two ways. The first way is when you cut the graph again, you cut it bigger. The second is to write your own code to control the operation area



Completed effect display:

Video address