likes
comments
collection
share

vite5+vue3+typescript完整项目框架搭建

作者站长头像
站长
· 阅读数 4
创建基础框架
    • 使用你常用的包管理器
      • npm pnpm yarn bun
  # npm 7+, extra double-dash is needed:
npm create vite@latest my-vue-app -- --template vue

# yarn
yarn create vite my-vue-app --template vue

# pnpm
pnpm create vite my-vue-app --template vue

# bun
bun create vite my-vue-app --template vue  
      • 更多的基础框架模版
          • vue vue-ts react react-ts
    • 我们是ts项目,并且我选择的包管理器是pnpm 所以我们的构建模版命令是
      • pnpm create vite my-vue-app --template vue-ts
关联git仓库
    • gitee 新建项目步骤
      • 点击新建

vite5+vue3+typescript完整项目框架搭建

      • 填写仓库信息

vite5+vue3+typescript完整项目框架搭建

      • 创建完之后出来本地项目关联仓库步骤

vite5+vue3+typescript完整项目框架搭建

  • 使用vscode 打开项目,然后打开终端,输入以下命令
git init 
git add .
git commit -m "first commit"
git remote add origin 你的git仓库地址
git push -u origin "master"
  • 成功如下
    • vscodevite5+vue3+typescript完整项目框架搭建
    • gitee

vite5+vue3+typescript完整项目框架搭建

约束和统一代码规范
  • 多人协作项目中,代码规范约束是必不可少的
  • 使用的vscode插件列表
    • .vscode目录下新增 extensions.json 文件,填入以下插件列表,然后点击vscode插件图标,搜索框右边点击漏斗图标,点击推荐,安装相应插件
{
  "recommendations": ["Vue.volar","dbaeumer.vscode-eslint","rvest.vs-code-prettier-eslint","esbenp.prettier-vscode","editorconfig.editorconfig","stylelint.vscode-stylelint"]
}
  • 使用的插件如下

  pnpm i eslint eslint-config-prettier  eslint-plugin-prettier eslint-plugin-vue  prettier  stylelint@15.11 stylelint-config-html@1.1  stylelint-config-recess-order@4.3    stylelint-config-recommended-scss@13  stylelint-config-recommended-vue@1.5  stylelint-config-standard@34 stylelint-config-standard-scss@13 @typescript-eslint/eslint-plugin -D
  • 步骤如下
    • 项目根目录下创建如下文件 -- 跟package.json同级
.prettierrc.cjs  # prettier 格式化

.prettierignore # prettier 忽略文件

.eslintrc.cjs # eslint 格式化

.eslintignore #eslint 忽略文件

.stylelintrc.cjs # css 格式化

.stylelintignore # css 忽略文件

.editorconfig # 编译器配置 用于抹平各个编译器的配置 统一设置

.gitignore # git 忽略文件
      • 各个文件的配置内容如下

eslint prettier stylelintrc editorconfig gitignore 详细配置

  • eslint
module.exports = {
  root: true,
  env: {
    browser: true,
    es2021: true,
    node: true,
  },
  parser: "vue-eslint-parser",
  extends: [
    // https://eslint.vuejs.org/user-guide/#usage
    "plugin:vue/vue3-recommended",
    // "./.eslintrc-auto-import.json",
    "prettier",
    "plugin:@typescript-eslint/recommended",
    "plugin:prettier/recommended",
    // "@unocss",
  ],
  parserOptions: {
    ecmaVersion: "latest",
    sourceType: "module",
    parser: "@typescript-eslint/parser",
    project: "./tsconfig.*?.json",
    createDefaultProgram: false,
    extraFileExtensions: [".vue"],
  },
  plugins: ["vue", "@typescript-eslint"],
  rules: {
    // https://eslint.vuejs.org/rules/#priority-a-essential-error-prevention
    "vue/multi-word-component-names": 0,
    "vue/no-v-model-argument": "off",
    "vue/script-setup-uses-vars": "error",
    "vue/no-reserved-component-names": "error",
    "vue/custom-event-name-casing": "error",
    "vue/attributes-order": "warn",
    "vue/one-component-per-file": "off",
    "vue/max-attributes-per-line": "off",
    "vue/multiline-html-element-content-newline": "off",
    "vue/attribute-hyphenation": "off",
    "vue/require-default-prop": "off",
    "vue/require-explicit-emits": "warn",
    "vue/html-self-closing": [
      "error",
      {
        html: {
          void: "always",
          normal: "never",
          component: "always",
        },
        svg: "always",
        math: "always",
      },
    ],

    "@typescript-eslint/no-empty-function": "error", // 关闭空方法检查
    "@typescript-eslint/no-explicit-any": "error", // 关闭any类型的警告
    "@typescript-eslint/no-non-null-assertion": "off",
    "@typescript-eslint/ban-ts-ignore": "off",
    "@typescript-eslint/ban-ts-comment": "off",
    "@typescript-eslint/ban-types": "off",
    "@typescript-eslint/explicit-function-return-type": 0,
    "@typescript-eslint/no-explicit-any": "off",
    "@typescript-eslint/no-var-requires": 2,
    "@typescript-eslint/no-use-before-define": "off",
    "@typescript-eslint/explicit-module-boundary-types": "off",
    "@typescript-eslint/no-unused-vars": 2,
    // "no-debugger": import.meta.env.mode === "production" ? "error" : "off",

    "prettier/prettier": [
      "error",
      {
        useTabs: false, // 不使用制表符
      },
    ],
   
    "vue/html-closing-bracket-newline": "off",
  },
  // eslint不能对html文件生效
  overrides: [
    {
      files: ["*.html"],
      processor: "vue/.vue",
    },
  ],
  // https://eslint.org/docs/latest/use/configure/language-options#specifying-globals
  globals: {
    DialogOption: "readonly",
    OptionType: "readonly",
  },
};
  • eslintignore
dist
node_modules
public
.husky
.vscode
.idea
*.sh
*.md
*.woff
*.ttf
.idea

src/assets

.eslintrc.cjs
.prettierrc.cjs
.stylelintrc.cjs
  • prettierrc
module.exports = {
  // (x)=>{},单个参数箭头函数是否显示小括号。(always:始终显示;avoid:省略括号。默认:always)
  arrowParens: "always",
  // 开始标签的右尖括号是否跟随在最后一行属性末尾,默认false
  bracketSameLine: false,
  // 对象字面量的括号之间打印空格 (true - Example: { foo: bar } ; false - Example: {foo:bar})
  bracketSpacing: true,
  // 是否格式化一些文件中被嵌入的代码片段的风格(auto|off;默认auto)
  embeddedLanguageFormatting: "auto",
  // 指定 HTML 文件的空格敏感度 (css|strict|ignore;默认css)
  htmlWhitespaceSensitivity: "css",
  // 当文件已经被 Prettier 格式化之后,是否会在文件顶部插入一个特殊的 @format 标记,默认false
  insertPragma: false,
  // 在 JSX 中使用单引号替代双引号,默认false
  jsxSingleQuote: false,
  // 每行最多字符数量,超出换行(默认80)
  printWidth: 80,
  // 超出打印宽度 (always | never | preserve )
  proseWrap: "preserve",
  // 对象属性是否使用引号(as-needed | consistent | preserve;默认as-needed:对象的属性需要加引号才添加;)
  quoteProps: "as-needed",
  // 是否只格式化在文件顶部包含特定注释(@prettier| @format)的文件,默认false
  requirePragma: false,
  // 结尾添加分号
  semi: true,
  // 使用单引号 (true:单引号;false:双引号)
  singleQuote: false,
  // 缩进空格数,默认2个空格
  tabWidth: 2,
  // 元素末尾是否加逗号,默认es5: ES5中的 objects, arrays 等会添加逗号,TypeScript 中的 type 后不加逗号
  trailingComma: "es5",
  // 指定缩进方式,空格或tab,默认false,即使用空格
  useTabs: false,
  // vue 文件中是否缩进 <style> 和 <script> 标签,默认 false
  vueIndentScriptAndStyle: false,

  endOfLine: "auto",
  overrides: [
    {
      files: "*.html",
      options: {
        parser: "html",
      },
    },
  ],
};
  • prettierignore
dist
node_modules
public
.husky
.vscode
.idea
*.sh
*.md

src/assets
  • stylelintrc
module.exports = {
  // 继承推荐规范配置
  extends: [
    'stylelint-config-standard',
    'stylelint-config-recommended-scss',
    'stylelint-config-recommended-vue/scss',
    'stylelint-config-html/vue',
    'stylelint-config-recess-order',
  ],
  // 指定不同文件对应的解析器
  overrides: [
    {
      files: ['**/*.{vue,html}'],
      customSyntax: 'postcss-html',
    },
    {
      files: ['**/*.{css,scss}'],
      customSyntax: 'postcss-scss',
    },
  ],
  // 自定义规则
  rules: {
    // 允许 global 、export 、v-deep等伪类
    'selector-pseudo-class-no-unknown': [
      true,
      {
        ignorePseudoClasses: ['global', 'export', 'v-deep', 'deep'],
      },
    ],
    // scss 语法提示
    // 参考 https://github.com/stylelint/stylelint/issues/3190
    'at-rule-no-unknown': null,
    'scss/at-rule-no-unknown': true,
    // css书写顺序
    'order/order': ['declarations', 'custom-properties', 'dollar-variables', 'rules', 'at-rules'],
    'order/properties-order': [
      'position',
      'z-index',
      // 其他样式的顺序
    ],
    // 其他规则
    'no-empty-source': null,
  },
}
  • stylelintignore
dist
node_modules
public
.husky
.vscode
.idea
*.sh
*.md

src/assets
  • editorconfig
# http://editorconfig.org
root = true

# 表示所有文件适用
[*]
charset = utf-8 # 设置文件字符集为 utf-8
end_of_line = lf # 控制换行类型(lf | cr | crlf)
indent_style = tab # 缩进风格(tab | space)
insert_final_newline = true # 始终在文件末尾插入一个新行
max_line_length = 80 # 设置最大行长度为 80 字符
# 表示仅 md 文件适用以下规则
[*.md]
max_line_length = off # 关闭最大行长度限制
trim_trailing_whitespace = false # 关闭末尾空格修剪
  • gitignore
node_modules
.DS_Store
dist
dist-ssr
*.local
.history

# Editor directories and files
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.local

package-lock.json
pnpm-lock.yaml
stats.html

