环境说明

组件名和标签名的映射规则

在 Vue 中,组件名和标签名之间的映射规则是基于 PascalCase(大驼峰命名法)和 kebab-case(短横线命名法)的转换。具体规则如下:

  1. PascalCase 到 kebab-case 的转换

    • 当你在模板中使用组件时,Vue 会自动将组件名从 PascalCase 转换为 kebab-case。例如,如果你有一个组件名为 MyComponent,在模板中你可以使用 <my-component> 来引用它。
  2. kebab-case 到 PascalCase 的转换

    • 当你在 JavaScript 中导入组件时,你需要使用组件的文件名(通常是 PascalCase)。例如,如果你有一个组件文件名为 MyComponent.vue,你需要使用 import MyComponent from './MyComponent.vue' 来导入它。
  3. 全局注册组件

    • 当你全局注册组件时,你可以使用任何有效的 JavaScript 标识符作为组件名。例如,你可以使用 app.component('my-component', MyComponent) 来全局注册 MyComponent 组件。
  4. 局部注册组件

    • 在局部注册组件时,你需要在组件的 components 选项中使用组件的 PascalCase 名称。例如:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <script>
    import MyComponent from './MyComponent.vue';

    export default {
    components: {
    MyComponent
    },
    // 其他组件选项
    }
    </script>
  5. <script setup> 语法

    • <script setup> 中,组件的名称会自动根据文件名来推断。例如,如果你的组件文件名为 MyComponent.vue,那么组件的名称就是 MyComponent。你可以在模板中直接使用 <my-component> 来引用它。

总结一下,Vue 会自动处理组件名和标签名之间的转换,使得你可以在模板中使用 kebab-case 命名的标签来引用 PascalCase 命名的组件。这种命名约定有助于提高代码的可读性,并且避免与 HTML 原生标签冲突。

创建项目

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
❯ yarn create vite
yarn create v1.22.22
[1/4] Resolving packages...
[2/4] Fetching packages...
[3/4] Linking dependencies...
[4/4] Building fresh packages...
success Installed "create-vite@6.1.1" with binaries:
- create-vite
- cva
✔ Project name: … vue3-blog
✔ Select a framework: › Vue
✔ Select a variant: › TypeScript

Scaffolding project in /root/vue3-blog...

Done. Now run:

cd vue3-blog
yarn
yarn dev

Done in 16.15s.

清理初始化内容

1
2
3
4
5
6
C:.
├───.vscode
├───public
└───src
├───assets
└───components
1
yarn add --dev @types/node

配置alias

vscode 插件: Path Intellisense

1
2
3
4
5
6
7
{
"path-intellisense.mappings": {
"@": "${workspaceFolder}/src", // Alias for absolute path to directory.
"@cp": "${workspaceFolder}/src/components",
"@views": "${workspaceFolder}/src/views"
}
}

vite.config.ts 里配置了这些 alias

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import { resolve } from 'path'

export default defineConfig({
// ...
resolve: {
alias: {
'@': resolve('src'), // 源码根目录
'@img': resolve('src/assets/img'), // 图片
'@less': resolve('src/assets/less'), // 预处理器
'@libs': resolve('src/libs'), // 本地库
'@plugins': resolve('src/plugins'), // 本地插件
'@cp': resolve('src/components'), // 公共组件
'@views': resolve('src/views'), // 路由组件
},
},
// ...
})

在该项目的 tsconfig.json 文件里就需要相应的加上这些 paths :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
{
"files": [],
"references": [
{ "path": "./tsconfig.app.json" }, // 配置应用程序相关的 TypeScript 编译选项
{ "path": "./tsconfig.node.json" } //配置与 Node.js 环境相关的 TypeScript 编译选
],
}

// 修改tsconfig.app.json文件

{
"compilerOptions": {
// ...
"baseUrl": ".",
"paths": {
"@/*": ["src/*"],
"@img/*": ["src/assets/img/*"],
"@less/*": ["src/assets/less/*"],
"@libs/*": ["src/libs/*"],
"@plugins/*": ["src/plugins/*"],
"@cp/*": ["src/components/*"],
"@views/*": ["src/views/*"]
},
// ...
},
// ...
}

设置vscode 的ts

  • command + shift + P ,输入 type,选择select typescript version ,选择 use WOrkspace Version
  • 重启vscode,可以发现页面报错消失

