Source: gongzhong 呺

Author: Hello Mr. Ward

Search is a very common function, we must have used, such as: Baidu search, Google search, e-commerce products search, Meituan merchants/food search and so on. With the explosive growth of Internet information, netizens need more effective personalized search services. Therefore, there is almost no Internet application that does not have a search function, and since this function is so important, as a qualified programmer must understand the implementation principle behind it. Arrangement!

This article will implement a simple version of e-commerce search system through Spring Boot + Elasticsearch + Vue, convenient for everyone to understand the principle behind.

Case analysis

According to the figure above, the business logic of search is actually quite simple. After the user enters the keyword of the product to be searched, the search keyword and page number information will be sent to the background, and the result set will be processed and returned after the background query. The following points need to be noted:

The query speed should be fast and the data matching degree should be high to facilitate sorting return result highlightingCopy the code

MySQL

The core table (goods) is as follows:

MySQL commodity table fields are very many, and the mall search page needs data is nothing more than commodity ID, commodity name, commodity price, number of commodity comments and commodity original map, etc. (according to their actual development situation, here according to the case front style analysis).

General commodity table will add commodity keyword field specially convenient search engine use, so a single table SQL query I believe should not be difficult to you.

Putting aside database performance issues, let’s look at what needs to be done simply to implement this functionality:

Suppose the search condition is Huawei mobile phone and tablet computer, the requirement is as long as any combination of words to meet the data to be queried, how to write SQL? In other words, how many pieces of SQL are you going to write?

Suppose you did the last step, you got so many result sets do you want to think about reordering?

You’ve done the first two steps, and now I’m asking you to highlight the combination of words in the search criteria when you return to the front. WTF, if you’re asked to use a relational database to do that, it’s a bit of a stretch. How to do? To look down.

Elasticsearch

With the explosive growth of Internet information, traditional search methods have been unable to provide effective search services for Internet users. If we are engaged in Intranet projects with few users, and the search fields are all short fields, such as name, number and so on, we can completely use database like statement. However, the query performance of database like is very low. If there are too many search requests, Or if you need to search for large-text content (full-text search), this search scheme is also not desirable.

The rapid development of Internet urgently needs a fast, comprehensive, accurate and stable information query method. Since we want to do high performance full text search, this requirement can not rely on the database, we have to implement by ourselves. We wanted it not only to be full-text search, but also to be stable and fast enough, with keyword highlighting, sorted by various matching scores, and to be able to switch between different word segmentation algorithms to meet various word segmentation requirements.

To sum up, if we want to make a full-text search function, powerful performance is not easy, and full-text search is a common requirement, in this environment, there are some open source solutions on the market. When these solutions are open source, they get a lot of support from the community of developers, who constantly develop plug-ins for them, so that they are constantly optimized and improved, and this becomes what we call a search engine. The most famous of them are Elasticsearch and Solr.

Elasticsearch is a search server based on Apache Lucene. It provides a distributed multi – user – capable full – text search engine based on RESTful Web interface. Elasticsearch, developed in Java and released as open source under the Apache license, is a popular enterprise-level search engine. Designed for cloud computing, can achieve real-time search, stable, reliable, fast, easy to install and use. Elasticsearch is the most popular enterprise search engine, followed by Apache Solr, also based on Lucene.

  • Elasticsearch is easy to install and configure, cheap to learn and use, and out of the box.

    Elasticsearch supports both standalone and distributed systems with built-in distributed coordination and management functions. Elasticsearch provides the sharding and copy mechanism. An index can be divided into multiple shards. Elasticsearch focuses on core features. Advanced features are provided by third-party add-ons such as Kibana graphical interface. Elasticsearch provides quick indexing and real-time query, and is suitable for emerging real-time search applications. Elasticseach has great text analysis (word segmentation) and inverted indexing to make search faster.Copy the code

participles

In the question we mentioned just now, assuming that the search condition is Huawei mobile phone and tablet computer, the requirement is to query the data as long as any combination of terms is satisfied. The text analysis feature of Elasticseach makes it easy to parse search terms and search quickly with inverted indexes. Elasticseach provides three word segmentation methods: single word segmentation, dichotomous word segmentation, and word library word segmentation.

Word segmentation, such as: “huawei mobile phone tablet” effect: “China”, “for” and “hand”, “machine”, “flat”, “plate”, “electricity” and “brain”

