Navigation網站收藏和導航平臺

初衷

估計不少人和小編同樣,一開始將瀏覽過的以爲不錯的網站收藏到瀏覽器的收藏夾中。日積月累,網站愈來愈多,在收藏夾裏找所需網站愈來愈麻煩。後來瀏覽器從火狐轉到Chrome,收藏的網站卻不能同步過來。瀏覽器須要登陸才能同步收藏夾的內容,這很痛苦……鑑於種種緣由,有這樣一個收藏、搜索和分類導航平臺真的是棒極了,小編就本身作了一個開源項目——Navigation網站收藏和導航平臺。javascript

實現功能

網站CRUD

搜索

登陸登出

網站截圖

網站導航塊瀑布流

網站導航塊瀑布流

網站嵌套iframe和搜索模塊

網站嵌套iframe

網站提交頁

網站提交

在線Demo

預覽css

簡單的實現思路

本地構建

<!-- 下載項目 -->
git clone https://github.com/qiufeihong2018/navigation-server.git

<!-- 安裝依賴 -->
npm install

<!-- 爬取數據 -->
node ./creeper/index.js

<!-- 啓動程序 -->
npm run dev

以後訪問http://localhost:1600便可html

後端

navigation-server後端代碼倉庫前端

基於express框架vue

express搭建web應用

特徵

  • 強大的路由
  • 專一於高性能
  • 超高的測試覆蓋率
  • HTTP助手(重定向,緩存等)
  • 查看支持14+模板引擎的系統
  • 內容協商
  • 可快速生成應用程序的可執行文件

解析

啓動express服務java

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, () => {
    // 開啓端口打印日誌
    log.info(`express running on ${config.expressHttpPort} port`);
  });

在config文件中動態配置端口node

裏面的方法主要是去掉各類模式webpack

'use strict';

var config = {
  development: {
    // mongodb
    database: 'mongodb://localhost/map',
    expressHttpPort: 1600,
    logFile: './log/express.log'
  },
  local: {
    // mongodb
    database: 'mongodb://127.0.0.1/map',
    expressHttpPort: 1600,
    logFile: './log/express.log'
  },
  production: {
    // mongodb
    database: 'mongodb://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;
};

express-session之express簡單的session中間件

特徵

  • resave:即便在請求期間會話從未被修改,也會強制將會話保存回會話存儲區。取決於你的store這多是必要的,但它也能夠建立競態條件,客戶讓兩個並行請求您的服務器,在一個請求中更改會話可能會覆蓋另外一個請求結束時,即便它沒有改變。默認值爲true。
  • saveUninitialized:強制將「未初始化」的會話保存到存儲區。當會話是新的但沒有修改時,它是未初始化的。選擇false對於實現登陸會話、減小服務器存儲使用或遵照在設置cookie以前須要得到許可的法律很是有用。選擇false還能夠幫助解決客戶端在沒有會話的狀況下發出多個並行請求的競態條件。默認值爲true,可是不建議使用默認值,由於默認值將在未來更改。
  • secret:這是用於對會話ID cookie簽名的密碼。這能夠是單個祕密的字符串,也能夠是多個祕密的數組。若是提供了一個祕密數組,則只使用第一個元素對會話ID cookie進行簽名,而在驗證請求中的簽名時將考慮全部元素。
  • cookie:每一個會話都有一個唯一的cookie對象。這容許您更改每一個訪問者的會話cookie。ios

    • maxAge:maxAge將返回剩餘的時間(以毫秒爲單位),小編們還能夠從新分配一個新值來適當調整.expires屬性。此時表示1天后過時。

解析

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

想知道更多的配置,請見小編以前翻譯的express-sessiongit

body-parser正文解析

特徵

  • 是一個Node.js正文解析中間件。

解析

在處理程序以前,利用中間件解析傳入的請求主體,在req.body屬性下可用。

注意因爲req.body形狀基於用戶控制的輸入,所以該對象中的全部屬性和值都是不可信的,應在信任以前進行驗證。例如,req.body.foo.toString()可能以多種方式失敗,例如foo屬性可能不存在或者可能不是字符串,而且toString可能不是函數,而是字符串或其餘用戶輸入。

  • urlenencoded: ([options])返回中間件,該中間件只解析urlencoded body,而且只查看內容類型頭部與類型選項匹配的請求。該解析器只接受正文的UTF-8編碼,並支持gzip和deflate編碼的自動膨脹。在中間件(即req.body)以後,在請求對象上填充一個包含已解析數據的新body對象。這個對象將包含鍵值對,其中的值能夠是字符串或數組(當擴展爲false時),也能夠是任何類型(當擴展爲true時)。

    • extended: 選項容許在使用querystring庫解析url編碼的數據(當爲false時)和使用qs庫(當爲true時)之間進行選擇。extended語法容許將豐富的對象和數組編碼爲url編碼格式,容許使用相似json的url編碼體驗。
const bodyParser = require('body-parser');

……

  // parse application/x-www-form-urlencoded
  app.use(bodyParser.urlencoded({
    extended: false
  }));

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

mongoose鏈接數據庫

Mongoose是一個MongoDB對象建模工具,旨在在異步環境中工做。

特徵

  • 堆棧溢出
  • bug報告
  • mongoose Slack Channel
  • 幫助論壇
  • MongoDB支持

解析

鏈接數據庫,處理鏈接的成功和失敗的信息。

'use strict';

const mongoose = require('mongoose');
const config = require('../config')();
// [koa警告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;

建立數據庫集合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);

