The original

It is estimated that many people and xiaobian, at the beginning of the browsing feel good website collection to the browser favorites. Over time, there are more and more websites, and it is more and more troublesome to find the websites you need in your favorites. When the browser switched from Firefox to Chrome, my favorite sites couldn’t sync up. Browsers need to be logged in to synchronize the contents of favorites, which is painful… For a variety of reasons, it’s great to have such a collection, search, and categorization Navigation platform, so I created my own open source project, Navigation.

This article was first published on the public account “Front-end Keep”. Welcome to follow it.

Realize the function

Site CRUD

search

Login to log out

Website screenshot

Site navigation block waterfall stream

The site is nested with iframe and search modules

Site Submission page

The online Demo

preview

Simple implementation ideas

Local building

<! -- Download the project --> gitclonehttps://github.com/qiufeihong2018/navigation-server.git <! NPM install <! /creeper/index.js <! -- Start program --> NPM run devCopy the code

Then visit http://localhost:1600

The back-end

Navigation-server back-end repository

Express Based Framework

expressBuilding a Web Application

Characteristics of the

  • Powerful Routing
  • Focus on high performance
  • Super high test coverage
  • HTTP helper (redirection, caching, etc.)
  • View systems that support 14+ template engines
  • Content negotiation
  • The executable files of the application can be quickly generated

parsing

Starting the Express Service

const express = require('express');
const app = express();
const config = require('.. /config') ();// start server
  // Set http port
  app.set('port', config.expressHttpPort); 

  app.listen(config.expressHttpPort, () = > {
    // Enable port log printing
    log.info(`express running on ${config.expressHttpPort} port`);
  });
Copy the code

Dynamically configure ports in the config file

The main approach is to get rid of patterns

'use strict';

var config = {
  development: {
    // mongodb
    database: 'mongodb://localhost/map'.expressHttpPort: 1600.logFile: './log/express.log'
  },
  local: {
    // mongodb
    database: 'mongo: / / 127.0.0.1 / map'.expressHttpPort: 1600.logFile: './log/express.log'
  },
  production: {
    // mongodb
    database: 'mongo: / / 127.0.0.1 / map'.expressHttpPort: 1600.logFile: './log/express.log'}};module.exports = function(mode) {
  var env;
  if(! mode) { env = process.env.NODE_ENV ||'development';
  } else if (mode && (mode === 'development' || 'local' || 'production')) {
    env = mode;
  } else {
    throw new Error(`config can only be 'development' || 'local' || 'production', 
    but you give ${mode}`);
  }
  var returnVal = config[env];
  return returnVal;
};
Copy the code

express-sessionExpress simple session middleware

Characteristics of the

  • Resave: The session is forced to be saved back to the session store even if the session is never modified during the request. Depending on your store this may be necessary, but it can also create race conditions where the client lets two parallel requests to your server, and changing the session in one request may overwrite the other at the end of the request, even if it hasn’t changed. The default value is true.
  • SaveUninitialized: Forcibly saves the “uninitialized” session to the storage area. When a session is new but not modified, it is uninitialized. Choosing False is useful for implementing login sessions, reducing server storage usage, or complying with laws that require permission before setting cookies. Selecting False can also help resolve race conditions where a client can issue multiple parallel requests without a session. The default value is true, but it is not recommended to use the default value because it will change in the future.
  • secret: This is used for the sessionID cookiePassword for signing. This can be a single secret string or an array of multiple secrets. If a secret array is provided, only the first element is used for the sessionID cookieIs signed, and all elements are considered when validating the signature in the request.
  • cookie: Each session has a unique cookie object. This allows you to change the session cookie for each visitor.
    • maxAge: maxAge will return the remaining time in milliseconds, and editors can reassign a new value to adjust appropriately.expiresProperties. In this case, it expires in 1 day.

parsing

const session = require('express-session');


  // Session configuration
  const sess = {
    resave: true.saveUninitialized: true.secret: 'I am hungry'.cookie: {
      maxAge: 24 * 60 * 60 * 1000}}; app.use(session(sess));// Set session middleware

Copy the code

For more information about the configuration, see the express-Session translation

body-parserThe text parsing

Characteristics of the

  • Is a Node.js body parsing middleware.

parsing

The incoming request body is parsed using middleware prior to the handler, available under the req.body attribute.

