vue3-ts-后台管理系统

315 阅读6分钟

项目搭建规范

一、代码规范

1.1. 新建 .editorconfig 文件

# http://editorconfig.org

root = true

[*] # 表示所有文件适用
charset = utf-8 # 设置文件字符集为 utf-8
indent_style = space # 缩进风格(tab | space)
indent_size = 2 # 缩进大小
end_of_line = lf # 控制换行类型(lf | cr | crlf)
trim_trailing_whitespace = true # 去除行首的任意空白字符
insert_final_newline = true # 始终在文件末尾插入一个新行

[*.md] # 表示仅 md 文件适用以下规则
max_line_length = off
trim_trailing_whitespace = false

VSCode 需要安装一个插件:EditorConfig for VS Code

1.2. 使用 prettier 工具

Prettier 是一款强大的代码格式化工具,基本上前端能用到的文件格式它都可以搞定,是当下最流行的代码格式化工具。

  1. 安装 prettier
npm install prettier -D
  1. 新建 .prettierrc 文件:
  • useTabs:使用tab缩进还是空格缩进,选择false;
  • tabWidth:tab是空格的情况下,是几个空格,选择2个;
  • printWidth:当行字符的长度,推荐80,也有人喜欢100或者120;
  • singleQuote:使用单引号还是双引号,选择true,使用单引号;
  • trailingComma:在多行输入的尾逗号是否添加,设置为 none
  • semi:语句末尾是否要加分号,默认值true,选择false表示不加;
{
  "useTabs": false,
  "tabWidth": 2,
  "printWidth": 80,
  "singleQuote": true,
  "trailingComma": "es5",
  "semi": true
}

image.png

trailing Comma

  1. 创建 .prettierignore 忽略文件