eslint規範代碼

特徵

  • ESLint是一種用於識別和報告ECMAScript / JavaScript代碼中的模式的工具。在許多方面,它相似於JSLint和JSHint,但有一些例外:
  • ESLint使用Espree進行JavaScript解析。
  • ESLint使用AST來評估代碼中的模式。
  • ESLint是徹底可插拔的,每一個規則都是一個插件,能夠在運行時添加更多。

想知道更多的配置,請見小編以前翻譯的《eslint在express中的配置》

cheerio爬取數據

cheerio爬蟲

快速,靈活和精簡的核心jQuery實現,專爲服務器而設計。

request

簡單的http請求客戶端

詳細用法請見小編以前的文章《node爬取某圖片網站的桌面壁紙》

pm2守護項目

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

詳細介紹請見小編以前的文章《pm2》

mocha測試

mocha

node.js和瀏覽器的簡單,靈活,有趣的javascript測試框架

mochawesome

Mochawesome是一個用於Javascript測試框架mocha的自定義報告器。它在Node.js上運行,並與mochawesome-report-generator結合使用,生成獨立的HTML / CSS報告,以幫助可視化您的測試運行。

should

node.js的BDD樣式斷言

是一個富有表現力,可讀,與框架無關的斷言庫。這個圖書館的主要目標是表達和幫助。它可使您的測試代碼保持乾淨,而且您的錯誤消息頗有用

supertest

用於使用流暢的API測試node.js HTTP服務器。

詳細介紹請見小編以前的文章《express項目集成mocha測試框架》

passport用戶名和密碼驗證

這三者有這密切的聯繫,前二者均可以歸passport-local-mongoose管理,主要解析就放在passport-local-mongoose這個依賴包中

passport

特徵

Passport是Node.js的Express兼容認證中間件。

Passport的惟一目的是驗證請求,它經過一組稱爲策略的可擴展插件來完成。Passport不會掛載路由或假設任何特定的數據庫架構,這能夠最大限度地提升靈活性,並容許開發人員作出應用程序級別的決策。Passport提供了用於控制身份驗證成功或失敗時的鉤子。

  • session:Passport將維護持久的登陸會話。爲了使持久會話工做,必須將通過身份驗證的用戶序列化到會話,並在發出後續請求時反序列化。Passport對用戶記錄的存儲方式沒有任何限制。相反,您爲Passport提供了一些函數,這些函數實現了必要的序列化和反序列化邏輯。在典型的應用程序中,這與序列化用戶ID以及反序列化時按ID查找用戶同樣簡單。
  • initialize:要在基於Express或鏈接的應用程序中使用Passport,請使用所需的passport.initialize()中間件對其進行配置。若是您的應用程序使用持久性登陸會話(推薦使用,但不是必需的),還必須使用passport.session()中間件。

passport-local

特徵

