vite+uniapp项目搭建喂饭教程

项目搭建文档

项目概述

本项目是一个基于 Uni-app 框架开发的多端应用,支持 H5、微信小程序、支付宝小程序等多种平台。项目使用 Vue 3 作为前端框架,结合 Pinia 进行状态管理,同时集成了 Tailwind CSS 进行样式开发。

技术栈

主要框架和库

  • Uni-app :版本 3.0.0-4060620250520001,用于开发多端应用。
  • Vue 3 :版本 ^3.5.17,用于构建用户界面。
  • Pinia :版本 ^2.3.1,Vue 3 的状态管理库。
  • Tailwind CSS :版本 ^4.1.10,用于快速构建样式。
  • weapp-tailwindcss :版本 ^4.1.9,uniapp适配库。

开发工具

  • Vite :版本 5.2.8,快速的构建工具。
  • TypeScript :版本 ^4.9.5,为 JavaScript 添加静态类型检查。
  • ESLint :版本 ^9.29.0,用于代码规范检查。
  • prettier :版本 ^3.5.3,用于代码规范检查。
  • stylelint :版本 ^16.20.0,用于代码规范检查。

提交校验

  • husky :版本 ^15.5.2,用于代码规范检查。
  • lint-staged :版本 ^8.0.3,用于代码规范检查。

UI库

  • uni-ui:官方UI库。
  • wot-design-uni:wot-design-uni组件库基于vue3+Typescript构建(sass版本:1.78.0)。

项目基本结构

plaintext 复制代码
f:\taokou-front-end/
├── .editorconfig
├── .env.development
├── .env.production
├── .env.test
├── .gitignore
├── .prettierignore
├── .prettierrc
├── .stylelintignore
├── .stylelintrc.mjs
├── .vscode/
│   └── settings.json
├── README.md
├── commitlint.config.mjs
├── docs/
│   └── .vitepress/
│       ├── cache/
│       ├── config.ts
│       └── utils/
├── eslint.config.mjs
├── index.html
├── package.json
├── pnpm-lock.yaml
├── pnpm-workspace.yaml
├── shims-uni.d.ts
├── src/
│   ├── App.vue
│   ├── api/
│   ├── components/
│   ├── components.d.ts
│   ├── core/
│   ├── env.d.ts
│   ├── hook/
│   ├── index.md
│   ├── main.ts
│   ├── manifest.json
│   ├── pages/
│   ├── pages.json
│   ├── shime-uni.d.ts
│   ├── static/
│   ├── store/
│   ├── uni-app.d.ts
│   ├── uni.scss
│   ├── uni_modules/
│   └── utils/
├── tailwind.config.js
├── tsconfig.json
├── tsconfig.node.json
└── vite.config.ts

项目搭建步骤

1. 初始化项目

首先,确保你已经安装了 Node.js 和 pnpm。然后使用 Uni-app 官方脚手架创建一个新的项目:

bash 复制代码
pnpm init uni-app@latest my-uniapp-project --template vue-ts
cd my-uniapp-project

2. 集成依赖

集成 Vue 3 和 Pinia
bash 复制代码
pnpm add vue@^3.5.17 pinia@^2.3.1 pinia-plugin-persistedstate@^4.3.0

src/store/index.ts 中配置 Pinia:

typescript 复制代码
import { createPinia } from 'pinia'
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'

const pinia = createPinia()
pinia.use(piniaPluginPersistedstate)

export default pinia

src/main.ts 中引入并使用 Pinia:

typescript 复制代码
import { createApp } from 'vue'
import App from './App.vue'
import pinia from './store'

const app = createApp(App)
app.use(pinia)
app.mount('#app')
集成 Tailwind CSS
bash 复制代码
pnpm add -D tailwindcss@^4.1.10 @tailwindcss/vite@^4.1.10 weapp-tailwindcss@^4.1.9

初始化 Tailwind CSS 配置文件:

bash 复制代码
npx tailwindcss init

tailwind.config.mjs 中配置:

javascript 复制代码
module.exports = {
  content: ['./src/**/*.{vue,js,ts,jsx,tsx}'],
  theme: {
    extend: {}
  },
  plugins: []
}

vite.config.ts 中添加 Tailwind CSS 插件:

typescript 复制代码
import tailwindcss from '@tailwindcss/vite'
import { UnifiedViteWeappTailwindcssPlugin } from 'weapp-tailwindcss/vite'

const isH5 = process.env.UNI_PLATFORM === 'h5'
const isApp = process.env.UNI_PLATFORM === 'app'
const WeappTailwindcssDisabled = isH5 || isApp

// 在 defineConfig 的 plugins 数组中添加
plugins: [
  tailwindcss(),
  UnifiedViteWeappTailwindcssPlugin({
    rem2rpx: true,
    disabled: WeappTailwindcssDisabled,
    cssPreflight: {
      replace: true,
      rootValue: 32
    }
  })
]

3. 配置开发工具

配置 TypeScript

tsconfig.json 中添加以下配置:

json 复制代码
{
  "compilerOptions": {
    "noImplicitAny": false,
    "target": "ESNext",
    "module": "ESNext",
    "moduleResolution": "node",
    "lib": ["ESNext", "DOM", "DOM.Iterable"],
    "skipLibCheck": false,
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "preserve",
    "jsxImportSource": "vue",
    "strict": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noFallthroughCasesInSwitch": true,
    "esModuleInterop": true,
    "allowJs": true,
    "baseUrl": ".",
    "paths": { "@/*": ["src/*"], "#/*": ["types/*"] },
    "types": ["@dcloudio/types"]
  },
  "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"]
}
配置 ESLint 和 Prettier

安装相关依赖:

bash 复制代码
pnpm add -D eslint@^9.28.0 eslint-config-prettier@^10.1.5 eslint-plugin-prettier@^5.4.1 prettier@^3.5.3

在项目根目录创建 eslint.config.mjs.prettierrc 文件进行配置。