Dichotomy: To divide two words into two parts. Such as: “huawei mobile phone tablet” effect: “huawei”, “hand”, “mobile phone”, “machine”, “tablet” and “electricity”, “computer”.

Thesaurus segmentation: according to a certain algorithm to construct words, and then to match the built thesaurus set, if matched, segmented into words. In general, thesaurus segmentation is regarded as the most ideal algorithm for Chinese word segmentation. The most commonly used word participle is the IK participle.

IK word divider provides two word segmentation modes: IK_max_word For example, “National anthem of the People’s Republic of China” will be split into “People’s Republic of China, People’s Republic of China, People’s Republic of China, People’s Republic of China, People’s Republic of China, People’s Republic of China, republic of China, and Guo Guo, national anthem”, which will exhaust all possible combinations and is suitable for Term Query.

Ik_smart: the text will be split in the coarsest granularity, for example, “The national anthem of the People’s Republic of China” will be split into “the National anthem of the People’s Republic of China”, which is suitable for Phrase Query.

Inverted index

For search engines: the forward index is the document ID to the document content, word association. That is, get the contents of the document by ID.

An inverted index is an association of words to document ids. That is, search for the document ID by word.

The query process of inverted index is as follows: first, search the corresponding document ID according to the keyword, then search the complete content of the document ID according to the forward index, and finally return the result that the user wants.

Part of the

An inverted index is the core of a search engine and consists of two parts: Trem Dictionary: It records the results of all the word segmentation of documents. Generally, B+Tree is used to ensure high efficiency.

Posting List: A collection of documents for Posting words, consisting of Posting index entries.

Posting is used to retrieve information about the original document.

Word Frequency (TF, Term Frequency) records the Frequency of occurrence of the word in the document for subsequent correlation score.

Position: records the word segmentation Position (multiple) in the document for word search.

Offset, which records the beginning and end positions of words in the document for highlighting.

case

Inverted index reference “This is the Search engine: A Detailed explanation of core technology” by Zhang Junlin. Suppose the document collection contains five documents, each with the contents as shown in the figure below. In the far left column of the figure is the corresponding document number for each document. Our task is to create an inverted index of this collection of documents.

First, a word segmentation system is used to automatically cut the document into word sequences. In order to facilitate the subsequent processing of the system, it is necessary to assign a unique word number to each different word and record which documents contain the word. After such processing, the simplest inverted index can be obtained, as shown in the figure below.

In the figure, the “Word ID” column records the word number of each word, the second column is the corresponding word, and the third column is the inverted list of each word. The word Google, for example, has a word number of 1 and an inverted list of 1,2,3,4,5, indicating that every document in the document collection contains the word.

For your convenience, the figure above is a simple inverted index of which documents contain which words. In fact, indexing systems can record much more than that.

Under the plan is a relatively complex inverted index, not only in words corresponding inverted list record the document number, also recorded the word frequency (TF) information, namely the number of occurrences of the word in a document, is to record the information, because the word frequency information in the search results sorted, calculate the query and the document similarity calculation factor is an important one, Therefore, it is recorded in the inverted list to facilitate the subsequent sorting of the score calculation.

The word number of the word founder in the figure is 7, and the corresponding inverted list content is (3; 1), where 3 represents that the document numbered 3 contains this word, and the number 1 represents word frequency information, that is, this word only appears once in the document no. 3.

Practical inverted index can also record more information. As shown in the figure below, the index system records two additional types of information besides document number and word frequency information, that is, the “document frequency information” corresponding to each word and the position information (POS) of the word in a certain document in the inverted list.

With this indexing system, the search engine can easily respond to the user’s query, such as the user input the query word “Facebook”, the search system looks up the inverted index, from which the documents containing the word, these documents are provided to the user’s search results. Word frequency information and document frequency information can be used to sort these candidate search results, calculate the similarity of documents and queries, and sort the output according to the similarity score from high to low.

So with that said, you know, without further ado, let’s get to the real thing. The image below is from: db-engines.com/en/ranking

The preparatory work

The environment

Elasticsearch: 7.9.3 Spring Boot: 2.4.3 JDK: 11.0.7 front-end: copy JINGdong store template + Vue, article with detailed code IDE: IntelliJ IDEA

Elasticsearch

This document uses the Elasticsearch cluster environment and has the IK Chinese word segmentation installed. The following figure shows the cluster information displayed by the ElasticSearch-head plug-in.

