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 cookie
Password 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 cookie
Is 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.expires
Properties. 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 body
And 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).extended
The: option is allowed in usequerystring
Choose between the library parsing urL-encoded data (when false) or using the QS library (when true).extended
The 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
- 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
- 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