IT评测·应用市场-qidao123.com技术社区

标题: 【前端】Vue3 + AntdVue + Ts + Vite4 + pnpm + Pinia 实战 [打印本页]

作者: 麻花痒    时间: 2025-4-8 19:46
标题: 【前端】Vue3 + AntdVue + Ts + Vite4 + pnpm + Pinia 实战
背景:如今前端框架多、版本多、迭代快。以 vue 为例,就有 vue2/vue3,依赖管理工具就有 npm/yarn,打包工具就有 webpack/vite,UI 框架就有 antd-vue/element_ui/element_ui_plus,没有统一的路径可循,影象心智负担较重。
为了方便后续快速搭建项目,能利用最新的框架,而且减轻心智负担,本文采用 vue3 + yarn + vite + antd-vue,方便快速上手。
教程
一、项目搭建

1.1 生态工具对比

包管理 pnpm(npm、yarn)


打包工具 vite (webpack)


1.2 项目创建

vite 项目搭建教程
vite 官网
1.2.0 项目搭建

此中各下令的表明, 详见下文
  1. nvm install 22
  2. nvm alias default v22.14.0
  3. nvm use 22
  4. node -v # v22.14.0 (nvm ls 可查看/选择版本)
  5. npm install -g corepack # 升级最新版 corepack
  6. corepack -v # 查 corepack 版本 0.31.0
  7. corepack enable # 安装 pnpm
  8. corepack prepare pnpm@latest --activate # 升级 pnpm 到最新版本
  9. pnpm -v # 10.5.2
  10. pnpm create vite fe --template vue-ts # 初始化项目
  11. cd fe && pnpm install && pnpm run dev # 安装依赖并run
  12. pnpm add @types/node --save-dev # 为保证 node 的使用. 这是 Node.js 的 TypeScript 类型定义文件. 详见下文
复制代码
1.2.1 node 版本

  1. # 安装 nvm (~/.zshrc 可配置 nvm 的环境变量如下:)
  2. # nvm
  3. export NVM_DIR="$HOME/.nvm"
  4. [ -s "/opt/homebrew/opt/nvm/nvm.sh" ] && \. "/opt/homebrew/opt/nvm/nvm.sh"                                       # This loads nvm
  5. [ -s "/opt/homebrew/opt/nvm/etc/bash_completion.d/nvm" ] && \. "/opt/homebrew/opt/nvm/etc/bash_completion.d/nvm" # This loads nvm    bash_completion
  6. nvm use 22
  7. nvm ls
复制代码
node 版本, 不会对整套技术栈有太大影响, 反而建议尽量新, 这样可以支持更多特性:

1.2.2 corepack

Corepack 是 Node.js 内置的一个包管理工具管理器(Package Manager Manager),用于管理差别的 JavaScript 包管理工具(如 npm、yarn、pnpm 等)。它的重要目的是简化包管理工具的安装和版本管理,确保开辟者在差别项目中使用同等的包管理工具版本。
Corepack 从 Node.js 16.9.0 开始默认启用,并在后续版本中逐步增强。
corepack enable 是一个下令,用于启用 Corepack 的功能。具体来说:运行 corepack enable 后,Corepack 会在系统中启用,并准备好管理包管理工具。启用后,你可以直接使用 yarn 或 pnpm 等下令,而无需手动安装这些工具。
默认 Node.js 16.9.0 及更高版本中,Corepack 默认是启用的,因此通常不需要手动运行 corepack enable。
示例:
启用 Corepack:corepack enable
安装特定版本的包管理工具:corepack prepare yarn@1.22.19 --activate
在项目中使用指定的包管理工具:在 package.json 中指定包管理工具及其版本:
  1. {
  2.   "packageManager": "yarn@1.22.19"
  3. }
复制代码
然后运行 yarn 或 pnpm 时,Corepack 会自动使用指定的版本
1.2.3 npm install -g

npm install -g vite @vue/cli 安装的是 下令行工具(CLI), 而不是 nodejs 的代码库.

  1. vite create my-project
  2. cd my-project
  3. vite dev
复制代码

npm install 可加/不加 -g 参数
pnpm add 可加/不加 -g 参数
上述二者, 都既可以安装 下令行 cli, 也可以安装代码库.
如果一个项目的 package.json 中含 “bin” 则表示为 下令行 cli, 否则表示为代码库
例如 vite 的 package.json 如下:
  1. {
  2.   "name": "vite",
  3.   "bin": {
  4.     "vite": "bin/vite.js"
  5.   }
  6. }
复制代码
例如 lodash 的 package.json 如下:
  1. {
  2.   "name": "lodash",
  3.   "main": "lodash.js"
  4. }
复制代码
如果下令行, 未指定 -g, 则被安装在本项目的 node_modules 目录中(而不在全局 node_modules 目录中), 则可通过 npx 运行, 例如 npx vite
1.2.4 pnpm

配置方式:
  1. Appended new lines to /Users/y/.zshrc
  2. Next configuration changes were made:
  3. export PNPM_HOME="/Users/y/Library/pnpm"
  4. case ":$PATH:" in
  5.   *":$PNPM_HOME:"*) ;;
  6.   *) export PATH="$PNPM_HOME:$PATH" ;;
  7. esac
  8. To start using pnpm, run:
  9. source /Users/y/.zshrc
复制代码
升级最新版 pnpm self-update, (注意项目的 package.json 文件中写死了 pnpm 的旧版本号, 可手动改该文件, 或切换到非项目目录再 pnpm -v).
检察版本 pnpm -v
pnpm store path 可检察其全局存储路径:
  1. # pnpm store path
  2. /Users/abc/Library/pnpm/store/v3
  3. # du -sh /Users/abc/Library/pnpm/store/v3
  4. 335M        /Users/abc/Library/pnpm/store/v3