全局样式

  • 安装sass依赖

    1
    yarn add -D sass
  • 新建 src/styles/global.scss

    1
    2
    3
    4
    5
    6
    7
    rm src/style.css  
    mkdir -p $(dirname src/styles/global.less) && touch src/styles/global.scss

    *{
    padding: 0;
    margin: 0
    }
  • 引入全局样式

    1
    2
    3
    ❯ code ./src/main.ts

    import './styles/global.scss'

tailwindcss

  • 安装 - Tailwind CSS 中文网

    1
    2
    3
    4
    5
    # npm install -D tailwindcss@latest postcss@latest autoprefixer@latest
    # npx tailwindcss-cli@latest init -p

    yarn add -D tailwindcss@3 postcss autoprefixer
    npx tailwindcss init -p
  • 配置css

    • 这个配置文件中的 content 部分指定了 Tailwind CSS 应该扫描哪些文件来寻找 Tailwind 类。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // vi tailwind.config.js
    /** @type {import('tailwindcss').Config} */
    export default {
    content: ["./index.html", "./src/**/*.{vue,js,ts,jsx,tsx}"],
    theme: {
    extend: {},
    },
    plugins: [],
    }


  • 在 src/styles/tailwind.css 文件中添加@tailwind 指令。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    // touch  src/styles/tailwind.css
    @tailwind base;
    @tailwind components;
    @tailwind utilities;


    # 这里可以同全局样式关联
    /* src/styles/global.scss */
    @tailwind base;
    @tailwind components;
    @tailwind utilities;

    /* 这里可以添加自定义的全局样式 */
    body {
    font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
    }
  • main.ts 中引入tailwind.css

    1
    2
    3
    4
    5
    6
    import { createApp } from 'vue'
    import App from './App.vue'
    import './styles/global.scss'
    import './styles/tailwind.css'

    createApp(App).mount('#app')
  • 测试

    1
    2
    3
    4
    5
    <template>
    <h1 class="text-3xl font-bold underline">
    Hello world!
    </h1>
    </template>

引入Elemen-plus

设计 | Element Plus

  • 导入依赖

    1
    2
    3
    yarn add element-plus
    按需导入
    yarn add -D unplugin-vue-components unplugin-auto-import
  • 完整引入引入依赖和样式

    1
    2
    3
    4
    // main.ts
    import ElementPlus from 'element-plus'
    import 'element-plus/dist/index.css'
    app.use(ElementPlus)
  • 按需导入: 配置插件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    // vite.config.ts

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

    export default defineConfig({
    // ...
    plugins: [
    vue(),
    AutoImport({
    resolvers: [ElementPlusResolver()],
    }),
    Components({
    resolvers: [ElementPlusResolver()],
    }),
    ],
    // ...
    })

    src/main.js(或 src/main.ts)中引入 ElementPlus 的 CSS 样式:

    1
    2
    3
    4
    5
    6
    7
    // src/main.ts
    import { createApp } from 'vue'
    import App from './App.vue'
    import 'element-plus/dist/index.css'

    const app = createApp(App)
    app.mount('#app')
  • 测试

    1
    2
    3
    <template>
    <el-button>click me</el-button>
    </template>

引入Vue-router

  • 添加依赖

    1
    yarn add vue-router
  • 项目里引入路由,新建 router文件夹,新建index.ts

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    // src/router/index.ts

    import { createRouter, createWebHistory } from 'vue-router'
    import type { RouteRecordRaw } from 'vue-router'

    const routes: Array<RouteRecordRaw> = [
    // ...
    ]

    const router = createRouter({
    history: createWebHistory(import.meta.env.BASE_URL),
    routes,
    })

    export default router
  • main.ts引入

    1
    2
    3
    4
    5
    import router from '@/router'

    const app = createApp(App);
    app.use(router);
    app.mount('#app')
  • 一级路由写法

    1
    2
    3
    4
    5
    6
    7
    8
    const routes: Array<RouteRecordRaw> = [
    {
    path: '/',
    name: 'home',
    // 异步组件
    component: () => import('@views/home.vue'),
    },
    ]
  • 测试

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    <script setup lang="ts">
    </script>

    <template>
    <div>
    <router-link to="/">Home</router-link> |
    <router-link to="/about">About</router-link>
    <router-view></router-view>
    </div>
    </template>

    <style scoped>

    </style>

