Vue Job Search Online

Keywords: axios MongoDB Database REST

Knowledge Points: v-on, v-for, v-if, props, $emit, dynamic Prop, Class and Style binding

P1 Paging Query


Query parameters


Query parameters: company name, job type, salaryMin salaryMax

Description: The request is made by axios.post with parameters, and the back end uses paging query to return the data of specified number of bars to the front end. MongoDB Limit() is mainly used to limit the number of records read, Skip() skips the specified number of data, the amount of data is very small 1w +.

// paging
exports.pageQuery = function (page, pageSize, Model, populate, queryParams, projection, sortParams, callback) {
    var start = (page - 1) * pageSize;  // Get the number of records skip will skip based on page and page Size
    var $page = {
        pageNumber: page
    };
    async.parallel({
        count: function (done) {  // Find out that there are count s in total
            Model.count(queryParams).exec(function (err, count) {
                done(err, count);
            });
        },
        records: function (done) {   // Query to get records of sorted and excluded fields
            Model.find(queryParams, projection).skip(start).limit(pageSize).populate(populate).sort(sortParams).exec(function (err, doc) {
                done(err, doc);
            });
        }
    }, function (err, results) {

        var list = new Array();
        for (let item of results.records) {
            list.push(item.toObject())
        }

        var count = results.count;
        $page.pageCount = parseInt((count - 1) / pageSize + 1); // PageCount
        $page.results = list;   // Single page result
        $page.count = count;    // Total Record Volume
        callback(err, $page);
    });
};

With the paging function, the query function can only pass in parameters.

Fuzzy Query about MongoDB

// A database command is a regular expression: / parameter/
db.getCollection('jobs').find({company: /NetEase/})

// In js, if you write / data.company / directly, it will be a string. The Model.find({}) function can't recognize it, so you can only use new RegExp()
company: new RegExp(data.company)
// Query work
exports.findJobs = function (data, cb) {
    let searchItem = {
        company: new RegExp(data.company),
        type: new RegExp(data.type),
        money: { $gte: data.salaryMin, $lte: data.salaryMax }
    }
    for (let item in searchItem) {  // Delete the property if the condition is empty
        if (searchItem[item] === '//') {
            delete searchItem[item]
        }
    }
    var page = data.page || 1
    this.pageQuery(page, PAGE_SIZE, Job, '', searchItem, {_id: 0, __v: 0}, {
        money: 'asc'
    }, function (error, data) {
        ...
    })
}

P2 Displays Query Results


Query results

data structure

Explanation: The result of the query is an array of objects, which can be easily displayed by nesting v-for.

// html
<div class="searchResult">
    <table class="table table-hover">
        <tbody class="jobList">
            <tr>
                <th v-for="item in title">{{ item }}</th>
            </tr>
            <tr v-for="(item, index) in searchResults" @click="showDesc(index)">
              <td v-for="value in item">{{ value }}</td>
          </tr>
      </tbody>
  </table>
</div>
// onSubmit()
Axios.post('http://localhost:3000/api/searchJobs', searchData)
.then(res => {
    this.searchResults = res.data.results         // Single page query results
    this.page.count = res.data.pageCount           // PageCount
    console.log('PageCount' + this.page.count)     // Total data volume
    ...
})
.catch(err => {
    console.log(err)
})

P3 Details Card


Detail Card

Description: The specific content is displayed by clicking on the single-line data to display the customized detail box component DescMsg.
Composition: Mask + Content Box
Idea: Click on the single line data in the parent component SearchJob and pass the selected row data jobDesc and showMsg: true to the child component DescMsg through props. Click on the rest of the child component DescMsg except the details box, and use $emit('hideMsg') to trigger the closing details page event. The parent component uses v-on directly to listen for the events triggered by the child component where the child component is used, and set showMsg: false to close the details page.

// Using DescMsg in parent components
<DescMsg :jobDesc="jobDesc" :showMsg="showMsg" v-on:hideMsg="hideJobDesc"></DescMsg>
// Display Details Box
showDesc (index) {
    let item = this.searchResults[index]
    this.jobDesc = [
        { title: 'Title', value: item.posname },
        { title: 'company', value: item.company },
        { title: 'A monthly salary', value: item.money },
        { title: 'place', value: item.area },
        { title: 'Release time', value: item.pubdate },
        { title: 'Minimum educational background', value: item.edu },
        { title: 'Hands-on background', value: item.exp },
        { title: 'details', value: item.desc },
        { title: 'welfare', value: item.welfare },
        { title: 'Position Category', value: item.type },
        { title: 'Recruitment', value: item.count }
    ]
    this.showMsg = true
},
// Close Details Box
hideJobDesc () {
    this.showMsg = false
}
// Subcomponent DescMsg

