Vue 创建多人共享博客

简介: Vue 创建多人共享博客

Vue 创建多人共享博客


项目介绍

描述: 使用vue-cli作为脚手架,使用vue-router进行前端路由的切换及传参,使用axios获取数据,使用vuex来做全局状态管理,JSON格式化工具对数据整理,webpack打包。整个共享博客项目主要有以下功能:ToDo:

  • 首页: 展示所有作者设置到首页的博客列表
  • 详情:展示博客详情
  • 登录、注册: 用户登录注册
  • 用户页面: 展示某个用户的所有博客列表
  • 我的: 展示个人主页
  • 编辑、删除、创建博客

测试账号:zxjdzxb

测试密码:zxjqwe

源码链接:

zxjdzxb/blog

预览链接:

blog-client

准备工作

1、前后端接口约定&测试

后端接口规范约定

接口一般包括以下数据:

  1. 当前接口的路径是什么? 如 /auth/register
  2. 当前接口提交数据的类型是什么? 如
  • GET 获取数据
  • POST 提交或者创建
  • PATCH 修改数据,部分修改
  • DELETE 删除数据
  • PUT 修改数据,整体替换原有数据

3.参数类型/格式,比如是 json 格式,还是 application/x-www-form-urlencoded的数据格式

  1. 参数字段,及限制条件

5.返回成功的数据格式

6.返回失败的数据格式

后端接口线上地址根路径:

blog-server.hunger-valley.comblog-server.hunger-valley.com/

下面我们和后端做以下接口约定,开发阶段可以用 postman 或者curl 命令测试接口

比如注册请求:

POST /auth/register

功能: 用户注册

提交参数

  • 参数类型:Content-Type: application/x-www-form-urlencoded;charset=utf-8
  • 参数字段
  • username : 用户名, 长度1到15个字符,只能是字母数字下划线中文
  • password : 密码, 长度6到16个任意字符

返回数据

  • 失败
  • 返回格式 {"status": "fail", "msg": "错误原因"}
  • 成功
  • 返回格式
{
  "status": "ok",
  "msg": "注册成功",
  "data": {
    "id": 1,
    "username": "hunger",
    "avatar": "http://avatar.com/1.png",
    "updatedAt": "2017-12-27T07:40:09.697Z",
    "createdAt": "2017-12-27T07:40:09.697Z"
  }
}

测试(curl一下)

# -d 是用来传递数据
# 对于 POST 和 PUT 可以:  -X POST, 对于 GET,不加 -X
curl -d "username=lalala&password=123456" -X POST "http://blog-server.hunger-valley.com/auth/register"

项目初始化

老套路了..使用 vue-cli 创建项目骨架

创建路由

在 vue 项目中使用 less: <style scoped lang='less'></style>

如果要在某个组件中引入 less 文件,则在 style 中写入 @import '../assets/base.less'; 即可(记得 npm 装上 less 和 less-loader 哦)


ElementUI 的使用

ElementUI 的有很详细的安装使用文档

主要步骤:

1.安装 cnpm i element-ui

2.引入

import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';

3.挂载到 Vue Vue.use(ElementUI)

然后就可以在组件中使用 element-ui 了。


前后端接口约定&测试

饥人谷科技 (xiedaimala.com)

数据请求接口封装

另外,若愚老师前期还对 axios 底层请求做了进一步的定制和封装,其中一些技巧很值得学习。

1.先把 axios 请求封装成了输入参数更简洁明了、报错信息更「人性化」的 Promise 对象。

//  /helpers/request.js
axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded'
axios.defaults.baseURL = 'http://blog-server.hunger-valley.com'
axios.defaults.withCredentials = true
export default function request(url, type = 'GET', data = {}) {
  return new Promise((resolve, reject) => {
    let option = {
      url,
      method: type
    }
    if(type.toLowerCase() === 'get') {
      option.params = data
    }else {
      option.data = data
    }
    axios(option).then(res => {
      console.log(res.data)
      if(res.data.status === 'ok') {
        resolve(res.data)
      }else{
        Message.error(res.data.msg)
        reject(res.data)
      }
    }).catch(err => {
      Message.error('网络异常')
      reject({ msg: '网络异常' })
    })
  })
}