Note that since the req.body shape is based on user-controlled input, all properties and values in this object are untrusted and should be validated before being trusted. For example, req.body.foo.tostring () can fail in a number of ways, such as the foo property may not exist or may not be a string, and toString may not be a function but a string or other user input.

  • urlenencoded: ([options]) returns middleware, which only parsesurlencoded bodyAnd only look at requests whose content-type headers match the type option. The parser accepts only utF-8 encoding of the text and supports automatic bloating of GZIP and DEFLate encoding. After the middleware (req.body), a new body object containing parsed data is populated on the request object. This object will contain key-value pairs where the values can be strings or arrays (when extended to false), or of any type (when extended to true).
    • extendedThe: option is allowed in usequerystringChoose between the library parsing urL-encoded data (when false) or using the QS library (when true).extendedThe syntax allows rich objects and arrays to be encoded in URL-encoding format, allowing for a JSON-like URL-encoding experience.
const bodyParser = require('body-parser'); ...// parse application/x-www-form-urlencoded
  app.use(bodyParser.urlencoded({
    extended: false
  }));

  // parse application/json
  app.use(bodyParser.json());


Copy the code

mongooseConnecting to a Database

Mongoose is a MongoDB object modeling tool designed to work in asynchronous environments.

Characteristics of the

  • Stack overflow
  • Bug reports
  • mongoose Slack Channel
  • Help BBS
  • Mongo support

parsing

Connect to the database and process connection success and failure information.

'use strict';

const mongoose = require('mongoose');
const config = require('.. /config') ();// [KOA warning DeprecationWarning: Mongoose: 'findOneAndUpdate()' and 'findOneAndDelete()' without the 'use... (https://www.jianshu.com/p/f3128e7ae3c5)
mongoose.set('useFindAndModify'.false);
let reconnectTimes = 0;// Mongodb reconnect times
let reconnectInterval = 0.1;// The interval seconecd time between two reconnection;
const maxReconnectInterval = 120;// The max interval time between two reconnection;

// Connect to mongodb
function connect() {
  const options = {
    socketTimeoutMS: 3000.keepAlive: true.reconnectTries: 4.useNewUrlParser: true
  };
  mongoose.connect(config.database, options);
}

// Mongoose error handler
mongoose.connection.on('error'.function(err) {
  log.error(err);
});

// Mongoose reconnect when closed
mongoose.connection.on('disconnected'.function() {
  reconnectTimes++;
  reconnectInterval = reconnectInterval * 2;
  if (reconnectInterval > maxReconnectInterval) reconnectInterval = maxReconnectInterval;
  setTimeout(() = > {
    connect();
  }, reconnectInterval * 1000);
});

mongoose.connection.on('connected'.function() {
  reconnectTimes = 0;
  reconnectInterval = 0.1;
});

exports.connect = connect;

Copy the code

Create the database collection AdminMap

'use strict';
const mongoose = require('mongoose');
const Schema = mongoose.Schema;

const AdminMap = new Schema({
  category: { type: String.required: true.trim: true },
  name: { type: String.required: true.trim: true },
  website: { type: String.required: true.trim: true },
  describe: { type: String.trim: true },
  logo: { type: String.trim: true },
  way: { type: String.trim: true}, {},timestamps: { createdAt: 'created_at'.updatedAt: 'updated_at'}});module.exports = mongoose.model('AdminMap', AdminMap);
Copy the code

eslintStandard code

Characteristics of the

  • ESLint is a tool for identifying and reporting patterns in ECMAScript/JavaScript code. In many ways, it is similar to JSLint and JSHint, with some exceptions:

  • ESLint uses Espree for JavaScript parsing.

  • ESLint uses an AST to evaluate patterns in code.

  • ESLint is fully pluggable, and each rule is a plug-in that can add more at run time.

For more information on configuration, see esLint configuration in Express.

Cheerio crawls data

cheerioThe crawler

Fast, flexible and streamlined core jQuery implementation designed for servers.

request

Simple HTTP request client

For details, see the previous article “Node to crawl the desktop wallpaper of an image site”.

Pm2 daemon project

"pm2": "pm2 start index.js --name='navigation'"
Copy the code

For detailed introduction, please see the previous article “PM2” of Xiaobian.

Mocha tests

mocha

Simple, flexible, and fun javascript testing framework for Node.js and browsers

mochawesome

Mochawesome is a custom reporter for mocha, the Javascript testing framework. It runs on Node.js and is used in conjunction with mochawesome-report-generator to generate standalone HTML/CSS reports to help visualize your test runs.

should

Node.js BDD style assertion

Is an expressive, readable, framework-independent assertion library. The main goal of this library is to express and help. It keeps your test code clean and your error messages useful

supertest

Use to test the Node.js HTTP server using the smooth API.