用於使用用戶名和密碼進行身份驗證的Passport策略。

此模塊容許您使用Node.js應用程序中的用戶名和密碼進行身份驗證。經過插入Passport,能夠輕鬆且不顯眼地將本地身份驗證集成到支持Connect風格中間件(包括 Express)的任何應用程序或框架中 。

作驗證以前,首先須要對策略進行配置

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);
    });
  }
));

passport-local-mongoose

特徵

passport-local-mongoose是一個Mongoose插件,它簡化了使用Passport構建用戶名和密碼的權限

解析
  1. 首先須要將依賴包導入schema中。
const passportLocalMongoose = require('passport-local-mongoose');

const options = {
  interval: 200,
  maxInterval: 6 * 60 * 1000,
  maxAttempts: 6,
  limitAttempts: true
};
User.plugin(passportLocalMongoose, options);
  1. 配置Passport和Passport-Local

能夠簡化二者的配置

passport-local-mongoose能夠經過設置LocalStrategyserializeUserdeserializeUser來配置來這二者

具體參數解析見《mongoose之passport-local-mongoose》

// 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());

winston記錄日誌

winston記錄日誌

  • winston被設計爲一個簡單和通用的日誌記錄庫,支持多個傳輸。傳輸本質上是日誌的存儲設備。每一個winston記錄器能夠具備在不一樣級別配置的多個傳輸。例如,可能但願將錯誤日誌存儲在持久遠程位置(如數據庫)中,但全部日誌都輸出到控制檯或本地文件。
  • winston旨在將部分日誌記錄過程分離,使其更加靈活和可擴展。注意支持日誌格式和級別的靈活性,並確保這些API與傳輸日誌記錄的實現分離

想知道更多的配置,請見小編以前翻譯的《winston》

winston-daily-rotate-file

winston的傳輸,記錄到旋轉文件。能夠根據日期,大小限制輪換日誌,而且能夠根據計數或通過的天數刪除舊日誌。

想知道更多的配置,請見小編以前翻譯的《winston-daily-rotate-file》

封裝winston日誌,當在開發模式時,產生的日誌存在express.log中,而且日誌級別爲debug;當在生產模式時,存在時間戳日誌中,日誌級別是info,能夠存7天的文件,最大文件不得超過20兆;其餘模式日誌級別也是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
  });
};

CRUD

增刪改查的業務邏輯沒什麼好講的,代碼在倉庫裏

就是注意一點:

小編這裏是get請求要作的是去想數據庫請求某個類別的網站的某頁的數據,limit等關鍵詞小編是從req._parsedOriginalUrl.query中分割的。

要獲取總長度,因此此處查找了兩次。

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
      });
    });
  });
});

apidoc文檔神器

爲了方便查看api,因此用上apidoc是絕對要的

想知道更多的配置,請見小編以前翻譯的《apiDoc生成接口文檔,不費吹灰之力》

此處是後端查找superAdmin數據庫的get請求的註釋

/**
 * @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": "2019-08-22T07:19:34.924Z",
 *            "updated_at": "2019-08-22T07:19:34.924Z",
 *            "__v": 0
 *        },
 *        {
 *            "_id": "5d5e4209443bdd63d0f82328",
 *            "category": "recommendationFront-end",
 *            "name": "test1",
 *            "website": "test5",
 *            "describe": "test",
 *            "logo": "test",
 *            "created_at": "2019-08-22T07:19:37.430Z",
 *            "updated_at": "2019-08-22T07:19:37.430Z",
 *            "__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!"
 *     }
 */

執行npm run apidoc命令後生成api文檔

前端

navigation-web前端代碼倉庫

是基於花褲衩的vue-admin-template的簡單版的後臺管理模板,這一款基於vue2.0的後臺管理平臺深受大衆喜好。

Vuex存儲狀態

特徵

Vuex是一個專爲Vue.js應用程序開發的狀態管理模式。它採用集中式存儲管理應用的全部組件的狀態,並以相應的規則保證狀態以一種可預測的方式發生變化.

解析

自動從modules文件夾中導入文件