引入pinia

  1. 首先,您需要安装pinia。您可以通过npm或yarn来安装它。在终端中运行以下命令:

    1
    2
    3
    npm install pinia
    # 或者
    yarn add pinia
  2. 在您的main.ts文件中引入pinia并将其挂载到您的Vue应用程序上

    1
    2
    3
    4
    // main.ts
    import { createPinia } from 'pinia' // 引入pinia
    const app = createApp(App);
    app.use(createPinia()); // 使用pinia
  3. 启用数据持久化插件

    1
    yarn add pinia-plugin-persistedstate
  4. main.ts 文件中导入并使用这个插件:

    1
    2
    3
    4
    5
    6
    import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'

    const app = createApp(App);
    const pinia = createPinia()
    pinia.use(piniaPluginPersistedstate)
    app.use(pinia); // 使用pinia
  5. 编写stores

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    import { defineStore } from "pinia"

    export const useStore = defineStore('user', {
    state: () => {
    return {
    username: '张三',
    age: 18,
    sex: '男'
    }
    },
    // 这是按照插件的文档,在实例上启用了该插件,这个选项是插件特有的
    persist: true,
    // 自定义存储
    persist: {
    key: 'my-user-store', // 自定义存储的键名
    storage: sessionStorage // 使用 sessionStorage 而不是 localStorage
    }
    })
    1
    2
    3
    4
    5
    6
    // 每一次变化后也会将其写入 localStorage 进行记忆存储。

    setTimeout(() => {
    const { username } = storeToRefs(user);
    username.value = "李四";
    }, 2000);
  6. 可以在浏览器查看到 localStorage 的存储变化,以 Chrome 浏览器为例,按 F12 ,打开 Application 面板,选择 Local Storage ,可以看到以当前 Store ID user 为 Key 的存储数据。

SVG图标

vite-plugin-svg-icons 是一个 Vite 插件,用于在 Vite 项目中轻松地使用 SVG 图标。它允许你将多个 SVG 文件导入为 Vue 组件,并且可以通过配置来优化 SVG 的加载和使用。

  1. 安装 vite-plugin-svg-icons 插件

    1
    yarn add -D vite-plugin-svg-icons 
  2. 配置插件: 在 vite.config.js 文件中配置 vite-plugin-svg-icons 插件。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    import { createSvgIconsPlugin } from 'vite-plugin-svg-icons' // 注意这里的导入方式

    export default defineConfig({
    plugins: [
    vue(),
    createSvgIconsPlugin({
    // 指定需要缓存的图标文件夹
    iconDirs: [resolve(process.cwd(), 'src/icons/svg')],
    // 指定symbolId格式
    symbolId: 'icon-[dir]-[name]'
    })
    ]
    })
  3. 在 src/main.ts 中引入注册脚本

    1
    import 'virtual:svg-icons-register'
  4. 创建 SVG 图标组件: 在 src/components 目录下创建一个 SvgIcon.vue 文件,用于渲染 SVG 图标。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    // /src/components/SvgIcon.vue
    <template>
    <svg class="svg-icon" aria-hidden="true">
    <use :href="iconName" />
    </svg>
    </template>

    <script setup lang="ts">
    import { computed } from 'vue';
    const props = defineProps({
    prefix:{
    type: String,
    default: 'icon'
    },
    name: {
    type: String,
    required: true
    }
    })

    const iconName = computed(() => `#${props.prefix}-${props.name}`);
    </script>

    <style scoped>
    .svg-icon {
    /* 设置 SVG 图标在垂直方向上的对齐方式。-0.15em 表示将图标向上移动 0.15em 可以使图标与文本的基线对齐*/
    vertical-align: -0.15em;
    /* 使用当前文本的颜色作为填充颜色 */
    fill: currentColor;
    /* 隐藏超出 SVG 图标边界的内容 */
    overflow: hidden;
    }
    </style>

  5. 下载SVG图标 iconfont-阿里巴巴矢量图标库

  6. 使用 SVG 图标: 在需要使用 SVG 图标的组件中,通过 <svg-icon> 标签来引用图标。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    <template>
    <div>
    <p class="w-1/2">This is home page</p>
    <svg-icon class="float w-[2rem] h-[2rem] svg-icon-custom" name="vue" />
    </div>
    </template>

    <script setup>
    import SvgIcon from "@/components/SvgIcon.vue"; // 显式引入 SvgIcon 组件
    </script>

    <style lang="scss" scoped>
    .svg-icon-custom {
    }
    </style>