# typings 
src/typings/auto-imports.d.ts
src/typings/components.d.ts
    • packge.json 文件内的scripts内新增
"lint:eslint": "eslint "src/**/*.{vue,ts,js}" --fix",
"lint:prettier": "prettier --write "**/*.{js,ts,json,css,less,scss,vue,html,md}"",
"lint:stylelint": "stylelint  "**/*.{css,scss,vue,html}" --fix",
"lint:fix": "pnpm  lint:eslint && pnpm lint:prettier && pnpm lint:stylelint", 

vite5+vue3+typescript完整项目框架搭建

  • .vscode 目录下新增 settings.json文件,填入以下内容
{
  "editor.formatOnSave": true, //选择性开启
  "eslint.enable": true, // 开启eslint检查
  "prettier.useEditorConfig": true,
  "prettier.requireConfig": true,
  "editor.defaultFormatter": "esbenp.prettier-vscode", // 指定 prettier 为所有文件默认格式化器
  //针对共用的语言如JS、TS和JSX关闭文件保存自动格式化功能,通过eslint来做这件事 
  "[javascript]": {
    "editor.formatOnSave": false
  },
  "[javascriptreact]": {
    "editor.formatOnSave": false
  },
  "[typescript]": {
    "editor.formatOnSave": false
  },
  "editor.codeActionsOnSave": {
    "source.fixAll": "always",
    "source.fixAll.eslint": "always", // 开启eslint自动检测
    "source.fixAll.stylelint": "always", // 开启 Stylelint 保存自动检测
  },
  // Stylelint 校验文件
  "stylelint.validate": [
    "css",
    "scss",
    "vue",
    "less",
    "html"
  ],
  "cSpell.words": [
    "Attributify",
    "breakline",
    "brotliccompress",
    "commitlint",
    "consola",
    "ERUDA",
    "flac",
    "Pausable",
    "recyclabledev",
    "Triggerable",
    "updatepassword",
    "vuejsx"
  ],
	"css.customData": [".vscode/tailwindcss.json"],
	"editor.quickSuggestions": {
    "strings": true,
    "other": true,
    "comments": true,
  },
	// "unocss.root": "packages/client"

}
  
  • 测试校验 警告或者报错之类的不用管,稍后增加自动修复
    • 运行 eslint 检查命令 pnpm lint:eslint
      • 结果如下vite5+vue3+typescript完整项目框架搭建
    • 运行 prettier 检查命令 pnpm lint:prettier
      • 结果如下vite5+vue3+typescript完整项目框架搭建
    • 运行 stylelint 检查命令 pnpm lint:stylelint
      • 结果如下 vite5+vue3+typescript完整项目框架搭建
    • 以上三条命令合并为一条
      • pnpm lint:fix 运行起来后效果一样
添加代码提交规范校验-husky
  • 所需的插件列表
    • husky
    • lint-staged
    • @commitlint
    • @commitlint/config-conventional
    • commitizen
    • cz-git
  • 安装husky
  pnpm dlx husky-init && pnpm install
    • 上一个步骤完成后,packge.json 内的 scripts 内应该有 "prepare": "husky install" 这条命令
    • 执行创建钩子命令 npx husky add .husky/pre-commit "npm test"
    • 添加一条记录 git add .husky/pre-commit
  • 安装 lint-staged
pnpm i lint-staged -D
    • package.json文件内 新增如下内容,注意需要和scripts同级
"lint-staged": {
  "*.{js,ts}": [
    "eslint --fix",
    "prettier --write"
  ],
  "*.{cjs,json}": [
    "prettier --write"
  ],
  "*.{vue,html}": [
    "eslint --fix",
    "prettier --write",
    "stylelint --fix"
  ],
  "*.{scss,css}": [
    "stylelint --fix --allow-empty-input",
    "prettier --write"
  ],
  "*.md": [
    "prettier --write"
  ]
},
    • package.jsonscripts 内新增一条命令:"lint:lint-staged": "lint-staged"
    • 修改提交前钩子触发命令
      • 根目录.husky 目录下的 pre-commit 文件中的 npm test 修改为 pnpm lint:lint-staged
    • git提交一次记录试试 出现如下表示成功vite5+vue3+typescript完整项目框架搭建
  • Commitlint 检查您的提交消息是否符合 Conventional commit format.
pnpm i @commitlint @commitlint/config-conventional -D
    • 根目录创建配置文件commitlint.config.cjs,内容如下 参考配置
module.exports = {
  // 继承的规则
  extends: ["@commitlint/config-conventional"],
  // @see: https://commitlint.js.org/#/reference-rules
  rules: {
  "subject-case": [0], // subject大小写不做校验

  // 类型枚举,git提交type必须是以下类型
  "type-enum": [
    2,
    "always",
    [
      'feat', // 新增功能
    'fix', // 修复缺陷
    'docs', // 文档变更
    'style', // 代码格式(不影响功能,例如空格、分号等格式修正)
    'refactor', // 代码重构(不包括 bug 修复、功能新增)
    'perf', // 性能优化
    'test', // 添加疏漏测试或已有测试改动
    'build', // 构建流程、外部依赖变更(如升级 npm 包、修改 webpack 配置等)
    'ci', // 修改 CI 配置、脚本
    'revert', // 回滚 commit
    'chore', // 对构建过程或辅助工具和库的更改(不影响源文件、测试用例)
  ],
  ],
},
};
    • 终端运行如下命令,添加提交信息钩子
npx husky add .husky/commit-msg "npx --no -- commitlint --edit $1"  
    • 验证
      • 正确的提交格式:<type>(<scope>): <subject> ,type 和 subject 默认必填

注意: : 是英文冒号,冒号后面需要一个空格

vite5+vue3+typescript完整项目框架搭建

vite5+vue3+typescript完整项目框架搭建

  • Commitizen & cz-git
    • commitizen: 基于Node.js的 git commit 命令行工具,辅助生成标准化规范化的 commit message
    • cz-git: 一款工程性更强,轻量级,高度自定义,标准输出格式的 commitizen 适配器
    • 安装
pnpm i commitizen cz-git -D
    • cz-git 配置
      • package.jsonscripts同级的地方添加如下命令
"config": {
  "commitizen": {
    "path": "node_modules/cz-git"
  }
}    
        • cz-git 与 commitlint 进行联动给予校验信息,所以可以编写于 commitlint 配置文件之中
    • 添加提交指令,执行git 操作和校验
   "scripts": {
    "dev": "vite",
    "build": "vue-tsc && vite build",
    "preview": "vite preview",
    "lint:eslint": "eslint "src/**/*.{vue,ts,js}" --fix",
    "lint:prettier": "prettier --write "**/*.{js,ts,json,css,less,scss,vue,html,md}"",
    "lint:stylelint": "stylelint  "**/*.{css,scss,vue,html}" --fix",
    "lint:fix": "pnpm  lint:eslint && pnpm lint:prettier && pnpm lint:stylelint",
    "prepare": "husky install",
    "lint:lint-staged": "lint-staged",
    "commit": "git pull && git add -A && git-cz && git push" // 添加这一行
  },
    • 修改commitlint.config.cjs 文件内容
module.exports = {
  // 继承的规则
  extends: ["@commitlint/config-conventional"],
  // 自定义规则
  rules: {
    // @see https://commitlint.js.org/#/reference-rules

    // 提交类型枚举,git提交type必须是以下类型
    "type-enum": [
      2,
      "always",
      [
        "feat", // 新增功能
        "fix", // 修复缺陷
        "docs", // 文档变更
        "style", // 代码格式(不影响功能,例如空格、分号等格式修正)
        "refactor", // 代码重构(不包括 bug 修复、功能新增)
        "perf", // 性能优化
        "test", // 添加疏漏测试或已有测试改动
        "build", // 构建流程、外部依赖变更(如升级 npm 包、修改 webpack 配置等)
        "ci", // 修改 CI 配置、脚本
        "revert", // 回滚 commit
        "chore", // 对构建过程或辅助工具和库的更改(不影响源文件、测试用例)
      ],
    ],
    "subject-case": [0], // subject大小写不做校验
  },

  prompt: {
    messages: {
      type: "选择你要提交的类型 :",
      scope: "选择一个提交范围(可选):",
      customScope: "请输入自定义的提交范围 :",
      subject: "填写简短精炼的变更描述 :\n",
      body: '填写更加详细的变更描述(可选)。使用 "|" 换行 :\n',
      breaking: '列举非兼容性重大的变更(可选)。使用 "|" 换行 :\n',
      footerPrefixesSelect: "选择关联issue前缀(可选):",
      customFooterPrefix: "输入自定义issue前缀 :",
      footer: "列举关联issue (可选) 例如: #31, #I3244 :\n",
      generatingByAI: "正在通过 AI 生成你的提交简短描述...",
      generatedSelectByAI: "选择一个 AI 生成的简短描述:",
      confirmCommit: "是否提交或修改commit ?",
    },
    // prettier-ignore
    types: [
      { value: "feat",     name: "特性:     ✨  新增功能", emoji: ":sparkles:" },
      { value: "fix",      name: "修复:     🐛  修复缺陷", emoji: ":bug:" },
      { value: "docs",     name: "文档:     📝  文档变更", emoji: ":memo:" },
      { value: "style",    name: "格式:     🌈  代码格式(不影响功能,例如空格、分号等格式修正)", emoji: ":lipstick:" },
      { value: "refactor", name: "重构:     🔄  代码重构(不包括 bug 修复、功能新增)", emoji: ":recycle:" },
      { value: "perf",     name: "性能:     🚀  性能优化", emoji: ":zap:" },
      { value: "test",     name: "测试:     🧪  添加疏漏测试或已有测试改动", emoji: ":white_check_mark:"},
      { value: "build",    name: "构建:     📦️  构建流程、外部依赖变更(如升级 npm 包、修改 vite 配置等)", emoji: ":package:"},
      { value: "ci",       name: "集成:     ⚙️  修改 CI 配置、脚本",  emoji: ":ferris_wheel:"},
      { value: "revert",   name: "回退:     ↩️  回滚 commit",emoji: ":rewind:"},
      { value: "chore",    name: "其他:     🛠️  对构建过程或辅助工具和库的更改(不影响源文件、测试用例)", emoji: ":hammer:"},
    ],
    useEmoji: true,
    emojiAlign: "center",
    useAI: false,
    aiNumber: 1,
    themeColorCode: "",
    scopes: [],
    allowCustomScopes: true,
    allowEmptyScopes: true,
    customScopesAlign: "bottom",
    customScopesAlias: "custom",
    emptyScopesAlias: "empty",
    upperCaseSubject: false,
    markBreakingChangeMode: false,
    allowBreakingChanges: ["feat", "fix"],
    breaklineNumber: 100,
    breaklineChar: "|",
    skipQuestions: [],
    issuePrefixes: [
      { value: "closed", name: "closed:   ISSUES has been processed" },
    ],
    customIssuePrefixAlign: "top",
    emptyIssuePrefixAlias: "skip",
    customIssuePrefixAlias: "custom",
    allowCustomIssuePrefix: true,
    allowEmptyIssuePrefix: true,
    confirmColorize: true,
    maxHeaderLength: Infinity,
    maxSubjectLength: Infinity,
    minSubjectLength: 0,
    scopeOverrides: undefined,
    defaultBody: "",
    defaultIssues: "",
    defaultScope: "",
    defaultSubject: "",
  },
};
    • 测试提交vite5+vue3+typescript完整项目框架搭建
  • release-it自动生成changelog
    • release-it 是一个命令行工具,用于在发布新版本时自动化处理一系列任务。它可以帮助您自动化执行以下任务:
      • 增加版本号并提交 Git
      • 生成变更日志(Changelog)并提交到 Git
      • 创建 Git 标签并推送到远程仓库
      • 发布到 npm 等软件仓库
      • 在 GitHub、GitLab 等平台创建发行版
    • 安装插件
      • pnpm add release-it @release-it/conventional-changelog -D
    • 在根目录下添加脚本文件release-it.json
{
  "plugins": {
    "@release-it/conventional-changelog": {
      "preset": "angular",
      "infile": "CHANGELOG.md"
    }
  },
  "git": {
    "commitMessage": "chore: Release v${version}"
  }
}
    • 添加执行命令
 "scripts": {
    "dev": "vite",
    "build": "vue-tsc && vite build",
    "preview": "vite preview",
    "lint:eslint": "eslint "src/**/*.{vue,ts,js}" --fix",
    "lint:prettier": "prettier --write "**/*.{js,ts,json,css,less,scss,vue,html,md}"",
    "lint:stylelint": "stylelint  "**/*.{css,scss,vue,html}" --fix",
    "lint:fix": "pnpm  lint:eslint && pnpm lint:prettier && pnpm lint:stylelint",
    "prepare": "husky install",
    "lint:lint-staged": "lint-staged",
    "commit": "git pull && git add -A && git-cz && git push",
    "release": "release-it" // 这一行
  },
        • 注意: 运行 pnpm release时, 工作目录必须是干净的, 也就是说你需要把修改了的文件提交, 或者使用git stash放入缓存中, 运行完了, 别忘了在git stash pop取出来
          • 以下是我没有提交就运行 pnpm release的报错vite5+vue3+typescript完整项目框架搭建
    • npm 版本号
      • 先补充一下npm版本号相关知识,npm的版本号遵循SemVer 规范,版本号格式必须采用X.Y.Z的格式,其中 X、YZ 为非负的整数。X 是主版本号、Y 是次版本号、而 Z 为修订号,英文对应表示为 majorminorpatch,每个号必须采用递增。
        • 例如