推薦一本老姚的正則手冊《JavaScript正則表達式迷你書(1.1版).pdf》

  • ^(脫字符)匹配開頭,在多行匹配中匹配行開頭。
  • $(美圓符號)匹配結尾,在多行匹配中匹配行結尾。
  • ^、$、.、*、+、?、|、、/、(、)、[、]、{、}、=、!、:、- ,

當匹配上面的字符自己時,能夠一概轉義:

  • w 表示 [0-9a-zA-Z_]。表示數字、大小寫字母和下劃線。

記憶方式:w 是 word 的簡寫,也稱單詞字符。

  • +等價於 {1,},表示出現至少一次。

記憶方式:加號是追加的意思,得先有一個,而後才考慮追加。

根據正則(在modules文件夾中找到結尾是js的文件)匹配全部的文件

  • replace一個新的字符串
// 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
})

axios進行先後端數據通訊

特徵

支持http數據通訊。Axios 是一個基於 promise 的 HTTP 庫,能夠用在瀏覽器和 node.js 中。

尤大推薦用Axios,讓Axios進入了不少人的目光中。Axios本質上也是對原生XHR的封裝,只不過它是Promise的實現版本,符合最新的ES規範。

  • 客戶端支持防止CSRF,每一個請求都帶一個從cookie中拿到的key, 根據瀏覽器同源策略,假冒的網站是拿不到cookie中的key,這樣,後臺就能夠輕鬆辨別出這個請求是不是用戶在假冒網站上的誤導輸入,從而採起正確的策略。
  • 登陸完成後,將用戶的token經過cookie存在本地,而後在頁面跳轉前攔截讀取token,若是token存在則說明已經登陸過,刷新vuex中的token狀態。每次發送請求時都會攜帶token。後端會經過攜帶的token判斷是否登陸或過時。

解析

在其封裝Axios對象的request文件中,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

element-ui快速搭建後臺

特徵

餓了嗎的web平臺UI庫

Element,一套爲開發者、設計師和產品經理準備的基於 Vue 2.0 的桌面端組件庫

解析

在main.js中全局導入element-ui

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
})

el-breadcrumb 麪包屑

特徵

顯示當前頁面的路徑,快速返回以前的任意頁面。

解析

<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>

el-drawer抽屜組件彈出搜索信息

解析

搜索欄經過改變vuex中的openDrawer狀態來控制底層抽屜組件。在彈出的抽屜中能夠經過關鍵詞搜索mongo數據庫中的導航網站的title和描述,點擊iframe和外鏈查看收藏的網站。

<el-drawer title="搜索網站" :visible.sync="openDrawer" :before-close="closeDrawer" direction="btt" size="50%">
      <div class="search-container">
        <el-input slot="prepend" v-model="queryData.query" placeholder="請輸入,例如: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="名字" width="200" show-overflow-tooltip />
        <el-table-column prop="website" label="網站連接" 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="描述" show-overflow-tooltip />
        <el-table-column prop="created_at" label="建立時間" width="200" show-overflow-tooltip />
        <el-table-column prop="category" label="分類" width="200" show-overflow-tooltip />
        <el-table-column fixed="right" label="操做" width="100">
          <template slot-scope="scope">
            <router-link class="font-website" :to="{ path: 'iframeNav', query: { website: scope.row.website }}">
              iframe連接
            </router-link>
            <a class="font-website" :href="scope.row.website" target="_blank">新窗口連接</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>

js-cookie處理瀏覽器cookie

特徵

一個簡單,輕量級的JavaScript API,用於處理瀏覽器cookie

  • 適用於全部瀏覽器
  • 接受任何角色
  • 通過嚴格測試
  • 沒有依賴
  • 不顯眼的 JSON支持
  • 支持AMD / CommonJS
  • 符合RFC 6265
  • 啓用自定義編碼/解碼

對cookie進行CRUD

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)
}

normalize.css

特徵

在默認的HTML元素樣式上提供了跨瀏覽器的高度一致性。相比於傳統的css reset,Normalize.css是一種現代的,爲HTML5準備的優質替代方案。

  • 與許多CSS重置不一樣,保留有用的默認值,而不是刪除他們。
  • 規範化各類元素的樣式。
  • 更正了錯誤和常見的瀏覽器不一致性。
  • 經過微妙的修改提升可用性。
  • 使用詳細註釋說明代碼的做用。