typescript 复制代码
/**.eslint.config.mjs*/
// import pluginJs from '@eslint/js'
import simpleImportSort from 'eslint-plugin-simple-import-sort'
import pluginVue from 'eslint-plugin-vue'
import globals from 'globals'
import tseslint from 'typescript-eslint'
import vueParser from 'vue-eslint-parser'
/** @type {import('eslint').Linter.Config[]} */
export default [
  {
    // 设置 ECMAScript 版本和模块类型
    languageOptions: {
      parser: vueParser, // 使用vue解析器,这个可以识别vue文件
      parserOptions: {
        parser: tseslint.parser, // 在vue文件上使用ts解析器
        sourceType: 'module', // 指定代码使用 ES 模块化(import 和 export)语法
        ecmaFeatures: {
          jsx: true
        }
      },

      globals: {
        ...globals.browser,
        ...globals.node,
        var1: 'writable', // 声明一个可写的全局变量
        ResponseResult: 'readonly', // 声明一个只读的全局变量
        ResponseList: 'readonly',
        ResponseData: 'readonly'
      }
    }
  },
  /** js推荐配置 */
  // pluginJs.configs.recommended,
  ...tseslint.configs.recommended,
  ...pluginVue.configs['flat/essential'],
  /** vue推荐配置 */
  ...pluginVue.configs['flat/recommended'],
  {
    languageOptions: { parserOptions: { parser: tseslint.parser } },
    files: ['**/*.{js,mjs,cjs,ts,vue}'],
    plugins: {
      'simple-import-sort': simpleImportSort
    },
    rules: {
      'simple-import-sort/imports': 2,
      'simple-import-sort/exports': 2,
      'vue/no-v-html': 'off',
      'no-undef': 'off',
      'no-unused-vars': 1,
      '@typescript-eslint/no-unused-vars': 1,
      'prettier/prettier': 'off',
      'arrow-body-style': 'off',
      'prefer-arrow-callback': 'off',
      'vue/attributes-order': [
        'error',
        {
          order: [
            'DEFINITION',
            'LIST_RENDERING',
            'CONDITIONALS',
            'RENDER_MODIFIERS',
            'GLOBAL',
            'UNIQUE',
            'TWO_WAY_BINDING',
            'OTHER_DIRECTIVES',
            'OTHER_ATTR',
            'EVENTS',
            'CONTENT'
          ]
        }
      ],
      'vue/one-component-per-file': 'off',
      'vue/comment-directive': [
        'error',
        {
          reportUnusedDisableDirectives: false
        }
      ],
      'vue/max-attributes-per-line': [
        'error',
        {
          singleline: {
            max: 10
          },
          multiline: {
            max: 4
          }
        }
      ],
      'vue/html-self-closing': [
        0,
        {
          html: { normal: 'never', void: 'always' },
          svg: 'always',
          math: 'always'
        }
      ],
      'vue/singleline-html-element-content-newline': 'off',
      'vue/block-order': [
        'error',
        {
          order: [['template', 'script'], 'style']
        }
      ],
      'vue/multi-word-component-names': 'off', // 禁用vue文件强制多个单词命名
      '@typescript-eslint/no-explicit-any': 'off', //允许使用any
      '@typescript-eslint/no-this-alias': [
        'error',
        {
          allowedNames: ['that'] // this可用的局部变量名称
        }
      ],
      'no-unused-expressions': 'off',
      '@typescript-eslint/no-unused-expressions': 'off',
      '@typescript-eslint/ban-ts-comment': 'off', //允许使用@ts-ignore
      '@typescript-eslint/no-non-null-assertion': 'off', //允许使用非空断言
      'no-console': [
        //提交时不允许有console.log
        'off',
        {
          allow: ['warn', 'error']
        }
      ],

      'no-debugger': 'warn' //提交时不允许有debugger
    }
  }
]
json 复制代码
/**.prettierrc*/
{
  "eslintIntegration": true,
  "printWidth": 100,
  "tabWidth": 2,
  "useTabs": false,
  "semi": false,
  "vueIndentScriptAndStyle": true,
  "singleQuote": true,
  "quoteProps": "as-needed",
  "bracketSpacing": true,
  "trailingComma": "none",
  "jsxBracketSameLine": false,
  "jsxSingleQuote": false,
  "arrowParens": "always",
  "insertPragma": false,
  "requirePragma": false,
  "proseWrap": "never",
  "htmlWhitespaceSensitivity": "strict",
  "endOfLine": "lf",
  "plugins": ["prettier-plugin-tailwindcss"]
}
配置 stylelint

安装相关依赖:

bash 复制代码
pnpm add -D stylelint stylelint-config-recess-order stylelint-config-standard stylelint-config-standard-scss stylelint-config-standard-vue stylelint-order stylelint-scss

在项目根目录创建 .stylelintrc.mjs 文件进行配置。

typescript 复制代码
/**.stylelintrc.mjs*/
export default {
  extends: [
    'stylelint-config-standard',
    'stylelint-config-standard-scss',
    'stylelint-config-standard-vue',
    'stylelint-config-recess-order'
  ],
  overrides: [
    {
      files: ['*.scss', '**/*.scss'],
      customSyntax: 'postcss-scss'
    },
    {
      files: ['*.vue', '**/*.vue'],
      customSyntax: 'postcss-html'
    }
  ],
  ignoreFiles: ['**/uni_modules/**/*'],
  rules: {
    'property-no-unknown': [
      true,
      {
        ignoreProperties: ['page']
      }
    ],
    'selector-type-no-unknown': [
      true,
      {
        ignore: ['custom-elements'],
        ignoreTypes: ['page']
      }
    ],
    'function-no-unknown': null,
    'scss/no-global-function-names': null,
    'no-empty-source': null,
    'selector-class-pattern': null,
    'no-duplicate-selectors': null,
    'declaration-property-value-no-unknown': null,
    'unit-no-unknown': [
      true,
      {
        ignoreUnits: ['rpx']
      }
    ],
    'function-name-case': [
      'lower',
      {
        ignoreFunctions: ['constant', 'env']
      }
    ]
  }
}
配置 lint-staged提交规范