<template>
  <div class="wrapper" v-if="showMsg">
    <div class="shade" @click="hideShade"></div>
    <div class="msgBox">
      <h4 class="msgTitle">Details</h4>
      <table class="table table-hover">
        <tbody class="jobList">
          <tr v-for="item in jobDesc" :key="item.id">
            <td class="title">{{ item.title }}</td>
            <td class="ctn">{{ item.value }}</td>
          </tr>
        </tbody>
      </table>
      <div class="ft">
        <button type="button" class="btn btn-primary" @click="fllow">follow</button>
      </div>
    </div>
  </div>
</template>

<script>

export default {
  data () {
    return {
    }
  },
  props: {
    jobDesc: {
      type: Array,
      default: []
    },
    showMsg: {
      type: Boolean,
      default: false
    }
  },
  methods: {
    hideShade () {
      this.$emit('hideMsg')
    },
    fllow () {
      alert('1')
    }
  }
}
</script>

P4 page number


Page number

Description: According to the total number of pages count ed by the query, it is stipulated that the maximum number of pages should be displayed at one time.
Idea: Render the page number by v-for, i.e. v-for="(item, index) of pageList", and bind Class for each li, i.e. class="{active: item.active}. When the number of pages is greater than 10, click on the nth page number greater than 6, the number of pages moves 1 to the right as a whole, otherwise it moves 1 to the left as a whole. To click item.active = true after a page number, add a style. active to the page number.

html

<!-- Bottom page number column -->
<div class="pageButtons">
    <nav aria-label="Page navigation">
        <ul class="pagination">
            <li :class="{disabled: minPage}">
                <a aria-label="Previous">
                    <span aria-hidden="true">«</span>
                </a>
            </li>
            <li v-for="(item, index) of pageList" :class="{active: item.active}">
                <a @click="onSubmit(index)">{{ item.value }}</a>
            </li>
            <li :class="{disabled: maxPage}">
                <a aria-label="Next">
                    <span aria-hidden="true">»</span>
                </a>
            </li>
        </ul>
    </nav>
</div>

js

export default {
  data () {
    return {
      page: {
        selected: 0,  // Select the number of pages
        count: 0,     // PageCount
        size: 10      // Maximum number of display pages
      },
      pageList: [
        {active: false, value: 1} // Default contains page number 1
      ]
    }
  },
  methods: {
      // Index stands for page number index from left to start. Okay, I'm confused, up to 10.
    onSubmit (index) {
      if (index === -1) {    // index is - 1, which represents the event triggered by clicking the query button directly to initialize the data.
        index = 0
        this.page.selected = 0
        this.pageList = [
          {active: false, value: 1}
        ]
      }
      Axios.post('http://localhost:3000/api/searchJobs', searchData)
      .then(res => {
        this.page.count = res.data.pageCount   // PageCount
        let pageNumber = 1  // Default page 1

        // If the index >= 6 and the last page number displayed is less than the total number of pages, then move 1 backward as a whole, and the selected page number moves 1 to the left accordingly, that is index--
        if (index >= 6 && (this.page.count - this.pageList[9].value) > 0) {
          pageNumber = this.pageList[1].value
          index--
        } else if (index < 6 && this.pageList[0].value !== 1) {
          pageNumber = this.pageList[0].value - 1
          index++
        }
        this.pageList = []    // Initialize pageList and then render it again
        this.page.size = (this.page.count > 10) ? 10 : this.page.count
        for (let i = 0; i < this.page.size; i++) {
          let item = {
            active: false,
            value: pageNumber
          }
          pageNumber++
          this.pageList.push(item)
        }
        // Change the current selected page number subscript style, index represents the index number from left to start, up to 10
        this.pageList[this.page.selected].active = false
        this.pageList[index].active = true
        this.page.selected = index
        console.log(searchData.page)
      })
      .catch(err => {
        console.log(err)
      })
    }
  }
}

Github source code

Posted by l_kris06 on Thu, 04 Jul 2019 12:02:47 -0700