推薦閱讀Normalize.css 與傳統的 CSS Reset 有哪些區別?

nprogress進度條

特徵

超薄進度條

解析

經過調用start()和done()來控制進度條。

用在permission頁面跳轉時候

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

NProgress.start()

NProgress.done()

還能夠調整速度

NProgress.configure({ easing: 'ease', speed: 500 });

關閉加載微調器。(默認值:true)

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

更改其父容器

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

path-to-regexp處理 url 中地址與參數

特徵

該工具庫用來處理 url 中地址與參數,可以很方便獲得小編們想要的數據。

js 中有 RegExp 方法作正則表達式校驗,而 path-to-regexp 能夠當作是 url 字符串的正則表達式。

解析

應用於麪包屑組件components/Breadcrumb/index.vue中,

分析下這個組件的原理:

  • 拿到而且過濾當前路由中的matched屬性,找到須要展現的meta屬性
  • 觸發點擊時,得到當前路由,判斷redirect屬性,若是值存在,塞進路由;不然有攜帶params的話,將路由補充完整。
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)
    },

vue-router管理路由

Vue RouterVue.js 官方的路由管理器。它和 Vue.js 的核心深度集成,讓構建單頁面應用變得易如反掌。

特徵

  • 嵌套的路由/視圖表
  • 模塊化的、基於組件的路由配置
  • 路由參數、查詢、通配符
  • 基於 Vue.js 過渡系統的視圖過渡效果
  • 細粒度的導航控制
  • 帶有自動激活的 CSS class 的連接
  • HTML5 歷史模式或 hash 模式,在 IE9 中自動降級
  • 自定義的滾動條行爲

解析

集成vue-router

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

Vue.use(Router)

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

往路由中心router/index.js導入頁面,下面是截取路由-頁面映射的一部分。

其中getNav是獲取模板/page/NavPage/index路徑的方法。

function getNav() {
  return () => import('@/page/NavPage/index')
}
……

{
  path: '/jobs',
  component: Layout,
  redirect: '/jobs/recruitmentPlatform',
  name: 'Jobs',
  meta: {
    title: '工做',
    icon: 'jobs'
  },
  children: [{
    path: 'recruitmentPlatform',
    name: 'RecruitmentPlatform',
    component: getNav(),
    meta: {
      title: '工做-招聘平臺',
      icon: 'recruitmentPlatform'
    }
  },
  {
    path: 'partTimeProgram',
    name: 'PartTimeProgram',
    component: getNav(),
    meta: {
      title: '工做-程序兼職',
      icon: 'partTimeProgram'
    }
  },
  {
    path: 'partTimeDesign',
    name: 'PartTimeDesign',
    component: getNav(),
    meta: {
      title: '工做-設計兼職',
      icon: 'partTimeDesign'
    }
  },
  {
    path: '/jobs/iframeNav',
    name: 'jobsIframeNav',
    hidden: true,
    component: () => import('@/page/iframeNav/index'),
    meta: {
      title: '網站',
      icon: 'iframeNav'
    }
  }
  ]
},


……

使用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

模板NavPage/index.vue代碼見github倉庫

screenfull

用於跨瀏覽器使用JavaScript Fullscreen API的簡單包裝器,可以讓頁面或任何元素全屏顯示。

vue-waterfall2構建瀑布流佈局

適用於vue和支持延遲加載的瀑布自適應插件,很是簡單!

import waterfall from 'vue-waterfall2'
Vue.use(waterfall)
<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="加載錯誤">
              <el-form label-width="100px" label-position="left">
                <el-form-item label="網站名稱">
                  {{ nav.name }}
                </el-form-item>
                <el-form-item label="iframe連接">
                  <router-link class="font-website" :to="{ path: 'iframeNav', query: { website: nav.website }}">
                    {{ nav.website }}
                  </router-link>
                </el-form-item>
                <el-form-item label="新窗口連接">
                  <a class="font-website" :href="nav.website" target="_blank">{{ nav.website }}</a>
                </el-form-item>
                <el-form-item label="網站描述">
                  <div>{{ nav.describe || '須要您添加網站描述' }}</div>
                </el-form-item>
              </el-form>
              <div class="bottom clearfix">
                <time class="time">建立時間:{{ nav.created_at|timeTrans }}</time>
                <el-button type="text" class="button" @click="openDialog(nav)">編輯</el-button>
                <el-button type="text" class="button" @click="deleteMap(nav)">刪除</el-button>
              </div>
            </el-card>
          </div>
        </template>
      </waterfall>