Spring Boot

Create a project

Use Spring Initializr to initialize the Spring Boot project, add Spring Web, Spring Data Elasticsearch, MySQL, MyBatis, Lombok.

The configuration file

Application. yml Configures information about MySQL, Elasticsearch, and MyBatis. Spring: # datasource: driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/example? serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=false
    username: root
    password: 123456
  # elasticsearch
  elasticsearch:
    rest:
      uris: 192.16810.10.:9200.192.16810.11.:9200.192.16810.12.:9200

mybatis:
  configuration:
    map-underscore-to-camel-case: true# Enable hump mappingCopy the code

Start the class

Startup class adds Mapper interface scanning.

package com.example;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@MapperScan("com.example.mapper") // Mapper interface scanning
@SpringBootApplication
public class SearchDemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(SearchDemoApplication.class, args); }}Copy the code

The front end

Add the front-end resource files I prepared for you to the static directory in the resources directory of the project. Front-end resource file acquisition method: please pay attention to the public 呺 “code without bugs” reply to search.

Add Vue and Axios using CDN in list.html without downloading the file.

Start the

Visit: http://localhost:8080/list.html effect is as follows:

The function development

Create indexes

specifications

This function is used to import MySQL commodity information into Elasticseach.

If you set up an ELK system, you can use Logstash to import MySQL commodity information into Elasticseach. I will update this later, so stay tuned, we won’t talk about it today.

Since there is no Logstash we will import MySQL data into Elasticseach using code.

MySQL commodity table fields are very many, and the mall search page needs data is nothing more than commodity ID, commodity name, commodity price, number of commodity comments and commodity original map, etc. (according to their actual development situation, here according to the case front style analysis). General commodity table will add commodity keyword field specially convenient search engine use, so a single table SQL query I believe should not be difficult to you.

Entity class

Create the entity class goods.java and add the @document annotation to map the Elasticsearch library. Shards: Fragment number replicas: number of replicas createIndex: Whether to create an index database. @ID: primary key. @field: Set an index rule. Text, default analyzer ="ik_max_word"Ik_max_word will split the text at the finest granularity. Type = fieldType. Double: Double and indivisible as a whole type = fieldtype. Short: Type = fieldtype. Keyword: text, and as a whole is indivisible package com.example.pojo;import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;

import java.io.Serializable;
import java.math.BigDecimal;

@Data
@NoArgsConstructor
** ** @document Specifies the name of the Elasticsearch index. * shards: number of fragments * replicas: number of copies * createIndex: whether to create an index */
@Document(indexName = "goods", shards = 5, replicas = 1, createIndex = true)
public class Goods implements Serializable {

    private static final long serialVersionUID = -1989082640160920658L;
    @Id
    private Integer goodsId; ID / / commodities
    @Field(type = FieldType.Text, analyzer = "ik_max_word") / / word segmentation
    private String goodsName; // Product name
    @Field(type = FieldType.Text, analyzer = "ik_max_word") / / word segmentation
    private String keywords; // Product keyword
    @Field(type = FieldType.Double)
    private BigDecimal marketPrice; / / the market price
    @Field(type = FieldType.Short)
    private Short commentCount; // Number of product reviews
    @Field(type = FieldType.Keyword)
    private String originalImg; // The original map address of the commodity
    private String goodsRemark; // A brief description of the product
    private String goodsContent; // Commodity description, storage commodity detail map address
    private Integer catId; / / category ID
    private Integer extendCatId; // Extend the class ID
    private String goodsNo; // Product id
    private Integer clickCount; / / clicks
    private Short brandId; / / brand ID
    private Short storeCount; // Inventory quantity
    private Integer weight; // Product weight (unit: grams
    private BigDecimal shopPrice; / / this shop price
    private BigDecimal costPrice; // The cost of goods
    private Byte isReal; // Whether it is a real object
    private Byte isOnSale; // Whether it is available
    private Byte isFreeShipping; 0 No, 1 Yes
    private Integer onTime; // Product shelf time
    private Short sort; // Sort items
    private Byte isRecommend; // Whether recommended
    private Byte isNew; // Is it new
    private Byte isHot; // Is it hot
    private Integer lastUpdate; // Last updated
    private Short goodsType; // The type ID of the item
    private Short specType; // Product specification type
    private Integer giveIntegral; // Free points for purchases
    private Integer exchangeIntegral; // Points exchange: 0 does not participate in points exchange
    private Short suppliersId; // Supplier ID
    private Integer salesSum; // Product sales
    private Byte promType; // 0 ordinary order, 1 flash sale, 2 group purchase, 3 promotional offers
    private Integer promId; // ID of a special event
    private BigDecimal commission; // Commission for distribution
    private String spu; // SPU
    private String sku; // SKU

}
Copy the code