2.再把获取数据的 API 进行封装,使其更易用:

import request from '@/helpers/request'
const URL = {
  REGISTER: '/auth/register',
  LOGIN: '/auth/login',
  LOGOUT: '/auth/logout',
  GET_INFO: '/auth'
}
export default {
  // 注册
  register({username, password}) {
    return request(URL.REGISTER, 'POST', { username, password })
  },
    // 登录
  login({username, password}) {
    return request(URL.LOGIN, 'POST', { username, password })
  },
    // 登出
  logout() {
    return request(URL.LOGOUT)
  },
    // 获取信息
  getInfo() {
    return request(URL.GET_INFO)
  }
}

这样子处理的话,登录请求就可以不使用 axios('http://blog-server.hunger-valley.com/auth/login','POST',{username,password}) 那么繁琐了,直接 auth.login({username,password}) 就完事了~

可以查看此 commit


grid

使用 grid 进行布局。

关于 grid 布局之前有了解过,grid 通过在页面上划分 columns 和 rows ,然后把内容分别放进不同区域来建立布局,也写过 demo,但真正在项目中使用还是第一次。关于 grid 的教程可以参考CSS Grid 布局完整指南

如在项目中的使用:

#app {
  display: grid;
  // 分成三列,左右列宽度分别是页面的12%,中间内容宽度自适应
  grid-template-columns: 12% auto 12%;
  // 分成三行,上下行高度自适应,中间内容占满剩余宽度
  grid-template-rows: auto 1fr auto;
  // 划分区域
  grid-template-areas: "header header header"
                       ".      main   .     "
                       "footer footer footer";
  #header{
    grid-area: header;
    padding-left: 12%;
    padding-right: 12%;
  }
  #main{
    grid-area: main;
  }
  #footer{
    grid-area: footer;
    padding-left: 12%;
    padding-right: 12%;
  }
}

可以查看此 commit


async/await

在完成项目的过程中接触到了 async/await :

async/await 是异步编程的一种解决方案。

async 声明一个函数为异步函数,这个函数返回的是一个 Promise 对象;

await 用于等待一个 async 函数的返回值(注意到 await 不仅仅用于等 Promise 对象,它可以等任意表达式的结果,所以,await 后面实际是可以接普通函数调用或者直接量的。)

以项目中用户注册为例:

// 声明 register 为一个异步函数,他会返回一个 Promise 对象
async register({commit},{username,password}){
    // 用户注册成功后后会返回的一个 Promise 对象,其中包含了用户的信息,let res 就是异步 auth.register 获取的结果
    let res = await auth.register({username,password})
    commit('setUser',{user:res.data})
    commit('setLogin',{isLogin:true})
    // 把 res.data 返回出去,使用 register() 后就可以用 then 来处理这个结果
    return res.data
},

对于 async/await,我参考了 边城 在 segmentfault 中的这边文章


Vuex

如何在项目中使用 vuex 管理状态?(以登录为例)

  • 1.创建store,定义 state/getters/mutations/actions

由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。 为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割\

在这里针对不同的内容状态管理划分到不同的文件,保持可读性:

store
|
│  index.js // 引入子模块
└─modules
        auth.js   // 负责用户注册、登录状态
        blog.js   // 负责用户获取、发布、修改博客等

把负责用户注册登录的 state 写在了 auth.js:

// auth.js
import auth from '@/api/auth'
const state = {
    // 先定义一个默认的用户状态
    user:null,
    //登录状态
    isLogin:false
}
const getters = {
    // 获取 state 数据
    user:state => state.user,
    isLogin:state => state.isLogin
}
const mutations = {
    // 更新用户数据
    setUser(state,payload){
        state.user = payload.user
    },
    // 更新用户登录状态
    setLogin(state,payload){
        state.isLogin = payload.isLogin
    }
}
const actions = {
    ...
    // 检测用户是否登录
    async checkLogin({commit,state}){
        // 先从本地store的state去看用户是否登录,如果登录了 就返回true
        if(state.isLogin) return true
        let res = await auth.getInfo()
        commit('setLogin',{isLogin:res.isLogin})
        // 如果本地没有这个状态,就发ajax请求去服务器,服务器会返回一个isLogin的响应,根据这个值来确定是否登录
        if(!res.isLogin) return false
        commit('setUser',{user:res.data})
        // 最后的 return true 是为了在实例中then拿到这个true,方便做下一步处理
        return true
    },
    // 用户登录  {commit} 是默认参数,相当于 context.commit,使用了 ES6 的参数结构
    login({commit},{username,password}){
        // 调用底层接口,返回的是一个 Promise 对象
        return auth.login({username,password})
            .then(res => {
        // 把通过 axios 获取回来的用户数据提交 mutation,更新到 state: commit -> setUser -> state
                commit('setUser',{user:res.data})
                commit('setLogin',{isLogin:true})
            })
    },
    ...
}
export default {
    state,
    getters,
    mutations,
    actions
}
  • 2.在 /store/index.js 把模块引入进来:
// index.js
import Vue from 'vue'
import Vuex from 'vuex'
import auth from './modules/auth'
Vue.use(Vuex)
export default new Vuex.Store({
    modules:{
        auth,
    }
})
  • 3.在相关组件中映射属性,commit 更新

在 Login.vue 中,我们要做的事情就是:点击按钮后,调用 auth.js 中的 login() 方法,完成登录,更新 state 中的数据,并给需要的组件更新状态(如 header.vue)

首先映射 login 方法到此组件,这样此组件就可以通过 this.login 来调用这个 auth.js 中的方法了:

//Login.vue
import {mapActions} from 'vuex'
export default {
  name:'Login',
  methods:{
    ...mapActions([
        'login'
      ]),
  },
}

接着设置点击事件,点击按钮会执行 onLogin,调用 this.login ,发送 axios 请求 auth.login({username,password}),成功注册后 commit mutation,更新 state 数据,跳转到首页:

<el-button size="medium" @click="onLogin">立即登录</el-button>
//Login.vue
methods:{
    ...mapActions([
        'login'
      ]),
      onLogin(){
        this.login({username:this.username,password:this.password})
          .then(()=>{
            console.log(`${this.username},${this.password}`)
            this.$router.push({path:'/'})
          })
      }
  },
  • 4.在相关组件中关联 state 数据,实现状态切换

在 Header 中,登录和未登录,他的样式在两种状态下是不一样的:

未登录的时候,header 会显示提示登录和注册的按钮;登录之后,header 会显示用户头像及其他操作选项。

而这两种状态的切换,就要依靠我们的 state 了,首先引入映射 {mapGetters,mapActions},在页面还未渲染的时候检查 state 中用户登录状态,用户已登录,则返回 isLogin = true ,获取用户信息,渲染到页面上;用户未登录,则返回 isLogin = false