在 Vue 3 中,如果你使用的是 <script setup> 语法,那么组件会自动注册,不需要显式地使用 import 语句来引入组件。这是因为 <script setup> 是 Vue 3.2 中引入的一个新特性,它允许你在 <script> 标签中直接使用 Composition API,并且自动将所有顶级变量和函数暴露给模板。

在你的代码中,<svg-icon> 组件应该在项目定义的,并且已经被全局注册或者在当前组件的父组件中注册过了。因此,你可以直接在模板中使用 <svg-icon> 组件,而不需要显式地引入它。

自动引入icon

unplugin-icons - npm 使用约定 ~icons/{collection}/{icon} 导入图标名称

图标:

  1. 下载依赖

    1
    2
    3
    4
    yarn add -D unplugin-icons  @iconify/json
    # 如果您只想使用几个图标集,而不想下载整个集合,您也可以使用 @iconify-json/[collection-id] 单独安装它们。例如,要安装 Material Design 图标,您可以执行以下操作:
    npm i -D @iconify-json/mdi
    # @iconify/json (~120MB) 包括 Iconify 中的所有图标集,因此您可以安装一次并根据需要使用其中任何一个(只有您实际使用的图标才会捆绑到生产版本中)。
  2. 配置 Vite

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    import { defineConfig } from 'vite';
    import Icons from 'unplugin-icons/vite';
    import IconsResolver from 'unplugin-icons/resolver';
    import Components from 'unplugin-vue-components/vite';

    export default defineConfig({
    plugins: [
    Components({
    resolvers: [
    IconsResolver({
    prefix: 'icon', // 图标前缀,例如 <icon-home />
    enabledCollections: ['mdi'], // 启用的图标集,例如 Material Design Icons
    }),
    ],
    }),
    Icons({
    compiler: 'vue3', // 使用 Vue 3 编译器
    autoInstall: true, // 自动安装图标集
    }),
    ],
    });

  3. 配置完成后,你可以在 Vue 组件中直接使用图标。

1
2
3
4
5
6
7
8
9
10
11
12
13
<template>
<div>
<icon-mdi-account-reactivate class=" size-2rem" style="font-size: 2em; color: red" />
<icon-mdi-home />
<icon-carbon-accessibility/>
<icon-mdi-account-box style="font-size: 2em; color: red"/>
</div>
</template>

<script setup>
// 不需要显式引入图标组件
</script>

环境变量管理

  1. 在项目根目录下创建 .env 文件,用于存放公共的环境变量;创建 .env.development.env.production 文件,分别用于存放开发环境和生产环境的特定环境变量。

  2. 使用环境变量:在代码中通过 import.meta.env 来访问环境变量。

  3. 配置打包脚本:在 package.json 中配置不同环境的打包脚本,以便在不同环境下使用不同的环境变量。

    • package.json 中配置不同环境的打包脚本:

      1
      2
      3
      4
      5
      6
      7
      8
      {
      "scripts": {
      "serve": "vite",
      "build": "vue-tsc --noEmit && vite build",
      "build:dev": "vue-tsc --noEmit && vite build --mode development",
      "build:prod": "vue-tsc --noEmit && vite build --mode production"
      }
      }

这样,在开发环境下运行 npm run servenpm run build:dev 时,会使用 .env.development 中的环境变量;在生产环境下运行 npm run build:prod 时,会使用 .env.production 中的环境变量。

引入 axios

  1. 安装依赖

    1
    2
    3
    4
    # 使用 npm 安装
    npm install axios
    # 或者使用 yarn 安装
    yarn add axios
  2. src 目录下创建一个名为 config 的文件j夹,新建request.ts 内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
import axios from 'axios'
import type { AxiosRequestConfig ,InternalAxiosRequestConfig, AxiosResponse } from 'axios';
import { ElMessage } from 'element-plus'; // 这里使用 ElementPlus 的消息提示,你可以根据项目替换