X.Y.Z => {major}.{minor}.{patch}
0.0.1

# 更新主版本号
pnpm release major

# 更新次版本号
pnpm release minor

# 更新修订号
pnpm release patch
    • 执行过程如下vite5+vue3+typescript完整项目框架搭建
      • release-it会自动执行配置的任务,更新版本号码和变更日志的描述。完成后,它会自动提交代码(Commit)、打标签(Tag)、推送(Push)到仓库等等。
      • 查看 changelog: 生成的 changelog 将会保存在 CHANGELOG.md 文件中。你可以查看这个文件来了解新版本的变化。
添加unocss 原子化插件
  • 安装
    • pnpm i unocss -D
  • 配置 按照官网进行配置
    • 配置vite
// vite.config.ts
import UnoCSS from 'unocss/vite'
import { defineConfig } from 'vite'

export default defineConfig({
  plugins: [
    UnoCSS(),
  ],
})
    • 根目录创建uno.config.ts
// uno.config.ts
import { defineConfig, presetUno, presetAttributify } from "unocss";

export default defineConfig({
  // ...UnoCSS options
  // 自定义简写以提供自动补齐建议。 如果 values 是一个数组,它将与 | 连接并用 () 封装。
  // 以下是我的部分配置
  shortcuts: [
    {
      "flex-center-center": "flex  justify-center items-center",
      "flex-between-center": "flex  justify-between items-center",
      "absolute-center":
        "absolute top-[50%] left-[50%] translate-x-[-50%] translate-y-[-50%]",
      "border-s-w": "border-[1px] border-solid border-white",
      "hw-full": "h-full w-full",
      "hw-100": "h-[calc(100vh-70px)] w-screen",
      "t-3c": "text-[#3cbdc5]",
      "t-c": "text-center",
      "bg-3c": "bg-[#3cbdc5]",
      "border-b-solid": "border-b-1px border-b-solid border-b-[#e5e5e5]",
    },
  ],
  //  自定义css类
  rules: [
    [
      "bg-no-repeat-cover",
      { "background-repeat": "no-repeat", "background-size": "cover" },
    ],
    [/^br-(\d+)$/, ([, d]) => ({ "border-radius": `${d}px` })],
    [
      /^base-shadow-(\d+)$/,
      ([, d]) => ({ "box-shadow": `0 0 ${d}px 0 rgba(0, 0, 0, 0.05)` }),
    ],
  ],
  presets: [presetUno(), presetAttributify()],
});
    • 安装 css 样式重置文件
      • pnpm add @unocss/reset -D
    • main.ts入口引入 样式重置文件 和 unocss
// main.ts
/** 重置样式 这里引入自定义的重置样式也可 */
import '@unocss/reset/tailwind-compat.css'
// 引入项目的自定义样式,需要在引入unocss之前
import "./style.css";
import 'virtual:uno.css'
    • 安装 unocss eslint 插件
      • pnpm i @unocss/eslint-config -D
      • 更新eslintrc.cjs文件
module.exports = {
  root: true,
  env: {
    browser: true,
    es2021: true,
    node: true,
  },
  parser: "vue-eslint-parser",
  extends: [
    // https://eslint.vuejs.org/user-guide/#usage
    "plugin:vue/vue3-recommended",
    // "./.eslintrc-auto-import.json",
    "prettier",
    "plugin:@typescript-eslint/recommended",
    "plugin:prettier/recommended",
    "@unocss",
  ],
  parserOptions: {
    ecmaVersion: "latest",
    sourceType: "module",
    parser: "@typescript-eslint/parser",
    project: "./tsconfig.*?.json",
    createDefaultProgram: false,
    extraFileExtensions: [".vue"],
  },
  plugins: ["vue", "@typescript-eslint"],
  rules: {
    // https://eslint.vuejs.org/rules/#priority-a-essential-error-prevention
    "vue/multi-word-component-names": 0,
    "vue/no-v-model-argument": "off",
    "vue/script-setup-uses-vars": "error",
    "vue/no-reserved-component-names": "error",
    "vue/custom-event-name-casing": "error",
    "vue/attributes-order": "warn",
    "vue/one-component-per-file": "off",
    "vue/max-attributes-per-line": "off",
    "vue/multiline-html-element-content-newline": "off",
    "vue/attribute-hyphenation": "off",
    "vue/require-default-prop": "off",
    "vue/require-explicit-emits": "warn",
    "vue/html-self-closing": [
      "error",
      {
        html: {
          void: "always",
          normal: "never",
          component: "always",
        },
        svg: "always",
        math: "always",
      },
    ],

    "@typescript-eslint/no-empty-function": "error", // 关闭空方法检查
    "@typescript-eslint/no-explicit-any": "error", // 关闭any类型的警告
    "@typescript-eslint/no-non-null-assertion": "off",
    "@typescript-eslint/ban-ts-ignore": "off",
    "@typescript-eslint/ban-ts-comment": "off",
    "@typescript-eslint/ban-types": "off",
    "@typescript-eslint/explicit-function-return-type": 0,
    "@typescript-eslint/no-explicit-any": "off",
    "@typescript-eslint/no-var-requires": 2,
    "@typescript-eslint/no-use-before-define": "off",
    "@typescript-eslint/explicit-module-boundary-types": "off",
    "@typescript-eslint/no-unused-vars": 2,
    "no-console": process.env.NODE_ENV === "production" ? "error" : "off",
    "no-debugger": process.env.NODE_ENV === "production" ? "error" : "off",

    "prettier/prettier": [
      "error",
      {
        useTabs: false, // 不使用制表符
      },
    ],

    "vue/html-closing-bracket-newline": "off",
  },
  // eslint不能对html文件生效
  overrides: [
    {
      files: ["*.html"],
      processor: "vue/.vue",
    },
  ],
  // https://eslint.org/docs/latest/use/configure/language-options#specifying-globals
  globals: {
    DialogOption: "readonly",
    OptionType: "readonly",
  },
};
    • 运行效果如下vite5+vue3+typescript完整项目框架搭建
    • 安装vscode unocss插件,获得更好的unocss书写体验
      • unocss
      • vscode-unocss-highlight
      • vscode-uno-magic
自动导入vue的相关函数
  • ref watch 等等
  • 步骤
    • 安装自动导入插件
      • pnpm i unplugin-auto-import -D
    • vite.config.ts 配置
import { defineConfig, loadEnv, UserConfig, ConfigEnv } from "vite";
import vue from "@vitejs/plugin-vue";
import UnoCSS from "unocss/vite";
import AutoImport from "unplugin-auto-import/vite";
// https://vitejs.dev/config/
export default defineConfig(({ mode }: ConfigEnv): UserConfig => {
  const env = loadEnv(mode, process.cwd());
  console.log(env);
  return {
    plugins: [
      vue(),
      UnoCSS(),
      AutoImport({
        // 自动导入参考: https://github.com/sxzz/element-plus-best-practices/blob/main/vite.config.ts
        imports: ["vue", "vue-router"],
        eslintrc: {
          enabled: true,
          globalsPropValue: true,
          filepath: "./.eslintrc-auto-import.json",
        },
        vueTemplate: true,
        // 配置文件生成位置(false:关闭自动生成)
        dts: mode === "development" ? "src/typings/auto-imports.d.ts" : false,
      }),
    ],
  };
});
    • 重新运行项目,尝试不导入vue的函数,直接使用