<header :class="{login:isLogin,'no-login':!isLogin}">
// Header.vue
import {mapGetters,mapActions} from 'vuex'
export default {
  name:'Header',
  // 把 store 中 getter 属性映射到此组件
  computed:{
    ...mapGetters([
      'isLogin',
      'user'
    ])
  },
  //在页面没有渲染之前检查用户是否登录
  created(){
    this.checkLogin()
  },
  methods:{
    // 把 auth.js 中的 checkLogin 方法映射到此组件
    ...mapActions([
      'checkLogin'
    ]),
  },

这就是 vuex 在在项目中管理登录状态了。


完善路由

添加路由元信息

项目中有一些页面,比如添加文章、编辑文章等等,都需要先确认用户是否登录才能操作,否则将会自动跳转到登录页。

路由元信息做的就是这样一件事情,我们给某段路由添加一个 meta 字段 meta:{ requiresAuth:true },这段路由路由匹配到的所有路由记录会暴露为 route对象(还有在导航守卫中的路由对象)的route 对象 (还有在导航守卫中的路由对象) 的 route()route.matched 数组。通过遍历 $route.matched 来检查路由记录中的 meta 字段,对访问的路径做一个状态检查,从而确定是否允许访问。

const router = new Router({
  routes: [
    ...
    {
      path: '/Create',
      component: () =>import ('@/pages/Create/Create'),
      // 路由添加 meta 字段
      meta:{ requiresAuth:true }
    },
    ...
  ]
})
router.beforeEach((to, from, next) => {
  if (to.matched.some(record => record.meta.requiresAuth)) {
    // 如果 store.dispatch('checkLogin') 返回的结果 isLogin 为 false,则说明用户没有登录,就会跳转到 /login
    store.dispatch('checkLogin').then(isLogin=>{
      if (!isLogin) {
        next({
          path: '/login',
          query: { redirect: to.fullPath }
        })
      } else {
        next()
      }
    })
  } else {
    next() // 确保一定要调用 next()
  }
})

懒加载

小技巧 - 按需加载,节约性能:

按需加载的适用场景,比如说「访问某个路由的时候再去加载对应的组件」,用户不一定会访问所有的路由,所以没必要把所有路由对应的组件都先在开始的加载完;更典型的例子是「某些用户他们的权限只能访问某些页面」,所以没必要把他们没权限访问的页面的代码也加载。\

// before
import Index from '@/pages/Index/Index'
const router = new Router({
  routes: [
    {
      path: '/',
      component: Index
    },
    ...
  ]
})
// after
const router = new Router({
  routes: [
    {
      path: '/',
      component: () =>import ('@/pages/Index/Index')
    },
    ...
  ]
})

marked

使用 marked.js 对 markdown 内容进行转换:

文章详情页(Detail)中,通过服务器返回的文章内容是 markdown 格式的,先用 marked.js 库处理一下,再使用 v-html 渲染到页面中。

1.安装 cnpm install marked

2.在组件中引入 import marked from 'marked'

3.把内容进行转换:

computed:{
    markdown(){
      return marked(this.rawContent)  //this.rawContent 是从服务器获取的文章正文
    }
  },

4.在页面中引入: <section class="article" v-html='markdown'></section>


vue-devtools

最后介绍一款小插件,可以在使用 vue 开发的时候更好地调试和 debug。

vue-devtools是一款基于chrome游览器的插件,用于调试vue应用,这可以极大地提高我们的调试效率。\

GitHub 文档:vue-devtools

Chrome 插件下载地址:Get the Chrome Extension

当我们添加完vue-devtools扩展程序之后,我们在调试vue应用的时候,chrome开发者工具中会看一个vue的一栏,点击之后就可以看见当前页面vue对象的一些信息。\

网络异常,图片无法展示
|

网络异常,图片无法展示
|

完善注册和登录页面的功能

实现登录页面

在template.vue文件只要写上对应的点击事件就可以了

<template>
  <div id="login">
    <h4>用户名</h4>
    <input v-model="username" placeholder="用户名">
    <h4>密码</h4>
    <input v-model="password" type="password" placeholder="密码" @keyup.enter="onLogin">
    <!--添加了enter键盘事件-->
    <el-button size="medium" @click="onLogin">立即登录</el-button>
    <p class="notice">没有账号?<router-link to="/register">注册新用户</router-link></p>
  </div>
</template>
<script src="./template.js"></script>
<style src="./template.less" lang="less"></style>

在template.js文件要引入vuex状态管理

import { mapActions } from 'vuex'
export default {
    data () {
      return {
        username:'',
        password:''
      }
    },
    methods:{
      ...mapActions(['login']),
      onLogin(){
        this.login({username:this.username , password:this.password})
          .then(()=>{
            this.$router.push({path:'/'})
          })
      }
    }

注册页面也是如此,就不写了


终极插播:if else自己研究了一个小时才懂的代码,

router.beforeEach((to,from,next) => {  //对router的每一个path进行一个遍历
  if(to.matched.some(record => record.meta.requireAuth)){  //第一个if
    if(!store.getters.isLogin){  //第二个if
      next({
        path:'/login',
        query:{redirect :to.fullPath}
      })
    }else{
      next()
    }
  }else{
    next()
  }
})
  1. 这是一个判断用户是否已经登录的代码
  2. 首先这是一个遍历所有路由的函数,第一个if的表达式是判断router路由里有没有一个meta他的requireAuth是true,
  3. 如果他是有meta他的requireAuth是true,那么就进入第二个if else,否则直接next,因为我们有一些页面是需要用户在登录状态下才能运行的,例如编辑博客等等
  4. 进入到第二个if ,我们就判断store.getters.isLogin状态是true还是false,再取他的反值,也就是说我isLogin是true,说明他处于登录状态,而他的反值就是false,也就是直接next,而他如果处于isLogin是false,说明他处于没登录,而他的反值就是true,也就是进入第一个next,会跳转到登录页面,

params、query 是什么?

  • params:/router1/:id,这里的 id 叫做 params。例如/router1/123, /router1/789
  • query:/router1?id=123,这里的 id 叫做 query。例如/router1?id=456

query方式传参和接收参数

传参: 
this.$router.push({
        path:'/xxx',
        query:{
          id:id
        }
      })
接收参数:
this.$route.query.id

注意:传参是this.$router,接收参数是this.$route,这里千万要看清了!!!


this.router和this.router 和this.routerthis.route有何区别?

在控制台打印两者可以很明显的看出两者的一些区别:

网络异常,图片无法展示
|

  • 1.router为VueRouter实例,想要导航到不同URL,则使用router为VueRouter实例,想要导航到不同URL,则使用routerVueRouterURL使router.push方法
  • 2.$route为当前router跳转对象,里面可以获取name、path、query、params等

params方式传参和接收参数

传参: 
this.$router.push({
        name:'xxx',
        params:{
          id:id
        }
      })
接收参数:
this.$route.params.id

注意:params传参,push里面只能是 name:'xxxx',不能是path:'/xxx',因为params只能用name来引入路由,如果这里写成了path,接收参数页面会是undefined!!!

另外,二者还有点区别,直白的来说query相当于get请求,页面跳转的时候,可以在地址栏看到请求参数,而params相当于post请求,参数不会再地址栏中显示


总结

思路:

1创建脚手架 vue-cli

2创建路由 创建每个网页对应的path,以及引入每一个网页的组件,一个page对应三个文件

3数据请求的接口封装 jwt模式(token);封装request接口函数,返回一个promise对象

4api接口封装-auth 将我们用到的后端接口传入到写好的数据请求接口,返回request函数

5blog接口封装-blog 同上

6用到element-ui组件 提供很多的组件

7项目结构 分析项目解构,将网页的header和footer写入组件,将组件写入APP.vue里面,这样我们就省了好多代码,不用再每一个page里面写他们对应的header和footer了,只要我们把我们的路由里每一个page写好就可以,我们只需要在header组件和footer组件的中间写入我们的路由组件就可以 ,我们改对应路由组件的内容就可以了

8写页面样式 响应式@media(接受最大宽度){里面的选择器怎样怎样变化}

9使用vuex 对我们页面登录,修改博客等操作进行状态管理,新建一个Stroe文件夹

10登录和注册页面 这里登录input使用v-model实现双向绑定,当点击提交的时候,调用onLogin方法,而这个方法是和vuex状态有这联系,当点击onLogin将调用vuex里面的login方法,我们在input传入的账号密码,使用v-model实行双向绑定时,我们的data里面就能收到username和password,将这个账号密码作为对象传入login方法,我们就实行了登录;至于注册也是同样的道理

11路由的完善 (路由元信息)需要判断我们是否处于登录状态,才能去操作的路由,加一个meta参数,以及id,的不同页面也不同,比如某一篇博客id为1就是一篇,另外一个id为2 ,那就是另外一个

12登录鉴权 检查有meta属性有一个参数为true;只要有这个参数,我就要检查用户是否砸登录状态,如果用户在登录我就next(),如果没有在登录状态我就要它跳去登录页面;

13模块懒加载 对我们的路由组件进行异步加载;作用是我加载页面的时候不用一次性就把全部的页面js加载完,而是说我用到哪个页面的js我才加载哪个页面的js

14其他页面的完成 在api调用对应的函数方法,细节的完善

相关文章
|
7天前
|
JavaScript 前端开发
如何在 Vue 项目中配置 Tree Shaking?
通过以上针对 Webpack 或 Rollup 的配置方法,就可以在 Vue 项目中有效地启用 Tree Shaking,从而优化项目的打包体积,提高项目的性能和加载速度。在实际配置过程中,需要根据项目的具体情况和需求,对配置进行适当的调整和优化。
|
8天前
|
存储 缓存 JavaScript
在 Vue 中使用 computed 和 watch 时,性能问题探讨
本文探讨了在 Vue.js 中使用 computed 计算属性和 watch 监听器时可能遇到的性能问题,并提供了优化建议,帮助开发者提高应用性能。
|
8天前
|
存储 缓存 JavaScript
如何在大型 Vue 应用中有效地管理计算属性和侦听器
在大型 Vue 应用中,合理管理计算属性和侦听器是优化性能和维护性的关键。本文介绍了如何通过模块化、状态管理和避免冗余计算等方法,有效提升应用的响应性和可维护性。
|
8天前
|
存储 缓存 JavaScript
Vue 中 computed 和 watch 的差异
Vue 中的 `computed` 和 `watch` 都用于处理数据变化,但使用场景不同。`computed` 用于计算属性,依赖于其他数据自动更新;`watch` 用于监听数据变化,执行异步或复杂操作。
|
7天前
|
JavaScript 前端开发 UED
vue学习第二章
欢迎来到我的博客!我是一名自学了2年半前端的大一学生,熟悉JavaScript与Vue,目前正在向全栈方向发展。如果你从我的博客中有所收获,欢迎关注我,我将持续更新更多优质文章。你的支持是我最大的动力!🎉🎉🎉
|
9天前
|
存储 JavaScript 开发者
Vue 组件间通信的最佳实践
本文总结了 Vue.js 中组件间通信的多种方法,包括 props、事件、Vuex 状态管理等,帮助开发者选择最适合项目需求的通信方式,提高开发效率和代码可维护性。
|
7天前
|
JavaScript 前端开发 开发者
vue学习第一章
欢迎来到我的博客!我是瑞雨溪,一名热爱JavaScript和Vue的大一学生。自学前端2年半,熟悉JavaScript与Vue,正向全栈方向发展。博客内容涵盖Vue基础、列表展示及计数器案例等,希望能对你有所帮助。关注我,持续更新中!🎉🎉🎉
|
21天前
|
数据采集 监控 JavaScript
在 Vue 项目中使用预渲染技术
【10月更文挑战第23天】在 Vue 项目中使用预渲染技术是提升 SEO 效果的有效途径之一。通过选择合适的预渲染工具,正确配置和运行预渲染操作,结合其他 SEO 策略,可以实现更好的搜索引擎优化效果。同时,需要不断地监控和优化预渲染效果,以适应不断变化的搜索引擎环境和用户需求。
|
9天前
|
存储 JavaScript
Vue 组件间如何通信
Vue组件间通信是指在Vue应用中,不同组件之间传递数据和事件的方法。常用的方式有:props、自定义事件、$emit、$attrs、$refs、provide/inject、Vuex等。掌握这些方法可以实现父子组件、兄弟组件及跨级组件间的高效通信。
|
14天前
|
JavaScript
Vue基础知识总结 4:vue组件化开发
Vue基础知识总结 4:vue组件化开发