Vue3 项目上手学习——V3 Admin

This article was last updated on <span id="expire-date"></span> days ago, the information described in the article may be outdated.

Vue3 项目上手学习——V3 Admin

Start

学了那么久的 Vue3,终于到上手实战项目的时候了。之前一直是看视频教程,没有真正的上手打开 VSC 摸过项目,是时候写代码了。

这次选用的是 V3 Admin 这个项目,毕竟人家使用了主流技术 Vue3 + Pinia + Router + Vite + Element-Plus 。本文是在官网文档的基础上进一步抽丝剥茧分析这个项目,并提炼一些 通用写法

reference:

  1. GitHub v3-admin-vite
  2. 项目对应老鸟中文文档:V3 Admin Vite 中文文档 - 掘金
  3. 适用于小白的 step by step tutorial V3 Admin Vite - pany 的专栏 - 掘金

Technology Stack 技术栈

  • Vue3:采用 Vue3 + script setup 最新的 Vue3 组合式 API
  • Element Plus:Element UI 的 Vue3 版本
  • Pinia: 传说中的 Vuex5
  • Vite:真的很快,取代 webpack
  • Vue Router:路由路由
  • TypeScript:JavaScript 语言的超集
  • PNPM:更快速的,节省磁盘空间的包管理工具
  • Scss:和 Element Plus 保持一致
  • CSS 变量:主要控制项目的布局和颜色
  • ESlint:代码校验
  • Prettier:代码格式化
  • Axios:发送网络请求(已封装好)
  • UnoCSS:具有高性能且极具灵活性的即时原子化 CSS 引擎
  • 注释:各个配置项都写有尽可能详细的注释
  • 兼容移动端: 布局兼容移动端页面分辨率

Prepare 准备

Install base Module

Proxy first,在 墙国,下个 npm 包前提是一个好用的代理:

npm config list
npm config set proxy=socks5://127.0.0.1:10808/
npm config delete proxy

Install module

npm install -g pnpm
# like npm insall xxx
pnpm add packageName

# initiate vite vue3
pnpm add vite -g
pnpm create vite@latest
pnpm install
pnpm add -D typescript ts-node @types/node less
pnpm add --save-dev prettier

Install ElementUI-Plus

pnpm install element-plus

In main.ts

// Element Plus
import ElementPlus from "element-plus"
import "element-plus/dist/index.css"

const app = createApp(App)

app.use(ElementPlus)
app.mount("#app")

安装按需引入

pnpm install -D unplugin-vue-components unplugin-auto-import

vite.config.ts 添加插件:

import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'

plugins: [
    // …
    AutoImport({
      resolvers: [ElementPlusResolver()],
    }),
    Components({
      resolvers: [ElementPlusResolver()],
    }),
  ],

configure:

AutoImport({
      // 自动导入 Vue 相关函数,如:ref, reactive, toRef 等
      imports: ["vue"],
      // eslint auto import
      eslintrc: {
        enabled: true, // 是否自动生成 eslint 规则,建议生成之后设置 false
        filepath: "./.eslintrc-auto-import.json", // 指定自动导入函数 eslint 规则的文件
      },
      //
      resolvers: [ElementPlusResolver(), IconsResolver({})],
      vueTemplate: true, // 是否在 vue 模板中自动导入
      dts: path.resolve(pathSrc, "types", "auto-imports.d.ts"), // 指定自动导入函数TS类型声明文件路径
    }),
    // 组件
    Components({
      resolvers: [
        // 自动导入 Element Plus 组件
        ElementPlusResolver(),
        // 自动注册图标组件
        IconsResolver({
          enabledCollections: ["ep"],
        }),
      ],
      dts: path.resolve(pathSrc, "types", "components.d.ts"), // 指定自动导入组件TS类型声明文件路径
    }),
    // 图标
    Icons({
      // 自动安装图标库
      autoInstall: true,
    }),

Configure 配置项

Vscode code snippets

vue3 code snippet:

{
    "Vue3 SFC template": {
        "prefix": "Vue3 SFC",
        "body": [
            "<template>",
            "\t<div class=\"app-container\">…</div>",
            "</template>\n",
            "<script lang=\"ts\" setup></script>\n",
            "<style scoped></style>",
            "$1"
        ],
        "description": "Vue3 SFC"
    }
}

vue3 hook code snippet:

{
    "Vue3 Hook 代码结构一键生成": {
        "prefix": "Vue3 Hook",
        "body": [
            "import { ref } from \"vue\"\n",
            "const refName1 = ref<string>(\"This is a ref variable\")\n",
            "export function useHookName() {",
            "\tconst refName2 = ref<string>(\"This is a ref variable\")\n",
            "\tconst fnName = () => {}\n",
            "\treturn { refName1, refName2, fnName }",
            "}",
            "$1"
        ],
        "description": "Vue3 Hook"
    }
}