For more details, see our previous article “Express Project Integration Mocha Testing Framework”.

Passport User name and password authentication

The three are closely related. The first two can be managed by passport- Local – Mongoose, and the main analysis is placed in the dependency package of Passport – Local – Mongoose

passport

Characteristics of the

Passport is the Express-compatible authentication middleware for Node.js.

The sole purpose of the Passport is to validate the request, which is done through a set of extensible plug-ins called policies. Passport does not mount routes or assume any particular database schema, which maximizes flexibility and allows developers to make application-level decisions. Passport provides hooks to control whether authentication succeeds or fails.

  • Session: Passport maintains a persistent login session. For a persistent session to work, the authenticated user must be serialized into the session and deserialized when subsequent requests are made. Passport has no restrictions on how user records can be stored. Instead, you provide functions for Passport that implement the necessary serialization and deserialization logic. In a typical application, this is as simple as serializing the user ID and finding the user by ID when deserializing.

  • Initialize: To use Passport in Express – or connect-based applications, configure it with the required pasper.Initialize () middleware. If your application uses a persistent login session(recommended, but not required), you must also use the pasper.session () middleware.

passport-local

Characteristics of the

Passport policy for authentication with username and password.

This module allows you to authenticate with the username and password in your Node.js application. By inserting Passport, you can easily and unobtrussively integrate local authentication into any application or framework that supports Connect style middleware, including Express.

Before performing authentication, configure policies

passport.use(new LocalStrategy(
  function(username, password, done) {
    User.findOne({ username: username }, function (err, user) {
      if (err) { return done(err); }
      if(! user) {return done(null.false); }
      if(! user.verifyPassword(password)) {return done(null.false); }
      return done(null, user); }); }));Copy the code

passport-local-mongoose

Characteristics of the

Paspert-local-mongoose is a Mongoose plug-in that simplifies permissions to build usernames and passwords using Passport

parsing
  1. First you need to import the dependency packages into the schema.
const passportLocalMongoose = require('passport-local-mongoose');

const options = {
  interval: 200.maxInterval: 6 * 60 * 1000.maxAttempts: 6.limitAttempts: true
};
User.plugin(passportLocalMongoose, options);
Copy the code
  1. Configure Passport and Passport-Local

You can simplify the configuration of both

Passport -local- Mongoose can be configured by setting LocalStrategy, serializeUser, and deserializeUser

See Passport – Local – Mongoose for detailed parameter analysis.

  // requires the model with Passport-Local Mongoose plugged in
  var User = require('.. /collections/user');
  app.use(passport.initialize());
  app.use(passport.session());
  // use static authenticate method of model in LocalStrategy
  passport.use(new LocalStrategy(User.authenticate()));
  // use static serialize and deserialize of model for passport session support
  passport.serializeUser(User.serializeUser());
  passport.deserializeUser(User.deserializeUser());
Copy the code

Winston Logs

winstonlog

  • Winston is designed to be a simple and generic logging library that supports multiple transports. Transport is essentially a storage device for logs. Each Winston logger can have multiple transports configured at different levels. For example, you might want to store error logs in a persistent remote location, such as a database, but all logs are output to a console or local file.
  • Winston aims to separate parts of the logging process to make it more flexible and extensible. Care is taken to support flexibility in logging formats and levels, and to ensure that these apis are separate from the implementation of transferring logging records

For more information on the configuration, please see our previous translation of Winston

winston-daily-rotate-file

Winston transfers records to rotating files. Logs can be rotated by date, size, and old logs can be deleted by count or number of days that have passed.

For more information on the configuration, please refer to “Winston-daily-rotate-file”.

Encapsulate Winston logs. When in development mode, generated logs are stored in express.log at debug level. In production mode, timestamp logs are stored at the INFO log level. Files that can be saved for 7 days cannot exceed 20 MB. Other mode log levels are also info

'use strict';

/** * Logger is to custom winston to provide different log pattern in 'development', * 'production' and other mode. * 'development' will use Console and File output with 'debug' level * 'production' will use DailyRotateFile output with 'info' level, * and the maxFiles is 7d. * other mode will use File output with 'info' level. */
const {
  createLogger,
  format,
  transports
} = require('winston');
const {
  combine,
  timestamp,
  label,
  printf
} = format;

require('winston-daily-rotate-file');
const config = require('.. /config') ();const MODE = require('.. /constant/system').MODE;
let mode = process.env.NODE_ENV;
if(! mode) mode = MODE.DEVE;let logFile = config.logFile;

logFile = logFile.replace('.log'.' '); // remove '.log' from the logFile

