vue3-ts-cms项目学习笔记

341 阅读6分钟

项目构建

第三方库集成

element-plus

全局引用

所有的组件全部集成

优点:集成比较简单 缺点:全部打包

按需引用

优点:包会小一点 缺点:引用起来稍微麻烦一点

npm install -D unplugin-vue-components unplugin-auto-import
```js
// vue.config.js
const { defineConfig } = require('@vue/cli-service')
const AutoImport = require('unplugin-auto-import/webpack')
const Components = require('unplugin-vue-components/webpack')
const { ElementPlusResolver } = require('unplugin-vue-components/resolvers')
module.exports = defineConfig({
  transpileDependencies: true,
  outputDir: './build',
  configureWebpack: {
    plugins: [
      // ...
      AutoImport({
        resolvers: [ElementPlusResolver()]
      }),
      Components({
        resolvers: [ElementPlusResolver()]
      })
    ]
  }
})
<template>
  <div id="main">
    <h1>main页面</h1>
    <el-button type="primary">按钮</el-button>
  </div>
</template>

axios集成

npm install axios

模拟请求

模拟请求

import axios, { AxiosResponse } from 'axios'

// axios的实例对象
axios
  .get('http://httpbin.org/get', {
    params: {
      name: 'cvCoder'
    }
  })
  .then((res: AxiosResponse) => {
    console.log(res)
  })

请求拦截

可以做哪些

  • 设置请求需要携带的token:header[token]='token'
  • 请求时间比较长时,显示loading
  • 修改返回的响应数据
// axios的拦截器
// fn1: 请求发送成功会执行的函数
// fn2:请求发送失败会执行的函数
// axios.interceptors.request.use(fn1, fn2)

axios.interceptors.request.use(
  (config) => {
    // 1.给请求添加token
    // 2,.loading动画
    console.log('请求成功的拦截')

    return config
  },
  (err) => {
    console.log('请求发送错误')
    return err
  }
)
// axios.interceptors.response.use(fn1, fn2)
// fn1: 数据响应成功(服务器正常的返回了数据 20x[响应状态])
// fn2: 数据响应失败
axios.interceptors.response.use(
  (res) => {
    console.log('响应成功的拦截')
    return res // 修改返回的响应数据
  },
  (err) => {
    console.log('服务器响应失败')
    return err
  }
)

区分不同环境

  • 在开发中,有时候我们需要根据不同的环境设置不同的环境变量,常见的有三种环境:

    • 开发环境:development;

    • 生产环境:production;

    • 测试环境:test;

  • 如何区分环境变量呢?常见有三种方式:

    • 方式一:手动修改不同的变量;

    • 方式二:根据process.env.NODE_ENV的值进行区分;

    • 方式三:编写不同的环境变量配置文件;

/**
 * 开发 http://httpbin.org/dev
 * 生产 http://httpbin.org/prod
 *
 */
// 根据process.env.NODE_ENV
/**
 * 开发环境:development
 * 成产环境:production
 * 测试环境:test
 */
let BASE_URL = ''
let BASE_NAME = ''
console.log(process.env.NODE_ENV)
if (process.env.NODE_ENV === 'development') {
  BASE_URL = 'http://httpbin.org/dev'
  BASE_NAME = 'D'
} else if (process.env.NODE_ENV === 'production') {
  BASE_URL = 'http://httpbin.org/prod'
  BASE_NAME = 'P'
} else {
  BASE_URL = 'http://httpbin.org/test'
  BASE_NAME = 'T'
}
export { BASE_URL, BASE_NAME }

VueCLI下

VueCLI-环境变量

image.png

  • 名字不可以乱起
    • 1.BASE_URL
    • 2.NODE_ENV
    • 3.VUE_APP_XXXX[以VUE_APP_开头]

只有 NODE_ENVBASE_URL 和以 VUE_APP_ 开头的变量将通过 webpack.DefinePlugin 静态地嵌入到客户端侧的代码中

// .env.development文件
VUE_APP_BASE_URL = http://httpbin.org/development
VUE_APP_BASE_NAME = D
// .env.production文件
VUE_APP_BASE_URL = http://httpbin.org/prod
VUE_APP_BASE_NAME = p
// .env.test
VUE_APP_BASE_URL = http://httpbin.org/test
VUE_APP_BASE_NAME = T
// main.ts
console.log(process.env.VUE_APP_BASE_URL)
console.log(process.env.VUE_APP_BASE_NAME)

不同环境打印对应环境内容

axios封装

  • axios第三方库不维护(浏览器升级、webpack更新等问题就会产生bug)
  • 后续更换新的库时,项目中对axios都有依赖,那么很多地方都要修改,会产生很多冗余操作
  • 共同的特性抽离,避免冗余
// 使用
import cvRequest from './service'
interface DataType {
  data: any
  returnCode: string
  success: boolean
}
cvRequest
  .request<DataType>({
    showLoadings: true,
    method: 'post',
    url: '/post'
  })
  .then((res) => {
    console.log('res----', res.data)
  })
cvRequest
  .post<DataType>({
    showLoadings: true,
    url: '/post'
  })
  .then((res) => {
    console.log('res----post', res.data)
  })

ts配置文件

ts源于JS,归于JS

tsconfig.json

编译选项:compilerOptions

  • target: esnext
    • 目标代码(ts -> js[es5|6|7])
    • 配置项目的时候选择了babel编译,后面最终会用把babel对代码再做转换,babel不需要配置目标es,因为他会根据配置的.browserslistrc来自动适配浏览器
  • module
    • 目标代码需要使用的模块化方案([commonJS:require/module.exports][es-module:import/export]【umd: 多种模块化】)
  • strict
    • 严格模式的检查
  • jsx
    • 对JSX进行怎么样的处理(h函数、createElement,preserve[保留,不转化])
  • importHelpers
    • 辅助的导入功能
  • moduleResolution
    • 按照node的方式去解析模块
  • skipLibCheck
    • 跳过一些库的类型检测
  • esModuleInterop、allowSyntheticDefaultImports
    • export default 和 module.exports = {} 能不能混合一起使用
    • es moudle和commonJS
  • sourceMap
    • 要不要生成映射文件
  • baseUrl
    • 文件路径在解析时,基本URL
  • types
    • 指定具体要解析使用的类型
  • paths
    • 路径解析
  • lib
    • 可以指定在项目中可以使用哪些库的类型(Proxy/window/Document) include

哪些代码需要进行编译解析 exclude 排除哪些内容

脚手架创建的配置一般不需要修改

说明

image.png

  • vue文件中的defineComponent
    • 意义:TS,类型推导

image.png

业务相关代码

样式相关

  • 把脚手架默认的样式内容删除
  • 添加初始化样式内容

初始化文件

方法一:github搜 方法二:npm i normalize.css

编写业务逻辑的相关内容

获取组件的实例类型

<login-account ref='accountRef' />
const accountRef = ref<InstanceType<typeof LoginAccount>>()

获取实例类型的原因,当通过ref去操作组件的内容时,可以有提示,使得编写逻辑更加的严谨

image.png image.png

TS-InstanceType

緩存

image.png

登录相关处理

  • 1.登录的逻辑(网络请求,拿到数据后的处理)
  • 2.数据保存到某一个位置
  • 3.发送其他的请求(请求当前用户的信息)
  • 4.拿到用户的菜单(动态路由菜单,根据角色获取【角色权限管理】)
  • 5.跳到首页

获取到的数据在多处使用(共享数据),将其保存在VUEX。

image.png

文件夹介绍

  • 多个项目公共的组件放在base-ui文件夹中
  • 主要的组件放在components文件夹中

vuex对TS的支持较差

其中在useStore上面可以体现

image.png

解决

// store/index.ts
import { createStore, Store, useStore as useVuexStore }
import { IRootState, IStoreType } from './types'
export function useStore(): Store<IStoreType> {
    return useVuexStore()
}
import { ILoginState } from './login/types'
export interface IRootState {
  name: string
  age: number
}
export interface IRootWithModule {
  login: ILoginState
}
export type IStoreType = IRootState & IRootWithModule

image.png

element-plus使用

element-plus使用

菜单权限控制

role based access control 基于角色的访问控制

image.png

  • 方法一: 所有路由全部匹配

  • 方法二:根据角色 image.png

  • 方法三:菜单动态生成路由映射 菜单-路由映射 菜单-url-路由-path-component

image.png

image.png

自动构建

npm install coderwhy -g

新建

// add3page -> 3表示vue3
// user -> 表示组件
// -d -> d表示dest
// src/views/main/user -》 表示安装在哪个目录下
coderwhy add3page user -d src/views/main/user

coderWhy-自动构建

自动构建组件以及相关的路由引用

1678348576267.jpg

动态路由配置

require.context

// 遍历获取指定目录下的所有ts文件
const routeFiles = require.context('../router/main', true, /\.ts/)
// true: 表示递归查找

动态路由下刷新,页面不存在

54e8f325288c0e18ba82b777f74d15c.png

setupStore()
app.use(router)
app.use(store)

如上不能解决,考虑网友方法:解决

首次进入主页定位到第一个路由

直接在router页面的redirect到指定的路由,缺点: 当动态路由的数据发生变化,redirect的内容需要手动跟着调整

解决:

根据获取的菜单数据去拿到第一个路由firstRoute

router.beforeEach((to) => {
  console.log('to', to, routes)
  if (to.path !== '/login') {
    const token = localCache.getCache('token')
    if (!token) {
      // router.push('/login')
      return '/login'
    }
    if (to.path === '/main') {
      return firstRoute?.path
    }
  }
})

vue3的TS

获取组件实例的类型

import loginAccount from './login-account.vue'
/**
 * InstanceType<typeof loginAccount>> 获取组件实例
 */
const accountRef = ref<InstanceType<typeof loginAccount>>()

store中子模块

import { ILoginState } from './login/types' //
import { ISystemState } from './main/system/types'
import { IAnalysis } from './main/analysis/types'
/**
 * IRootState 根
 */
export interface IRootState {
  name: string
  age: number
  entireRoles: any[]
  entireDepartments: any[]
  entireMenus: any[]
}
/**
 * 各个模块
 * login system analysis
 */
export interface IRootWithModule {
  login: ILoginState
  system: ISystemState
  analysis: IAnalysis
}
/**
 * 由于vuex对TS的兼容性不好,在模块上的内不能直接提示(根类型取子模块),所以使用交叉类型
 */
export type IStoreType = IRootState & IRootWithModule
// store/index,ts
/**
 * 使用自己定义的useStore的原因
 * 由于vuex对TS的兼容不是很好
 * 当模块进行引用获取相关内容的时候,可能存在没有提示的情况等
 */
export function useStore(): Store<IStoreType> {
  return useVuexStore()
}

其他知识点

小技巧