用 create-vue 从 0 到 1 搭建一个后台管理系统

当前网站开发用到的是 Vue3 版本,如果平时项目用 Vue2 版本可以当扩展阅读,在网站和目录布局设计上看其实两个版本差太多。当然很多细节实现还是有一些差异的。

1. 环境准备

1.1 安装 nodejs

如果本地没有装 node的先去 node 官网 进行下载。

1.2 安装 Vue CLI

现在 Vue 官网建议使用 create-vue 去搭建 vue 项目。

2. 搭建 Vue 项目

2.1 使用 CLI 创建项目

执行命令 npm init vue@3
![image.png](https://img-blog.csdnimg.cn/img_convert/184bc5d3e8524850d1cffb9d3f370844.png#averageHue=#191e24&clientId=u59131d76-f2bf-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=217&id=ue63fedff&margin=[object Object]&name=image.png&originHeight=433&originWidth=736&originalType=binary&ratio=1&rotation=0&showTitle=false&size=69772&status=done&style=none&taskId=u020e39ec-8e15-4159-9b4a-f39d63a5b75&title=&width=368)

2.2 安装依赖

进入项目目录去安装相应的依赖。推荐使用 pnpm进行安装依赖,但是现阶段 pnpm install有时候会有依赖缺失问题,所以可以先进行 pnpm install,如果有依赖缺失的话,可以使用 npm install进行安装。
![image.png](https://img-blog.csdnimg.cn/img_convert/409df297c34dc6f31cfc46391bcbe2a0.png#averageHue=#1a1f24&clientId=u59131d76-f2bf-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=378&id=uc4f7eb35&margin=[object Object]&name=image.png&originHeight=378&originWidth=348&originalType=binary&ratio=1&rotation=0&showTitle=false&size=33574&status=done&style=none&taskId=ube1b448c-a647-44f3-81aa-e95206c8010&title=&width=348)

2.3 启动项目

如果不知道启动的命令可以打开 package.json进行查看。也可以通过 cat package.json进行查看。
![image.png](https://img-blog.csdnimg.cn/img_convert/a111b932fc239e11dbcd46c31b298d59.png#averageHue=#1c1f25&clientId=u59131d76-f2bf-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=113&id=ud5e10213&margin=[object Object]&name=image.png&originHeight=151&originWidth=583&originalType=binary&ratio=1&rotation=0&showTitle=false&size=18236&status=done&style=none&taskId=uae0fddf4-2e92-41ac-922a-d475a96d25b&title=&width=437)
执行命令 npm run dev启动。💢 可恶报错了。
![image.png](https://img-blog.csdnimg.cn/img_convert/766a3acc86a7706549575ed4d2c14610.png#averageHue=#15191f&clientId=u59131d76-f2bf-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=97&id=uebf5dca4&margin=[object Object]&name=image.png&originHeight=129&originWidth=605&originalType=binary&ratio=1&rotation=0&showTitle=false&size=16438&status=done&style=none&taskId=u436d25a5-3d6c-4cba-8e6f-9c8c704022e&title=&width=454)
然后经过一番周旋知道是因为node过低导致的。所以我们借助 nrm或者 n来切换我们使用的 node 版本。这里我用 n安装了 18.12.0版本。
![image.png](https://img-blog.csdnimg.cn/img_convert/f0fc3c725de03847c0a94592c0b9acaf.png#averageHue=#181c21&clientId=u59131d76-f2bf-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=111&id=u5359ef7e&margin=[object Object]&name=image.png&originHeight=148&originWidth=733&originalType=binary&ratio=1&rotation=0&showTitle=false&size=16286&status=done&style=none&taskId=u7de58404-c13a-4646-9d8f-70cf20379ac&title=&width=548)
然后使用 node/18.12.0版本。最后,我们再去试一下,项目能否正常运行。
![image.png](https://img-blog.csdnimg.cn/img_convert/a9f58605a12ef644f6fbe60b3177ff83.png#averageHue=#171d23&clientId=u59131d76-f2bf-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=115&id=u098cbc3d&margin=[object Object]&name=image.png&originHeight=115&originWidth=562&originalType=binary&ratio=1&rotation=0&showTitle=false&size=17842&status=done&style=none&taskId=u277ebe43-9d29-42ba-9bf6-c326b3ca141&title=&width=562)
![image.png](https://img-blog.csdnimg.cn/img_convert/4b6e20bb1d069f3366b6d5ece9c6e7f8.png#averageHue=#1a1a1a&clientId=u59131d76-f2bf-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=1518&id=u4168ba87&margin=[object Object]&name=image.png&originHeight=1518&originWidth=2860&originalType=binary&ratio=1&rotation=0&showTitle=false&size=419743&status=done&style=none&taskId=u4e5d5ce9-05f5-440c-8d95-92d7bb0c692&title=&width=2860)
🎉 项目正常运行了

2.4 规范目录结构

项目能正常运行之后,我们就需要在基础项目上,搭建一个完整的前端项目了。
![image.png](https://img-blog.csdnimg.cn/img_convert/9e1e339d9c23e5001e0341c329a2ce24.png#averageHue=#272728&clientId=u59131d76-f2bf-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=239&id=u51ef8464&margin=[object Object]&name=image.png&originHeight=477&originWidth=267&originalType=binary&ratio=1&rotation=0&showTitle=false&size=33587&status=done&style=none&taskId=uaaf81174-78ae-4d4c-ae09-3ffb413af37&title=&width=134)
稍微的改动一下目录结构,在原有的基础上增加了两个目录 utilsstyles。接下来我们具体列一下这几个目录的作用。
public放置静态资源目录,可通过路由拼接直接访问。
src/assets 放置图片等资源,不可通过url拼接直接访问,打包会直接打入代码内部。
src/components 放置公共的组件,提取的公共组件可放入这个文件夹。
src/router放置路由配置,可在内部实现进入路由和出路由处理业务的相关逻辑。
src/stores放置全局使用的变量,可以理解为一个单例。
src/styles放置全局通用的样式代码。
src/utils放置全局共用的脚本。
src/views放置网页代码。
App.vue为项目的入口界面。
main.ts为项目的入口文件。

2.5 使用 vue-router

我们通过脚手架搭建的项目,自己就自带了 vue-router,如果我们不通过脚手架的话,需要自己安装 vue-router。安装 vue-router通过下面指令。

# npm
$ npm i -S vue-router

# yarn
$ yarn add vue-router

# pnpm
$ pnpm i -S vue-router

其次,我们需要在主入口 main.ts文件中进行引用。

// main.ts
import router from '@/router'

// ...

app.use(router)

然后,我们进入 src/router文件中。看一下现有的代码是怎么写的。

// router/index.ts
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',
      // route level code-splitting
      // this generates a separate chunk (About.[hash].js) for this route
      // which is lazy-loaded when the route is visited.
      component: () => import('@/views/AboutView.vue')
    }
  ]
})

export default router

我们可以看到通过 vue-router的方法 createRouter创建了 router 实例。其中传参有两个,一个 history,另一个 routes。这两个参数有什么含义呢?vue-router路由几种模式:创建 h5 历史、创建 Hash 历史和创建基于内存的历史记录。对于这几种模式的讲解会在另外的博客中进行讲解。这里就不讲解了,节省文章内容。根据以上说明,可以了解 history现阶段分三种模式 createWebHistroycreateWebHashHistroycreateMemoryHistroy
routes是什么呢?
routes里面你可以理解为是存放网页访问界面的匹配规则。比如我现在启动项目,页面通过访问 http://localhost:5173就可以访问界面,这个界面访问的就是 ‘/’ 规则匹配的首页。
![image.png](https://img-blog.csdnimg.cn/img_convert/977cf1fc4f489385c1c6798358195180.png#averageHue=#292a36&clientId=u59131d76-f2bf-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=105&id=u4a67b28b&margin=[object Object]&name=image.png&originHeight=105&originWidth=293&originalType=binary&ratio=1&rotation=0&showTitle=false&size=9491&status=done&style=none&taskId=u871d6c54-017d-4a58-a27d-af6b6d66e30&title=&width=293)
那很明显,就是我们在网页访问 http://localhost:5173/about就可以访问 /about匹配的界面了。
![image.png](https://img-blog.csdnimg.cn/img_convert/8696bd6a728f94756b5f096b91de9929.png#averageHue=#2a2c39&clientId=u59131d76-f2bf-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=159&id=u71944089&margin=[object Object]&name=image.png&originHeight=159&originWidth=580&originalType=binary&ratio=1&rotation=0&showTitle=false&size=30729&status=done&style=none&taskId=uf674089a-d4df-4ef3-9130-d94029b26cf&title=&width=580)
![image.png](https://img-blog.csdnimg.cn/img_convert/89d47e0716222708c8ee5dc8450d250a.png#averageHue=#191919&clientId=u59131d76-f2bf-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=815&id=u4ce6c8fc&margin=[object Object]&name=image.png&originHeight=1630&originWidth=2866&originalType=binary&ratio=1&rotation=0&showTitle=false&size=259219&status=done&style=none&taskId=u2210a272-cf1d-49d4-9b81-83c5717cfa4&title=&width=1433)
接下来,我们自己写一个界面,写一个简单的那就个人信息吧!让我们更熟悉一下界面是怎么生成的。
首先,我们在 src/views目录下新建一个文件src/user/basic/index.vue

备注: 这里我们统一文件命名以小写字母。

src/user/basic/index.vue具体的内容为下面。(Vue2 和 Vue3 的一个差别就是Vue3支持 Fragment 碎片化,节省了多余标签的定义)

// src/user/basic/index.vue
<template>
  <h1>个人信息</h1>
  <div>
    <!-- 展示具体的个人信息 -->
  </div>
</template>

然后,我们需要在 router/index.ts文件中,新建一个规则去匹配这个界面。

// outer/index.ts
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',
      // route level code-splitting
      // this generates a separate chunk (About.[hash].js) for this route
      // which is lazy-loaded when the route is visited.
      component: () => import('@/views/AboutView.vue')
    },
    {
      path: '/basicInfo',
      name: 'basicInfo',
      component: () => import('@/views/user/basic/index.vue')
    }
  ]
})

export default router

通过 () => import()方式去拉取页面,是为了节省资源的加载引发的内存和时间。这种方式也被称为按需加载,只在正确的时机加载需要的内容。
![image.png](https://img-blog.csdnimg.cn/img_convert/34df56c547e09aeeddccf1527cc0804b.png#averageHue=#181818&clientId=u59131d76-f2bf-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=1592&id=u400e398f&margin=[object Object]&name=image.png&originHeight=1592&originWidth=2880&originalType=binary&ratio=1&rotation=0&showTitle=false&size=255657&status=done&style=none&taskId=u54bd6df9-41d6-43e6-84f1-dca0b9b54cc&title=&width=2880)
看,我们的界面代码起效果了,感觉有点不合适,我们将多余的内容给优化一下吧。
看看我们优化后的 App.vue的代码。删除了很多代码。当然还有很多 css 的样式的去处,在这里就不一一列举了。

// App.vue
<script setup lang="ts">
import { RouterView } from 'vue-router'
</script>

<template>
  <RouterView />
</template>

![image.png](https://img-blog.csdnimg.cn/img_convert/405f349f5c315ab0fda60e4369859ef8.png#averageHue=#fefdfd&clientId=u59131d76-f2bf-4&crop=0&crop=0&crop=1&crop=0.3305&from=paste&height=788&id=u098537d4&margin=[object Object]&name=image.png&originHeight=1576&originWidth=2878&originalType=binary&ratio=1&rotation=0&showTitle=false&size=212391&status=done&style=none&taskId=u06659146-7771-4d6d-b243-32f0cf31dce&title=&width=1439)

2.6 使用 vuex

在通过 create-vue创建的项目没有使用 vuex而是使用了Pinia,根据大部分人的习惯,我们这边还是对原来的项目进行改造一下。
首先,我们安装 vuex到项目中

# npm
$ npm i -S vuex

# yarn
$ yarn add vuex

# pnpm
$ pnpm i -S vuex

安装完成之后,我们在主入口 main.ts中引入。同时,去掉 Pinia

// main.ts
import store from '@/stores'

// ...
app.use(store)

文件 stores/index.ts的内容为

// stores/index.ts
import { createStore } from 'vuex'
import getters from './getters'

const modulesFiles: any = import.meta.globEager('./modules/*.ts')
const modules = Object.keys(modulesFiles).reduce((modules: Record<string, any>, modulePath: string) => {
  const moduleName = modulePath.replace(/^\.\/(.*)\.\w+$/, '$1')
  modules[moduleName] = modulesFiles[modulePath].default
  return modules
}, {})

export default createStore({
  getters,
  modules,
})

stores/getters.ts的内容为

// stores/getters.ts
export default {
  // TODO
}

然后目录结构为下面这张图展示
![image.png](https://img-blog.csdnimg.cn/img_convert/351d802fb1e9f679b192e9dc005f25eb.png#averageHue=#2a2b2d&clientId=u59131d76-f2bf-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=86&id=ua8cf7854&margin=[object Object]&name=image.png&originHeight=86&originWidth=240&originalType=binary&ratio=1&rotation=0&showTitle=false&size=9228&status=done&style=none&taskId=u965c1f47-44c9-41b9-9b04-e2d42947e72&title=&width=240)

注意在这边 index.ts 文件中会存在 类型“NodeReuire”上不存在属性“context”的报错,这里我们需要使用import.meta.globEager的方式去写

改造后的 stores/index.ts文件如下

// stores/index.ts
import { createStore } from 'vuex'
import getters from './getters'

const modulesFiles: any = import.meta.globEager('./modules/*.ts')
const modules = Object.keys(modulesFiles).reduce((modules: Record<string, any>, modulePath: string) => {
  const moduleName = modulePath.replace(/^\.\/(.*)\.\w+$/, '$1').replace('modules/', '')
  modules[moduleName] = modulesFiles[modulePath].default
  return modules
}, {})

export default createStore({
  getters,
  modules,
})

改造完成之后,我们需要测试一下这样的设计是否有用,所以我们需要在 stores/modules下面新建 user.ts,文件内容如下:

// stores/modules/user.ts
import type { ActionContext } from 'vuex'

export interface IUser {
  name: string;
}

const state = {
  name: '杨洋',
}

const mutations = {
  SER_NAME: (state: IUser, name: string) => {
    state.name = name
  }
}

const actions = {
  changeName(context: ActionContext<IUser, any>, name: string) {
    context.commit('SER_NAME', name)
  },
}

export default {
  namespace: true,
  state,
  mutations,
  actions,
}

stores/getters.ts进行改造

// stores/getters.ts
import type { IUser } from "./modules/user"

export default {
  name: (state: Record<'user', IUser>) => state.user.name
}

然后,在个人信息界面去使用

// src/user/basic/index.vue
<script lang="ts">
import { computed } from 'vue'
import { useStore, } from 'vuex'

export default {
  name: 'Basic',
  setup () {
    const store = useStore()
    const name = computed(() => store.state.user.name)
    return {
      name,
      changeName () {
        store.commit('SER_NAME', 'commit 开发杨洋')
      },
      changeDispatchName () {
        store.dispatch('changeName', 'dispatch 开发杨洋')
      }
    }
  }
}
</script>

<template>
  <h1>个人信息</h1>
  <div>
    <!-- 展示具体的个人信息 -->
    {{ name }}
    
    <button @click="changeName">commit 方式修改个人信息</button>
    <button @click="changeDispatchName">dipatch 方式修改个人信息</button>
  </div>
</template>

界面展示如图所示:
![image.png](https://img-blog.csdnimg.cn/img_convert/6ed6f07f1e491e50d1a5b5d08f366a5a.png#averageHue=#fbfafa&clientId=u59131d76-f2bf-4&crop=0&crop=0&crop=1&crop=0.6185&from=paste&height=732&id=ua6a2d032&margin=[object Object]&name=image.png&originHeight=732&originWidth=2872&originalType=binary&ratio=1&rotation=0&showTitle=false&size=168617&status=done&style=none&taskId=u1bd83233-c71d-42de-9160-c153a0c0dfd&title=&width=2872)

2.7 使用 axios 调用外部数据

Axios是基于 Promise 的 HTTP 客户端,用于 node.js 和浏览器。

我们的网页肯定不是静态网站,所以我们需要跟外界,比如跟服务器进行交互,由此我们需要在项目中使用 axios
在我们网站中通过 npm安装 axios

# npm
$ npm i -S axios

# yarn
$ yarn add axios

# pnpm
$ pnpm i -S axios

安装完成之后,我们在 utils目录下面新建文件 request.ts文件。文件内容如下:

// utils/request.ts
import axios from 'axios'

const service = axios.create({
  baseURL: '', // url = basic url + request url
  timeout: 5000, // 请求超时时间设定,超过时间限制,自动认为请求失败
})

// 请求拦截器
service.interceptors.request.use(
  config => {
    // 在发送请求的过程中,需要验证当前的 Token 或其他相关权限校验
    return config
  },
  error => {
    console.error(error)
    return Promise.reject(error)
  }
)

// 相应拦截器
service.interceptors.response.use(
  response => {
    const res = response.data

    // 在这里根据不同的返回 code 去做不同的逻辑判断

    return res
  },
  error => {
    console.error(error)
    return Promise.reject(error)
  }
)

export default service

代码使用

// src/user/basic/index.vue
<script lang="ts">
import { computed } from 'vue'
import { useStore, } from 'vuex'
import request from '@/utils/request'

export default {
  name: 'Basic',
  setup () {
    const store = useStore()
    const name = computed(() => store.state.user.name)

    // 请求接口
    request.get('http://www.baidu.com').then(res => {
      console.log('得到的数据', res)
    })

    return {
      name,
      changeName () {
        store.commit('SER_NAME', 'commit 开发杨洋')
      },
      changeDispatchName () {
        store.dispatch('changeName', 'dispatch 开发杨洋')
      }
    }
  }
}
</script>

2.8 集成 Element Plus 绘制基础界面

现阶段用的组件库比较流行的是 Element Plus。官网地址: https://element-plus.org/zh-CN/
在项目中安装并使用:

# 选择一个你喜欢的包管理器

# NPM
$ npm install element-plus --save

# Yarn
$ yarn add element-plus

# pnpm
$ pnpm install element-plus

main.ts中引入

// main.ts
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'

// ...

app.use(ElementPlus)

// ...

改造一下基础信息界面

// src/user/basic/index.vue
<script lang="ts">
import { computed } from 'vue'
import { useStore, } from 'vuex'

export default {
  name: 'Basic',
  setup () {
    const store = useStore()
    const name = computed(() => store.state.user.name)
    return {
      name,
      changeName () {
        store.commit('SER_NAME', 'commit 开发杨洋')
      },
      changeDispatchName () {
        store.dispatch('changeName', 'dispatch 开发杨洋')
      }
    }
  }
}
</script>

<template>
  <h1>个人信息</h1>
  <div>
    <!-- 展示具体的个人信息 -->
    {{ name }}
    
    <el-button type="primary" @click="changeName">commit 方式修改个人信息</el-button>
    <el-button type="success" @click="changeDispatchName">dipatch 方式修改个人信息</el-button>
  </div>
</template>

![image.png](https://img-blog.csdnimg.cn/img_convert/f04c6b5ba0a06c34c2a38da2611106a5.png#averageHue=#50312c&clientId=u59131d76-f2bf-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=329&id=uf0771091&margin=[object Object]&name=image.png&originHeight=658&originWidth=2866&originalType=binary&ratio=1&rotation=0&showTitle=false&size=170443&status=done&style=none&taskId=uba65609d-2533-4785-a601-b1e7571c897&title=&width=1433)

3. 总结

大体的界面代码目录结构和基础代码都写完了,如有不对的地方,请指出,谢谢🙏

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值