Vue3.2开发一个博客Demo项目

Vue3.2开发一个博客Demo项目

技术选型
Vue3 + Vue-router + Pinia + ElementPlus

代码地址
https://gitee.com/galen.zhang/vue3-demo/tree/master/tech-blog

Mock后台服务器代码
https://gitee.com/galen.zhang/vue3-demo/tree/master/mock-server

cd mock-server
npm install
npm run dev

创建项目

npm create vue@latest
√ Project name: ... tech-blog
√ Add TypeScript? ... No 
√ Add JSX Support? ... No 
√ Add Vue Router for Single Page Application development? ...  Yes
√ Add Pinia for state management? ...  Yes
√ Add Vitest for Unit Testing? ... No 
√ Add an End-to-End Testing Solution? » No
√ Add ESLint for code quality? ... Yes
√ Add Prettier for code formatting? ... Yes

cd tech-blog
npm install
npm run format
npm run dev

浏览器访问
http://localhost:5173/


安装Element Plus

官方文档
https://element-plus.org/zh-CN/guide/installation.html

安装Element Plus

npm install element-plus --save
npm install @element-plus/icons-vue

src/main.js 中引入Element Plus

import './assets/main.css'

import { createApp } from 'vue'
import { createPinia } from 'pinia'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'

import * as ElementPlusIconsVue from '@element-plus/icons-vue'

import App from './App.vue'
import router from './router'

const app = createApp(App)

for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
    app.component(key, component)
}

app.use(ElementPlus)
app.use(createPinia())
app.use(router)

app.mount('#app')

路由配置

修改文件 src/router/index.js

import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'

const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes: [
    {
      path: '/',
      name: 'home',
      component: HomeView
    },
    {
      path: '/about',
      name: 'about',
      component: () => import('../views/AboutView.vue')
    },
    {
      path: '/blog/:id',
      component: () => import('../views/BlogDetailView.vue')
    },
    {
      path: '/blog/:id/edit',
      component: () => import('../views/CreateEditBlogView.vue')
    },
    {
      path: '/create',
      component: () => import('../views/CreateEditBlogView.vue')
    },
    {
      path: '/myBlog',
      component: () => import('../views/MyBlogView.vue')
    },
    {
      path: '/register',
      component: () => import('../views/RegisterView.vue')
    },
    {
      path: '/login',
      component: () => import('../views/LoginView.vue')
    }
  ]
})

export default router

封装网络请求工具类Axios

npm install axios --save
// 进度条
npm install nprogress --save

封装网络请求工具类 src/utils/request.js

import axios from 'axios'
import NProgress from 'nprogress'
import 'nprogress/nprogress.css'

export const serverUrl = 'http://localhost:3000'

const service = axios.create({
  baseURL: serverUrl,
  timeout: 5000
})

// Add a request interceptor 全局请求拦截
service.interceptors.request.use(
  function (config) {
    // Do something before request is sent
    const token = localStorage.getItem('token')
    if (token) {
      config.headers['Authorization'] = `Bearer ${token}`
    }
    NProgress.start() // 启动进度条
    // 此处还可以设置token
    return config
  },
  function (error) {
    // Do something with request error
    return Promise.reject(error)
  }
)

// Add a response interceptor 全局相应拦截
service.interceptors.response.use(
  function (response) {
    // Any status code that lie within the range of 2xx cause this function to trigger
    // Do something with response data
    NProgress.done()

    // 如果是固定的数据返回模式,此处可以做继续完整的封装
    const resData = response.data || {}
    if (resData.code == '0') {
      return resData.data
    }
    return Promise.reject(resData.message)
  },
  function (error) {
    // Any status codes that falls outside the range of 2xx cause this function to trigger
    // Do something with response error
    NProgress.done()

    // 此处需要对返回的状态码或者异常信息作统一处理
    return Promise.reject(error)
  }
)

export const get = (url, params) => {
  return service.get(url, {
    params
  })
}

export const post = (url, data) => service.post(url, data)

export const put = (url, data) => service.put(url, data)

export const del = (url, data) => service.delete(url)


后台请求api接口

博客后台请求接口api文件 src/api/blogApi.js

import { get, post, put, del } from "../utils/request";

// 获取博客列表
export async function getBlogList(opt = {}) {
    const { page = 1, pageSize = 10, category = '', keyword = '', my = false } = opt;
    return get(`/api/blogs?page=${page}&pageSize=${pageSize}&category=${category}&keyword=${keyword}&my=${my}`);
}

// 创建博客
export async function createBlog(data) {
    return post('/api/blogs', data);
}

// 编辑博客
export async function editBlog(id, data) {
    return put(`/api/blogs/${id}`, data);
}

// 删除博客
export async function deleteBlog(id) {
    return delete(`/api/blogs/${id}`);
}

// 根据 ID 获取单个博客
export async function getBlogById(id) {
    return get(`/api/blogs/${id}`);
}