const trans = [];
const ts = {
  console: new transports.Console({
    level: 'debug'
  }),
  file: new transports.File({
    filename: `${logFile}.log`.level: 'info'})};// daily rotate file transport config
const dailyRotateFileTrans = new (transports.DailyRotateFile)({
  filename: `${logFile}-%DATE%.log`.datePattern: 'YYYY-MM-DD-HH'.zippedArchive: true.maxSize: '20m'.maxFiles: '7d'
});
// Dynamically change the log level of the transfer
if (mode === MODE.DEVE) {
  trans.push(ts.console);
  ts.file.level = 'debug';
  trans.push(ts.file);
} else if (mode === MODE.PROD) {
  trans.push(dailyRotateFileTrans);
} else {
  trans.push(ts.file);
}
exports.createLogger = function(source) {
  const myFormat = combine(
    label({
      label: source
    }),
    timestamp({
      format: 'YYYY-MM-DD HH:mm:ss'
    }),
    printf(({ level, message, label, timestamp }) = > {
      return `${timestamp} [${label}] [${level.toUpperCase()}] :${message}`; }));return new (createLogger)({
    format: myFormat,
    transports: trans
  });
};

Copy the code

CRUD

Add, delete, change and review business logic is nothing to talk about, the code is in the warehouse

Just pay attention:

This is a get request for a page of a web site of a particular category. Limit is a request for a page of a web site of a particular category.

To get the total length, I looked here twice.

router.get('/'.function(req, res) {
  const arr = req._parsedOriginalUrl.query.split('&');
  const limit = arr[0].split('=') [1];
  const offset = arr[1].split('=') [1];
  const cate = arr[2].split('=') [1];
  let total = 0;
  SuperAdminMap.find({ category: cate }).then((data) = > {
    total = data.length;
    SuperAdminMap.find({ category: cate })
    .limit(Number(limit))
    .skip(Number(offset))
    .then((data) = > {
      log.info(`Get ${cate} data`);
      res.status(200).json({
        data,
        total
      });
    });
  });
});
Copy the code

Apidoc document artifact

Using apidoc is a must for ease of viewing the API

For more information on the configuration of apiDoc, please refer to apiDoc.

Here is a comment on the back-end GET request to find the superAdmin database


/ * * *@api {get} /superAdmin/ SuperAdmin getMap
 * @apiName SuperAdminGet
 * @apiGroup superAdminOperation
 *
 * @apiParam {String} limit  Number of pages per page.
 * @apiParam {String} offset  Number of skips.
 * @apiParam {String} category  New website's category.
 *
 *
 * @apiSuccessExample Success-Response: * HTTP/1.1 200 OK *{* "data": [*{* "_id": "5d5e4206443bdd63d0f82327", * "category": "recommendationFront-end", * "name": "test1", * "website": "test4", * "describe": "test", * "logo": "Test", "created_at" : "the 2019-08-22 T07: when. 924 z," * "updated_at" : "the 2019-08-22 T07: when. 924 z," * "__v" : 0 * }, * { * "_id": "5d5e4209443bdd63d0f82328", * "category": "recommendationFront-end", * "name": "test1", * "website": "test5", * "describe": "test", * "logo": "test", * "created_at": "The 2019-08-22 T07: for. 430 z," * "updated_at" : "the 2019-08-22 T07: for. 430 z", * "__v" : 0 *} *], * "total" : 655 * *}@apiError NOT_LOGIN The current User was not logon.
 *
 * @apiErrorExample Error-Response: * HTTP/1.1 401 Unauthorized * {* "err": "NOT_LOGIN", * "message": "User has not logon in!" *} * /
Copy the code

After the NPM run apidoc command is executed, the API document is generated

The front end

Navigation – Web front-end code repository

Vue – Admin-Template is a simple version of background management template based on Vue 2.0 background management platform popular with the public.

VuexStorage condition

Characteristics of the

Vuex is a state management mode developed specifically for vue.js applications. It uses centralized storage to manage the state of all components of an application and rules to ensure that the state changes in a predictable way.

parsing

Automatically import files from the Modules folder

Recommend Lao Yao’s regular expression manual “JavaScript Regular Expressions Mini Book (version 1.1).pdf”.

  • ^(off character) matches the beginning of a line in a multi-line match.

  • The $(dollar sign) matches the end of a line in a multi-line match.

  • ^, $., *, +,? , |, /, /, (,), [and], {and}, =,! , :, -,

When matching the above characters themselves, we can escape them all:

  • \w stands for [0-9A-zA-z_]. Represents digits, uppercase letters, and underscores.