复制代码
安装生产依赖:
如果包是项目运行所必需的,则可不填 (或 --save)(或 -S):pnpm add lodash
全局安装:
如果包需要全局使用,使用 -g:pnpm add typescript -g
删除包:
如果需要删除包,使用 uninstall:pnpm uninstall @types/node --save-dev
1.2.5 pnpm add @types/node --save-dev

为包管 node 的使用. 这是 Node.js 的 TypeScript 类型定义文件.
–save-dev 是把包作为 开辟依赖 devDependencies 安装(而不是作为生产依赖).
pnpm 会从 npm 堆栈下载 @types/node 包,并将其安装到项目的 node_modules 目录中. @types/node 会被添加到 package.json 的 devDependencies 字段中.
  1. {
  2.   "devDependencies": {
  3.     "@types/node": "^20.0.0"
  4.   }
  5. }
复制代码
安装后,TypeScript 编译器会自动识别 Node.js 的类型,避免类型错误
使用场景:
当你使用 TypeScript 开辟 Node.js 项目时,需要安装 @types/node 来获得 Node.js API 的类型支持。
例如,在代码中使用 fs 模块时,TypeScript 会检查 fs.readFile 的参数和返回值类型。
示例:
假设你在开辟一个 Node.js + TypeScript 项目,代码中使用了 fs 模块:
  1. import fs from 'fs';
  2. fs.readFile('file.txt', 'utf-8', (err, data) => {
  3.   if (err) throw err;
  4.   console.log(data);
  5. });
复制代码
如果没有安装 @types/node,TypeScript 会报错,因为它无法识别 fs 模块的类型。安装 @types/node 后,TypeScript 就能正确识别 fs 的类型。
1.2.6 pnpm create vite

大概有一种征象: vite -v 和 vue -v 都无输出(说明没安装), 但 pnpm create vite fe --template vue-ts 却可以正常创建 vite-vue-ts 的项目.
  1. "devDependencies": {
  2.   "vite": "^6.2.0"
  3. }
复制代码
在项目中, pnpm vite -v 即可运行本地安装的 vite
  1. # pnpm vite -v
  2. vite/6.2.0 darwin-arm64 node-v20.18.3
复制代码
1.3 集成配置

tsconfig.json

tsconfig.json 就用 脚手架 生成的即可, 无需修改
最新的脚手架分为如下三个文件:
tsconfig.json 如下:
  1. {
  2.   "files": [],
  3.   "references": [
  4.     { "path": "./tsconfig.app.json" },
  5.     { "path": "./tsconfig.node.json" }
  6.   ]
  7. }
复制代码
tsconfig.app.json 如下:
  1. {
  2.   "extends": "@vue/tsconfig/tsconfig.dom.json",
  3.   "compilerOptions": {
  4.     "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
  5.     /* Linting */
  6.     "strict": true,
  7.     "noUnusedLocals": true,
  8.     "noUnusedParameters": true,
  9.     "noFallthroughCasesInSwitch": true,
  10.     "noUncheckedSideEffectImports": true
  11.   },
  12.   "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"]
  13. }
复制代码
tsconfig.node.json 如下:
  1. {
  2.   "compilerOptions": {
  3.     "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
  4.     "target": "ES2022",
  5.     "lib": ["ES2023"],
  6.     "module": "ESNext",
  7.     "skipLibCheck": true,
  8.     /* Bundler mode */
  9.     "moduleResolution": "bundler",
  10.     "allowImportingTsExtensions": true,
  11.     "isolatedModules": true,
  12.     "moduleDetection": "force",
  13.     "noEmit": true,
  14.     /* Linting */
  15.     "strict": true,
  16.     "noUnusedLocals": true,
  17.     "noUnusedParameters": true,
  18.     "noFallthroughCasesInSwitch": true,
  19.     "noUncheckedSideEffectImports": true
  20.   },
  21.   "include": ["vite.config.ts"]
  22. }
复制代码
vite.config.ts

修改 vite.config.ts 参考. 此中关键是指定 @ 为 src 目录的别名.
(可选) 新建 /src/assets/styles/variables.scss 用于放公共的 scss 变量,其可被 .scss 文件或 .vue 文件 引用。
  1. pnpm add mockjs -S # 用于生成随机数据的 JavaScript 库,主要用于前端开发中的模拟数据(Mock Data)
  2. pnpm add vite-plugin-mock -D # 是一个 Vite 插件,用于在 Vite 项目中集成 Mock 数据功能
  3. pnpm add postcss-px-to-viewport -S # 是一个 PostCSS 插件,用于将 CSS 中的 px 单位转换为 viewport 单位(如 vw、vh),以实现移动端自适应布局
复制代码
  1. // vite.config.ts
  2. import { defineConfig } from 'vite'
  3. import vue from '@vitejs/plugin-vue'
  4. import { viteMockServe } from "vite-plugin-mock";
  5. import * as path from "path";
  6. let backendAddr = process.env.BACKEND_ADDR || 'http://192.168.2.180:334';
  7. export default defineConfig({
  8.   // base: "/foo/", // 开发或生产环境服务的公共基础路径
  9.   base: "/", // 开发或生产环境服务的公共基础路径
  10.   optimizeDeps: {
  11.     force: true, // 强制进行依赖预构建
  12.   },
  13.   css: {
  14.     preprocessorOptions: {
  15.       scss: {
  16.         additionalData: `@use '/src/assets/styles/variables.scss';`, // 引入全局变量文件
  17.       },
  18.     },
  19.   },
  20.   resolve: {
  21.     alias: {
  22.       "@": path.resolve(__dirname, "./src"), // 路径别名
  23.     },
  24.     extensions: [".js", ".ts", ".json"], // 导入时想要省略的扩展名列表
  25.   },
  26.   server: {
  27.     host: true, // 监听所有地址
  28.     proxy: {
  29.       // // 字符串简写写法
  30.       "/foo": "http://localhost:4567",
  31.       // // 选项写法
  32.       "^/api": {
  33.         target: backendAddr,
  34.         changeOrigin: true,
  35.         rewrite: (path: string) => path.replace(/^\/api/, ""),
  36.       },
  37.       "^/ws": {
  38.         target: backendAddr,
  39.         changeOrigin: true,
  40.         rewrite: (path: string) => path.replace(/^\/ws/, ""),
  41.       }
  42.     },
  43.   },
  44.   build: {
  45.     outDir: "dist", // 打包文件的输出目录
  46.     assetsDir: "static", // 静态资源的存放目录
  47.     assetsInlineLimit: 4096, // 图片转 base64 编码的阈值
  48.   },
  49.   plugins: [vue(), viteMockServe()],
  50. });