安装相关依赖:

bash 复制代码
pnpm add -D husky  lint-staged

package.json 中添加以下配置:

json 复制代码
{
  "scripts": {
    "prepare": "husky install && (grep -q 'commitlint' .husky/commit-msg 2>/dev/null || husky add .husky/commit-msg 'npx --no-install commitlint --edit $1')"
  }
  "lint-staged": {
    "src/**/*.{vue,js,jsx,ts,tsx,json}": ["pnpm run lint", "pnpm run prettier"],
    "*.{css, less}": ["stylelint --fix"]
  }
}

文档搭建

bash 复制代码
pnpm add vitepress
目录结构
lua 复制代码
├─ docs
│  ├─ .vitepress
│  │  └─ config.js
│  │  └─ utils.js
│  │  │ └─ sidebar.ts
├─ src
│  └─ index.md
└─ package.json
typescript 复制代码
/**
 sidebar.ts
 */
import { readdirSync, statSync } from 'fs'
import { join } from 'path'

interface SidebarItem {
  text: string
  link?: string
  items?: SidebarItem[]
}

function generateSidebarItems(dir: string, basePath: string = ''): SidebarItem[] {
  const items: SidebarItem[] = []
  const files = readdirSync(dir)

  files.forEach((file) => {
    const fullPath = join(dir, file)
    const stat = statSync(fullPath)

    if (stat.isDirectory()) {
      // 如果是目录,递归处理
      const subItems = generateSidebarItems(fullPath, join(basePath, file))
      if (subItems.length > 0) {
        items.push({
          text: file,
          items: subItems
        })
      }
    } else if (file.endsWith('.md')) {
      // 如果是 Markdown 文件
      const fileName = file.replace('.md', '')
      // 确保 link 以 / 开头,并移除多余的斜杠
      const link = '/' + join(basePath, fileName).replace(/\\/g, '/').replace(/\/+/g, '/')
      items.push({
        text: fileName,
        link: link
      })
    }
  })

  return items
}

export function generateSidebar(srcPath: string) {
  const sidebar = generateSidebarItems(srcPath)
  return {
    '/': [
      {
        items: sidebar
      }
    ]
  }
}
typescript 复制代码
/**
 config.js
 */
import { resolve } from 'path'
import { defineConfig } from 'vitepress'

import { generateSidebar } from './utils/sidebar'

// https://vitepress.dev/reference/site-config
export default defineConfig({
  lang: 'zh',
  title: '软件文档',
  cleanUrls: true,
  lastUpdated: true,
  srcDir: '../src',
  description: 'A VitePress Site',
  vue: {
    // @vitejs/plugin-vue 选项
  },

  themeConfig: {
    search: {
      provider: 'local'
    },
    footer: {
      message: 'Released under the MIT License.',
      copyright: 'Copyright © 2019-present Evan You'
    },
    nav: [],

    sidebar: generateSidebar(resolve(__dirname, '../../src')),

    socialLinks: [{ icon: 'gitee', link: 'https://gitee.com/fxg1997/taokou-front-end.git' }]
  }
})

开发环境运行

H5 平台
bash 复制代码
pnpm run dev:h5
微信小程序平台
bash 复制代码
pnpm run dev:mp-weixin

5. 生产环境构建

H5 平台
bash 复制代码
pnpm run build:h5
微信小程序平台
bash 复制代码
pnpm run build:mp-weixin

package.json