How you remember it: W is short for Word, also known as word character.

  • + is equivalent to {1,}, indicating at least one occurrence.

How to remember: The plus sign means to add, you have to have one first, then you can consider adding.

  • require.context

Match all files based on the re (find files ending in JS in the Modules folder)

  • Replace A new string

// https://webpack.js.org/guides/dependency-management/#requirecontext
const modulesFiles = require.context('./modules'.true./\.js$/)

// you do not need `import app from './modules/app'`
// it will auto require all vuex module from modules file
const modules = modulesFiles.keys().reduce((modules, modulePath) = > {
  // set './app.js' => 'app'
  const moduleName = modulePath.replace(/^\.\/(.*)\.\w+$/.'$1')
  const value = modulesFiles(modulePath)
  modules[moduleName] = value.default
  return modules
}, {})

const store = new Vuex.Store({
  modules,
  getters
})
Copy the code

axiosData communication between front and back ends

Characteristics of the

Supports HTTP data communication. Axios is a Promise-based HTTP library that can be used in browsers and Node.js.

Especially recommended Axios, which put Axios in a lot of people’s eyes. Axios is essentially a wrapper around the native XHR, but it’s an implementation of Promise that meets the latest ES specification.

  • The client supports CSRF prevention. Each request carries a key obtained from the cookie. According to the browser’s same-origin policy, the fake website cannot obtain the key in the cookie.

  • After the login is complete, the user saves the user’s token using a cookie. Before the page is displayed, the user intercepts and reads the token. If the token exists, the user has logged in to the VUEX. Each time a request is sent, the token is carried. The backend determines whether the login or expiration is based on the token carried.

parsing

In its Request file that encapsulates the Axios object, the custom status code is removed from the Response response.

import axios from 'axios'
import {
  Message
} from 'element-ui'
// production
import store from '@/store'
import {
  getToken
} from '@/utils/auth'

// create an axios instance
const service = axios.create({
  baseURL: process.env.VUE_APP_BASE_API, // url = base url + request url
  // withCredentials: true, // send cookies when cross-domain requests
  timeout: 5000 // request timeout
})

// request interceptor
service.interceptors.request.use(
  config= > {
    if (process.env.NODE_ENV === 'production' && store.getters.token) {
      // do something before request is sent
      // let each request carry token
      // ['X-Token'] is a custom headers key
      // please modify it according to the actual situation
      config.headers['X-Token'] = getToken()
    }
    return config
  },
  error= > {
    // do something with request error
    console.log(error) // for debug
    return Promise.reject(error)
  }
)

// response interceptor
service.interceptors.response.use(
  /** * If you want to get http information such as headers or status * Please return response => response */

  /** * Determine the request status by custom code * Here is just an example * You can also judge the status by HTTP Status Code */
  response= > {
    const res = response.data
    return res
  },
  error= > {
    console.log('err' + error) // for debug
    Message({
      message: error.message,
      type: 'error'.duration: 5 * 1000
    })
    return Promise.reject(error)
  }
)

export default service


Copy the code

element-uiQuickly set up the background

Characteristics of the

Hungry me web platform UI library

Element, a Vue 2.0-based desktop component library for developers, designers, and product managers

parsing

Import element-ui globally in main.js

import Vue from 'vue'
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
// import enLocale from 'element-ui/lib/locale/lang/en'
import zhLocale from 'element-ui/lib/locale/lang/zh-CN'
// set ElementUI lang to EN
Vue.use(ElementUI, {
  zhLocale
})
Copy the code

El – breadcrumb bread crumbs

Characteristics of the

Displays the path of the current page and quickly returns to any previous page.

parsing

  <el-breadcrumb class="app-breadcrumb" separator=">">
    <transition-group name="breadcrumb">
      <el-breadcrumb-item v-for="(item,index) in levelList" :key="item.path">
        <span v-if="item.redirect==='noRedirect'||index==levelList.length-1" class="no-redirect">{{ item.meta.title }}</span>
        <a v-else @click.prevent="handleLink(item)">{{ item.meta.title }}</a>
      </el-breadcrumb-item>
    </transition-group>
  </el-breadcrumb>
Copy the code

El-drawer component pops up search information

parsing