GoodsMapper.java

MySQL > select * from product where ID, name, keyword, market price, number of product reviews, address of original product map; These fields are used by search engines.

package com.example.mapper;

import com.example.pojo.Goods;
import org.apache.ibatis.annotations.Select;

import java.util.List;

/** * @author Mr. Harry Ward * @wechat public account Mr. Harry Ward * @website https://mrhelloworld.com * @wechat 124059770 */
public interface GoodsMapper {

    /** ** Query commodity ID, commodity name, commodity keyword, market price, commodity comment number, commodity original map address ** @return commodity list */
    @Select("select goods_id, goods_name, keywords, market_price, comment_count, original_img from goods")
    List<Goods> selectGoodsList(a); } goodsRepository.java application.yml configuration file adds a custom configuration that controls the creation of indexes and the setting of mappings.Copy the code

Custom Configuration

search:
  index:
    enabled: true# whether to create index and set mapping. The default isfalseInject ElasticsearchRestTemplate GoodsRepository. Java, writing create indexes and setting up the mapping code. package com.example.repository;import com.example.pojo.Goods;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.IndexOperations;
import org.springframework.data.elasticsearch.core.document.Document;
import org.springframework.stereotype.Repository;

import javax.annotation.Resource;
import java.util.Arrays;
import java.util.List;

/** * @author Mr. Harry Ward * @wechat public account Mr. Harry Ward * @website https://mrhelloworld.com * @wechat 124059770 */
@Repository
public class GoodsRepository {

    @Resource
    private ElasticsearchRestTemplate elasticsearchRestTemplate;

    // Whether to create an index and set the mapping. The default is false
    @Value("${search.index.enabled}")
    private boolean indexEnabled;

    /** * Add commodity info to Elasticsearch ** @param goodsList */
    public void save(List<Goods> goodsList) {
        // Whether to create an index and set the mapping
        if (indexEnabled) {
            createIndexAndPutMapping(a); }// Batch add
        elasticsearchRestTemplate.save(goodsList);
    }

    /** * Added single item info to Elasticsearch ** @param goods */
    public void save(Goods goods) {
        save(Arrays.asList(goods));
    }

    /** * Create index and set mapping */
    private void createIndexAndPutMapping(a) {
        // Set index information (entity class)
        IndexOperations indexOperations = elasticsearchRestTemplate.indexOps(Goods.class);
        // Create index
        indexOperations.create(a);// Create the mapping
        Document mapping = indexOperations.createMapping(a);// Write the map to the index
        indexOperations.putMapping(mapping); }} SearchService.java searchService.java uses GoodsMapper and GoodsRepository to import MySQL commodity information into Elasticsearch. package com.example.service;import com.example.mapper.GoodsMapper;
import com.example.repository.GoodsRepository;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

/** * @author Mr. Harry Ward * @wechat public account Mr. Harry Ward * @website https://mrhelloworld.com * @wechat 124059770 */
@Service
public class SearchService {

    @Resource
    private GoodsMapper goodsMapper;
    @Resource
    private GoodsRepository goodsRepository;

    /** * import MySQL commodity info into Elasticsearch */
    public void importGoods(a) {
        goodsRepository.save(goodsMapper.selectGoodsList()); }}Copy the code

Unit testing

For the current case, there is no need to expose an interface to call this function, just write unit tests in-house.

package com.example.service;

import com.example.SearchDemoApplication;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

import javax.annotation.Resource;

@SpringBootTest(classes = SearchDemoApplication.class)
public class SearchServiceTest {

    @Resource
    private SearchService searchService;

    @Test
    public void testImportGoods(a) {
        searchService.importGoods();
    }

}
Copy the code

After executing the unit test, refresh the ElasticSearch-head plugin page and the result is as follows:

Take a look at the mapping information for the Goods index library.

MySQL > alter table goods;

Elasticsearch goods index library:

If the preceding command output is displayed, all MySQL commodity information has been imported to Elasticsearch.

search

Next comes today’s topic, the implementation of the search function. To do a complete set, we use imitation JINGdong mall template + Vue to achieve a real e-commerce search function.

The search logic is pretty simple. After the user enters the keyword of the product to be searched, the search keyword and page number information will be sent to the background, and then the Spring Data Elasticseach can be easily done.

GoodsRepository.java

According to international conventions, the DAO layer is only responsible for data processing, one line of code./** ** paged, highlighted query ** @param query * @return */
public SearchHits<Goods> selectGoodsListForPage(NativeSearchQuery query) {
    return elasticsearchRestTemplate.search(query, Goods.class);
}
Copy the code

PageInfo.java

This function is certainly needed with paging processing, encapsulation of a paging object for easy use.

package com.example.result;

import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;
import java.util.List;

/** * @author Mr. Harry Ward * @wechat public account Mr. Harry Ward * @website https://mrhelloworld.com * @wechat 124059770 */
@Data
@NoArgsConstructor
public class PageInfo<T> implements Serializable {

    private static final long serialVersionUID = 6260163970867016563L;
    private int currentPage; / / the current page
    private int pageSize; // Display the number of entries per page
    private int total; // Total number of records
    private int totalPage; / / the total number of pages
    private int prePage; / / back
    private int nextPage; / / the next page
    private boolean hasPre; // Is there a previous page
    private boolean hasNext; // Is there a next page
    private List<T> result; // Return the result set

    public PageInfo(int currentPage, int pageSize) {
        / / the current page
        this.currentPage = currentPage < 1 ? 1 : currentPage;
        // Display the number of entries per page
        this.pageSize = pageSize;
        // Is there a previous page
        this.hasPre = currentPage == 1 ? false : true;
        // Is there a next page
        this.hasNext = currentPage == totalPage ? false : true;
        / / back
        if (hasPre) {
            this.prePage = currentPage - 1;
        }
        / / the next page
        if (hasNext) {
            this.nextPage = currentPage + 1; }}public PageInfo(int currentPage, int pageSize, int total) {
        / / the current page
        this.currentPage = currentPage < 1 ? 1 : currentPage;
        // Display the number of entries per page
        this.pageSize = pageSize;
        // Total number of records
        this.total = total;
        // Count the total pages
        if (total == 0) {
            this.totalPage = 0;
        } else {
            this.totalPage = (total - 1) / pageSize + 1;
        }
        // Is there a previous page
        this.hasPre = currentPage == 1 ? false : true;
        // Is there a next page
        this.hasNext = currentPage == totalPage ? false : true;
        / / back
        if (hasPre) {
            this.prePage = currentPage - 1;
        }
        / / the next page
        if (hasNext) {
            this.nextPage = currentPage + 1; }}}Copy the code

SearchService.java

The Service business logic layer focuses on the following details:

<span style= "box-sizing: border-box! Important'color:red; '></ SPAN > Build search criteria object Set search criteria goodsName and keywords Settings Highlight set paging call DAO to complete the search processing search result set build paging object and return/** * @param searchStr Search criteria * @param pageNum Number of pages * @param pageSize Number of pages to display per page * @return */
public PageInfo<GoodsVo> doSearch(String searchStr, Integer pageNum, Integer pageSize) {
    
    HighlightBuilder.Field field = new HighlightBuilder.Field("goodsName");
    field.preTags("");
    field.postTags("</span>");
    // Build the search criteria object
    BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery(a);// Set the search criteria goodsName and keywords
    boolQueryBuilder.must(QueryBuilders.multiMatchQuery(searchStr, "goodsName"."keywords"));
    // Build the search object
    NativeSearchQuery query = new NativeSearchQueryBuilder().withQuery(boolQueryBuilder) // Search criteria
            .withHighlightFields(field) / / highlight
            .withPageable(PageRequest.of(pageNum - 1, pageSize)) // paging, the current page starts at 0
            .build(a);/ / search
    SearchHits<Goods> searchHits = goodsRepository.selectGoodsListForPage(query);
    / / the total number of article
    Long total = searchHits.getTotalHits(a);if (0 > total) {
        return new PageInfo<>(pageNum, pageSize, 0);
    }
    // Initialize the return result set
    List<GoodsVo> goodsVoList = new ArrayList<>();
    // Process the search result set
    for (SearchHit<Goods> searchHit : searchHits) {
        // Initialize the return result object
        GoodsVo goodsVo = new GoodsVo(a);// Get the result object
        Goods goods = searchHit.getContent(a);// Copy attributes
        BeanUtils.copyProperties(goods, goodsVo);
        // Process the highlighting information
        Map<String, List<String>> highlightFields = searchHit.getHighlightFields(a);// Whether there is highlighting information
        if (highlightFields.containsKey("goodsName")) {
            String goodsNameHl = highlightFields.get("goodsName").get(0);
            goodsVo.setGoodsNameHl(goodsNameHl);
        } else {
            goodsVo.setGoodsNameHl(goods.getGoodsName());
        }
        goodsVoList.add(goodsVo);
    }
    // Initialize the split-page object
    PageInfo<GoodsVo> pageInfo = new PageInfo<GoodsVo>(pageNum, pageSize, total.intValue());
    pageInfo.setResult(goodsVoList);
    return pageInfo;
}
Copy the code