json 复制代码
/** package.json*/
{
  "name": "@apps/shoumaba",
  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "ins": "rm -rf node_modules pnpm-lock.yaml && pnpm install",
    "force": "pnpm store prune && pnpm install --force",
    "dev:custom": "uni -p",
    "dev:test": "uni --mode test",
    "dev:h5": "uni",
    "dev:h5:ssr": "uni --ssr",
    "dev:mp-alipay": "uni -p mp-alipay",
    "dev:mp-baidu": "uni -p mp-baidu",
    "dev:mp-jd": "uni -p mp-jd",
    "dev:mp-kuaishou": "uni -p mp-kuaishou",
    "dev:mp-lark": "uni -p mp-lark",
    "dev:mp-qq": "uni -p mp-qq",
    "dev:mp-toutiao": "uni -p mp-toutiao",
    "dev:mp-weixin": "uni -p mp-weixin",
    "dev:mp-xhs": "uni -p mp-xhs",
    "dev:quickapp-webview": "uni -p quickapp-webview",
    "dev:quickapp-webview-huawei": "uni -p quickapp-webview-huawei",
    "dev:quickapp-webview-union": "uni -p quickapp-webview-union",
    "build:custom": "uni build -p",
    "build:h5": "uni build",
    "build:h5:test": "uni build --mode test",
    "build:h5:ssr": "uni build --ssr",
    "build:mp-alipay": "uni build -p mp-alipay",
    "build:mp-baidu": "uni build -p mp-baidu",
    "build:mp-jd": "uni build -p mp-jd",
    "build:mp-kuaishou": "uni build -p mp-kuaishou",
    "build:mp-lark": "uni build -p mp-lark",
    "build:mp-qq": "uni build -p mp-qq",
    "build:mp-toutiao": "uni build -p mp-toutiao",
    "build:mp-weixin": "uni build -p mp-weixin",
    "build:mp-xhs": "uni build -p mp-xhs",
    "build:quickapp-webview": "uni build -p quickapp-webview",
    "build:quickapp-webview-huawei": "uni build -p quickapp-webview-huawei",
    "build:quickapp-webview-union": "uni build -p quickapp-webview-union",
    "type-check": "vue-tsc --noEmit",
    "docs:dev": "vitepress dev docs --port 8080",
    "docs:build": "vitepress build docs",
    "docs:preview": "vitepress preview docs",
    "lint": "eslint src --cache --fix --ext .ts,.tsx,.vue,.js,.jsx --max-warnings 0",
    "prettier": "prettier --write .",
    "lint:style": "stylelint --cache --fix \"src/**/*.{less,postcss,css,scss,vue}\" --cache --cache-location node_modules/.cache/stylelint/",
    "prepare": "husky install && (grep -q 'commitlint' .husky/commit-msg 2>/dev/null || husky add .husky/commit-msg 'npx --no-install commitlint --edit $1')"
  },
  "dependencies": {
    "@dcloudio/uni-app": "3.0.0-4060620250520001",
    "@dcloudio/uni-app-harmony": "3.0.0-4060620250520001",
    "@dcloudio/uni-app-plus": "3.0.0-4060620250520001",
    "@dcloudio/uni-components": "3.0.0-4060620250520001",
    "@dcloudio/uni-h5": "3.0.0-4060620250520001",
    "@dcloudio/uni-mp-alipay": "3.0.0-4060620250520001",
    "@dcloudio/uni-mp-baidu": "3.0.0-4060620250520001",
    "@dcloudio/uni-mp-harmony": "3.0.0-4060620250520001",
    "@dcloudio/uni-mp-jd": "3.0.0-4060620250520001",
    "@dcloudio/uni-mp-kuaishou": "3.0.0-4060620250520001",
    "@dcloudio/uni-mp-lark": "3.0.0-4060620250520001",
    "@dcloudio/uni-mp-qq": "3.0.0-4060620250520001",
    "@dcloudio/uni-mp-toutiao": "3.0.0-4060620250520001",
    "@dcloudio/uni-mp-weixin": "3.0.0-4060620250520001",
    "@dcloudio/uni-mp-xhs": "3.0.0-4060620250520001",
    "@dcloudio/uni-quickapp-webview": "3.0.0-4060620250520001",
    "@vue/runtime-dom": "^3.5.17",
    "crypto-js": "^4.2.0",
    "dayjs": "^1.11.13",
    "deep-pick-omit": "^1.2.1",
    "destr": "^2.0.5",
    "jsencrypt": "^3.3.2",
    "lodash": "^4.17.21",
    "pinia": "^2.3.1",
    "pinia-plugin-persistedstate": "^4.3.0",
    "postcss": "^8.5.6",
    "vconsole": "^3.15.1",
    "vue": "^3.5.17",
    "vue-i18n": "^9.14.4"
  },
  "devDependencies": {
    "@commitlint/cli": "^18.6.1",
    "@commitlint/config-conventional": "^18.6.3",
    "@dcloudio/types": "^3.4.15",
    "@dcloudio/uni-automator": "3.0.0-4060620250520001",
    "@dcloudio/uni-cli-shared": "3.0.0-4060620250520001",
    "@dcloudio/uni-stacktracey": "3.0.0-4060620250520001",
    "@dcloudio/vite-plugin-uni": "3.0.0-4060620250520001",
    "@eslint/js": "^9.29.0",
    "@tailwindcss/vite": "^4.1.10",
    "@types/crypto-js": "^4.2.2",
    "@types/lodash": "^4.17.18",
    "@types/node": "^22.15.32",
    "@typescript-eslint/eslint-plugin": "^8.34.1",
    "@typescript-eslint/parser": "^8.34.1",
    "@vitejs/plugin-basic-ssl": "^1.2.0",
    "@vitejs/plugin-vue-jsx": "^4.2.0",
    "@vue/runtime-core": "^3.5.17",
    "@vue/tsconfig": "^0.1.3",
    "eslint": "^9.29.0",
    "eslint-config-prettier": "^10.1.5",
    "eslint-plugin-prettier": "^5.5.0",
    "eslint-plugin-simple-import-sort": "^12.1.1",
    "eslint-plugin-vue": "^9.33.0",
    "globals": "^13.24.0",
    "husky": "^8.0.3",
    "lint-staged": "^15.5.2",
    "prettier": "^3.5.3",
    "prettier-plugin-tailwindcss": "^0.6.12",
    "rollup-plugin-visualizer": "^5.14.0",
    "sass": "1.78.0",
    "lightningcss": "^1.30.1",
    "stylelint": "^16.20.0",
    "stylelint-config-recess-order": "^6.1.0",
    "stylelint-config-standard": "^37.0.0",
    "stylelint-config-standard-scss": "^14.0.0",
    "stylelint-config-standard-vue": "^1.0.0",
    "stylelint-order": "^6.0.4",
    "stylelint-scss": "^6.12.1",
    "tailwindcss": "^4.1.10",
    "typescript": "^4.9.5",
    "typescript-eslint": "^8.34.1",
    "unplugin-auto-import": "^19.3.0",
    "unplugin-vue-components": "^28.7.0",
    "vite": "5.2.8",
    "vite-plugin-cdn-import": "^1.0.1",
    "vite-plugin-compression": "^0.5.1",
    "vite-plugin-externals": "^0.6.2",
    "vite-plugin-imagemin": "^0.6.1",
    "vite-plugin-stylelint": "^6.0.0",
    "vite-plugin-svg-icons": "^2.0.1",
    "vite-plugin-vue-devtools": "^7.7.7",
    "vitepress": "^1.6.3",
    "vue-eslint-parser": "^9.4.3",
    "vue-tsc": "^1.8.27",
    "weapp-tailwindcss": "^4.1.9"
  },
  "lint-staged": {
    "src/**/*.{vue,js,jsx,ts,tsx,json}": ["pnpm run lint", "pnpm run prettier"],
    "*.{css, less}": ["pnpm run lint:style"]
  }
}