Code specification 代码规范

命名规范

Components 组件和 Views 页面命名规范,这样做是为了更好的区分 component 和 view

  1. Components 组件:都采用单词大写开头的  大驼峰  命名方式 (PascalCase):@/components/Index.vue@/components/NotifyList.vue
  2. Views 页面:页面由 components 组件组成,一律采用 短横线连接 (kebab-case)  的命名方式,比如:- @/views/fetch-select.vue

hook 命名:采用  **小驼峰 (camelCase)**,比如:@/hooks/useTheme.ts

Prop 命名:在声明 prop 的时候,其命名应该始终采用  **小驼峰 (camelCase)**。

const props = defineProps({
    /** 打开全屏提示语 */
    openTips: {type: String, default: "全屏"},
    /** 关闭全屏提示语 */
    exitTips: {type: String, default: "退出全屏"},
})

代码规范

  • prop 的定义应该尽量详细,至少需要指定其类型
  • 在组件上总是必须用 key 配合 v-for,以便维护内部组件及其子树的状态。
  • 避免 v-if 和 v-for 用在一起。

Annotation 注释规范

由于项目采用 TS 5.x 进行开发,为了获得更好的 TS 提示,项目采用了大量的  /** xxx */  注释,它的优点就是鼠标放上去会有注释显示出来。

Code format and verify 代码格式化和校验

.tsconfig

项目最重要的 ts 配置

.tsconfig

{
    "compilerOptions": {
        "target": "esnext",
        /** https://cn.vitejs.dev/guide/features.html#typescript-compiler-options */
        "useDefineForClassFields": true,
        "module": "esnext",
        "moduleResolution": "bundler",
        /** TS 严格模式 */
        "strict": true,
        "jsx": "preserve",
        "jsxImportSource": "vue",
        "importHelpers": true,
        "experimentalDecorators": true,
        "allowSyntheticDefaultImports": true,
        "sourceMap": true,
        "resolveJsonModule": true,
        /** https://cn.vitejs.dev/guide/features.html#typescript-compiler-options */
        "isolatedModules": true,
        "esModuleInterop": true,
        "lib": ["esnext", "dom"],
        "skipLibCheck": true,
        "types": [
            "node",
            "vite/client",
            /** Element Plus 的 Volar 插件支持 */
            "element-plus/global",
            "vitest"
        ],
        /** baseUrl 用来告诉编译器到哪里去查找模块,使用非相对模块时必须配置此项 */
        "baseUrl": ".",
        /** 非相对模块导入的路径映射配置,根据 baseUrl 配置进行路径计算 */
        "paths": {
            "@/*": ["src/*"]
        }
    },
    "include": [
        "src/**/*.ts",
        "src/**/*.d.ts",
        "src/**/*.tsx",
        "src/**/*.vue",
        "tests/**/*.ts",
        "types/**/*.d.ts",
        "vite.config.ts",
        "vitest.config.ts"
    ],
    /** 编译器默认排除的编译文件 */
    "exclude": ["node_modules", "dist"]
}

ESlint

  • package.json 文件的 devDependencies 中有所需的 ESlint 依赖包。
  • ESlint配置文件是根目录下的 .eslintrc.cjs,它里面定义了很多校验规则
  • ESlint忽略文件是根目录下的 .eslintignore,它里面定义的目录和文件都不会被 ESlint 检查

.eslintrc.cjs

module.exports = {
    root: true,
    env: {
        browser: true,
        node: true,
        es6: true,
    },
    extends: [
        "plugin:vue/vue3-essential",
        "eslint:recommended",
        "@vue/typescript/recommended",
        "@vue/prettier",
        "@vue/eslint-config-typescript",
    ],
    parser: "vue-eslint-parser",
    parserOptions: {
        parser: "@typescript-eslint/parser",
        ecmaVersion: 2020,
        sourceType: "module",
        jsxPragma: "React",
        ecmaFeatures: {
            jsx: true,
            tsx: true,
        },
    },
    rules: {
        // TS
        "@typescript-eslint/no-explicit-any": "off",
        "no-debugger": "off",
        "@typescript-eslint/explicit-module-boundary-types": "off",
        "@typescript-eslint/ban-types": "off",
        "@typescript-eslint/ban-ts-comment": "off",
        "@typescript-eslint/no-empty-function": "off",
        "@typescript-eslint/no-non-null-assertion": "off",
        "@typescript-eslint/no-unused-vars": [
            "error",
            {
                argsIgnorePattern: "^_",
                varsIgnorePattern: "^_",
            },
        ],
        "no-unused-vars": [
            "error",
            {
                argsIgnorePattern: "^_",
                varsIgnorePattern: "^_",
            },
        ],
        // Vue
        "vue/no-v-html": "off",
        "vue/require-default-prop": "off",
        "vue/require-explicit-emits": "off",
        "vue/multi-word-component-names": "off",
        "vue/html-self-closing": [
            "error",
            {
                html: {
                    void: "always",
                    normal: "always",
                    component: "always",
                },
                svg: "always",
                math: "always",
            },
        ],
        // Prettier
        "prettier/prettier": [
            "error",
            {
                endOfLine: "auto",
            },
        ],
    },
}