vite5+vue3+typescript完整项目框架搭建

    • 如果eslint有爆红,重启vscode即可解决
自动导入 第三方组件库和自定义组件文件

以下使用vant组件库示例,别的组件库也是如此,不再过多赘述 参照各个组件库官网即可

  • 安装 vant 和自动导入插件
    • pnpm i vant unplugin-vue-components @vant/auto-import-resolver -D
  • vite.config.ts 导入
import { defineConfig, loadEnv, UserConfig, ConfigEnv } from "vite";
import vue from "@vitejs/plugin-vue";
import UnoCSS from "unocss/vite";
// 自动导入 vue 等的 相关的函数
import AutoImport from "unplugin-auto-import/vite";
// 自动导入第三方组件 和项目组件
import components from "unplugin-vue-components/vite";
import { VantResolver } from "@vant/auto-import-resolver";
// https://vitejs.dev/config/
export default defineConfig(({ mode }: ConfigEnv): UserConfig => {
  const env = loadEnv(mode, process.cwd());
  console.log(env);
  return {
    plugins: [
      vue(),
      UnoCSS(),
      AutoImport({
        // 自动导入参考: https://github.com/sxzz/element-plus-best-practices/blob/main/vite.config.ts
        imports: ["vue", "vue-router"],
        resolvers: [VantResolver()],
        eslintrc: {
          enabled: true,
          globalsPropValue: true,
          filepath: "./.eslintrc-auto-import.json",
        },
        vueTemplate: true,
        // 配置文件生成位置(false:关闭自动生成)
        dts: mode === "development" ? "src/typings/auto-imports.d.ts" : false,
      }),
      components({
        resolvers: [VantResolver()], //第三方组件库 例如 vant element 等
        dirs: ["src/components", "src/**/components"], //自定义的组件位置
        dts: mode === "development" ? "src/typings/components.d.ts" : false, //生成声明文件位置
      }),
    ],
  };
});
  • main.ts 引入 css文件
import { createApp } from "vue";
/** 重置样式 这里引入自定义的重置样式也可 */
import "@unocss/reset/tailwind-compat.css";
// 引入项目的自定义样式,需要在引入unocss之前
import "./style.css";
import "virtual:uno.css";
import "vant/es/toast/style";
import "vant/es/dialog/style";
import "vant/es/notify/style";
import "vant/es/image-preview/style";
import App from "./App.vue";

createApp(App).mount("#app");
  • 体验
<script setup lang="ts">
  const num = ref("100");
  onMounted(() => {
    console.log(num.value);
  });
</script>

<template>
  <div class="uno-css-demo-box">
    <div class="title text-center text-[30px] text-warm-gray-200 font-bold">
      体验uno-css
    </div>
    <div class="url text-center text-[16px] text-[pink]">
      https://unocss.nodejs.cn/
    </div>
    <van-button text="vant-button" type="primary" />
    <HelloWorld :msg="num" />
  </div>