The search bar controls the bottom drawer components by changing the openDrawer state in VUex. In the pop-up drawer, you can search the title and description of the navigation website in the Mongo database by keywords, and click on iframe and the outer chain to view the collected website.

   <el-drawer title="Search sites" :visible.sync="openDrawer" :before-close="closeDrawer" direction="btt" size="50%">
      <div class="search-container">
        <el-input slot="prepend" v-model="queryData.query" placeholder="Please enter, e.g. PPT" @keyup.enter.native="getSuperSearch">
          <el-button slot="append" icon="el-icon-search" @click.stop="getSuperSearch" />
        </el-input>
      </div>
      <el-table :data="tableData" stripe style="width: 100%" highlight-current-row>
        <el-table-column type="index" />
        <el-table-column prop="name" label="Name" width="200" show-overflow-tooltip />
        <el-table-column prop="website" label="Website link" width="200" show-overflow-tooltip>
          <template slot-scope="slot">
            <router-link class="font-website" :to="{ path: 'iframeNav', query: { website: slot.row.website }}">
              {{ slot.row.website }}
            </router-link>
          </template>
        </el-table-column>
        <el-table-column prop="describe" label="Description" show-overflow-tooltip />
        <el-table-column prop="created_at" label="Creation time" width="200" show-overflow-tooltip />
        <el-table-column prop="category" label="Classification" width="200" show-overflow-tooltip />
        <el-table-column fixed="right" label="Operation" width="100">
          <template slot-scope="scope">
            <router-link class="font-website" :to="{ path: 'iframeNav', query: { website: scope.row.website }}">The iframe link</router-link>
            <a class="font-website" :href="scope.row.website" target="_blank">New window link</a>
          </template>
        </el-table-column>
      </el-table>
      <div class="pagination-container">
        <el-pagination small background layout="prev, pager, next" :total="total" :page-size="2" @current-change="handleCurrentChange" />
      </div>
    </el-drawer>

Copy the code

js-cookieHandling browser cookies

Characteristics of the

A simple, lightweight JavaScript API for handling browser cookies

  • Works with all browsers
  • Accept any role
  • Rigorously tested
  • There is no dependence
  • Unobtrusive JSON support
  • AMD/CommonJS support
  • In line with the RFC 6265
  • Enable custom encoding/decoding

CRUD cookies

import Cookies from 'js-cookie'

const TokenKey = 'navigation_token'

export function getToken() {
  return Cookies.get(TokenKey)
}

export function setToken(token) {
  return Cookies.set(TokenKey, token)
}

export function removeToken() {
  return Cookies.remove(TokenKey)
}
Copy the code

normalize.css

Characteristics of the

Provides a high degree of consistency across browsers in the default HTML element styles. Normalize.css is a modern, premium HTML5 alternative to traditional CSS reset.

  • Unlike many CSS resets, keep useful defaults, not remove them.
  • Normalize the style of various elements.
  • Corrected errors and common browser inconsistencies.
  • Improve usability with subtle changes.
  • Use detailed comments to explain what the code does.

What are the differences between normalize. CSS and traditional CSS Reset?

nprogressThe progress bar

Characteristics of the

Slim progress bar

parsing

Control the progress bar by calling start() and done().

Used when permission page jumps

import NProgress from 'nprogress' // progress bar
import 'nprogress/nprogress.css' // progress bar style
NProgress.configure({
  showSpinner: false
}) // NProgress Configuration

NProgress.start()

NProgress.done()
Copy the code

You can also adjust the speed

NProgress.configure({ easing: 'ease'.speed: 500 });
Copy the code

Close the load spinner. (Default: true)

NProgress.configure({
  showSpinner: false
}) // NProgress Configuration
Copy the code

Change the parent container

NProgress.configure({ parent: '#container' });
Copy the code

path-to-regexpHandles addresses and parameters in urls

Characteristics of the

This tool library is used to process url addresses and parameters, can be very convenient to get small editor want data.

Js has the RegExp method for regular expression verification, and path-to-regexp can be regarded as a url string regular expression.

parsing

Components used to bread crumbs components/Breadcrumb/index. The vue,

Analyze how this component works:

  • Get and filter the matched attribute in the current route, find the meta attribute to display
  • When the click is triggered, get the current route and determine the redirect attribute. If the redirect attribute exists, insert the route. Otherwise, if params are carried, the route will be complete.
import pathToRegexp from 'path-to-regexp'


   pathCompile(path) {
      // To solve this problem https://github.com/PanJiaChen/vue-element-admin/issues/561
      const { params } = this.$route
      var toPath = pathToRegexp.compile(path)
      return toPath(params)
    },
Copy the code

vue-routerManagement of routing,

Vue Router is the official route manager of vue.js. Its deep integration with vue.js core makes building single-page applications a breeze.