.eslintignore:

# Eslint 会忽略的文件

.DS_Store
node_modules
dist
dist-ssr
*.local
.npmrc

Prettier

  • package.json 文件的 devDependencies 中有所需的 Prettier 依赖包
  • Prettier配置文件是根目录下的 prettier.config.js,它里面定义了很多格式化规则
  • Prettier忽略文件是根目录下的 .prettierignore,它里面定义的目录和文件都不会被 Prettier 格式化

prettier.config.js

/**
 * 修改配置后重启编辑器
 * 配置项文档:https://prettier.io/docs/en/configuration.html
 * @type {import("prettier").Config}
 */

export default {
    /** 每一行的宽度 */
    printWidth: 120,
    /** 在对象中的括号之间是否用空格来间隔 */
    bracketSpacing: true,
    /** 箭头函数的参数无论有几个,都要括号包裹 */
    arrowParens: "always",
    /** 换行符的使用 */
    endOfLine: "auto",
    /** 是否采用单引号 */
    singleQuote: false,
    /** 对象或者数组的最后一个元素后面不要加逗号 */
    trailingComma: "none",
    /** 是否加分号 */
    semi: false,
    /** 缩进 */
    tabWidth: 2,
}

.prettierignore

# Prettier 会忽略的文件

.DS_Store
node_modules
dist
dist-ssr
*.local
.npmrc

vite.config.ts

项目 Vite 最重要的配置

vite.config.ts

/// <reference types="vitest" />

import { type ConfigEnv, type UserConfigExport, loadEnv } from "vite"
import path, { resolve } from "path"
import vue from "@vitejs/plugin-vue"
import { createSvgIconsPlugin } from "vite-plugin-svg-icons"
import svgLoader from "vite-svg-loader"
import UnoCSS from "unocss/vite"

/** 配置项文档:https://cn.vitejs.dev/config */
export default (configEnv: ConfigEnv): UserConfigExport => {
  // 从文件加载环境变量
  const viteEnv = loadEnv(configEnv.mode, process.cwd()) as ImportMetaEnv
  const { VITE_PUBLIC_PATH } = viteEnv
  return {
    /** 打包时根据实际情况修改 base */
    base: VITE_PUBLIC_PATH,
    resolve: {
      alias: {
        /** @ 符号指向 src 目录 */
        "@": resolve(__dirname, "./src")
      }
    },
    server: {
      /** 设置 host: true 才可以使用 Network 的形式,以 IP 访问项目 */
      host: true, // host: "0.0.0.0"
      /** 端口号 */
      port: 3333,
      /** 是否自动打开浏览器 */
      open: false,
      /** 跨域设置允许 */
      cors: true,
      /** 端口被占用时,是否直接退出 */
      strictPort: false,
      /** 接口代理 */
      proxy: {
        "/api/v1": {
          target: "https://mock.mengxuegu.com/mock/63218b5fb4c53348ed2bc212",
          ws: true,
          /** 是否允许跨域 */
          changeOrigin: true
        }
      },
      /** 预热常用文件,提高初始页面加载速度 */
      warmup: {
        clientFiles: []
      }
    },
    build: {
      /** 单个 chunk 文件的大小超过 2048KB 时发出警告 */
      chunkSizeWarningLimit: 2048,
      /** 禁用 gzip 压缩大小报告 */
      reportCompressedSize: false,
      /** 打包后静态资源目录 */
      assetsDir: "static",
      rollupOptions: {
        output: {
          /**
           * 分块策略
           * 1. 注意这些包名必须存在,否则打包会报错
           * 2. 如果你不想自定义 chunk 分割策略,可以直接移除这段配置
           */
          manualChunks: {
            vue: ["vue", "vue-router", "pinia"],
            element: ["element-plus", "@element-plus/icons-vue"],
            // vxe: ["vxe-table", "vxe-table-plugin-element", "xe-utils"]
          }
        }
      }
    },
    /** 混淆器 */
    esbuild: {
      /** 打包时移除 console.log */
      pure: ["console.log"],
      /** 打包时移除 debugger */
      drop: ["debugger"],
      /** 打包时移除所有注释 */
      legalComments: "none"
    },
    /** Vite 插件 */
    plugins: [
      vue(),
      // vueJsx(),
      /** 将 SVG 静态图转化为 Vue 组件 */
      svgLoader({ defaultImport: "url" }),
      /** SVG */
      // createSvgIconsPlugin({
        iconDirs: [path.resolve(process.cwd(), "src/icons/svg")],
        symbolId: "icon-[dir]-[name]"
      }),
      /** UnoCSS */
      UnoCSS()
    ],
    /** Vitest 单元测试配置:https://cn.vitest.dev/config */
    test: {
      include: ["tests/**/*.test.ts"],
      environment: "jsdom"
    }
  }
}