</template>

  <style scoped>
  .logo {
  height: 6em;
  padding: 1.5em;
  transition: filter 300ms;
  will-change: filter;
  }

  .logo:hover {
  filter: drop-shadow(0 0 2em #646cffaa);
  }

  .logo.vue:hover {
  filter: drop-shadow(0 0 2em #42b883aa);
  }
</style>
路由自动导入和布局组件自动导入
  • 使用到的插件是
    • vite-plugin-vue-layouts
    • vite-plugin-pages github
  • 安装
    • pnpm i vite-plugin-pages vite-plugin-vue-layouts nprogress @types/nprogress vue-router -D
      • nprogress 是顶部进度条展示,根据项目要求来,可装可不装
  • vite.config.ts 配置
    • 顺便一起配置 src--@ 映射
  import { defineConfig, loadEnv, UserConfig, ConfigEnv } from "vite";
  import vue from "@vitejs/plugin-vue";
  import UnoCSS from "unocss/vite";
  // 自动导入 vue 等的 相关的函数
  import AutoImport from "unplugin-auto-import/vite";
  // 自动导入第三方组件 和项目组件
  import components from "unplugin-vue-components/vite";
  import { VantResolver } from "@vant/auto-import-resolver";
  // 路由自动导入和布局组件自动合成
  import { resolve } from "path";
  import Pages from "vite-plugin-pages";
  import Layouts from "vite-plugin-vue-layouts";
  const pathSrc = resolve(__dirname, "src");
  const pathComp = resolve(__dirname, "src/components");
  const pathAssets = resolve(__dirname, "src/assets");
  // https://vitejs.dev/config/
  export default defineConfig(({ mode }: ConfigEnv): UserConfig => {
    const env = loadEnv(mode, process.cwd());
    console.log(env);
    return {
      plugins: [
        vue(),
        UnoCSS(),
        AutoImport({
          // 自动导入参考: https://github.com/sxzz/element-plus-best-practices/blob/main/vite.config.ts
          imports: ["vue", "vue-router"],
          resolvers: [VantResolver()],
          eslintrc: {
            enabled: true,
            globalsPropValue: true,
            filepath: "./.eslintrc-auto-import.json",
          },
          vueTemplate: true,
          // 配置文件生成位置(false:关闭自动生成)
          dts: mode === "development" ? "src/typings/auto-imports.d.ts" : false,
        }),
        components({
          resolvers: [VantResolver()], //第三方组件库 例如 vant element 等
          dirs: ["src/components", "src/**/components"], //自定义的组件位置
          dts: mode === "development" ? "src/typings/components.d.ts" : false, //生成声明文件位置
        }),
        Pages({
          pagesDir: "src/views", //需要生成路由的文件目录,默认就是识别src下面的pages文件
          extensions: ["vue"],
          exclude: ["**/components/*.vue"], // 忽略的文件夹
          importMode: "async", // 是否是异步路由
        }),
        Layouts({
          // 如果是默认 layouts文件夹,默认 default.vue文件,则不需要配置
          layoutsDirs: "src/layouts", // 布局文件存放目录
          defaultLayout: "default", //对应 src/layouts/default.vue
        }),
      ],
      resolve: {
        alias: {
          "@": pathSrc,
          comp: pathComp,
          // 配置图片要这样引用也可以自定义方法引入图片静态资源
          assets: pathAssets,
        },
      },
    };
  });
  • tsconfig.json
{
  "compilerOptions": {
    "target": "esnext",
    "useDefineForClassFields": true,
    "module": "esnext",
    "moduleResolution": "node",
    "strict": true,
    "noLib": false,
    "sourceMap": true,
    "resolveJsonModule": true,
    "esModuleInterop": true,
    "lib": ["esnext", "dom"],
    "baseUrl": ".",
    "allowJs": true,
    "paths": {
      "@/*": ["src/*"],
      "comp/*": ["src/components/*"],
      "assets/*": ["src/assets/*"]
    },
    "types": [
      "vite/client",
      // "unplugin-icons/types/vue",
      "vue"
      // "vite-plugin-vue-layouts/client"
    ],
    "skipLibCheck": true /* Skip type checking all .d.ts files. */,
    "allowSyntheticDefaultImports": true /* 允许默认导入 */,
    "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */,

    "jsx": "preserve",
    "jsxFactory": "h",
    "jsxFragmentFactory": "Fragment"
  },
  "include": [
    "src/**/*.ts",
    "src/**/*.vue",
    "src/typings/**/*.d.ts",
    "mock/**/*.ts",
    "vite.config.ts"
  ],
  "exclude": ["node_modules", "dist", "**/*.js"]
}
  • 步骤
  • 自动导入路由文件
    • src 下创建router目录,然后目录下新建index.ts,内容入下
  // vue3:
//    createRouter:创建路由实例
//    路由模式:history(localhost:5173/login) hash(localhost:5173/#/login)
//      createWebHistory:设置 history 模式
//      createWebHashHistory:设置 hash 模式
//    import.meta.env.BASE_URL:去配置文件(vite.config.ts)中找到设置 base 路径
//      设置之后,开启服务时,会在服务前添加 base 对应的路径(默认值:/)
import { createRouter, createWebHistory } from "vue-router";
import NProgress from "nprogress";
import "nprogress/nprogress.css";

NProgress.configure({
  showSpinner: false, // 关闭右侧加载动画
});
// 创建路由对象
const router = createRouter({
  // 设置模式:history 模式
  history: createWebHistory(import.meta.env.BASE_URL),
  routes: [],
});
//导航守卫
router.beforeEach((to, from, next) => {
  //进度条
  NProgress.start();
  //动态修改标题
  if (to.meta.title) {
    document.title = `${to.meta.title || ""}`;
  }

  next();
});
//后置守卫
router.afterEach((to) => {
  document.title = `${to.meta?.title || ""}`;
  NProgress.done();
});
export default router;
    • man.ts 导入
  import { createApp } from "vue";
/** 重置样式 这里引入自定义的重置样式也可 */
import "@unocss/reset/tailwind-compat.css";
// 引入项目的自定义样式,需要在引入unocss之前
// import "./style.css";
import "virtual:uno.css";
import "vant/es/toast/style";
import "vant/es/dialog/style";
import "vant/es/notify/style";
import "vant/es/image-preview/style";
import App from "./App.vue";

//挂载路由
import router from "./router";

const app = createApp(App);

app.use(router);

app.mount("#app");
    • typings 目录下新建env.d.ts文件
/// <reference types="vite/client" />
/// <reference types="vite-plugin-pages/client" /> // 去除 ts告警
/// <reference types="vite-plugin-vue-layouts/client" />// 去除 ts告警
declare module "*.vue" {
  import { DefineComponent } from "vue";
  // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types
  const component: DefineComponent<{}, {}, any>;
  export default component;
}
interface ImportMetaEnv {
  /** @description 网站标题 */
  readonly VITE_APP_TITLE: string;
  /** @description 开发环境 端口 : 9527 */
  readonly VITE_APP_PORT: number;
  /** @description  代理前缀 : /api */
  readonly VITE_APP_BASE_API: string;
  /** @description  代理网站地址  也是正式环境的网站地址  */
  readonly VITE_APP_BASE_URL: string;
  /** @description  打包访问目录 默认 : / */
  readonly VITE_PUBLIC_PATH: string;
  /** @description  本地存储token的标识 : token  */
  readonly VITE_TOKEN: string;
  /** @description oss  图片地址  */
  readonly VITE_APP_IMG_URL: string;
}

interface ImportMeta {
  readonly env: ImportMetaEnv;
}
    • src 下创建 views目录,目录下创建2个文件夹,目录结构如下

vite5+vue3+typescript完整项目框架搭建

      • 释义
        • [id].vue,是指的是动态路由,访问这个路由需要传递一个id的参数
        • [...all].vue,是适配404的页面
    • 去手动创建路由 router/index
// vue3:
//    createRouter:创建路由实例
//    路由模式:history(localhost:5173/login) hash(localhost:5173/#/login)
//      createWebHistory:设置 history 模式
//      createWebHashHistory:设置 hash 模式
//    import.meta.env.BASE_URL:去配置文件(vite.config.ts)中找到设置 base 路径
//      设置之后,开启服务时,会在服务前添加 base 对应的路径(默认值:/)
import { createRouter, createWebHistory } from "vue-router";
import NProgress from "nprogress";
import "nprogress/nprogress.css";

NProgress.configure({
  showSpinner: false, // 关闭右侧加载动画
});
// 创建路由对象
const router = createRouter({
  // 设置模式:history 模式
  history: createWebHistory(import.meta.env.BASE_URL),
  routes: [

    {
      path: "/home",
      name: "home",
      meta: {
        title: "首页",
      },
      component: () => import("@/views/home/index.vue"),
    },
    {
      path: "/home/test",
      name: "home-test",
      meta: {
        title: "首页-test",
      },
      component: () => import("@/views/home/test.vue"),
    },
    {
      path: "/login",
      name: "login",
      meta: {
        title: "登录页",
      },
      component: () => import("@/views/login/index.vue"),
    },

  ],
});
//导航守卫
router.beforeEach((to, from, next) => {
  //进度条
  NProgress.start();
  //动态修改标题
  if (to.meta.title) {
    document.title = `${to.meta.title || ""}`;
  }

  next();
});
//后置守卫
router.afterEach((to) => {
  document.title = `${to.meta?.title || ""}`;
  NProgress.done();
});
export default router;
    • App.vue文件内删除内容,填充以下内容
<script setup lang="ts"></script>

<template>
  <router-view />
</template>

<style scoped></style>
  
    • 以上创建路由就是常规的创建,现在,改造router/index.ts,使用插件自动生成,只需要2个步骤,导入和使用即可,内容如下
// vue3:
//    createRouter:创建路由实例
//    路由模式:history(localhost:5173/login) hash(localhost:5173/#/login)
//      createWebHistory:设置 history 模式
//      createWebHashHistory:设置 hash 模式
//    import.meta.env.BASE_URL:去配置文件(vite.config.ts)中找到设置 base 路径
//      设置之后,开启服务时,会在服务前添加 base 对应的路径(默认值:/)
import { createRouter, createWebHistory } from "vue-router";
import NProgress from "nprogress";
import "nprogress/nprogress.css";

import routes from "~pages"; // 导入插件自动生成的路由
// 如果这里导入报错,请查看上面的env.d.ts 文件内有没有导入 第二行代码
console.log(routes);

NProgress.configure({
  showSpinner: false, // 关闭右侧加载动画
});
// 创建路由对象
const router = createRouter({
  // 设置模式:history 模式
  history: createWebHistory(import.meta.env.BASE_URL),
  routes: [
    {
      path: "/",
      redirect: "/home",
    },
    // {
    //   path: "/home",
    //   name: "home",
    //   meta: {
    //     title: "首页",
    //   },
    //   component: () => import("@/views/home/index.vue"),
    // },
    // {
    //   path: "/home/test",
    //   name: "home-test",
    //   meta: {
    //     title: "首页-test",
    //   },
    //   component: () => import("@/views/home/test.vue"),
    // },
    // {
    //   path: "/login",
    //   name: "login",
    //   meta: {
    //     title: "登录页",
    //   },
    //   component: () => import("@/views/login/index.vue"),
    // },
    ...routes,//使用
  ],
});
//导航守卫
router.beforeEach((to, from, next) => {
  //进度条
  NProgress.start();
  //动态修改标题
  if (to.meta.title) {
    document.title = `${to.meta.title || ""}`;
  }

  next();
});
//后置守卫
router.afterEach((to) => {
  document.title = `${to.meta?.title || ""}`;
  NProgress.done();
});
export default router;
    • 关于需要添加meta信息和自定义路由的名字,需要在响应的页面下写一个route 标签 格式与常规书写路由的格式一致,可用语言有json yaml json5 ,默认json5
<script setup lang="ts">
import {} from "vue";
</script>

<template>
  <div class="">login-index</div>
</template>

<style scoped lang="scss"></style>
<route lang="json5">
{
  meta: {
    title: "自定义登录页标题",
  },
  name: "c-login",
  path: "/c-login",
}
</route>
  • 自动导入和使用layouts布局组件
    • src下创建layouts目录,目录下创建default.vue文件,常规的手机端布局,上中下
<script setup lang="ts">
import {} from "vue";
</script>

<template>
  <div class="h-[100vh] flex flex-col ">
    <div class="header h-[50px] bg-pink text-center">Header</div>
    <div class="main flex flex-1 bg-[lavenderblush]">
      <router-view />
    </div>
    <div class="footer h-[50px] bg-[paleturquoise] text-center">Footer</div>
  </div>
</template>

<style></style>
    • 然后改造下router/index.ts文件
// vue3:
//    createRouter:创建路由实例
//    路由模式:history(localhost:5173/login) hash(localhost:5173/#/login)
//      createWebHistory:设置 history 模式
//      createWebHashHistory:设置 hash 模式
//    import.meta.env.BASE_URL:去配置文件(vite.config.ts)中找到设置 base 路径
//      设置之后,开启服务时,会在服务前添加 base 对应的路径(默认值:/)
import { createRouter, createWebHistory } from "vue-router";
import NProgress from "nprogress";
import "nprogress/nprogress.css";

import routes from "~pages";
import { setupLayouts } from "virtual:generated-layouts";
const route = setupLayouts(routes);

NProgress.configure({
  showSpinner: false, // 关闭右侧加载动画
});
// 创建路由对象
const router = createRouter({
  // 设置模式:history 模式
  history: createWebHistory(import.meta.env.BASE_URL),
  routes: [
    {
      path: "/",
      redirect: "/home",
    },

    ...route,
  ],
});
//导航守卫
router.beforeEach((to, from, next) => {
  //进度条
  NProgress.start();
  //动态修改标题
  if (to.meta.title) {
    document.title = `${to.meta.title || ""}`;
  }

  next();
});
//后置守卫
router.afterEach((to) => {
  document.title = `${to.meta?.title || ""}`;
  NProgress.done();
});
export default router;
    • 如果某个页面不需要布局组件或者使用别的布局组件,那么同样可以在对应的文件内的route标签内填写
<route lang="json5">
{
  meta: {
    title: "test",
    layout: "headers", // layouts/下面需要有这个布局组件 ,如果不需要布局组件,给个false/或者给个不存在的布局组件即可
  },
}
</route>
增加打包文件标题管理,入口文件管理
  • vite-plugin-html 更多的是用来处理多页应用程序,但是现在我们没有这个场景,只需要用来根据环境变量修改标题,引入入口js文件即可。
  • 安装以及导入使用
    • pnpm i vite-plugin-html -D
    • vite.config.ts 中导入以及使用
import { defineConfig, loadEnv, UserConfig, ConfigEnv } from "vite";
import vue from "@vitejs/plugin-vue";
import UnoCSS from "unocss/vite";
// 自动导入 vue 等的 相关的函数
import AutoImport from "unplugin-auto-import/vite";
// 自动导入第三方组件 和项目组件
import components from "unplugin-vue-components/vite";
import { VantResolver } from "@vant/auto-import-resolver";
// 路由自动导入和布局组件自动合成
import { resolve } from "path";
import Pages from "vite-plugin-pages";
import Layouts from "vite-plugin-vue-layouts";
// 导入 vite-plugin-html 根据环境变量修改标题
import { createHtmlPlugin } from "vite-plugin-html";
// 配置路径简写
const pathSrc = resolve(__dirname, "src");
const pathComp = resolve(__dirname, "src/components");
const pathAssets = resolve(__dirname, "src/assets");

// https://vitejs.dev/config/
export default defineConfig(({ mode }: ConfigEnv): UserConfig => {
  const env = loadEnv(mode, process.cwd());
  console.log(env);
  return {
    base: env.VITE_PUBLIC_PATH,
    plugins: [
      vue(),
      UnoCSS(),
      AutoImport({
        // 自动导入参考: https://github.com/sxzz/element-plus-best-practices/blob/main/vite.config.ts
        imports: ["vue", "vue-router"],
        resolvers: [VantResolver()],
        eslintrc: {
          enabled: true,
          globalsPropValue: true,
          filepath: "./.eslintrc-auto-import.json",
        },
        vueTemplate: true,
        // 配置文件生成位置(false:关闭自动生成)
        dts: mode === "development" ? "src/typings/auto-imports.d.ts" : false,
      }),
      components({
        resolvers: [VantResolver()], //第三方组件库 例如 vant element 等
        dirs: ["src/components", "src/**/components"], //自定义的组件位置
        dts: mode === "development" ? "src/typings/components.d.ts" : false, //生成声明文件位置
      }),
      Pages({
        pagesDir: "src/views", //需要生成路由的文件目录,默认就是识别src下面的pages文件
        extensions: ["vue"],
        exclude: ["**/components/*.vue"], // 忽略的文件夹
        importMode: "async", // 是否是异步路由
      }),
      Layouts({
        // 如果是默认 layouts文件夹,默认 default.vue文件,则不需要配置
        layoutsDirs: "src/layouts", // 布局文件存放目录
        defaultLayout: "default", //对应 src/layouts/default.vue
      }),
      createHtmlPlugin({
        minify: true,
        /**
         * 在这里写entry后,你将不需要在`index.html`内添加 script 标签,原有标签需要删除
         * @default src/main.ts
         */
        entry: "/src/main.ts",
        /**
         * 需要注入 index.html ejs 模版的数据
         */
        inject: {
          data: {
            // 查找.env 等环境变量文件里面的VITE_PROJECT_TITLE,请以VITE_标识开头
            title: loadEnv(mode, process.cwd()).VITE_PROJECT_TITLE,
            injectScript: `<script src="/inject.js"></script> `,
            ENABLE_ERUDA: env.VITE_ENABLE_ERUDA || "false",
          },
        },
      }),
    ],
    resolve: {
      alias: {
        "@": pathSrc,
        comp: pathComp,
        // 配置图片要这样引用也可以自定义方法引入图片静态资源
        assets: pathAssets,
      },
    },
  };
});
    • 改造index.html文件
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <!-- <link rel="icon" type="image/svg+xml" href="/vite.svg" /> -->
    <meta
      name="viewport"
      content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, viewport-fit=cover"
      />

    <title><%- title %></title>
  </head>
  <body>
    <div id="app"></div>
    <%- injectScript %>
      <script>
        global = globalThis;
      </script>
      <!-- <script type="module" src="/src/main.ts"></script> -->
  </body>
</html>
配置gzip压缩
  • 安装vite-plugin-compression 插件 github
    • pnpm i vite-plugin-compression -D
    • 参数说明:
参数类型默认值说明
verbosebooleantrue是否在控制台输出压缩结果
filterRegExp or (file: string) => booleanDefaultFilter指定哪些资源不压缩DefaultFilter:/.(jsmjsjsoncsshtml)$/i
disablebooleanfalse是否禁用
thresholdnumber-体积大于 threshold 才会被压缩,单位 b
algorithmstringgzip压缩算法,可选 [ 'gzip' , 'brotliCompress' ,'deflate' , 'deflateRaw']
extstring.gz生成的压缩包后缀
compressionOptionsObject-对应的压缩算法参数
deleteOriginFileboolean-压缩后是否删除文件
  • vite.config.ts 导入使用
import { defineConfig, loadEnv, UserConfig, ConfigEnv } from "vite";
import vue from "@vitejs/plugin-vue";
import UnoCSS from "unocss/vite";
// 自动导入 vue 等的 相关的函数
import AutoImport from "unplugin-auto-import/vite";
// 自动导入第三方组件 和项目组件
import components from "unplugin-vue-components/vite";
import { VantResolver } from "@vant/auto-import-resolver";
// 路由自动导入和布局组件自动合成
import { resolve } from "path";
import Pages from "vite-plugin-pages";
import Layouts from "vite-plugin-vue-layouts";
// 导入 vite-plugin-html 根据环境变量修改标题
import { createHtmlPlugin } from "vite-plugin-html";
// 配置gzip压缩插件
import viteCompression from "vite-plugin-compression";
// 配置路径简写
const pathSrc = resolve(__dirname, "src");
const pathComp = resolve(__dirname, "src/components");
const pathAssets = resolve(__dirname, "src/assets");

// https://vitejs.dev/config/
export default defineConfig(({ mode }: ConfigEnv): UserConfig => {
  const env = loadEnv(mode, process.cwd());
  console.log(env);
  return {
    base: env.VITE_PUBLIC_PATH,
    plugins: [
      vue(),
      UnoCSS(),
      AutoImport({
        // 自动导入参考: https://github.com/sxzz/element-plus-best-practices/blob/main/vite.config.ts
        imports: ["vue", "vue-router"],
        resolvers: [VantResolver()],
        eslintrc: {
          enabled: true,
          globalsPropValue: true,
          filepath: "./.eslintrc-auto-import.json",
        },
        vueTemplate: true,
        // 配置文件生成位置(false:关闭自动生成)
        dts: mode === "development" ? "src/typings/auto-imports.d.ts" : false,
      }),
      components({
        resolvers: [VantResolver()], //第三方组件库 例如 vant element 等
        dirs: ["src/components", "src/**/components"], //自定义的组件位置
        dts: mode === "development" ? "src/typings/components.d.ts" : false, //生成声明文件位置
      }),
      Pages({
        pagesDir: "src/views", //需要生成路由的文件目录,默认就是识别src下面的pages文件
        extensions: ["vue"],
        exclude: ["**/components/*.vue"], // 忽略的文件夹
        importMode: "async", // 是否是异步路由
      }),
      Layouts({
        // 如果是默认 layouts文件夹,默认 default.vue文件,则不需要配置
        layoutsDirs: "src/layouts", // 布局文件存放目录
        defaultLayout: "default", //对应 src/layouts/default.vue
      }),
      createHtmlPlugin({
        minify: true,
        /**
         * 在这里写entry后,你将不需要在`index.html`内添加 script 标签,原有标签需要删除
         * @default src/main.ts
         */
        entry: "/src/main.ts",
        /**
         * 需要注入 index.html ejs 模版的数据
         */
        inject: {
          data: {
            // 查找.env 等环境变量文件里面的VITE_PROJECT_TITLE,请以VITE_标识开头
            title: loadEnv(mode, process.cwd()).VITE_PROJECT_TITLE,
            injectScript: `<script src="/inject.js"></script> `,
            ENABLE_ERUDA: env.VITE_ENABLE_ERUDA || "false",
          },
        },
      }),
      viteCompression(),
    ],
    resolve: {
      alias: {
        "@": pathSrc,
        comp: pathComp,
        // 配置图片要这样引用也可以自定义方法引入图片静态资源
        assets: pathAssets,
      },
    },
  };
});
配置svg-icon图标
  • 新增一个既可以使用本地svg,又可以自动导入网上的svg-icon使用的组件
  • 用到的插件
  • 安装
    • pnpm i vite-plugin-svg-icons unplugin-icons @iconify/vue -D
  • src/assets/ 目录下新建svg-icons目录,用于保存本地svg-icon资源
    • 创建一个18.svgsvg-error.svg2个文件
    • 18.svg 用于测试,svg-error.svg 用于使用本地icon时,没有传的时候的报错显示,并且控制台也会输出错误提醒
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M8.5 15H10V9H7v1.5h1.5zm3 0H16V9h-4.5zm1.5-1v-1.5h1.5V14zm0-2.5V10h1.5v1.5zM3 21V3h18v18z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="red" d="m12 1l11.951 20.7H.05zM3.513 19.7h16.974L12 5zM13 9.5V15h-2V9.5zm-2 7h2.004v2.004H11z"/></svg>
  • 根目录下新增.env配置文件
VITE_ICON_LOCAL_PREFIX=Icon-local
VITE_ICON_PREFIX=Icon
  • viteconfig.ts 配置
import { defineConfig, loadEnv, UserConfig, ConfigEnv } from "vite";
import vue from "@vitejs/plugin-vue";
import UnoCSS from "unocss/vite";
// 自动导入 vue 等的 相关的函数
import AutoImport from "unplugin-auto-import/vite";
// 自动导入第三方组件 和项目组件
import components from "unplugin-vue-components/vite";
import { VantResolver } from "@vant/auto-import-resolver";
// 路由自动导入和布局组件自动合成
import { resolve } from "path";
import Pages from "vite-plugin-pages";
import Layouts from "vite-plugin-vue-layouts";
// 导入 vite-plugin-html 根据环境变量修改标题
import { createHtmlPlugin } from "vite-plugin-html";
// 配置gzip压缩插件
import viteCompression from "vite-plugin-compression";
// 配置icon相关的
import Icons from "unplugin-icons/vite";
import IconsResolver from "unplugin-icons/resolver";
import { FileSystemIconLoader } from "unplugin-icons/loaders";
import { createSvgIconsPlugin } from "vite-plugin-svg-icons";
// 配置路径简写
const pathSrc = resolve(__dirname, "src");
const pathComp = resolve(__dirname, "src/components");
const pathAssets = resolve(__dirname, "src/assets");
const pathSvgIcon = resolve(__dirname, "src/assets/svg-icons");
// https://vitejs.dev/config/
export default defineConfig(({ mode }: ConfigEnv): UserConfig => {
  const env = loadEnv(mode, process.cwd());
  // console.log(env);
  const { VITE_ICON_LOCAL_PREFIX, VITE_ICON_PREFIX } = env;
  /** 本地svg图标集合名称 */
  const collectionName = VITE_ICON_LOCAL_PREFIX.replace(
    `${VITE_ICON_PREFIX}-`,
    ""
  );
 
  return {
    base: env.VITE_PUBLIC_PATH,
    plugins: [
      vue(),
      UnoCSS(),
      AutoImport({
        // 自动导入参考: https://github.com/sxzz/element-plus-best-practices/blob/main/vite.config.ts
        imports: ["vue", "vue-router"],
        resolvers: [VantResolver(), IconsResolver({ prefix: "Icon" })],
        eslintrc: {
          enabled: true,
          globalsPropValue: true,
          filepath: "./.eslintrc-auto-import.json",
        },
        vueTemplate: true,
        // 配置文件生成位置(false:关闭自动生成)
        dts: mode === "development" ? "src/typings/auto-imports.d.ts" : false,
      }),
      components({
        resolvers: [
          VantResolver(), // 自动注册图标组件
          IconsResolver({
            // 修改Icon组件前缀,不设置则默认为i,禁用则设置为false
            prefix: "icon",
            // 指定collection,默认为全部
            // enabledCollections: [],
          }),
        ], //第三方组件库 例如 vant element 等
        dirs: ["src/components", "src/**/components"], //自定义的组件位置
        dts: mode === "development" ? "src/typings/components.d.ts" : false, //生成声明文件位置
      }),
      Pages({
        pagesDir: "src/views", //需要生成路由的文件目录,默认就是识别src下面的pages文件
        extensions: ["vue"],
        exclude: ["**/components/*.vue"], // 忽略的文件夹
        importMode: "async", // 是否是异步路由
      }),
      Layouts({
        // 如果是默认 layouts文件夹,默认 default.vue文件,则不需要配置
        layoutsDirs: "src/layouts", // 布局文件存放目录
        defaultLayout: "default", //对应 src/layouts/default.vue
      }),
      createHtmlPlugin({
        minify: true,
        /**
         * 在这里写entry后,你将不需要在`index.html`内添加 script 标签,原有标签需要删除
         * @default src/main.ts
         */
        entry: "/src/main.ts",
        /**
         * 需要注入 index.html ejs 模版的数据
         */
        inject: {
          data: {
            // 查找.env 等环境变量文件里面的VITE_PROJECT_TITLE,请以VITE_标识开头
            title: loadEnv(mode, process.cwd()).VITE_PROJECT_TITLE,
            injectScript: `<script src="/inject.js"></script> `,
            ENABLE_ERUDA: env.VITE_ENABLE_ERUDA || "false",
          },
        },
      }),
      viteCompression(),
      Icons({
        autoInstall: true, // 自动安装
        compiler: "vue3", // 支持vue3
        scale: 1, // 图标大小
        defaultClass: "inline-block", // 插入icon的类名
        defaultStyle: "marginTop:-0.1875em", // 插入icon的样式
        customCollections: {
          [collectionName]: FileSystemIconLoader(pathSvgIcon, (svg) =>
            svg.replace(/^<svg\s/, '<svg width="1em" height="1em" ')
          ),
        },
      }),
      createSvgIconsPlugin({
        // 指定需要缓存的图标文件夹
        iconDirs: [resolve(pathSvgIcon)],
        // 指定symbolId格式
        symbolId: `${VITE_ICON_LOCAL_PREFIX}-[name]`,
      }),
    ],
    resolve: {
      alias: {
        "@": pathSrc,
        comp: pathComp,
        // 配置图片要这样引用也可以自定义方法引入图片静态资源
        assets: pathAssets,
      },
    },
  };
});
  • main.ts 引入 import "virtual:svg-icons-register";
  • src/components 目录下新建BaseIcon/SvgIcon.vue 组件
<template>
  <template v-if="renderLocalIcon">
    <svg aria-hidden="true" width="1em" height="1em" v-bind="bindAttrs">
      <use :xlink:href="symbolId" fill="currentColor" />
    </svg>
  </template>
  <template v-else>
    <Icon v-if="icon" :icon="icon" v-bind="bindAttrs" />
  </template>
</template>

<script setup lang="ts">
import { computed, useAttrs } from "vue";
import { Icon } from "@iconify/vue";

/**
 * 图标组件
 * - 支持iconify和本地svg图标
 * - 同时传递了icon和localIcon,localIcon会优先渲染
 */
interface Props {
  /** 图标名称 */
  icon?: string;
  /** 本地svg的文件名 */
  localIcon?: string;
}

const props = defineProps<Props>();

const attrs = useAttrs();

const bindAttrs = computed<{ class: string; style: string }>(() => ({
  class: (attrs.class as string) || "",
  style: (attrs.style as string) || "",
}));

const symbolId = computed(() => {
  const { VITE_ICON_LOCAL_PREFIX: prefix } = import.meta.env;
  if (!props.localIcon) {
    window.console.error(
      `SvgIcon: 没有传递图标名称,使用项目图标请确保给localIcon传递有效值!
      - localIcon: ${props.localIcon}
      - now error-icon-name: svg-error
      `
    );
    return `#${prefix}-svg-error`;
  }
  const icon = props.localIcon;

  return `#${prefix}-${icon}`;
});

/** 渲染本地icon */
const renderLocalIcon = computed(() => props.localIcon || !props.icon);
</script>

<style scoped></style>
    • 第14行代码 ts 可能会校验不过,解决办法,新增declare文件
      • typings目录下新建plugins-decalare.d.ts文件,
      • 填入declare module "@iconify/vue";
  • 使用组件的2中方式
<!-- 使用在线icon时,请复制 icon名字 下图 说明 -->
<SvgIcon icon="material-symbols-light:10k" class="color-pink" />
  <!-- 使用本地svg时,需要把文件放入src/assets/svg-icons目录下,使用的时候直接书写svg文件名即可 -->
<SvgIcon local-icon="18" class="color-red" />
  • 使用在线icon时,只需要复制icon的名字,插件会自动下载导入使用 示例网址vite5+vue3+typescript完整项目框架搭建
  • 效果如下可正常展示
    • vite5+vue3+typescript完整项目框架搭建
  • 另一种使用方式
    • 使用vue的h函数 处理一遍
    • src下新建utils/icons.ts 文件
import { h } from "vue";
import SvgIcon from "comp/BaseIcon/SvgIcon.vue";

export const useIconRender = () => {
  interface IconConfig {
    /**
     * 图标名称(iconify图标的名称)
     * - 例如:mdi-account 或者 mdi:account
     */
    icon?: string;
    /**
     * 本地svg图标文件名(assets/svg文件夹下)
     */
    localIcon?: string;
    /** 图标颜色 */
    color?: string;
    /** 图标大小 */
    fontSize?: number;
  }

  interface IconStyle {
    color?: string;
    fontSize?: string;
  }
  /**
   * 图标渲染
   * @param config
   * @property icon - 图标名称(iconify图标的名称), 例如:mdi-account 或者 mdi:account
   * @property localIcon - 本地svg图标文件名(assets/svg文件夹下)
   * @property color - 图标颜色
   * @property fontSize - 图标大小
   */
  const iconRender = (config: IconConfig) => {
    const { color, fontSize, icon, localIcon } = config;

    const style: IconStyle = {};

    if (color) {
      style.color = color;
    }
    if (fontSize) {
      style.fontSize = `${fontSize}px`;
    }

    if (!icon && !localIcon) {
      window.console.warn(
        "没有传递图标名称,请确保给icon或localIcon传递有效值!"
      );
    }

    return () => h(SvgIcon, { icon, localIcon, style });
  };
  return {
    iconRender,
  };
};
    • 在需要使用的地方导入,创建,然后使用component标签使用,效果一样
<script setup lang="ts">
import {} from "vue";
import { useIconRender } from "@/utils/icons";
const { iconRender } = useIconRender();

const icons = [
  {
    icon: iconRender({ localIcon: "18", color: "green", fontSize: 30 }),
  },
  {
    icon: iconRender({
      icon: "material-symbols-light:18mp-outline-rounded",
      color: "red",
      fontSize: 30,
    }),
  },
];
</script>

<template>
  <div class="">
    home-index

    <template v-for="(item, index) in icons" :key="index">
      <component :is="item.icon" />
    </template>
  </div>
</template>

<style scoped lang="scss"></style>
    • 如图
      • vite5+vue3+typescript完整项目框架搭建
引入控制台标识辅助打印插件
  • pnpm i vite-plugin-enhance-log -D
  • vite.config.ts 内的 plugins 内使用
  • 控制台报vite版本不对等的问题可以不管,因为我们只是在开发环境使用,为了方便我们调试而已
...
// 控制台标识符打印插件
import EnhanceLog from "vite-plugin-enhance-log";
...
// https://vitejs.dev/config/
export default defineConfig(({ mode }: ConfigEnv): UserConfig => {
 

  return {
    base: env.VITE_PUBLIC_PATH,
    plugins: [
     ...,
      EnhanceLog({
        splitBy: "\n",
        preTip: "🤡🤡🤡🤡🤡🤡🤡🤡🤡🤡",
        enableFileName: true,
        endLine: true,
      }),
    ],
    resolve: {
      alias: {
        "@": pathSrc,
        comp: pathComp,
        // 配置图片要这样引用也可以自定义方法引入图片静态资源
        assets: pathAssets,
      },
    },
  };
});
配置px转rem/vhvw,scss,以及css降级和添加css-polyfill
  • 以转rem为例
  • 安装 postcss-pxtorem
  • vite.config.ts 内添加css选项,内容如下

// px-rem
import postcssPxtorem from "postcss-pxtorem";

// https://vitejs.dev/config/
export default defineConfig(({ mode }: ConfigEnv): UserConfig => {
  return {
    css: {
      postcss: {
        plugins: [
          postcssPxtorem({
            rootValue: 16,
            propList: ["*"],
            unitPrecision: 6,
            selectorBlackList: [],
            replace: true,
            mediaQuery: false,
            minPixelValue: 0,
          }),
        ],
      },
    },
  };
});
  • ts类型可能会提示报错,typings/plugins-declare.d.ts内添加如下内容
declare module "postcss-preset-env";//css 自动添加浏览器前缀 先加上
declare module "postcss-pxtorem";
  • vite.config.ts内添加

// px-rem
import postcssPxtorem from "postcss-pxtorem";
//css 自动加前缀和polyfill
import postcssPresetEnv from "postcss-preset-env";
// https://vitejs.dev/config/
export default defineConfig(({ mode }: ConfigEnv): UserConfig => {
  return {
    css: {
      postcss: {
        plugins: [
          postcssPresetEnv()
        ],
      },
    },
  };
});
  • 添加sass 编译器
    • pnpm i sass sass-loader -D
    • src目录下 添加 styles/variables.scss 目录和文件,内容填写一个sass变量,即可随处引用
$myScssColor: #a22e;
    • vite.config.ts

// px-rem
import postcssPxtorem from "postcss-pxtorem";
//css 自动加前缀和polyfill
import postcssPresetEnv from "postcss-preset-env";
// https://vitejs.dev/config/
export default defineConfig(({ mode }: ConfigEnv): UserConfig => {
  return {
    css: {
       // CSS 预处理器
      preprocessorOptions: {
        // 定义全局 SCSS 变量
        scss: {
          javascriptEnabled: true,
          additionalData: `
            @use "@/styles/variables.scss" as *;
          `,
        },
      },
    },
  };
});
  • 然后在页面上使用
<script setup lang="ts">
import {} from "vue";
import { useIconRender } from "@/utils/icons";
const { iconRender } = useIconRender();

const icons = [
  {
    icon: iconRender({ localIcon: "18", color: "green", fontSize: 30 }),
  },
  {
    icon: iconRender({
      icon: "material-symbols-light:18mp-outline-rounded",
      color: "red",
      fontSize: 30,
    }),
  },
];
</script>

<template>
  <div class="home-index">
    home-index
    <div>5555</div>
    <template v-for="(item, index) in icons" :key="index">
      <component :is="item.icon" />
    </template>
  </div>
</template>

<style scoped lang="scss">
.home-index {
  div {
    color: $myScssColor;
  }
}
</style>

<route lang="json5">
{
  meta: {
    title: "首页",
    layout: false,
  },
}
</route>
创建axios副本
  • 安装 axios pnpm i axios -S
  • utils 下创建 request目录,然后目录下创建http.tstools.ts index.ts
import { showFailToast } from "vant";

export const handleNetworkError = (errStatus?: number): void => {
  const networkErrMap: any = {
    "400": "错误的请求", // token 失效
    "401": "未授权,请重新登录",
    "403": "拒绝访问",
    "404": "请求错误,未找到该资源",
    "405": "请求方法未允许",
    "408": "请求超时",
    "500": "服务器端出错",
    "501": "网络未实现",
    "502": "网络错误",
    "503": "服务不可用",
    "504": "网络超时",
    "505": "http版本不支持该请求",
  };
  if (errStatus) {
    showFailToast(networkErrMap[errStatus] ?? `其他连接错误 --${errStatus}`);
    return;
  }

  showFailToast("无法连接到服务器!");
};

export const handleAuthError = (errno: string): boolean => {
  const authErrMap: any = {
    "10031": "登录失效,需要重新登录", // token 失效
    "10032": "您太久没登录,请重新登录~", // token 过期
    "10033": "账户未绑定角色,请联系管理员绑定角色",
    "10034": "该用户未注册,请联系管理员注册用户",
    "10035": "code 无法获取对应第三方平台用户",
    "10036": "该账户未关联员工,请联系管理员做关联",
    "10037": "账号已无效",
    "10038": "账号未找到",
  };

  if (authErrMap.hasOwnProperty(errno)) {
    showFailToast(authErrMap[errno]);
    // 授权错误,登出账户
    // logout();
    return false;
  }

  return true;
};

export const handleGeneralError = (code: number, msg: string): boolean => {
  if (code !== 200) {
    showFailToast(msg);
    return false;
  }
  return true;
};
import axios, {
  AxiosError,
  AxiosInstance,
  AxiosRequestConfig,
  AxiosResponse,
  InternalAxiosRequestConfig,
  AxiosHeaders,
  Method,
} from "axios";
import { showLoadingToast, closeToast } from "vant";
import { handleGeneralError, handleNetworkError } from "./tools";
// 定义接口
export interface HRequestInterceptors {
  // 请求拦截器(成功与失败)
  requestInterceptors?: (
    config: InternalAxiosRequestConfig
  ) => InternalAxiosRequestConfig;
  requestInterceptorsCatch?: (error: any) => any;
  // 相应拦截器(成功与失败)
  responseInterceptor?: (res: AxiosResponse) => AxiosResponse;
  responseInterceptorCatch?: (error: any) => any;
}

/**
 * @description
 * 继承接口: 定义每个请求的拦截器并且设置请求状态显示
 *  axios  config 需要传递的参数
 *  */
export interface HRequestConfig extends AxiosRequestConfig {
  interceptors?: HRequestInterceptors;
  // 是否展示请求加载状态
  showLoading?: boolean;
  needToken?: boolean;
  // headers?: BaseHeaders;
}
/**
 * @description 判断 axios 请求返回值是否属于 AxiosError
 */
function isErrorObject(error: any): error is AxiosError {
  return (error as AxiosError).isAxiosError !== undefined;
}
// 请求基础配置
const baseConfig: HRequestConfig = {
  baseURL: import.meta.env.PROD ? import.meta.env.VITE_APP_BASE_URL : "/",
  timeout: 10000,
};
// 请求加载显示状态
const DEFAULT_LOADING = false;

const DEFAULT_NEED_TOKEN = true;

const TOKEN = import.meta.env.VITE_TOKEN;

class Http {
  // 类型
  instance: AxiosInstance;
  interceptors?: HRequestInterceptors;
  showLoading: boolean;
  needToken: boolean;
  //   loading?: LoadingInstance;
  constructor(config: HRequestConfig) {
    this.instance = axios.create(config);
    this.interceptors = config.interceptors;
    this.showLoading = config.showLoading ?? DEFAULT_LOADING;
    this.needToken = config.needToken ?? DEFAULT_NEED_TOKEN;

    // 请求拦截器 类型
    this.instance.interceptors.request.use(
      this.interceptors?.requestInterceptors,
      this.interceptors?.requestInterceptorsCatch
    );

    // 响应拦截器 类型
    this.instance.interceptors.response.use(
      this.interceptors?.responseInterceptor,
      this.interceptors?.responseInterceptorCatch
    );

    // 所有实例的请求拦截
    this.instance.interceptors.request.use(
      (config) => {
        if (this.showLoading) {
          showLoadingToast({
            duration: 0,
            message: "加载中...",
            forbidClick: true,
          });
        }
        if (this.needToken) {
          config.headers[TOKEN] = localStorage.getItem(TOKEN) || "";
        }
        // 测试环境的地址替换
        if (import.meta.env.MODE === "test") {
          config.url = config.url?.replace(
            //recyclable//g,
            "/recyclabledev/"
          );
        }
        console.log(config);

        return config;
      },
      (err: AxiosError) => {
        return err;
        // return Promise.reject(err);
      }
    );

    // 所有实例的响应拦截
    this.instance.interceptors.response.use(
      (res) => {
        closeToast();

        return res;
      },
      (err) => {
        return err;
      }
    );
  }

  server<T>(config: HRequestConfig): Promise<T> {
    this.showLoading = config.showLoading ?? DEFAULT_LOADING;
    this.needToken = config.needToken ?? DEFAULT_NEED_TOKEN;
    return new Promise((resolve, reject) => {
      this.instance
        .request(config)
        .then((res) => {
          console.log(
            isErrorObject(res),
            !handleGeneralError(
              res.data.data?.code || res.data.code,
              res.data.data?.msg || res.data.msg
            ),
            res
          );

          if (isErrorObject(res)) {
            const err = res as AxiosError;
            handleNetworkError(err.response?.status);
            reject(err);
          }
          console.log(res.data);

          if (!res) reject("请求失败");
          if (
            !handleGeneralError(
              res.data.data?.code || res.data.code,
              res.data.data?.msg || res.data.msg
            )
          ) {
            reject(res.data as T);
          }
          if (res.data.data?.code) {
            resolve(res.data.data as T);
          } else {
            resolve(res.data as T);
          }
        })
        .catch((err) => {
          reject(err);
        });
    });
  }
  // 对request 二次封装
  /**
   * @param T 返回值data 的类型声明
   * @param S 请求参数的类型声明
   * @param url  请求路径 必填
   * @param params 请求参数 可选
   * @param headers 请求头 可选
   * @param config  请求配置 可选
   * @returns  Promise 请求的响应参数
   */
  get<T, S>(
    url: string,
    params?: S,
    config?: HRequestConfig,
    headers?: AxiosHeaders
  ): Promise<AxiosResponse<T>> {
    return this.server<AxiosResponse<T>>({
      ...baseConfig,
      ...config,
      headers: { ...baseConfig.headers, ...config?.headers, ...headers },
      params,
      method: "GET",
      url,
    });
  }
  /**
   * @param T 返回值data 的类型声明
   * @param S 请求参数的类型声明
   * @param url  请求路径 必填
   * @param data 请求参数 可选
   * @param headers 请求头 可选
   * @param config  请求配置 可选
   * @returns  Promise 请求的响应参数
   */
  post<T, S>(
    url: string,
    data?: S,
    config?: HRequestConfig,
    headers?: AxiosHeaders
  ): Promise<AxiosResponse<T>> {
    return this.server<AxiosResponse<T>>({
      ...baseConfig,
      ...config,
      headers: { ...baseConfig.headers, ...config?.headers, ...headers },
      data,
      method: "POST",
      url,
    });
  }
  /**
   * @param T 返回值data 的类型声明
   * @param S 请求参数的类型声明
   * @param url  请求路径 必填
   * @param params 请求参数 可选
   * @param headers 请求头 可选
   * @param config  请求配置 可选
   * @returns  Promise 请求的响应参数
   */
  put<T, S>(
    url: string,
    data?: S,
    config?: HRequestConfig,
    headers?: AxiosHeaders
  ): Promise<AxiosResponse<T>> {
    return this.server<AxiosResponse<T>>({
      ...baseConfig,
      ...config,
      headers: { ...baseConfig.headers, ...config?.headers, ...headers },
      data,
      method: "PUT",
      url,
    });
  }
  /**
   * @param T 返回值data 的类型声明
   * @param S 请求参数的类型声明
   * @param url  请求路径 必填
   * @param params 请求参数 可选
   * @param headers 请求头 可选
   * @param config  请求配置 可选
   * @returns  Promise 请求的响应参数
   */
  delete<T, S>(
    url: string,
    data?: S,
    headers?: AxiosHeaders,
    config?: HRequestConfig
  ): Promise<AxiosResponse<T>> {
    return this.server<AxiosResponse<T>>({
      ...baseConfig,
      ...config,
      headers: { ...baseConfig.headers, ...config?.headers, ...headers },
      data,
      method: "DELETE",
      url,
    });
  }
  /**
   * @param T 返回值data 的类型声明
   * @param S 请求参数的类型声明
   * @param url  请求路径 必填
   * @param data 请求参数 可选
   * @param method 请求方法 可选 不传默认为 GET 当如果需要自定义 headers,config 时 必传
   * @param headers 请求头 可选
   * @param config  请求配置 可选
   * @returns  Promise 请求的响应参数
   * @example
   * request.request<{name:string,id:number},{username:string,password:string}>('/api/login', { username: 'admin', password: '123456' }, 'POST')
   */
  request<T, S>(
    url: string,
    data?: S,
    method?: Method,
    headers?: AxiosHeaders,
    config?: HRequestConfig
  ): Promise<AxiosResponse<T>> {
    return this.server<AxiosResponse<T>>({
      ...baseConfig,
      ...config,
      headers: { ...baseConfig.headers, ...config?.headers, ...headers },
      data,
      method: method ? method : "GET",
      url,
    });
  }
}

/**
 * @description
 * 使用 Http 类 new 一个 请求实例
 *
 *  @param {HRequestConfig} config
 *
 *  interceptors  当前实例的 自定义 请求 响应 拦截器 需要特殊处理的可以在这里配置
 */
const request = new Http({
  ...baseConfig,
  interceptors: {
    requestInterceptors: (config) => {
      return config;
    },
    requestInterceptorsCatch(err) {
      return err;
    },
    responseInterceptor(res) {
      return res;
    },
    responseInterceptorCatch(error) {
      return error;
    },
  },
});

export { Http, request };
export * from "./http";
创建pinia仓库实例
  • src 下新建 stores 目录,目录下新建index.ts文件和modules文件夹,文件夹内新建home.ts
export const useHomeStore = defineStore("home", () => {
  const count = ref(0);
  return {
    count,
  };
});
import { createPinia } from "pinia";
//数据持久化插件
import persist from "pinia-plugin-persistedstate";
const pinia = createPinia();
pinia.use(persist);
export default pinia;

export * from "./modules/home";
  • 然后在main.ts 内导入挂载
import { createApp } from "vue";

import App from "./App.vue";


// 导入pinia
import pinia from "./stores";

const app = createApp(App);


app.use(pinia);

app.mount("#app");