Characteristics of the

  • Nested routing/view chart
  • Modular, component-based routing configuration
  • Route parameters, queries, and wildcards
  • View transition effect based on vue. js transition system
  • Fine-grained navigation control
  • Links with automatically activated CSS classes
  • HTML5 historical mode or Hash mode is automatically degraded in IE9
  • Custom scroll bar behavior

parsing

Integrated vue – the router


import Vue from 'vue'
import Router from 'vue-router'

Vue.use(Router)

/* Layout */
import Layout from '@/layout'

Copy the code

Import the page to router/index.js in the routing center. The following is part of the intercepting route-page map.

Where getNav is the method to get the template /page/NavPage/index path.

function getNav() {
  return () = > import('@/page/NavPage/index')}Copy the code
... {path: '/jobs'.component: Layout,
  redirect: '/jobs/recruitmentPlatform'.name: 'Jobs'.meta: {
    title: 'work'.icon: 'jobs'
  },
  children: [{
    path: 'recruitmentPlatform'.name: 'RecruitmentPlatform'.component: getNav(),
    meta: {
      title: 'Jobs - Recruitment Platform'.icon: 'recruitmentPlatform'}}, {path: 'partTimeProgram'.name: 'PartTimeProgram'.component: getNav(),
    meta: {
      title: 'Work - Program part-time'.icon: 'partTimeProgram'}}, {path: 'partTimeDesign'.name: 'PartTimeDesign'.component: getNav(),
    meta: {
      title: 'Job - Design Part-time'.icon: 'partTimeDesign'}}, {path: '/jobs/iframeNav'.name: 'jobsIframeNav'.hidden: true.component: () = > import('@/page/iframeNav/index'),
    meta: {
      title: 'the website'.icon: 'iframeNav'}}]},......Copy the code

Generate the page using the Router

const createRouter = () = > new Router({
  // mode: 'history', // require service support
  scrollBehavior: () = > ({
    y: 0
  }),
  routes: constantRoutes
})

const router = createRouter()

// Detail see: https://github.com/vuejs/vue-router/issues/1234#issuecomment-357941465
export function resetRouter() {
  const newRouter = createRouter()
  router.matcher = newRouter.matcher // reset router
}

export default router

Copy the code

See the github repository for the NavPage/index.vue template code

screenfull

A simple wrapper for cross-browser use of the JavaScript Fullscreen API to make a page or any element display in Fullscreen.

vue-waterfall2Build the waterfall flow layout

Waterfall adaptive plugin for VUE and lazy loading, very simple!

import waterfall from 'vue-waterfall2'
Vue.use(waterfall)
Copy the code
 <waterfall
        :col="col"
        :width="itemWidth"
        :gutter-width="gutterWidth"
        :data="navArr"
        @loadmore="loadmore"
        @scroll="scroll"
      >
        <template>
          <div v-for="(nav,key) in navArr" :key="key" style="margin-top: 10px;">
            <el-card :body-style="{ padding: '10px' }" shadow="hover">
              <img :src="nav.logo" class="image" alt="Loading error">
              <el-form label-width="100px" label-position="left">
                <el-form-item label="Site Name">
                  {{ nav.name }}
                </el-form-item>
                <el-form-item label="The iframe link">
                  <router-link class="font-website" :to="{ path: 'iframeNav', query: { website: nav.website }}">
                    {{ nav.website }}
                  </router-link>
                </el-form-item>
                <el-form-item label="New window link">
                  <a class="font-website" :href="nav.website" target="_blank">{{ nav.website }}</a>
                </el-form-item>
                <el-form-item label="Site Description">
                  <div>{{nav. Describe | | 'need you to add website describes'}}</div>
                </el-form-item>
              </el-form>
              <div class="bottom clearfix">
                <time class="time">Create time: {{nav. Created_at | timeTrans}}</time>
                <el-button type="text" class="button" @click="openDialog(nav)">The editor</el-button>
                <el-button type="text" class="button" @click="deleteMap(nav)">delete</el-button>
              </div>
            </el-card>
          </div>
        </template>
      </waterfall>
Copy the code

commitizenCommit Git normalization

Commitizen command line utility.

When working in the Commitizen friendly repository, the required fields will be prompted and the submission message will be formatted according to criteria defined by the project maintainer.


Install the Commitizen node module globally
npm install commitizen -g 
Initialize your project to use the CZ-convention-change-elog adapter by typing the following command
commitizen init cz-conventional-changelog --save-dev --save-exact
Copy the code
Type indicates the type of commit. Only the following seven identifiers are allowed
  • Feat: New Feature
  • Fix: Fixes bugs
  • -Jenny: There are some docs.
  • Style: format (changes that do not affect code execution)
  • Refactor: refactoring (i.e. code changes that are not new features or bug fixes)
  • Test: Adds a test
  • Chore: Changes to the build process or helper