复制代码
启动脚本

通常,本地启动前端后,需要连本机环境和联调环境(和后端同砚调接口),则可在 vite.config.js 中设置如下(上文已包含):
  1. let backendAddr = process.env.BACKEND_ADDR || 'http://192.168.2.99:334'; // 若有环境变量则连本地后端服务,若无则连联调机器的后端服务
  2. // 并在 server.proxy 中引用该变量即可,示例如下:
  3.   server: {
  4.     host: true, // 监听所有地址
  5.     proxy: {
  6.       "^/ws": {
  7.         target: backendAddr,
  8.         changeOrigin: true,
  9.         rewrite: (path) => path.replace(/^\/ws/, ""),
  10.       },
  11.     }
复制代码
则指定环境变量启动即可连本地后端服务:
  1. BACKEND_ADDR='http://127.0.0.1:9999' pnpm run dev
复制代码
eslint(可选)

  1. pnpm add eslint eslint-plugin-vue --save-dev
  2. pnpm add @typescript-eslint/parser --save-dev
复制代码
创建配置文件: .eslintrc.js
  1. module.exports = {
  2.     parser: 'vue-eslint-parser',
  3.     parserOptions: {
  4.         parser: '@typescript-eslint/parser',
  5.         ecmaVersion: 2020,
  6.         sourceType: 'module',
  7.         ecmaFeatures: {
  8.             jsx: true
  9.         }
  10.     },
  11.     extends: [
  12.         'plugin:vue/vue3-recommended',
  13.         'plugin:@typescript-eslint/recommended',
  14.     ],
  15.     rules: {
  16.         // override/add rules settings here, such as:
  17.     }
  18. };
复制代码
创建忽略文件:.eslintignore
  1. node_modules/
  2. dist/
  3. index.html
复制代码
下令行式运行:修改 package.json
  1. {
  2.     ...
  3.     "scripts": {
  4.         ...
  5.         "eslint:comment": "使用 ESLint 检查并自动修复 src 目录下所有扩展名为 .js 和 .vue 的文件",
  6.         "eslint": "eslint --ext .js,.vue --ignore-path .gitignore --fix src",
  7.     }
  8.     ...
  9. }
复制代码
集成 prittier(可选)

  1. pnpm add prettier eslint-config-prettier eslint-plugin-prettier --save-dev
复制代码
创建配置文件: prettier.config.js 或 .prettierrc.js
  1. module.exports = {
  2.   // 一行最多 80 字符
  3.   printWidth: 80,
  4.   // 使用 4 个空格缩进
  5.   tabWidth: 4,
  6.   // 不使用 tab 缩进,而使用空格
  7.   useTabs: false,
  8.   // 行尾需要有分号
  9.   semi: true,
  10.   // 使用单引号代替双引号
  11.   singleQuote: true,
  12.   // 对象的 key 仅在必要时用引号
  13.   quoteProps: "as-needed",
  14.   // jsx 不使用单引号,而使用双引号
  15.   jsxSingleQuote: false,
  16.   // 末尾使用逗号
  17.   trailingComma: "all",
  18.   // 大括号内的首尾需要空格 { foo: bar }
  19.   bracketSpacing: true,
  20.   // jsx 标签的反尖括号需要换行
  21.   jsxBracketSameLine: false,
  22.   // 箭头函数,只有一个参数的时候,也需要括号
  23.   arrowParens: "always",
  24.   // 每个文件格式化的范围是文件的全部内容
  25.   rangeStart: 0,
  26.   rangeEnd: Infinity,
  27.   // 不需要写文件开头的 @prettier
  28.   requirePragma: false,
  29.   // 不需要自动在文件开头插入 @prettier
  30.   insertPragma: false,
  31.   // 使用默认的折行标准
  32.   proseWrap: "preserve",
  33.   // 根据显示样式决定 html 要不要折行
  34.   htmlWhitespaceSensitivity: "css",
  35.   // 换行符使用 lf
  36.   endOfLine: "auto",
  37. };
复制代码
修改 .eslintrc.js 配置
  1. module.exports = {
  2.     ...
  3.     extends: [
  4.         'plugin:vue/vue3-recommended',
  5.         'plugin:@typescript-eslint/recommended',
  6.         'prettier',
  7.         'plugin:prettier/recommended'
  8.     ],
  9.     ...
  10. };
复制代码
下令行式运行:修改 package.json
  1. {
  2.     ...
  3.     "scripts": {
  4.         ...
  5.         "prettier:comment": "自动格式化当前目录下的所有文件",
  6.         "prettier": "prettier --write"
  7.     }
  8.     ...
  9. }
复制代码
效果如下:
![](https://i-blog.csdnimg.cn/direct/d773c52752e44981a8bed24b1997401c.png = 200x200)
pinia

踩坑教训: 不可用时, 先检查是否 pnpm add 乐成了, (因为 vscode 提示时好时坏)
  1. pnpm add pinia # 若报错, 则可先停止 pnpm run dev 再执行
复制代码
新建 src/store 目录并在其下面创建 index.ts,导出 store
  1. import { createPinia } from 'pinia'
  2. const store = createPinia()
  3. export default store
复制代码
在 main.ts 中引入并使用
  1. import { createApp } from "vue";
  2. import "./style.css";
  3. import App from "./App.vue";
  4. import store from "./store";
  5. const app = createApp(App); // 创建vue实例
  6. app.use(store); // 挂载pinia
  7. app.mount("#app"); // 挂载实例
复制代码
定义State: 在 src/store 下面创建一个 user.ts
  1. import { defineStore } from "pinia";
  2. export const useUserStore = defineStore("user", {
  3.     state: () => {
  4.         return {
  5.             name: "张三",
  6.         };
  7.     },
  8.     actions: {
  9.         updateName(name: string) {
  10.             this.name = name;
  11.         },
  12.     },
  13. });
复制代码
获取和修改 State: 在 src/components/usePinia.vue 中使用
  1. <template>
  2.     <div>{{ userStore.name }}</div>
  3. </template>
  4. <script lang="ts" setup>
  5. import { useUserStore } from "@/store/user";
  6. const userStore = useUserStore();
  7. userStore.updateName("张三");
  8. </script>
复制代码
注意, 因为此中用到了 “@”, 所以需在 tsconfig.json 或 tsconfig.app.json 中定义如下:
  1.   "compilerOptions": {
  2.     "baseUrl": ".",
  3.     "paths": {
  4.       "@/*": ["src/*"]
  5.     }
  6.   }
复制代码
集成 vue-router4

  1. pnpm add vue-router
复制代码
新建 src/router 目录并在其下面创建 index.ts,导出 router
  1. import { createRouter, createWebHashHistory } from 'vue-router';
  2. import type { RouteRecordRaw } from 'vue-router';
  3. const routes: Array<RouteRecordRaw> = [
  4.     // {
  5.     //     path: "/login",
  6.     //     name: "Login",
  7.     //     meta: {
  8.     //         title: "登录",
  9.     //         keepAlive: true,
  10.     //         requireAuth: false,
  11.     //     },
  12.     //     component: () => import("@/pages/login.vue"),
  13.     // },
  14.     {
  15.         path: "/aa",
  16.         name: "VueUse",
  17.         meta: {
  18.             title: "鼠标",
  19.             keepAlive: true,
  20.             requireAuth: true,
  21.         },
  22.         component: () => import("@/pages/vueUse.vue"), // 组件的 懒加载
  23.     },
  24.     // {
  25.     //     path: "/hello",
  26.     //     name: "HelloWorld",
  27.     //     meta: {
  28.     //         title: "计数器",
  29.     //         keepAlive: true,
  30.     //         requireAuth: true,
  31.     //     },
  32.     //     component: () => import("@/components/HelloWorld.vue"),
  33.     // },
  34.     // {
  35.     //     path: "/request",
  36.     //     name: "request",
  37.     //     meta: {
  38.     //         title: "请求页",
  39.     //         keepAlive: true,
  40.     //         requireAuth: true,
  41.     //     },
  42.     //     component: () => import("@/pages/request.vue"),
  43.     // }
  44. ];
  45. const router = createRouter({
  46.     history: createWebHashHistory(),
  47.     routes,
  48. });
  49. export default router;
复制代码
在 main.ts 中引入 import router from '@/router'; 并使用
  1. import { createApp } from "vue";
  2. import "./style.css";
  3. import App from "./App.vue";
  4. import store from "./store";
  5. import router from "./router"; // 或 '@/router'
  6. const app = createApp(App); // 创建vue实例
  7. app.use(store).use(router); // 挂载组件
  8. app.mount("#app"); // 挂载实例
复制代码
修改 App.vue
  1. <script setup lang="ts">
  2. import HelloWorld from "./components/HelloWorld.vue";
  3. </script>
  4. <template>
  5.   <h1>abc</h1>
  6.   <div>123</div>
  7.   <HelloWorld msg="def" />
  8.   <RouterView />
  9. </template>
  10. <style>
  11. #app {
  12.   font-family: Avenir, Helvetica, Arial, sans-serif;
  13.   -webkit-font-smoothing: antialiased;
  14.   -moz-osx-font-smoothing: grayscale;
  15.   text-align: center;
  16.   color: #2c3e50;
  17.   margin-top: 60px;
  18. }
  19. </style>
复制代码
集成 vueUse

VueUse 是一个基于 Composition API 的实用函数聚集。
  1. pnpm add @vueuse/core
复制代码
新建 src/pages/vueUse.vue 如下:useMouse 只是 vueuse 的一个最根本的函数库
  1. <template>
  2.   <h1>测试 use 鼠标坐标</h1>
  3.   <h3>Mouse: {{ x }} x {{ y }}</h3>
  4. </template>
  5. <script lang="ts">
  6. import { defineComponent } from "vue";
  7. import { useMouse } from "@vueuse/core";
  8. export default defineComponent({
  9.   name: "VueUse",
  10.   setup() {
  11.     const { x, y } = useMouse();
  12.     return {
  13.       x,
  14.       y,
  15.     };
  16.   },
  17. });
  18. </script>
复制代码
localhost:5173 如下:

localhost:5173/aa 如下:

还有很多,总会有一个适合你;更多函数官方文档
localhost:/5173/hello 如下:

集成 sass

  1. pnpm add -D sass
复制代码
新建 src/assets/styles/variables.scss 文件, 内容如下:
  1. $blue: #007bff;
  2. $red: #dc3545;
  3. $primary-color: red;
  4. $secondary-color: blue;
复制代码
使用在 .vue 文件, 因为上文已在 vite.config.ts 中 @use '/src/assets/styles/variables.scss';, 所以直接使用即可. 例如设置 color 为 red:
  1. <template>
  2.     <h1>测试 use 鼠标坐标</h1>
  3.     <h6 class="c1">{{ x }} x {{ y }}</h6>
  4. </template>
  5. <script lang="ts">
  6. import { defineComponent } from "vue";
  7. import { useMouse } from "@vueuse/core";
  8. export default defineComponent({
  9.   name: "VueUse",
  10.   setup() {
  11.     const { x, y } = useMouse();
  12.     return {
  13.       x,
  14.       y,
  15.     };
  16.   },
  17. });
  18. </script>
  19. <style lang="scss">
  20. .c1 {
  21.     color: blue; // 亲测, red/blue/yellow等不需要自己定义, 默认已经有了. 其实在 vite.config.ts 里已经 @use '/src/assets/styles/variables.scss 了, 可以再其中再定义变量, 如 $blue 等
  22. }
  23. </style>
复制代码
localhost://5173 效果如下:

集成 axios

axios 是一个基于 promise 的 HTTP 库,可以用在浏览器和 node.js 中。
  1. pnpm add axios
复制代码
新建 src/utils/axios.ts
  1. import axios from 'axios';
  2. import type { AxiosResponse, InternalAxiosRequestConfig } from 'axios';
  3. const service = axios.create();
  4. // Request interceptors
  5. service.interceptors.request.use(
  6.     (config: InternalAxiosRequestConfig) => {
  7.         // do something
  8.         return config;
  9.     },
  10.     (error: any) => {
  11.         return Promise.reject(error);
  12.     }
  13. );
  14. // Response interceptors
  15. service.interceptors.response.use(
  16.     async (response: AxiosResponse) => {
  17.         console.log("response", response);
  18.         // do something
  19.         return response;
  20.     },
  21.     (error: any) => {
  22.         // do something
  23.         return Promise.reject(error);
  24.     }
  25. );
  26. export default service;
复制代码
在页面中使用即可, 此中涉及 await 调接口, setup() 的机会调用, 调用后赋值给变量, 并在 <template> 内渲染, 完整 vue 代码和关键步骤如下:

  1. <template>
  2.     <h1>测试 use 鼠标坐标</h1>
  3.     <h2 v-if="d">接口响应结果为 {{ d }}</h2>
  4.     <h3 class="c1">Mouse: {{ x }} x {{ y }}</h3>
  5. </template>
  6. <script lang="ts">
  7. import { defineComponent, ref } from "vue";
  8. import { useMouse } from "@vueuse/core"; // 安装后该引用将正常解析
  9. import request from '@/utils/axios';
  10. export default defineComponent({
  11.     name: "VueUse",
  12.     setup() {
  13.         const d = ref<any>(null); // 定义响应式变量, 用于存接口返回的数据
  14.         const requestRes = async () => {// 定义函数, 其调接口
  15.             try {
  16.                 const result = await request({
  17.                     url: '/api/abc-server/ping',
  18.                     method: 'get'
  19.                 })
  20.                 console.log(result);
  21.                 d.value = result.data; // 把接口响应的数据, 赋值给, 响应式变量
  22.             } catch (error) {
  23.                 console.error("请求失败", error);
  24.             }
  25.         }
  26.         requestRes(); // 组件初始化时, 调函数
  27.         const { x, y } = useMouse(); // 将正确获取鼠标坐标
  28.         return { x, y, d }; // 所有组件内的 变量, 都要 return 出去, template 才能使用
  29.     },
  30. });
  31. </script>
  32. <style lang="scss">
  33. @import '@/assets/styles/variables.scss';
  34. .c1 {
  35.     color: blue;
  36. }
  37. </style>
复制代码
其对应的 vite.config.ts 的关键配置如下, 具体内容上文有先容.

用 go 写个 http server 如下:
  1. func main() {
  2.         http.HandleFunc("/ping", Ping)
  3.         http.ListenAndServe(":9999", nil)
  4. }
  5. func Ping(w http.ResponseWriter, req *http.Request) {
  6.         fmt.Fprintf(w, "pong\n")
  7. }
  8. // curl --location --request GET 'http://localhost:9999/ping'
  9. pong
复制代码
封装哀求参数和相应数据的所有 api

新建 src/api/index.ts:
  1. import * as login from './module/login';
  2. import * as ping from './module/ping';
  3. export default Object.assign({}, login, ping);
  4. /*
  5. 其中 `import * as` 将 ./module/login 模块中的所有导出内容, 作为一个命名空间对象导入。
  6. 例如,如果 login.ts 导出了 loginUser 和 logoutUser 两个函数,那么 login 对象将包含这两个函数:
  7. login = {
  8.   loginUser: Function,
  9.   logoutUser: Function,
  10. };
  11. */
  12. /*
  13. Object.assign 是 JavaScript 的方法,用于将一个或多个对象的属性合并到目标对象中。
  14. 第一个参数是目标对象(这里是空对象 {}),后面的参数是源对象(这里是 login 和 index)。
  15. 合并后,目标对象将包含 login 和 index 的所有属性。
  16. 例如若
  17. login = {
  18.   loginUser: Function,
  19.   logoutUser: Function,
  20. };
  21. index = {
  22.   getData: Function,
  23.   setData: Function,
  24. };
  25. 则合并后的对象为
  26. {
  27.   loginUser: Function,
  28.   logoutUser: Function,
  29.   getData: Function,
  30.   setData: Function,
  31. }
  32. 属性冲突的情况:
  33. 其中, 如果 login 和 index 中有同名属性,Object.assign 会以后面的对象为准。例如:
  34. login = { foo: 'login' };
  35. index = { foo: 'index' };
  36. 则 Object.assign({}, login, index); // { foo: 'index' }
  37. 浅拷贝:
  38. Object.assign 是浅拷贝,如果属性值是对象,拷贝的是引用而不是值。
  39. */
  40. /*
  41. export default 将 Object.assign({}, login, index) 的结果作为默认导出。
  42. 其他模块可以通过 import combined from './path/to/module'; 导入这个合并后的对象
  43. */
复制代码
新建 src/api/module/login.ts
  1. import request from '@/utils/axios';
  2. // 登录
  3. // model
  4. interface IResponseType<P = {}> {
  5.     code?: number;
  6.     status: number;
  7.     msg: string;
  8.     data: P;
  9. }
  10. interface ILogin {
  11.     token: string;
  12.     expires: number;
  13. }
  14. // function
  15. export const login = (username: string, password: string) => {
  16.     return request<IResponseType<ILogin>>({
  17.         url: '/api/auth/login',
  18.         method: 'post',
  19.         data: {
  20.             username,
  21.             password
  22.         }
  23.     });
  24. };
复制代码
新建 src/api/module/ping.ts
  1. import request from '@/utils/axios';
  2. // function
  3. export const ping = () => {
  4.     return request<String>({
  5.         url: '/api/auth/login',
  6.         method: 'get'
  7.     });
  8. };
复制代码
由于使用了 typescript,所以需新增 src/types/shims-axios.d.ts
  1. import { AxiosRequestConfig } from 'axios';
  2. // 自定义扩展axios模块
  3. declare module 'axios' {
  4.     export interface AxiosInstance {
  5.         <T = any>(config: AxiosRequestConfig): Promise<T>;
  6.         request<T = any>(config: AxiosRequestConfig): Promise<T>;
  7.         get<T = any>(url: string, config?: AxiosRequestConfig): Promise<T>;
  8.         delete<T = any>(url: string, config?: AxiosRequestConfig): Promise<T>;
  9.         head<T = any>(url: string, config?: AxiosRequestConfig): Promise<T>;
  10.         post<T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T>;
  11.         put<T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T>;
  12.         patch<T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T>;
  13.     }
  14. }
复制代码
新建 src/pages/request.vue 页面,并在此中使用
  1. <template>
  2.     <h2>这是 request 请求页</h2>
  3.     <br />
  4.     <router-link to="/">点击跳转至首页</router-link>
  5.     <button @click="requestRes()"></button>
  6. </template>
  7. <script lang="ts">
  8. import { defineComponent } from 'vue';
  9. import request from '@/utils/axios';
  10. import API from "@/api";
  11. export default defineComponent({
  12.     name: "RequestPage",
  13.     setup() {
  14.         const requestRes = async () => {
  15.             let result = await request({
  16.                 url: "/api/corgi/ping",
  17.                 method: "get,"
  18.             })
  19.             console.log(result);
  20.         }
  21.         const requestResAPI = async () => {
  22.             let result = await API.login("zhangsan", "123456");
  23.             console.log(result);
  24.         }
  25.         return {
  26.             requestRes,
  27.             requestResAPI
  28.         }
  29.     }
  30. })
  31. </script>
复制代码
效果如下:

引入 antdvue

AntdVue,引入方式参考官网
  1. pnpm add ant-design-vue
  2. pnpm add vite-plugin-style-import --save-dev # 非必须
  3. pnpm add vue-svg-icon
复制代码
在 main.ts 全局完整引入如下:
  1. import { createApp } from 'vue'
  2. import './style.css'
  3. import "ant-design-vue/dist/reset.css";
  4. import Antd from 'ant-design-vue';
  5. import App from './App.vue'
  6. import store from './store'
  7. import router from './router'
  8. createApp(App).use(router).use(store).use(Antd).mount('#app')
复制代码
需要注意的是,样式文件需要单独引入。
使用 antdvue

新建 test.vue,此中用到了 <a-button> 标签
  1. <template>
  2.     <h2>测试</h2>
  3.     <a-button type="primary">添加用户</a-button>
  4. </template>
  5. <script lang="ts">
  6. import { defineComponent } from "vue";
  7. import request from "@/utils/axios";
  8. import API from "@/api";
  9. export default defineComponent({
  10.     name: "RequestPage",
  11.     setup() {
  12.         const requestRes = async () => {
  13.             let result = await request({
  14.                 url: "/api/corgi/ping",
  15.                 method: "get",
  16.             });
  17.             console.log(result);
  18.         };
  19.         const requestResAPI = async () => {
  20.             let result = await API.login("zhangsan", "123456");
  21.             console.log(result);
  22.         };
  23.         return {
  24.             requestRes,
  25.             requestResAPI,
  26.         };
  27.     },
  28. });
  29. </script>
复制代码
效果如下,说明跑通了

二、vue 用法

2.1 动画

2.1.1 原生 css 动画

transition

  1. <template>
  2.   <div class="box" :style="{ width: width + 'px' }"></div>
  3.   <button @click="change">click</button>
  4. </template>
  5. <script lang="ts" setup>
  6. import { ref } from "vue";
  7. let width = ref(100);
  8. function change() {
  9.   width.value += 100;
  10. }
  11. </script>
  12. <style>
  13. .box {
  14.   background: red;
  15.   height: 100px;
  16. }
  17. </style>
复制代码
效果如下:


为了优化效果,可以把样式改为如下,即 width 属性需要线性过度,时间为 1s:
  1. <style>
  2. .box {
  3.   background: red;
  4.   height: 100px;
  5.   transition: width 1s linear;
  6. }
  7. </style>
复制代码
效果如下:
![](https://img-blog.csdnimg.cn/1c31481b245640d19c22cdeb9dac63e1.gif =300)
animation

  1. <template>
  2.   <div class="box" :style="{ width: width + 'px' }"></div>
  3.   <button @click="change">click</button>
  4. </template>
  5. <script lang="ts" setup>
  6. import { ref } from "vue";
  7. let width = ref(30);
  8. function change() {
  9.   width.value += 100;
  10. }
  11. </script>
  12. <style>
  13. .box {
  14.   width: 30px;
  15.   height: 30px;
  16.   position: relative;
  17.   background: #d88986;
  18.   animation: move 2s linear infinite; /*持续 2s,线性变化,无限循环*/
  19.   /*
  20.   move:指定动画的名称,对应 @keyframes move 定义的动画。
  21. 2s:动画的持续时间为 2 秒。
  22. linear:动画的时间函数为线性(匀速)。
  23. infinite:动画无限循环。
  24. */
  25. }
  26. /* 定制动画在0%,50%,100%的位置 */
  27. @keyframes move {
  28.   0% {
  29.     left: 0px;
  30.   }
  31.   50% {
  32.     left: 200px;
  33.   }
  34.   100% {
  35.     left: 0;
  36.   }
  37. }
  38. /*
  39. @keyframes 定义了动画的关键帧,具体含义如下:
  40. 0%:动画开始时,元素的 left 值为 0px。
  41. 50%:动画进行到一半时,元素的 left 值为 200px。
  42. 100%:动画结束时,元素的 left 值回到 0px。
  43. 3. 动画效果
  44. 元素 .box 会从初始位置(left: 0px)向右移动到 left: 200px,然后再回到初始位置(left: 0px)。
  45. 整个动画持续 2 秒,匀速运动,并且无限循环。
  46. 4. 代码的完整行为
  47. 初始状态:
  48. .box 的宽度为 30px,高度为 30px,背景色为 #d49d9b。
  49. 动画开始前,元素位于 left: 0px。
  50. 动画过程:
  51. 在 0% 时,元素位于 left: 0px。
  52. 在 50% 时(1 秒后),元素移动到 left: 200px。
  53. 在 100% 时(2 秒后),元素回到 left: 0px。
  54. 循环:
  55. 动画完成后,重新开始,无限循环。
  56. 按钮点击:
  57. 点击按钮时,.box 的宽度会增加 100px,但动画效果不受影响,继续按照 @keyframes 的定义运行。
  58. 5. 可视化效果
  59. 你会看到一个宽度为 30px 的方块,在水平方向上左右移动:
  60. 从最左侧(left: 0px)向右移动到 left: 200px,然后再回到最左侧。
  61. 每次点击按钮,方块的宽度会增加 100px,但动画的移动范围(left: 0px 到 left: 200px)不变。
  62. */
  63. </style>
复制代码
效果如下:

2.1.2 vue 动画

  1. <template>
  2.     <button @click="toggle">click</button>
  3.     <transition name="fade">
  4.         <h1 v-if="showTitle">你好</h1>
  5.     </transition>
  6. </template>
  7. <script lang="ts" setup>
  8. import { ref } from "vue";
  9. let showTitle = ref(true);
  10. function toggle() {
  11.     showTitle.value = !showTitle.value;
  12. }
  13. </script>
  14. <style>
  15. .fade-enter-active,
  16. .fade-leave-active {
  17.     transition: opacity 0.5s linear;
  18. }
  19. .fade-enter-from,
  20. .fade-leave-to {
  21.     opacity: 0;
  22. }
  23. </style>
复制代码
vue 的 transition 约定如下:

效果如下:



2.2 jsx

简单示例

定义 Heading.jsx 如下:
  1. import { defineComponent, h } from "vue";
  2. export default defineComponent({
  3.   props: {
  4.     level: {
  5.       type: Number,
  6.       required: true,
  7.     },
  8.   },
  9.   setup(props, { slots }) {
  10.     return () =>
  11.       h(
  12.         "h" + props.level, // 标签名
  13.         {}, // prop 或 attribute
  14.         slots.default() // 子节点
  15.       );
  16.   },
  17. });
复制代码
在 about.vue 中使用,如下:
  1. <template>
  2.   <Heading :level="1">hello xy</Heading>
  3. </template>
  4. <script lang="ts" setup>
  5. import Heading from "@/components/Heading.jsx";
  6. </script>
复制代码
当 level 传 1时,效果如下:

当 level 传 6 时,效果如下:

通过 pnpm add @vitejs/plugin-vue-jsx -D 可安装 jsx 插件。
在 vite.config.ts 中配置如下:
  1. import vueJsx from '@vitejs/plugin-vue-jsx';
  2. export default defineConfig({
  3.   plugins: [vue(), viteMockServe(), vueJsx()],
  4. });
复制代码
修改 Heading.jsx 如下:
  1. import { defineComponent, h } from "vue";
  2. export default defineComponent({
  3.   props: {
  4.     level: {
  5.       type: Number,
  6.       required: true,
  7.     },
  8.   },
  9.   setup(props, { slots }) {
  10.     const tag = "h" + props.level;
  11.     return () => <tag>{slots.default()}</tag>;
  12.     // return () =>
  13.     //   h(
  14.     //     "h" + props.level, // 标签名
  15.     //     {}, // prop 或 attribute
  16.     //     slots.default() // 子节点
  17.     //   );
  18.   },
  19. });
复制代码
todo.jsx 示例

todo.jsx 如下:
  1. import { defineComponent, ref } from "vue";
  2. export default defineComponent({
  3.   setup(props) {
  4.     let title = ref("");
  5.     let todos = ref([
  6.       { title: "pc", done: true },
  7.       { title: "android", done: false },
  8.     ]);
  9.     function addTodo() {
  10.       todos.value.push({ title: title.value });
  11.       title.value = "";
  12.     }
  13.     return () => (
  14.       <div>
  15.         <input type="text" vModel={title.value} />
  16.         <button onClick={addTodo}>click</button>
  17.         <ul>
  18.           {todos.value.length ? (
  19.             todos.value.map((todo) => {
  20.               return <li>{todo.title}</li>;
  21.             })
  22.           ) : (
  23.             <li>no data</li>
  24.           )}
  25.         </ul>
  26.       </div>
  27.     );
  28.   },
  29. });
复制代码
效果如下:

三、AntdVue 组件库使用

业务开辟,重要就是用UI组件库(如antd vue)了,可以在 github 搜项目,例如vue3-antd-admin 和 vue-antd-admin,学习别人的组织思路。
3.1 弹窗

弹窗通常用 a-model 组件
父组件:
  1. <script setup lang="ts">
  2. import { ref } from 'vue';
  3. const visible = ref(false);
  4. <template>
  5.   <div>
  6.     <a-button type="primary" class="btn-upload" ghost @click="handleCreate">
  7.       <template #icon>
  8.         <svg-icon class="icon" icon="icon-upload" />
  9.       </template>
  10.       打开弹窗
  11.     </a-button>
  12.         <!-- 子组件。向子组件传参为 model,接收子组件的 @save 事件 -->
  13.     <form-user
  14.       v-model:visible="visible"
  15.       :model="currentProcedure"
  16.       @save="handleSave"
  17.     />
  18.   </div>
  19. </template>
复制代码
子组件:
  1. <script setup lang="ts">
  2. import { User } from '@/models/user';
  3. import { antdModal } from '@/utils/antd';
  4. import { reactive, ref, watch } from 'vue';
  5. import rules from './rules';
  6. // 数据
  7. export type FormStateModel = User;
  8. // 父组件传来的 prop
  9. const props = defineProps<{
  10.   model?: FormStateModel;
  11.   visible: boolean;
  12. }>();
  13. // 向父组件发送的 emit
  14. const emits = defineEmits<{
  15.   (e: 'save', model: FormStateModel): void;
  16.   (e: 'update:visible', visible: boolean): void;
  17. }>();
  18. // 将数据变为响应式
  19. const formState = reactive<FormStateModel>({
  20.   userName: '',
  21.   password: '',
  22. });
  23. // 用于二次确认的业务逻辑。将数据转为JSON字符串。当点击“取消“按钮时,将“现在的数据“和“刚打开弹窗时的数据”做对比,若有变动则需二次确认
  24. let initModelJSON = '';
  25. // 当 props.visible 时,调用 init 函数
  26. watch(() => props.visible, init);
  27. function init() {
  28.   // 组件初始化时,得到 initModelJSON 的原始值
  29.   const model = props.model;
  30.   if (!model) { // 若为“新增”业务,则父组件未传入 props.model,则先手动赋值为 ‘’ 空字符串做保护,再序列化
  31.     (formState as User).userName = '';
  32.     formState.password = '';
  33.     initModelJSON = JSON.stringify(formState);
  34.     return;
  35.   }
  36.   initModelJSON = JSON.stringify(formState); // 若为“编辑”业务,则父组件传入了 props.model,直接序列化即可
  37. }
  38. // 组件的引用:下文的 <a-form ref="formRef"/>
  39. const formRef = ref();
  40. async function handleOk() {
  41.   try {
  42.     await formRef.value.validate(); // a-form 官方提供了 validate() 方法
  43.     emits('save', normalizeModel()); // 向父组件emit save函数,参数为 normalizeModel()
  44.   } catch (e) {
  45.     console.error(e);
  46.   }
  47. }
  48. async function handleCancel() {
  49.   if (
  50.     // 点击取消按钮时,对比新旧值
  51.     JSON.stringify(formState) !== initModelJSON &&
  52.     !(await antdModal('尚未保存,确定取消?'))
  53.   ) {
  54.     emits('update:visible', true); // 当点击取消时, 向父组件 emit visible = true,让子组件(即本弹窗组件)可见
  55.     return;
  56.   }
  57.   emits('update:visible', false); // 当点击取消时, 向父组件 emit visible = false,让子组件(即本弹窗组件)不可见
  58. }
  59. // 将 normalizeModel 暴露给父组件,因为本组件在 <script setup> 中的变量默认是不被父组件可见的
  60. defineExpose({ normalizeModel });
  61. function normalizeModel() {
  62.   return { ...formState };
  63. }
  64. </script>
  65. <template>
  66.   <!-- v-bind="$attrs" 将调用组件时的组件标签上绑定的非props的特性(class和style除外)向下传递。在子组件中应当添加inheritAttrs: false(避免父作用域的不被认作props的特性绑定应用在子组件的根元素上)。-->
  67.   <a-modal
  68.     v-bind="$attrs"
  69.     :title="(formState as Procedure).id ? '编辑用户' : '添加用户'"
  70.     :visible="visible"
  71.     @ok="handleOk"
  72.     @cancel="handleCancel"
  73.   >
  74.     <a-form ref="formRef" class="form-device" :rules="rules" :model="formState">
  75.       <a-form-item label="用户账号" class="form-item" name="name">
  76.         <a-input v-model:value="formState.name" placeholder="请输入名称" />
  77.       </a-form-item>
  78.     </a-form>
  79.   </a-modal>
  80. </template>
  81. <style lang="scss" scoped>
  82. @import '~/css/variables';
  83. </style>
复制代码


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。




欢迎光临 IT评测·应用市场-qidao123.com技术社区 (https://dis.qidao123.com/) Powered by Discuz! X3.4