// 创建 Axios 实例
const service = axios.create({
baseURL: import.meta.env.VITE_API_BASE_URL, // 从环境变量中获取 baseURL
timeout: 5000 // 请求超时时间
});

// 请求拦截器
service.interceptors.request.use(
(config: InternalAxiosRequestConfig) => {
// 在发送请求之前做些什么,例如添加 token
const token = localStorage.getItem('token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
(error) => {
// 处理请求错误
console.error('请求错误:', error);
return Promise.reject(error);
}
);

// 响应拦截器
service.interceptors.response.use(
(response: AxiosResponse) => {
const { data } = response;
// 根据后端返回的状态码进行处理
if (data.code === 200) {
return data.data;
} else {
// 处理业务错误
ElMessage.error(data.message);
return Promise.reject(new Error(data.message));
}
},
(error) => {
// 处理网络错误
let message = '';
const status = error.response?.status;
switch (status) {
case 401:
message = '未授权,请登录';
// 跳转到登录页
break;
case 403:
message = '拒绝访问';
break;
case 404:
message = '请求地址出错';
break;
case 500:
message = '服务器内部错误';
break;
default:
message = '网络连接错误';
}
ElMessage.error(message);
return Promise.reject(error);
}
);

// 封装请求方法
const request = {
get<T>(url: string, config?: AxiosRequestConfig): Promise<T> {
return service.get(url, config);
},
post<T>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T> {
return service.post(url, data, config);
},
put<T>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T> {
return service.put(url, data, config);
},
delete<T>(url: string, config?: AxiosRequestConfig): Promise<T> {
return service.delete(url, config);
}
};

export default request;
  1. 使用封装后的 Axios , 在组件中引入封装好的 request 对象,并使用其中的方法发送请求:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    <template>
    <div>
    <button @click="fetchData">获取数据</button>
    <p v-if="data">{{ data }}</p>
    </div>
    </template>

    <script lang="ts" setup>
    import { ref } from 'vue';
    import request from './http';

    const data = ref('');

    const fetchData = async () => {
    try {
    const result = await request.get<{ name: string }>('/api/data');
    data.value = result.name;
    } catch (error) {
    console.error('请求出错:', error);
    }
    };
    </script>

跨域问题

  • 开发模式下可以使用代理服务器

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    import { defineConfig } from 'vite';

    export default defineConfig({
    plugins: [react()],
    server: {
    proxy: {
    // 配置代理规则
    '/api': {
    target: 'http://api.example.com',
    changeOrigin: true,
    rewrite: (path) => path.replace(/^\/api/, ''),
    },
    '/uploads': {
    target: 'http://uploads.example.com',
    changeOrigin: true,
    rewrite: (path) => path.replace(/^\/uploads/, ''),
    },
    },
    },
    });

优化打包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import { defineConfig } from 'vite';

export default defineConfig({
build: {
// 输出目录,默认为 dist
outDir: 'dist',
// 静态资源存放目录,默认为 assets
assetsDir: 'assets',
// 小于此阈值的导入或引用资源将内联为 base64 编码,以减少 HTTP 请求。设置为 0 可以完全禁用此项
assetsInlineLimit: 4096,
// 启用/禁用 CSS 代码分割。如果禁用,所有 CSS 文件将被合并为一个文件
cssCodeSplit: true,
// 构建后是否生成 source map 文件
sourcemap: false,
// 自定义底层的 Rollup 打包配置
rollupOptions: {
output: {
// 自定义入口 chunk 的文件名
entryFileNames: `assets/[name].[hash].js`,
// 自定义块文件的文件名
chunkFileNames: `assets/[name].[hash].js`,
// 自定义静态资源文件名
assetFileNames: `assets/[name].[hash].[ext]`,
// 手动配置分包策略
manualChunks(id) {
if (id.includes('node_modules')) {
return 'vendor'; // 将所有来自 node_modules 的模块打包到一个名为 vendor 的文件中
}
}
}
},
// 启用/禁用 brotli 压缩大小报告。压缩大型输出文件可能会很慢,因此禁用此功能可能会提高大型项目的构建性能
brotliSize: true,
// 规定触发警告的 chunk 大小,默认是 500kb
chunkSizeWarningLimit: 500
}
});