Scope describes the scope of the Commit. That is, it briefly describes the parts that will be affected by the change, such as the data layer, control layer, and view layer

The location of subject comment, a short description of this submission

Iframe nested web sites

                <el-form-item label="The iframe link">
                  <router-link class="font-website" :to="{ path: 'iframeNav', query: { website: nav.website }}">
                    {{ nav.website }}
                  </router-link>
                </el-form-item>
                <el-form-item label="New window link">
                  <a class="font-website" :href="nav.website" target="_blank">{{ nav.website }}</a>
                </el-form-item>
Copy the code

Add a router-link to the iframe page. However, the router links that jump to the page have routes of each category added. Therefore, add an IFrame route to each category in the routing file.


  {
    path: '/iframeNav'.name: 'frontIframeNav'.hidden: true.component: () = > import('@/views/iframeNav/index'),
    meta: {
      title: 'the website'.icon: 'iframeNav'}} {path: '/back-end/iframeNav'.name: 'backIframeNav'.hidden: true.component: () = > import('@/views/iframeNav/index'),
    meta: {
      title: 'the website'.icon: 'iframeNav'}}...Copy the code

All clicks from the iframe link jump to this page

<template>
  <iframe ref="inlineFrameExample" title="Inline Frame Example" width="100%" height="898px" :src="iframeSrc" />
</template>
<script>
export default {
  data() {
    return {
      iframeSrc: ' '}},created() {
    this.iframeSrc = this.$route.query.website
  }
}
</script>
Copy the code

adapter

Due to the unresponsive nature of VUe-waterfall2, adaptation can only be solved by the editor himself.

On mobile, set it to 1 column, open the sidebar and set it to 3 columns, and set the rest to 4 columns.

The width of each card is calculated based on the width of the screen and the number of columns

The distance between the cards, give it a fixed value. Note that it must be set to 0 when on the mobile side, otherwise the following columns will be offset to the right.

computed: {
    col() {
      if (this.device === 'mobile') {
        return 1
      }
      if (this.sidebar.opened === true) {
        return 3
      }
      return 4
    },
    itemWidth() {
      if (this.device === 'mobile') {
        return (0.885 * (document.documentElement.clientWidth / 1))}if (this.sidebar.opened === true) {
        return (0.8 * (document.documentElement.clientWidth / 3))}return (0.9 * (document.documentElement.clientWidth / 4))},gutterWidth() {
      if (this.device === 'mobile') {
        return 0
      }
      return (9 * 0.5 * (document.documentElement.clientWidth / 375))},... mapGetters(['sidebar'.'device'])},Copy the code

Site classification

The site classification data is from the Router, but the router data must be filtered to get the classification results.

The last three items in the categoryOptions array are not categorized, so they are removed.

/**
 * get categoryOptions from routes
 * @param {HTMLElement} routes
 * @param {HTMLElement} tag: text/label
 */
export function getOption(tag, routes) {
  let categoryOptions = []
  for (let i = 0; i < routes.length; i++) {
    if(routes[i].path ! = ='/redirect') {
      const children = routes[i].children
      for (const j in children) {
        const obj = {
          value: ' '
        }
        obj.value = children[j].path
        obj[tag] = children[j].meta.title
        categoryOptions.push(obj)
      }
    }
  }
  categoryOptions = categoryOptions.filter(item= > {
    returnitem.label ! = ='the website'
  })
  // Delete the last three elements
  return categoryOptions.slice(0, -3)}Copy the code

The template page then invokes the method

import {
  getOption
} from '@/utils/index'

this.categoryOptions = getOption('label', routes)
Copy the code

Looking forward to

Next post: Navigation Submission Tools for Chrome Developers

At present, the project is basically completed, but there is still a lot of room for expansion. If submitting a website is a bit of a hassle, there’s a Chrome submission tool that solves all the problems.

In addition, this project will be maintained for a long time. I hope everyone can actively mention PR and issue, so as to make this project more perfect, so as to help more people learn the practical application of VUE besides official demo and avoid more pits.

Finally, don’t forget to give this project a star, thank you for your support.

Navigation – Web front-end code repository

Navigation-server back-end repository

This article was first published on the public account “Front-end Keep”. Welcome to follow it.

Finally, I hope you must point to like three times.

Follow my blog: blog address