commitizen提交git規範化

commitizen命令行實用程序。

Commitizen友好存儲庫中工做時,系統將提示填寫必填字段,而且將提交消息根據項目維護人員定義的標準進行格式化。

// 全局安裝commitizen node模塊
npm install commitizen -g 
// 經過鍵入如下命令初始化您的項目,以使用cz-convention -change - elog適配器
commitizen init cz-conventional-changelog --save-dev --save-exact
type用於說明 commit 的類別,只容許使用下面7個標識
  • feat:新功能(feature)
  • fix:修補bug
  • docs:文檔(documentation)
  • style: 格式(不影響代碼運行的變更)
  • refactor:重構(即不是新增功能,也不是修改bug的代碼變更)
  • test:增長測試
  • chore:構建過程或輔助工具的變更

scope 用來講明本次Commit影響的範圍,即簡要說明修改會涉及的部分,好比數據層、控制層、視圖層等

subject comment所在的位置,此次提交的簡短描述

iframe嵌套網站

<el-form-item label="iframe連接">
                  <router-link class="font-website" :to="{ path: 'iframeNav', query: { website: nav.website }}">
                    {{ nav.website }}
                  </router-link>
                </el-form-item>
                <el-form-item label="新窗口連接">
                  <a class="font-website" :href="nav.website" target="_blank">{{ nav.website }}</a>
                </el-form-item>

頁面中的iframe連接添加router-link指向iframe頁面,可是跳轉過去的連接都加上了每個分類的路由,因此在路由文件的每個分類的路由中都添加iframe路由。

{
    path: '/iframeNav',
    name: 'frontIframeNav',
    hidden: true,
    component: () => import('@/views/iframeNav/index'),
    meta: {
      title: '網站',
      icon: 'iframeNav'
    }
  }

  {
    path: '/back-end/iframeNav',
    name: 'backIframeNav',
    hidden: true,
    component: () => import('@/views/iframeNav/index'),
    meta: {
      title: '網站',
      icon: 'iframeNav'
    }
  }

  ……

全部從iframe連接點擊的都跳到這個頁面

<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>

適配

根據vue-waterfall2的不響應的特性,適配功能只能靠小編本身解決。

移動端,給他設置1列,側邊欄打開設置3列,其他設置4列。

每一個卡片的寬度根據屏幕的寬度和列數計算

卡片間的距離,給它一個定值。注意的是,當在移動端時,必需要設爲0,不然後面幾列都會向右偏移。

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'
    ])
  },

網站分類

網站分類的數據是從router來的,可是router的數據必需要過濾才能獲得分類的結果。

categoryOptions數組中的最後三者不屬於分類項,因此要去掉。

/**
 * 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 => {
    return item.label !== '網站'
  })
  // Delete the last three elements
  return categoryOptions.slice(0, -3)
}

而後模板頁調用該方法

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

this.categoryOptions = getOption('label', routes)

展望

下一篇《chrome開發之Navigation提交工具》

目前這個項目的基本已經完成,可是仍是有不少擴展的餘地。好比提交網站比較麻煩,這個時候有一個chrome提交工具,全部的問題就迎刃而解。

還有,這個項目小編會長期來維護,但願你們能踊躍提pr,提issue,將這個項目打造的更加完美,可以幫助到更多的人學習到vue除了官方demo以外的實際應用,避開更多的坑。

最後,別忘了給這個項目點一個star哦,謝謝支持。

navigation-web前端代碼倉庫

navigation-server後端代碼倉庫

下面是小編的公衆號

一個學習編程技術的公衆號。天天推送高質量的優秀博文、開源項目、實用工具、面試技巧、編程學習資源等等。目標是作到我的技術與公衆號一塊兒成長。歡迎你們關注,一塊兒進步,走向全棧大佬的修煉之路

相關文章
相關標籤/搜索