SearchController.java

The control layer provides a /search GET interface.

package com.example.controller;

import com.example.result.PageInfo;
import com.example.service.SearchService;
import com.example.vo.GoodsVo;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

/** * @author Mr. Harry Ward * @wechat public account Mr. Harry Ward * @website https://mrhelloworld.com * @wechat 124059770 */
@RestController
@RequestMapping("search")
public class SearchController {

    @Resource
    private SearchService searchService;

    /** * @param searchStr Search criteria * @param pageNum Number of pages * @param pageSize Number of pages to display per page * @return */
    @GetMapping
    public PageInfo<GoodsVo> doSearch(String searchStr, Integer pageNum, Integer pageSize) {
        return searchService.doSearch(searchStr, pageNum, pageSize); }}Copy the code

list.html

Add Vue and Axios using CDN in list.html without downloading the file.

<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/axios.min.js"></script>
Copy the code

The page processing

Modify the form section of the header search (good with Ctrl + F) :

Add ID = to <input/> of the commodity keyword"searchStr"Add two <input/> of type hidden to hold page number information <form action="" name="search" method="get" class="fl" onsubmit="return false;">
    <input id="searchStr" type="text" class="txt" value="Please enter the product keyword"/>
    <input type="submit" class="btn" value="Search"/>
    <input id="pageNum" type="hidden" value="1"/>
    <input id="pageSize" type="hidden" value="12"/> </form> Modifies the list of items (good with Ctrl + F), leaving only a <li></li> : <! Start --> <div id="goodsList" class="goodslist mt10">
    <ul>
        <li>
            <dl>
                <dt><a href=""><img src="images/goods1.jpg" alt=""/></a></dt>
                <dd><a href=""><p class="beyondHidden"Word-wrap: break-word! Important; "> < p style =" max-width: 100%2G 500G DVD keyboard & Mouse) tape20Inch monitor </p></ A ></dd> <dd><strong>¥2399.00</strong></dd>
                <dd><a href=""> < em > already10</em></a></dd> </dl> </li> </ul> </div> <! End --> Change page information (good with Ctrl + F) : <! Start --> <div id="page" class="page mt20">
    <a href=""> </a> <a href=""< span style = "max-width: 100%; clear: both; min-height: 1em"" class="cur">3</a>
    <a href=""> next page </a> <a href=""> back < / a > & have spent &nbsp; < span > < em > altogether8Page & have spent &nbsp; To the < input type ="text" id="num" class="page_num"/> page </em> <a href="" class="skipsearch"</a> </span> </div> <! End -->Copy the code

Vue and Axios

First add a div globally and set id=”app”.