项目基础配置

env环境

  1. 在项目根目录添加 .env 文件,用于配置通用环境变量。在 src 目录中使用环境变量时,变量名必须以 VITE_ 开头。例如,在 main.js 中可以这样输出环境变量:
javascript 复制代码
console.log('环境变量', import.meta.env)
  1. 在 vite 配置中使用环境变量没有限制,可以直接通过 import.meta.env.XXX 访问。
vite 环境模式

vite支持两种默认环境模式:

  • 开发环境(development) :在执行 vite 命令时使用,对应的环境变量文件为 .env.development
  • 生产环境(production) :在执行 vite build 命令时使用,对应的环境变量文件为 .env.production
自定义生产环境

若需要自定义生产环境(如 release 环境),可按以下步骤操作:

  1. package.json 中添加脚本:
json 复制代码
"scripts": {
  "release": "vite build --mode release"
}
  1. 在根目录创建 .env.release 文件,并声明变量:
plaintext 复制代码
VITE_BASE_URL = ''
VITE_BASE_DEFAULTIMG = ''
  1. 执行 yarn release 命令,加载.env.release环境

api封装

  • 封装考虑重复请求,统一拦截,防止重复请求
  1. 重复用map实现,有相同的key,值会被覆盖
javascript 复制代码
const pendingMap = new Map()
/**
 * 生成每个请求唯一的键
 */