// 点赞博客
export async function likeBlog(id) {
    return post(`/api/blogs/${id}/like`);
}

// 取消点赞博客
export async function unlikeBlog(id) {
    return del(`/api/blogs/${id}/like`);
}

// 收藏博客
export async function favoriteBlog(id) {
    return post(`/api/blogs/${id}/favorite`);
}

// 取消收藏博客
export async function unfavoriteBlog(id) {
    return del(`/api/blogs/${id}/favorite`);
}

export default {
    getBlogList,
    createBlog,
    editBlog,
    deleteBlog,
    getBlogById,
    likeBlog,
    unlikeBlog,
    favoriteBlog,
    unfavoriteBlog
}

封装hooks函数

修改页面标题
添加文件 src/hooks/usePageTitle.js

import { ref, isRef, onMounted, onBeforeUnmount, watchEffect } from 'vue'

const NAME = 'TechBlog'

function usePageTitle(title) {
  const originalTitle = ref(document.title)

  // 更新网页标题
  const updatePageTitle = () => {
    const titleValue = isRef(title) ? title.value : title
    if (!titleValue) {
      return
    }
    document.title = titleValue + ' - ' + NAME
  }

  if (isRef(title)) {
    watchEffect(updatePageTitle)
  }

  // 在组件挂载时更新网页标题
  onMounted(() => {
    updatePageTitle()
  })

  // 在组件卸载时恢复原始网页标题
  onBeforeUnmount(() => {
    document.title = originalTitle.value
  })

  return {
    updatePageTitle
  }
}

export default usePageTitle

在页面上使用,如登录页面



封装首页导航栏组件

添加文件 src/components/NavMenu.vue




  


封装首页搜索组件

添加文件 src/components/SearchInput.vue







用户注册页

form表单提交数据 src/views/RegisterView.vue





  

  

用户登录页

在用户登录后,存储token到本地 src/views/LoginView.vue








状态管理Pinia

Pinia使用例子
src/stores/counter.js

import { ref, computed } from 'vue'
import { defineStore } from 'pinia'

export const useCounterStore = defineStore('counter', () => {
  const count = ref(0)
  const doubleCount = computed(() => count.value * 2)
  function increment() {
    count.value++
  }

  return { count, doubleCount, increment }
})

在页面中使用 counter







使用Pinia管理用户状态

添加文件 src/stores/user.js

import { reactive } from 'vue'
import { defineStore } from 'pinia'

export const useUserStore = defineStore('user', () => {
  const userInfo = reactive({
    username: '',
    nickname: ''
  })

  function setUserInfo({ username, nickname }) {
    userInfo.username = username
    userInfo.nickname = nickname
  }

  function clearUserInfo() {
    userInfo.username = ''
    userInfo.nickname = ''
  }

  return { userInfo, setUserInfo, clearUserInfo }
})

在首页 src/App.vue 中获取用户信息








封装用户菜单组件

添加文件 src/components/UserInfo.vue



 

添加hooks函数,判断登录状态

添加文件 src/hooks/useNavToHome.js

import { watch } from 'vue';
import { useRouter } from 'vue-router';
import { useUserStore } from '@/stores/user';

function useNavToHome() {
    const router = useRouter();
    const { userInfo } = useUserStore()
    // 监听 userInfo.username ,如果有值,说明用户已经登录,跳转到首页
    watch(() => userInfo.username, (val) => {
        if (val) {
            router.push('/');
        }
    }, { immediate: true });
}

export default useNavToHome

在注册、登录页面中添加,如果已登录则跳转到首页

import useNavToHome from '@/hooks/useNavToHome'

useNavToHome()

首页列表分页展示博客信息

添加卡片式组件,展示博客关键信息 src/components/BlogCard.vue




  
  
  

  

列表分页显示 src/views/HomeView.vue







博客详情页

博客内容存储为marddown格式的文本
安装markdown工具 markdown-it

npm install markdown-it --save

博客详情页 src/view/BlogDetailView.vue







封装分页组件

添加文件 src/components/ListPage.vue







我的博客页面

使用table表格展示列表数据 src/views/MyBlogView.vue







新建、编辑博客页面

添加文件 src/views/CreateEditBlogView.vue







性能优化–打包时拆分第三方依赖

打包项目

npm run build

可以查看打包后每个文件的大小

安装 Import Cost 插件,可以在每个vue文件中,查看第三方依赖库的文件大小

Vue、Element-Plus 可以拆分出来,减小首页index文件的大小
修改配置文件 vite.config.js

  build: {
    rollupOptions: {
      output: {
        manualChunks: {
          // 将 Vue 和 Element Plus 单独打包为一个公共块
          vue: ['vue'],
          'element-plus': ['element-plus'],
          'markdown-it': ['markdown-it']
        }
      }
    }
  }

你可能感兴趣的:(Vue,Vue)