You then initialize the Vue object, bind the elements, and define the component data and component methods. <script> var app =new Vue({
    // Short for element, mount the element, bind the HTML snippet with the id app
    el: '#app'.// Define component data
    data: {
      goodsList: [],
      page: []
    },
    // Define component methods
    methods: {
      / / search
      doSearch() {
        axios({
          url: "http://localhost:8080/search",
          method: "GET",
          params: {
            searchStr: $("#searchStr").val(),
            pageNum: $("#pageNum").val(),
            pageSize: $("#pageSize").val()}}).then(response => { // Return the result
          $("#pageNum").val(1);// Reset the current page
          if (response.data.total <= 0) {$('#goodsList').append(' Sorry, not found with ' + $("#searchStr").val() + '" for related products, please make sure your search terms are correct. ');
          }
          this.goodsList = response.data.result;
          this.page = response.data;
        }).catch(error => {// Exception catch
          alert('System is being upgraded, please try again later! ');
        });
      },
      / / back
      prePage() {
        // Get the value of the current page and subtract one, then reassign to the current page
        let page = parseInt($("#pageNum").val- ())1;
        $("#pageNum").val(page);
        // Call the search function
        this.doSearch(a); },/ / the next page
      nextPage() {
        // Gets the value of the current page and increments it, then reassigns it to the current page
        let page = parseInt($("#pageNum").val()) + 1;
        $("#pageNum").val(page);
        // Call the search function
        this.doSearch(a); },/ / what page
      whichPage(num) {
        // Get the button clicked (home page, 1, 2... Last page), and then reassign to the current page
        $("#pageNum").val(num);
        // Call the search function
        this.doSearch(a); },// To what page
      goToPage() {
        // Get the entered page number value and reassign it to the current page
        $("#pageNum").val($("#num").val());
        // Call the search function
        this.doSearch(a); }}}); </script>Copy the code

The binding element

Modify the form section of the header search (good with Ctrl + F) :

Add v - on: click ="doSearch"(@click="doSearch") to the search button <form action="" name="search" method="get" class="fl" onsubmit="return false;">
    <input id="searchStr" type="text" class="txt" value="Please enter the product keyword"/>
    <input v-on:click="doSearch" type="submit" class="btn" value="Search"/>
    <input id="pageNum" type="hidden" value="1"/>
    <input id="pageSize" type="hidden" value="12"/>
</form>
Copy the code

Modify the list of items (good with Ctrl + F) :

List rendering: <li></li> add V -for="(goods, index) in goodsList"<img/> Add v-bind: SRC ="goods.originalImg"(Short: SRC ="goods.originalImg") commodity name and highlight: <p></p> Add V-html ="goods.goodsNameHl"Merchandise Price: Add {{goods.marketPrice}} Merchandise comment: Add {{goods.commentCount}} <! Start --> <div id="goodsList" class="goodslist mt10">
    <ul>
        <li v-for="(goods, index) in goodsList">
            <dl>
                <dt><a href=""><img v-bind:src="goods.originalImg" alt=""/></a></dt>
                <dd><a href=""><p class="beyondHidden" v-html="goods.goodsNameHl"> < / p > < / a > < / dd > < dd > < strong > ${{goods. MarketPrice}} < / strong > < / dd > < dd > < a href =""> < em > {{goods.com mentCount}} people evaluation < / em > < / a > < / dd > < / dl > < / li > < / ul > < / div > <! End -->Copy the code

Modify page information (good with Ctrl + F) :

Home page: v - on: click ="whichPage(1)"Back: v - on: click ="whichPage(page.totalPage)"Previous page: V -if="page.hasPre" v-on:click="prePage"Next page: V -if="page.hasNext" v-on:click="nextPage"A page: Loop render: V -for="i in page.totalPage"Page: V-on :click="whichPage(i)"Current page style handling: V-bind :class="{ cur:i == page.currentPage }"{{page. TotalPage}} to page number: V-on :click="goToPage"<! Start --> <div id="page" class="page mt20">
    <a v-on:click="whichPage(1)" href="javascript:void(0);"< p style = "max-width: 100%; clear: both; min-height: 1emif="page.hasPre" v-on:click="prePage" href="javascript:void(0);"< p style = "max-width: 100%; clear: both; min-height: 1emfor="i in page.totalPage"
       v-on:click="whichPage(i)"
       v-bind:class="{ cur:i == page.currentPage }"
       href="javascript:void(0);">{{ i }}</a>
    <a v-if="page.hasNext" v-on:click="nextPage" href="javascript:void(0);"> <a > <a v-on:click="whichPage(page.totalPage)" href="javascript:void(0);"> back < / a > & have spent &nbsp; <span> <em> total {{page. TotalPage}} page & NBSP; &nbsp; To the < input type ="text" id="num" class="page_num"/> page </em> <a v-on:click="goToPage" href="javascript:void(0);" class="skipsearch"</a> </span> </div> <! End -->Copy the code

test

Access:http://localhost:8080/list.htmlJust type whatever you want and test it out.