function getPendingKey(config): string {
  const { url, method } = config
  const { data = {}, params = {} } = config
  return [url, method, JSON.stringify(data), JSON.stringify(params)].join('&')
}

/**
 * 储存每个请求唯一值, 用于取消请求
 */
function addPending(config, requestTask): void {
  const pendingKey = getPendingKey(config)
  pendingMap.set(pendingKey, requestTask)
}

/**
 * 删除重复的请求
 */
function removePending(config): void {
  const pendingKey = getPendingKey(config)
  if (pendingMap.has(pendingKey)) {
    const requestTask = pendingMap.get(pendingKey)!
    requestTask.abort() // 取消请求
    pendingMap.delete(pendingKey)
  }
}
/**
 * 删除重复的请求
 */
function completePending(config): void {
  const pendingKey = getPendingKey(config)
  if (pendingMap.has(pendingKey)) {
    pendingMap.delete(pendingKey)
  }
}
  1. 封装请求
javascript 复制代码
export function createRequest(defaultConfig {}) {
  // 默认配置
  const defaultOptions = {
    baseURL: '',
    timeout: 60000,
    header: {},
    dataType: 'json',
    responseType: 'text',
    repeatRequestCancel: true,
    ...defaultConfig
  }

  // 请求拦截器数组
  const requestInterceptors: RequestInterceptor[] = []
  // 响应拦截器数组
  const responseInterceptors: ResponseInterceptor[] = []

  /**
   * 添加请求拦截器
   */
  function interceptRequest(
    onFulfilled: RequestInterceptor['onFulfilled'],
    onRejected?: RequestInterceptor['onRejected']
  ) {
    requestInterceptors.push({
      onFulfilled,
      onRejected
    })
  }

  /**
   * 添加响应拦截器
   */
  function interceptResponse(
    onFulfilled: ResponseInterceptor['onFulfilled'],
    onRejected?: ResponseInterceptor['onRejected']
  ) {
    responseInterceptors.push({
      onFulfilled,
      onRejected
    })
  }

  /**
   * 执行请求拦截器链
   */
  async function processRequestInterceptors(config) {
    let currentConfig = { ...config }

    for (const interceptor of requestInterceptors) {
      try {
        const result = await interceptor.onFulfilled(currentConfig)
        currentConfig = result || currentConfig
      } catch (error) {
        if (interceptor.onRejected) {
          interceptor.onRejected(error)
        }
        throw error
      }
    }

    return currentConfig
  }

  /**
   * 执行响应拦截器链
   */
  async function processResponseInterceptors(response){
    let currentResponse = { ...response }

    for (const interceptor of responseInterceptors) {
      try {
        const result = await interceptor.onFulfilled(currentResponse)
        currentResponse = result || currentResponse
      } catch (error) {
        if (interceptor.onRejected) {
          interceptor.onRejected(error)
        }
        throw error
      }
    }

    return currentResponse
  }

  /**
   * 发起请求的核心函数
   */
  async function request<T = any>(config) {
    // 合并默认配置
    const mergedConfig = {
      ...defaultOptions,
      ...config,
      header: { ...defaultOptions.header, ...config.header }
    }

    try {
      // 执行请求拦截器链
      const processedConfig = await processRequestInterceptors(mergedConfig)

      // 处理重复请求
      if (processedConfig.repeatRequestCancel) {
        removePending(processedConfig)
      }

      // 发起请求
      return await new Promise((resolve, reject) => {
        const requestTask = uni.request({
          url: processedConfig.baseURL
            ? processedConfig.baseURL + processedConfig.url
            : processedConfig.url,
          data: processedConfig.data,
          method: processedConfig.method,
          header: processedConfig.header,
          timeout: processedConfig.timeout,
          dataType: processedConfig.dataType,
          responseType: processedConfig.responseType,
          success: async (res) => {
            // 请求完成后,移除pending中的请求
            if (processedConfig.repeatRequestCancel) {
              completePending(processedConfig)
            }

            // 构造响应对象
            const response{
              data: res.data,
              statusCode: res.statusCode,
              header: res.header,
              cookies: res.cookies,
              config: processedConfig
            }

            try {
              // 执行响应拦截器链
              const processedResponse = await processResponseInterceptors(response)
              resolve(processedResponse.data)
            } catch (error) {
              reject(error)
            }
          },
          fail: (err) => {
            // 请求失败后,移除pending中的请求
            if (processedConfig.repeatRequestCancel) {
              completePending(processedConfig)
            }
            reject(err)
          }
        })

        // 存储请求任务用于取消
        if (processedConfig.repeatRequestCancel) {
          addPending(processedConfig, requestTask)
        }
      })
    } catch (error) {
      console.log('【请求未知】', error)
      return Promise.reject(error)
    }
  }

  // 添加请求方法别名
  const requestInstance = {
    request,

    interceptors: {
      request: {
        use: interceptRequest
      },
      response: {
        use: interceptResponse
      }
    }
  }

  return requestInstance
}
  1. 使用
