運行圖
安裝
npx create-nuxt-app project
配置
koa + axios
typescript-vuex github
ui框架
element-ui
目錄結(jié)構(gòu)
assets ---資源目錄
layouts ---布局目錄
middleware ---中間件目錄
plugins ---插件目錄
static ---靜態(tài)(后臺)
- 在您的 vue 模板中, 如果你需要引入 assets 或者 static 目錄, 使用 ~/assets/your_image.png 和 ~/static/your_image.png方式歧匈。
異步數(shù)據(jù) SSR解析
- 頁面數(shù)據(jù) asyncData
先請求
扔個模板結(jié)構(gòu)(靜態(tài)渲染) asyncData(請求拿數(shù)據(jù))
把編譯的結(jié)果扔給客戶端 服務(wù)器下發(fā)一個script 掛載到window下
同步到瀏覽器(交互) 虛擬編譯和服務(wù)器扔過來的作對比, 不同重新請求
第一參數(shù): 當前頁面的上下文對象
- ts中操作
@Component({
async asyncData({params,app,$axios}) {
console.log(params,app);
app.store.dispatch('search/setName', params.key)
return {
keysword: params.key
}
},
components: {
ECrumb
},
})
- vuex fetch
nuxtServerInit
- 第一次請求
- 保存用戶登錄
- 全局數(shù)據(jù)
==如果你使用狀態(tài)樹模塊化的模式,只有主模塊(即 store/index.js)適用設(shè)置該方法(其他模塊設(shè)置了也不會被調(diào)用)锥余。==
layouts 頁面模板
[圖片上傳失敗...(image-ac40cb-1562038525452)]
pages 即是路由
- 基礎(chǔ)路由
- 動態(tài)路由
- 嵌套路由
ts中
npm i @nuxt/typescript -D
npm i vue-class@0.3.1 vue-property-decorator@7 -S
- tsconfig.json
{
"compilerOptions": {
"target": "esnext",
"module": "esnext",
"moduleResolution": "node",
"lib": [
"esnext",
"esnext.asynciterable",
"dom"
],
"esModuleInterop": true,
"experimentalDecorators": true,
"allowJs": true,
"sourceMap": true,
"strict": true,
"noImplicitAny": false,
"noEmit": true,
"baseUrl": ".",
"paths": {
"~/*": [
"./*"
],
"@/*": [
"./*"
]
},
"types": [
"@types/node",
"@nuxt/vue-app"
]
}
}
head layout asyncData...等 放在@Component使用
@Component({
//
head() {
return {
title: this.name
}
},
layout: 'search'
})
.vue中ts 獨立出來 才能引入單獨ts
- vuex 使用
index.js
import Vuex from 'vuex'
import state from './state'
import getters from './getters'
import mutations from './mutations'
import actions from './actions'
import * as search from './module/search'
import geo from './module/geo'
// webpack 中 生產(chǎn)模式或開發(fā)
const createStore = () => {
return new Vuex.Store({
state,
getters,
mutations,
actions,
modules: {
[search.name]: search,
geo
}
})
}
export default createStore
- 相關(guān)模塊 module/geo.ts
import { RootStateTypes } from '../types';
import { MutationTree, ActionTree } from 'vuex';
const namespaced = true;
interface stateInterface {
city: string;
}
const state: stateInterface = {
city: ''
};
export const types = {
CITY: 'CITY'
};
const mutations: MutationTree<stateInterface> = {
[types.CITY]: (state, city: string) => {
state.city = city;
}
};
const actions: ActionTree<stateInterface, RootStateTypes> = {
setCity({ commit }, city) {
commit(types.CITY, city);
}
};
export default { namespaced, state, actions, mutations };
- 如何拓展 webpack 配置 --- 添加 alias 配置
背景:給 utils 目錄添加別名
剛剛說到始锚,Nuxt.js內(nèi)置了 webpack 配置婿着,如果想要拓展配置,可以在 nuxt.config.js 文件中添加。同時也可以在該文件中能犯,將配置信息打印出來生百。
extend (config, ctx) {
console.log('webpack config:', config)
if (ctx.isClient) {
// 添加 alias 配置
config.resolve.alias['~src'] = __dirname
config.resolve.alias['~utils'] = path.join(__dirname, 'utils')
}
}
支持es6語法
安裝
yarn add babel-cli babel-core babel-preset-es2015 babel-preset-stage-0
修改package.json文件递雀,在“dev”和“start”命令后面新增:--exec babel-node
項目根目錄下新增babel配置文件“.babelrc”文件,寫入以下配置
{
"preset": ["es2015","stage-0"]
}
serve 相關(guān)
serve目錄
Passport 解決登陸認證的問題
Web應用一般有2種登陸認證的形式:
- 用戶名和密碼認證登陸 相關(guān)鏈接
- OAuth認證登陸 相關(guān)鏈接
這次項目實現(xiàn)用戶名和密碼認證登陸
基于本地 配置策略 進行 用戶名和密碼驗證
passport.js
// 身份驗證
// http://blog.fens.me/nodejs-express-passport/ 解決登陸認證的問題
import passport from 'koa-passport'
import LocalStrategy from 'passport-local'
// 用戶表
import UserModel from '../../dbs/models/user'
// 配置策略. (具體操作)
passport.use(new LocalStrategy(async function (username, password, done) {
let result = await UserModel.findOne({
username: username
})
// 存在
if (result != null) {
// 密碼對不對
if (result.password === password) {
return done(null, result)
} else {
return done(null, false, {
msg: '密碼錯誤'
})
}
} else {
// 不存在
return done(null, false, {
msg: '用戶不存在'
})
}
}))
// 保存用戶
passport.serializeUser(function (user, done) {
done(null, user)
})
// 刪除用戶
passport.deserializeUser(function (user, done) {
done(null, user)
})
export default passport
路由控制 user.js
- 登錄
- 注冊
- 驗證驗證碼 (發(fā)送驗證碼) npm i nodemailer@4.6.8
- 退出
相關(guān)資料
ctx.session.passport.user 用戶信息
ctx.request.body post傳參
ctx.params ctx.query get傳參
import Router from 'koa-router';
// 使用redis 驗證 --- 不同用戶同時發(fā)送驗證碼 區(qū)分不用戶,不能存表(量大,內(nèi)存會溢出),
import Redis from 'koa-redis';
// 給用戶發(fā)郵件
import nodeMailer from 'nodemailer';
import Email from '../dbs/config';
import userModel from '../dbs/models/user';
import axios from './utils/axios';
import passport from './utils/passport';
const router = new Router();
const client = new Redis({}).client;
function err(msg: string) {
return {
code: -1,
msg
};
}
// 注冊
router.post('/signup', async (ctx: any) => {
// ctx.request.body post傳參
const { username, password, email, code } = ctx.request.body;
// 驗證code
if (code) {
// 獲取對應的code 驗證碼
const saveCode = await client.hget(`nodemail:${username}`, 'code');
// 過去時間
const expire = await client.hget(`nodemail:${username}`, 'expire');
if (code === saveCode) {
// 是否過期
if (Date.now() - expire > 0) {
ctx.body = {
code: -1,
msg: '驗證碼已過期,請重新驗證'
};
return false;
}
} else {
// 驗證碼錯誤
ctx.body = {
code: -1,
msg: '驗證碼錯誤'
};
}
} else {
ctx.body = {
code: -1,
msg: '驗證碼不能為空'
};
}
// 驗證用戶是否被注冊過.
try {
await userModel.findOne(username);
ctx.body = err('用戶名被注冊過了');
} catch {
let user = userModel.create({
username,
password,
email
});
if (user) {
// 注冊后自動登錄
let res = await axios.post('/signin', { username, password });
if (res.data && res.data.code === 0) {
ctx.body = {
code: 0,
data: res.data.user,
msg: '注冊成功'
};
} else {
ctx.body = err('error');
}
} else {
// 創(chuàng)建失敗
ctx.body = err('注冊失敗');
}
}
});
// 登錄
router.post('/signin', (ctx: any, next: any) => {
// 登錄 驗證
return passport.authenticate(`local`, function(
error: any,
user: any,
info: any
) {
if (error) {
ctx.body = err(error);
return false;
}
if (user) {
ctx.body = {
code: 0,
msg: '登錄成功',
data: user
};
// passport 登錄用戶初始化session
return ctx.login(user);
} else {
ctx.body = {
code: 1,
msg: info
};
}
})(ctx, next);
});
// 驗證
router.post('/verify',async (ctx: any,next: any) => {
let {username,email} = ctx.request.body
// 阻止頻繁訪問
let expire = await client.hget(`nodemail:${username}`, 'expire');
if(expire && (Date.now() - expire) < 0) {
ctx.body = {
code: -1,
msg: '請求過于頻繁'
}
return false
}
// 郵件配置
let transporter = nodeMailer.createTransport({
host: Email.smtp.host,
post: Email.smtp.port,
// 監(jiān)聽其他端口(原: 465)
secure: false,
auth: {
user: Email.smtp.user,
// 授權(quán)碼
pass: Email.smtp.pass
}
})
// 新建一個驗證碼信息
let ko = {
code: Email.code(),
expire: Email.expire(),
user: username,
email: email,
}
// 郵件信息配置
let mailOptions = {
from: `認證郵件<${Email.smtp.user}>`,
to: ko.email,
// 標題
subject: `網(wǎng)站的注冊碼`,
// 發(fā)送的text或者html格式
html: `你的驗證碼是${ko.code}`
}
// 發(fā)送
await transporter.sendMail(mailOptions, (error,info) => {
if(error) {
return console.log(error)
}
// hmset 為散列里面的一個或多個鍵設(shè)置值 OK hmset('hash-key', obj)
client.hmset(`nodemail:${ko.user}`, ko)
})
ctx.body = {
code: 0,
msg: `驗證碼已發(fā)送, 有效期1min`
}
})
router.post(`/exit`, async (ctx,next) => {
// passport 刪除該用戶session
await ctx.logout()
// 二次驗證是否退出 passport的驗證
// isAuthenticated: 測試該用戶是否存在于session中(即是否已登錄)
if(ctx.isAuthenticated()) {
ctx.body = err('退出失敗')
}else{
ctx.body = {
code: 0
}
}
})
// 獲取用戶信息
router.get('/user', async (ctx) => {
if(ctx.isAuthenticated()) {
let {username,email} = ctx.session.passport.user
ctx.body = {
code: 0,
user: username,
email
}
}else{
ctx.body = {
code: -1,
user: '',
email: ''
}
}
})
export default router
app.js
npm install koa-bodyparser koa-generic-session koa-json koa-passport passport-local
// 引入mongoose redis
import mongoose from 'mongoose'
// 處理passport相關(guān)請求
import bodyParser from 'koa-bodyparser'
// session刪寫
import session from 'koa-generic-session'
import Redis from 'koa-redis'
// 代碼格式化. 打印.
import json from 'koa-json'
import dbsConfig from './dbs/config'
import Passpot from './interface/utils/passport'
import UserInterface from './interface/user'
import passport from './interface/utils/passport';
// session加密處理的兩字符
app.keys = ['keys','key']
app.proxy = true
// 存儲
app.use(session({
key: 'egg-mt',
prefix: 'mt:uid',
store: new Redis()
}))
app.use(bodyParser({
enbleTypes: ['text','json','form']
}))
app.use(json())
// 連接數(shù)據(jù)庫
mongoose.connect(dbsConfig.dbs, {
useNewUrlParser: true
})
app.use(passport.initialize())
app.use(passport.session())
// 添加路由
密碼加密
crypto-js (加密算法類庫)
ts識別全局方法/變量
shims-vue.d.ts
import VueRouter, { Route } from "vue-router";
import Vue from 'vue';
declare var document: Document;
declare module '*.vue' {
export default Vue;
}
declare module "*.ts" {
const value: any;
export default value;
}
declare global {
interface window {
require: any;
}
}
// 識別 this.$route
declare module 'vue/types/vue' {
interface Vue {
$router: VueRouter; // 這表示this下有這個東西
$route: Route;
$notify: any;
}
}
- this 的類型檢查
在根目錄的 tsconfig.json 里面加上 "noImplicitThis": false 蚀浆,忽略 this 的類型檢查
"noImplicitThis": false,
插件
JS實現(xiàn)中文轉(zhuǎn)拼音(首字母大寫和首字母簡拼)
js-pinyin
地圖的使用
地圖組件
- 添加點標記, 文本標簽
- 添加點擊事件
map.vue
<template>
<div>
<div :id="id"
:class='["m-map", {fixed: fixed}]'
:style="{width:width+'px',height:height+'px',margin:'34px auto' }" ref='map'></div>
<transition name="fade">
<div class="model"
v-show='show'
@click="show = false">
<div :id='"max-"+id'
class="fixed-map"
:style="{width:mapWidth+'px',height:mapHeight+'px'}">
</div>
</div>
</transition>
</div>
</template>
<script lang='ts'>
import { Component, Vue, Prop } from "vue-property-decorator";
declare var window: any;
declare var AMap: any;
@Component({
props: {
id: {
type: String,
default: "map"
},
// 點標記
// markerList: {
// type: Array,
// default() {
// return [{
// name: '天安門',
// location: [116.39, 39.9],
// add: '北京'
// }]
// }
// },
width: Number,
height: Number,
fixed: Boolean
}
})
export default class Map extends Vue {
key: string = "12ef08e92a0ce0963b4698a73de243bc";
map: any = null;
mapWidth: number = 0;
mapHeight: number = 0;
show: boolean = false;
@Prop({
type: Array,
default() {
return [
{
name: "天安門",
location: [116.39, 39.9],
add: "北京"
}
];
}
})
markerList: any[];
mounted() {
let that: any = this;
window.onMapLoad = () => {
// that.map = new AMap.Map(that.id, {
// resizeEnable: true,
// zoom: 11,
// center: that.markerList[0].location
// });
// AMap.plugin(
// [
// "AMap.ToolBar",
// "AMap.Scale",
// "AMap.OverView",
// "AMap.MapType",
// "AMap.Geolocation"
// ],
// function() {
// // 在圖面添加工具條控件缀程,工具條控件集成了縮放、平移市俊、定位等功能按鈕在內(nèi)的組合控件
// that.map.addControl(new AMap.ToolBar());
// // 在圖面添加比例尺控件杨凑,展示地圖在當前層級和緯度下的比例尺
// // map.addControl(new AMap.Scale());
// // 在圖面添加鷹眼控件,在地圖右下角顯示地圖的縮略圖
// // map.addControl(new AMap.OverView({ isOpen: true }));
// // 在圖面添加類別切換控件摆昧,實現(xiàn)默認圖層與衛(wèi)星圖撩满、實施交通圖層之間切換的控制
// // map.addControl(new AMap.MapType());
// // 在圖面添加定位控件,用來獲取和展示用戶主機所在的經(jīng)緯度位置
// that.map.addControl(new AMap.Geolocation());
// }
// );
// that.addMarker();
// mini
that.mapInit()
// normal
that.mapInit(`max-${that.id}`,`max-${that.id}`)
// let marker = new AMap.Marker({
// icon:
// "http://a.amap.com/jsapi_demos/static/demo-center/icons/poi-marker-red.png",
// position: that.map.getCenter(),
// offset: new AMap.Pixel(-13, -30)
// });
// marker.setLabel({
// offset: new AMap.Pixel(0, -5), //設(shè)置文本標注偏移量
// content: "<div class='info'>1</div>", //設(shè)置文本標注內(nèi)容
// direction: "center" //設(shè)置文本標注方位
// });
// that.add(marker);
};
var url = `https://webapi.amap.com/maps?v=1.4.14&key=${
this.key
}&callback=onMapLoad`;
var jsapi = document.createElement("script");
jsapi.charset = "utf-8";
jsapi.src = url;
document.head.appendChild(jsapi);
}
mapInit(id = 'map',name = 'map') {
let that: any = this;
that[name] = new AMap.Map(id, {
resizeEnable: true,
zoom: 11,
center: that.markerList[0].location
});
AMap.plugin(
[
"AMap.ToolBar",
"AMap.Scale",
"AMap.OverView",
"AMap.MapType",
"AMap.Geolocation"
],
function() {
// 在圖面添加工具條控件绅你,工具條控件集成了縮放伺帘、平移、定位等功能按鈕在內(nèi)的組合控件
that[name].addControl(new AMap.ToolBar());
// 在圖面添加比例尺控件忌锯,展示地圖在當前層級和緯度下的比例尺
// map.addControl(new AMap.Scale());
// 在圖面添加鷹眼控件伪嫁,在地圖右下角顯示地圖的縮略圖
// map.addControl(new AMap.OverView({ isOpen: true }));
// 在圖面添加類別切換控件,實現(xiàn)默認圖層與衛(wèi)星圖偶垮、實施交通圖層之間切換的控制
// map.addControl(new AMap.MapType());
// 在圖面添加定位控件张咳,用來獲取和展示用戶主機所在的經(jīng)緯度位置
that[name].addControl(new AMap.Geolocation());
}
);
that.addMarker(name);
// mini打開大的
if(name='map') {
that[name].on('click', (e) => {
that.mapWidth = (window.innerWidth / 2) > 1100 ? (window.innerWidth / 2) : 1100
that.mapHeight = window.innerHeight * 0.85
that.show = true
})
}
}
addMarker(name = 'map') {
let map = this[name];
this.markerList.forEach((item, index) => {
// 點標記
let marker = new AMap.Marker({
icon:
"http://a.amap.com/jsapi_demos/static/demo-center/icons/poi-marker-red.png",
position: item.location,
offset: new AMap.Pixel(-13, -30)
});
// 設(shè)置鼠標劃過點標記顯示的文字提示
marker.setTitle(item.add);
// 設(shè)置label標簽
marker.setLabel({
offset: new AMap.Pixel(0, -5), //設(shè)置文本標注偏移量
content: `<div class='info'>${index + 1}</div>`, //設(shè)置文本標注內(nèi)容
direction: "center" //設(shè)置文本標注方位
});
// 設(shè)置點擊事件
marker.on("click", function(e) {
// 阻止冒泡
e.stopPropagation ? e.stopPropagation() :
e.cancelBubble = true
// 純文本標記
let label = new AMap.Text({
offset: new AMap.Pixel(0, -30),
text: item.name,
anchor: "top", // 設(shè)置文本標記錨點
draggable: true,
cursor: "pointer",
angle: 0,
style: {
padding: ".25rem .75rem",
"margin-bottom": "1rem",
"border-radius": ".25rem",
"background-color": "white",
"border-width": 0,
"box-shadow": "0 2px 6px 0 rgba(114, 124, 245, .5)",
"text-align": "center",
"font-size": "14px"
// color: "blue"
},
position: item.location
});
map.add(label);
});
map.add(marker);
});
}
}
</script>
<style lang='scss'>
.amap-icon img {
width: 25px;
height: 34px;
}
.amap-marker-label {
border: 0;
background-color: transparent;
}
.info {
position: relative;
top: 0;
right: 0;
min-width: 0;
border-radius: 50%;
background-color: transparent;
color: #fff;
}
.info_text {
position: relative;
top: 0;
right: 0;
min-width: 0;
background: #fff;
box-shadow: 1px 1px 5px #999;
}
.model {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
background: rgba(0, 0, 0, 0.7);
z-index: 9999;
.fixed-map {
position: absolute;
top: 50%;
left: 50%;
transform: translate3d(-50%,-50%, 0);
}
}
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.4s;
}
.fade-enter, .fade-leave-to /* .fade-leave-active below version 2.1.8 */ {
opacity: 0;
}
.fixed {
position: fixed !important;
top: 0;
overflow: hidden;
margin: 0 10px !important;
}
</style>
另一種顯示方式: 標注圖層
單條數(shù)據(jù)格式
console.log(JSON.stringify(LabelsData[6]))
{
"name": "京味齋烤鴨店",
"position": [116.462483, 39.992492],
"zooms": [10, 20],
"opacity": 1,
"zIndex": 4,
"icon": {
"type": "image",
"image": "https://a.amap.com/jsapi_demos/static/images/poi-marker.png",
"clipOrigin": [547, 92],
"clipSize": [50, 68],
"size": [25, 34],
"anchor": "bottom-center",
"angel": 0,
"retina": true
},
"text": {
"content": "京味齋烤鴨店",
"direction": "top",
"offset": [0, 0],
"style": {
"fontSize": 15,
"fontWeight": "normal",
"fillColor": "#666",
"strokeColor": "#fff",
"strokeWidth": 1
}
},
"extData": {
"index": 6
}
}
滾動事件
// 滾動距離
let scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop;
mounted() {
window.addEventListener("scroll", this.handleScroll, true); // 監(jiān)聽(綁定)滾輪滾動事件
}
// 滾動事件
handleScroll(e) {
}
destroyed() {
window.removeEventListener("scroll", this.handleScroll); // 監(jiān)聽(綁定)滾輪滾動事件
}