/dist/*
.local
.output.js
/node_modules/**

**/*.svg
**/*.sh
shims-vue.d.ts

/public/*
  1. VSCode 需要安装 prettier 的插件

  2. 配置一次性修改的命令;在 package.json 中配置一个 scripts

// 执行 npm run prettier 即可格式化所有文件
"prettier": "prettier --write ."

1.3. 使用 ESLint 检测

  1. 在前面创建项目的时候,我们就选择了 ESLint,所以 Vue 会默认帮助我们配置需要的 ESLint 环境。
  2. VSCode 需要安装 ESLint 插件:
  3. 解决 Eslintprettier 冲突的问题:

安装插件:eslint-plugin-prettier eslint-config-prettier

// vue在创建项目时,如果选择prettier,那么这两个插件会自动安装
npm i eslint-plugin-prettier eslint-config-prettier -D

.eslintrc 文件中添加 prettier 插件:

  extends: [
    "plugin:vue/vue3-essential",
    "eslint:recommended",
    "@vue/typescript/recommended",
    "@vue/prettier",
    "@vue/prettier/@typescript-eslint",
    'plugin:prettier/recommended' // 会覆盖前面几个的格式
  ],

1.4. git Husky 和 eslint

虽然项目使用 eslint 了,但不能保证组员提交代码之前都将 eslint 中的问题解决掉:故在组员执行 git commit 命令的时候对其进行校验,如果不符合 eslint 规范,那么自动通过规范进行修复;

我们可以通过 Husky 工具实现这一点:

  • husky 是一个 git hook 工具,可以帮助我们触发 git 提交的各个阶段:pre-commit、commit-msg、pre-push

执行自动配置命令:

// 注意:需把 powershell 换成 cmd,再执行该命令
npx husky-init && npm install

这里会做三件事:

  1. 安装 husky 相关的依赖:

image.png

  1. 在项目目录下创建 .husky 文件夹:
npx huksy install

image.png

  1. package.json 中添加一个脚本:

image.png

接下来,我们需要去完成一个操作:在进行 commit 时,执行 lint 脚本:

image.png

这个时候我们执行 git commit 的时候会自动对代码进行 lint 校验。

1.5. git commit 规范

Type作用
feat新增特性 (feature)
fix修复 Bug(bug fix)
docs修改文档 (documentation)
style代码格式修改(white-space, formatting, missing semi colons, etc)
refactor代码重构(refactor)
perf改善性能(A code change that improves performance)
test测试(when adding missing tests)
build变更项目构建或外部依赖(例如 scopes: webpack、gulp、npm 等)
ci更改持续集成软件的配置文件和 package 中的 scripts 命令,例如 scopes: Travis, Circle 等
chore变更构建流程或辅助工具(比如更改测试环境)
revert代码回退

分割线---------------------------------------------------------------

关于登录后的逻辑处理

一旦用户登录成功,逻辑部分的处理放在哪个地方合适呢?

  • 登录的逻辑(网络请求,拿到数据后的处理)
  • 数据的保存到某一个位置
  • 发送其他的请求(请求当前用户的信息)
  • 拿到用户的菜单
  • 跳转到首页

注意:如此众多的逻辑交给首页处理是不合适的,我们可以交给 vuex 来管理。

Form 表单进行二次封装

目的:其他人在使用时,只需注册、引用 Form 组件,传入配置即可。

支持如下配置

支持以下配置,即有这几种数据类型。故可设计 Forminterface 如下:

type IFormType = 'input' | 'password' | 'select' | 'datepicker'

export interface IFormItem {
  type: IFormType
  label: string
  rules?: any[]
  placeholder?: any
  // 针对select
  options?: any[]
  // 针对特殊的属性
  otherOptions?: any
}

export interface IForm {
  formItems: IFormItem[]
  labelWidth?: string
  colLayout: any
  itemLayout: any
}

  • labelWidthlabel 的宽度
labelWidth: '120px' // 设置 label 的宽度为 120px
  • itemLayoutitem 的左右宽度
itemLayout: {
  padding: '10px 40px'
}
  • colLayoutFormItem 项随着浏览器的宽度变化而变化。即响应式栅格数或者栅格属性对象
colLayout: {
    span: 8
},
  • formItems:配置每一项 item 的类型。
formItems: [
    {
      type: 'input',
      label: 'id',
      placeholder: '请输入id',
      rule: [{ required: false, message: '请输入id', trigger: 'change' }], // 当前规则
    },
    {
      type: 'input',
      label: '用户名',
      placeholder: '请输入用户名'
    },
    {
      type: 'password',
      label: '密码',
      placeholder: '请输入密码'
    },
    {
      type: 'select',
      label: '喜欢的运动',
      placeholder: '请选择喜欢的运动',
      options: [
        { title: '篮球', value: 'basketball' },
        { title: '足球', value: 'football' }
      ]
    },
    {
      type: 'datepicker',
      label: '创建时间',
      otherOptions: {
        startPlaceholder: '开始时间',
        endPlaceholder: '结束时间',
        type: 'daterange'
      }
    },
    
  ]
}

bug修复

bug描述:任意选中一个二级菜单进行刷新,左侧菜单栏始终选中用户管理。

  • bug原因:<el-menu default-active="2"></el-menu>,恰好用户管理的id为2
  • bug解决:default-active 的值应该动态获取。
  • 解决思路:获取 currentPath,并与 userMenus 中的 path 进行匹配,得到 menuId

代码实现:通过 useRoute 获取当前路径,并在 store 中取出 userMenus,使用递归方法 pathMapToMenu 获取当前路径的 menuId.

  • 获取 currentPath
import { useRouter, useRoute } from 'vue-router'
const route = useRoute()
const currentPath = route.path
  • 获取 userMenus
import { useStore } from '@/store'
const store = useStore()
const userMenus = computed(() => store.state.login.userMenus)
console.log(userMenus.value)
  • 匹配方法 pathMapToMenu
export function pathMapToMenu(userMenus: any[], currentPath: string): any {
  for (const menu of userMenus) {
    if (menu.type === 1) {
      const findMenu = pathMapToMenu(menu.children ?? [], currentPath)
      if (findMenu) {
        return findMenu
      }
    } else if (menu.type === 2 && menu.url === currentPath) {
      return menu
    }
  }
}

对面包屑的二次封装

使用者传入一个对象数组,包含 name 和 path 属性,即可生成可点击的面包屑。

  • 使用过程
<jn-breadcrumb :breadcrumbs="breadcrumbs" />
  • 封装过程
// 获取当前路径
import { useRoute } from 'vue-router'

const route = useRoute()
const currentPath = route.path

// 获取菜单
import { useStore } from '@/store'

const store = useStore()
const userMenus = store.state.login.userMenus

// 传入 userMenus, currentPath 得到一个包含 name 和 path 属性的对象数组
const breadcrumbs = pathMapBreadcrumbs(userMenus, currentPath)

实现 pathMapBreadcrumbs

export function pathMapBreadcrumbs(userMenus: any[], currentPath: string) {
  const breadcrumbs: IBreadcrumb[] = []
  pathMapToMenu(userMenus, currentPath, breadcrumbs)
  return breadcrumbs
}

// /main/system/role  -> type === 2 对应menu
export function pathMapToMenu(
  userMenus: any[],
  currentPath: string,
  breadcrumbs?: IBreadcrumb[]
): any {
  for (const menu of userMenus) {
    if (menu.type === 1) {
      const findMenu = pathMapToMenu(menu.children ?? [], currentPath)
      if (findMenu) {
        breadcrumbs?.push({ name: menu.name })
        breadcrumbs?.push({ name: findMenu.name })
        return findMenu
      }
    } else if (menu.type === 2 && menu.url === currentPath) {
      return menu
    }
  }
}

一些 bugs

PS D:\_B.study\_B2.front-end\_11.vue3-ts\bms-vue3-ts> npm run serve

> bms-vue3-ts@0.1.0 serve D:\_B.study\_B2.front-end\_11.vue3-ts\bms-vue3-ts
> vue-cli-service serve

 ERROR  Error: Cannot find module 'node:module'
        Require stack:
        - D:\_B.study\_B2.front-end\_11.vue3-ts\bms-vue3-ts\node_modules\mlly\dist\index.cjs
        - D:\_B.study\_B2.front-end\_11.vue3-ts\bms-vue3-ts\node_modules\unimport\dist\shared\unimport.a775c700.cjs
        - D:\_B.study\_B2.front-end\_11.vue3-ts\bms-vue3-ts\node_modules\unimport\dist\addons.cjs
        - D:\_B.study\_B2.front-end\_11.vue3-ts\bms-vue3-ts\node_modules\unplugin-auto-import\dist\chunk-HA63LRAS.cjs
        - D:\_B.study\_B2.front-end\_11.vue3-ts\bms-vue3-ts\node_modules\unplugin-auto-import\dist\webpack.cjs
        - D:\_B.study\_B2.front-end\_11.vue3-ts\bms-vue3-ts\vue.config.js
        - D:\_B.study\_B2.front-end\_11.vue3-ts\bms-vue3-ts\node_modules\@vue\cli-shared-utils\lib\module.js
        - D:\_B.study\_B2.front-end\_11.vue3-ts\bms-vue3-ts\node_modules\@vue\cli-shared-utils\index.js
        - D:\_B.study\_B2.front-end\_11.vue3-ts\bms-vue3-ts\node_modules\@vue\cli-service\bin\vue-cli-service.js
Error: Cannot find module 'node:module'
- D:\_B.study\_B2.front-end\_11.vue3-ts\bms-vue3-ts\node_modules\unplugin-auto-import\dist\chunk-HA63LRAS.cjs
- D:\_B.study\_B2.front-end\_11.vue3-ts\bms-vue3-ts\node_modules\unplugin-auto-import\dist\webpack.cjs
- D:\_B.study\_B2.front-end\_11.vue3-ts\bms-vue3-ts\vue.config.js
- D:\_B.study\_B2.front-end\_11.vue3-ts\bms-vue3-ts\node_modules\@vue\cli-shared-utils\lib\module.js
- D:\_B.study\_B2.front-end\_11.vue3-ts\bms-vue3-ts\node_modules\@vue\cli-shared-utils\index.js
- D:\_B.study\_B2.front-end\_11.vue3-ts\bms-vue3-ts\node_modules\@vue\cli-service\bin\vue-cli-service.js
    at Function.Module._resolveFilename (internal/modules/cjs/loader.js:889:15)
    at Function.Module._load (internal/modules/cjs/loader.js:745:27)
    at Module.require (internal/modules/cjs/loader.js:961:19)
    at require (internal/modules/cjs/helpers.js:92:18)
    at Object.<anonymous> (D:\_B.study\_B2.front-end\_11.vue3-ts\bms-vue3-ts\node_modules\mlly\dist\index.cjs:4:21)       
    at Module._compile (internal/modules/cjs/loader.js:1072:14)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1101:10)
    at Module.load (internal/modules/cjs/loader.js:937:32)
    at Function.Module._load (internal/modules/cjs/loader.js:778:12)
    at Module.require (internal/modules/cjs/loader.js:961:19)
npm ERR! code ELIFECYCLE
npm ERR! errno 1
npm ERR! bms-vue3-ts@0.1.0 serve: `vue-cli-service serve`
npm ERR! Exit status 1
npm ERR!
npm ERR! Failed at the bms-vue3-ts@0.1.0 serve script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.

npm ERR! A complete log of this run can be found in:
npm ERR!     C:\Users\易朝松\AppData\Roaming\npm-cache\_logs\2023-01-31T01_11_07_068Z-debug.log