javascript 复制代码
import { createRequest } from './request/index'

const apiRequest = createRequest({
  baseURL: 'https://api.example.com',
  timeout: 30000,
  repeatRequestCancel: true
})

// 添加拦截器
apiRequest.interceptors.request.use(
   (config: RequestConfig) => {
    // 请求前处理
    return config
  },
   (error: Error)  => {
    return Promise.reject(error)
  }
)

// 添加默认响应拦截器
apiRequest.interceptors.response.use(
  (response: UniResponse) =>{
    // 请求前处理
    return config
  },
   (error: Error)  => {
    return Promise.reject(error)
  }
)
// 导出请求实例
export const request = apiRequest.request

export function request(data) {
  return request({
    url: '/index',
    method: 'POST',
    data
  })
}
  1. 根据业务需求拓展 缓存 重试
相关推荐
布兰妮甜1 分钟前
单例模式在前端(JavaScript)中的实现与应用
前端·javascript·单例模式
Mintopia1 分钟前
Three.js 加载模型文件:从二进制到像素的奇幻漂流
前端·javascript·three.js
前端小巷子20 分钟前
跨域问题解决方案:JSONP
前端·javascript·面试
eric*168828 分钟前
尚硅谷张天禹老师课程配套笔记
前端·vue.js·笔记·vue·尚硅谷·张天禹·尚硅谷张天禹
程序员爱钓鱼44 分钟前
Go语言中的反射机制 — 元编程技巧与注意事项
前端·后端·go
GIS之路1 小时前
GeoTools 结合 OpenLayers 实现属性查询(二)
前端·信息可视化
烛阴1 小时前
一文搞懂 Python 闭包:让你的代码瞬间“高级”起来!
前端·python
AA-代码批发V哥1 小时前
HTML之表单结构全解析
前端·html
聪聪的学习笔记1 小时前
【1】确认安装 Node.js 和 npm版本号
前端·npm·node.js
小磊哥er2 小时前
【前端工程化】你知道前端编码规范包含哪些内容吗
前端