.env

环境变量

.env

# 所有环境自定义的环境变量(命名必须以 VITE_ 开头)

## 项目标题
VITE_APP_TITLE = V3 Admin Vite

##  打包路径(就是网站前缀,例如部署到 https://whaleluo.top/v3-admin-vite/ 域名下,就需要填写 /v3-admin-vite/)
VITE_PUBLIC_PATH = '/'

.editorConfig

editorConfig 是一个适用于 多语言,多 IDE 的代码格式化工具。有利于各种项目 形成统一的代码格式风格
建议在所有项目中启动 editorConfig 因为真的很好用。

.editorConfig

# 修改配置后重启编辑器
# 配置项文档:https://editorconfig.org/

# 告知 EditorConfig 插件,当前即是根文件
root = true

# python format config
[*.py]
indent_style = space
indent_size = 4

# 适用全部文件
[*]
## 设置字符集
charset = utf-8
## 缩进风格 space | tab,建议 space(会自动继承给 Prettier)
indent_style = space
## 缩进的空格数(会自动继承给 Prettier)
indent_size = 2
## 换行符类型 lf | cr | crlf,一般都是设置为 lf
end_of_line = lf
## 是否在文件末尾插入空白行
insert_final_newline = true
## 是否删除一行中的前后空格
trim_trailing_whitespace = true

# 适用 .md 文件
[*.md]
insert_final_newline = false
trim_trailing_whitespace = false

.npmrc

npm、cnpm 包管理工具的配置项,设置代理之类的。

.npmrc

# 通过该配置兜底解决组件没有类型提示的问题
shamefully-hoist = true
# Proxy
# registry = https://registry.npm.taobao.org
https-proxy = socks5://127.0.0.1:10808
http-proxy = socks5://127.0.0.1:10808

Vscode

settings.json

{
    "editor.codeActionsOnSave": {"source.fixAll.eslint": "explicit"},
    "[vue]": {"editor.defaultFormatter": "esbenp.prettier-vscode"},
    "[javascript]": {"editor.defaultFormatter": "esbenp.prettier-vscode"},
    "[typescript]": {"editor.defaultFormatter": "esbenp.prettier-vscode"},
    "[json]": {"editor.defaultFormatter": "esbenp.prettier-vscode"},
    "[jsonc]": {"editor.defaultFormatter": "esbenp.prettier-vscode"},
    "[html]": {"editor.defaultFormatter": "esbenp.prettier-vscode"},
    "[css]": {"editor.defaultFormatter": "esbenp.prettier-vscode"},
    "[scss]": {"editor.defaultFormatter": "esbenp.prettier-vscode"}
}

Git commit 规范

  • feat: 增加新的业务功能
  • fix: 修复业务问题/BUG
  • perf: 优化性能
  • style: 更改代码风格, 不影响运行结果
  • refactor: 重构代码
  • revert: 撤销更改
  • test: 测试相关, 不涉及业务代码的更改
  • docs: 文档和注释相关
  • chore: 更新依赖/修改脚手架配置等琐事
  • workflow: 工作流改进
  • ci: 持续集成相关
  • types: 类型定义文件更改
  • wip: 开发中

完成一件事情,就提交一次 commit。而不是等到你写完一整天的代码后,才在下班前只提交一次。

vue-router 路由

CH Document:安装 | Vue Router

Install

pnpm add vue-router@4

Author: WhaleFall

Permalink: https://www.whaleluo.top/javascript/vue3-v3-admin-stady/

文章默认使用 CC BY-NC-SA 4.0 协议进行许可,使用时请注意遵守协议。

Comments