估計不少人和小編同樣,一開始將瀏覽過的以爲不錯的網站收藏到瀏覽器的收藏夾中。日積月累,網站愈來愈多,在收藏夾裏找所需網站愈來愈麻煩。後來瀏覽器從火狐轉到Chrome,收藏的網站卻不能同步過來。瀏覽器須要登陸才能同步收藏夾的內容,這很痛苦……鑑於種種緣由,有這樣一個收藏、搜索和分類導航平臺真的是棒極了,小編就本身作了一個開源項目——Navigation網站收藏和導航平臺。javascript
預覽css
<!-- 下載項目 --> git clone https://github.com/qiufeihong2018/navigation-server.git <!-- 安裝依賴 --> npm install <!-- 爬取數據 --> node ./creeper/index.js <!-- 啓動程序 --> npm run dev
以後訪問http://localhost:1600
便可html
基於express框架vue
啓動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; };
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
在處理程序以前,利用中間件解析傳入的請求主體,在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是一個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在express中的配置》
快速,靈活和精簡的核心jQuery實現,專爲服務器而設計。
簡單的http請求客戶端
詳細用法請見小編以前的文章《node爬取某圖片網站的桌面壁紙》
"pm2": "pm2 start index.js --name='navigation'"
詳細介紹請見小編以前的文章《pm2》
node.js和瀏覽器的簡單,靈活,有趣的javascript測試框架
Mochawesome是一個用於Javascript測試框架mocha的自定義報告器。它在Node.js上運行,並與mochawesome-report-generator結合使用,生成獨立的HTML / CSS報告,以幫助可視化您的測試運行。
node.js的BDD樣式斷言
是一個富有表現力,可讀,與框架無關的斷言庫。這個圖書館的主要目標是表達和幫助。它可使您的測試代碼保持乾淨,而且您的錯誤消息頗有用
用於使用流暢的API測試node.js HTTP服務器。
詳細介紹請見小編以前的文章《express項目集成mocha測試框架》
這三者有這密切的聯繫,前二者均可以歸passport-local-mongoose
管理,主要解析就放在passport-local-mongoose
這個依賴包中
Passport是Node.js的Express兼容認證中間件。
Passport的惟一目的是驗證請求,它經過一組稱爲策略的可擴展插件來完成。Passport不會掛載路由或假設任何特定的數據庫架構,這能夠最大限度地提升靈活性,並容許開發人員作出應用程序級別的決策。Passport提供了用於控制身份驗證成功或失敗時的鉤子。
session
:Passport將維護持久的登陸會話。爲了使持久會話工做,必須將通過身份驗證的用戶序列化到會話,並在發出後續請求時反序列化。Passport對用戶記錄的存儲方式沒有任何限制。相反,您爲Passport提供了一些函數,這些函數實現了必要的序列化和反序列化邏輯。在典型的應用程序中,這與序列化用戶ID以及反序列化時按ID查找用戶同樣簡單。initialize
:要在基於Express或鏈接的應用程序中使用Passport,請使用所需的passport.initialize()
中間件對其進行配置。若是您的應用程序使用持久性登陸會話(推薦使用,但不是必需的),還必須使用passport.session()
中間件。用於使用用戶名和密碼進行身份驗證的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
是一個Mongoose插件,它簡化了使用Passport構建用戶名和密碼的權限
const passportLocalMongoose = require('passport-local-mongoose'); const options = { interval: 200, maxInterval: 6 * 60 * 1000, maxAttempts: 6, limitAttempts: true }; User.plugin(passportLocalMongoose, options);
能夠簡化二者的配置
passport-local-mongoose
能夠經過設置LocalStrategy
、serializeUser
和deserializeUser
來配置來這二者
具體參數解析見《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-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 }); };
增刪改查的業務邏輯沒什麼好講的,代碼在倉庫裏
就是注意一點:
小編這裏是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 }); }); }); });
爲了方便查看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文檔
是基於花褲衩的vue-admin-template的簡單版的後臺管理模板,這一款基於vue2.0的後臺管理平臺深受大衆喜好。
Vuex是一個專爲Vue.js應用程序開發的狀態管理模式。它採用集中式存儲管理應用的全部組件的狀態,並以相應的規則保證狀態以一種可預測的方式發生變化.
自動從modules文件夾中導入文件
推薦一本老姚的正則手冊《JavaScript正則表達式迷你書(1.1版).pdf》
當匹配上面的字符自己時,能夠一概轉義:
記憶方式:w 是 word 的簡寫,也稱單詞字符。
記憶方式:加號是追加的意思,得先有一個,而後才考慮追加。
根據正則(在modules文件夾中找到結尾是js的文件)匹配全部的文件
// 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 })
支持http數據通訊。Axios 是一個基於 promise 的 HTTP 庫,能夠用在瀏覽器和 node.js 中。
尤大推薦用Axios,讓Axios進入了不少人的目光中。Axios本質上也是對原生XHR的封裝,只不過它是Promise的實現版本,符合最新的ES規範。
在其封裝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
餓了嗎的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 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>
搜索欄經過改變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>
一個簡單,輕量級的JavaScript API,用於處理瀏覽器cookie
對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) }
在默認的HTML元素樣式上提供了跨瀏覽器的高度一致性。相比於傳統的css reset,Normalize.css是一種現代的,爲HTML5準備的優質替代方案。
推薦閱讀Normalize.css 與傳統的 CSS Reset 有哪些區別?
超薄進度條
經過調用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' });
該工具庫用來處理 url 中地址與參數,可以很方便獲得小編們想要的數據。
js 中有 RegExp 方法作正則表達式校驗,而 path-to-regexp 能夠當作是 url 字符串的正則表達式。
應用於麪包屑組件components/Breadcrumb/index.vue
中,
分析下這個組件的原理:
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.js
官方的路由管理器。它和 Vue.js
的核心深度集成,讓構建單頁面應用變得易如反掌。
集成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倉庫
用於跨瀏覽器使用JavaScript Fullscreen API
的簡單包裝器,可以讓頁面或任何元素全屏顯示。
適用於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
命令行實用程序。
在Commitizen
友好存儲庫中工做時,系統將提示填寫必填字段,而且將提交消息根據項目維護人員定義的標準進行格式化。
// 全局安裝commitizen node模塊 npm install commitizen -g // 經過鍵入如下命令初始化您的項目,以使用cz-convention -change - elog適配器 commitizen init cz-conventional-changelog --save-dev --save-exact
<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哦,謝謝支持。
下面是小編的公衆號
一個學習編程技術的公衆號。天天推送高質量的優秀博文、開源項目、實用工具、面試技巧、編程學習資源等等。目標是作到我的技術與公衆號一塊兒成長。歡迎你們關注,一塊兒進步,走向全棧大佬的修煉之路