《硅谷甄选》项目笔记
📁 个人仓库地址
👉 我的 Git 仓库地址:
https://github.com/yongjannes/Star_Platform
欢迎 star、交流或提出建议!
一、Vue3 组件通信的艺术:构建灵活可复用的 UI
在任何复杂的单页应用中,组件之间的通信都是一个核心挑战。高效的组件通信能够显著提高代码的复用性、可维护性以及团队协作效率。
1.1 props
:父子组件通信的基石
props
是 Vue 中最直接、最常用的父子组件通信方式。在 Vue3 中,通过 defineProps
宏,我们可以轻松地接收父组件传递的数据,并且无需显式引入,可以直接在 <script setup>
中使用,极大地简化了开发流程。
父组件向子组件传递数据示例:
<template>
<div>
<h1>父组件</h1>
<Child info="我爱祖国" :money="money"></Child>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import Child from './Child.vue'; // 假设子组件名为 Child.vue
const money = ref(1000);
</script>
子组件接收父组件数据示例:
子组件可以通过两种方式接收 props
:
方式1:带类型和默认值的声明
这种方式提供了更强的类型检查和默认值设置,增加了代码的健壮性。
<script setup lang="ts">
let props = defineProps({
info: {
type: String, // 接受的数据类型
default: '默认参数', // 接受默认数据
},
money: {
type: Number,
default: 0,
},
});
// 在模板中可以直接使用 props.info, props.money
</script>
方式2:数组形式的简洁声明
适用于简单的 props
声明,但缺乏类型检查。
<script setup lang="ts">
let props = defineProps(['info', 'money']);
// 在模板中可以直接使用 props.info, props.money
</script>
重要提示: props
是单向数据流,子组件只能读取 props
数据,不能直接修改它。
1.2 自定义事件:子组件向父组件传递数据
自定义事件是实现子组件向父组件传递数据的关键机制。与原生 DOM 事件不同,自定义事件是 Vue 组件特有的通信方式。
父组件绑定自定义事件:
父组件在子组件标签上通过 @
符号绑定自定义事件。
<template>
<div>
<h1>父组件</h1>
<Event2 @xxx="handler3"></Event2>
</div>
</template>
<script setup lang="ts">
import Event2 from './Event2.vue'; // 假设子组件名为 Event2.vue
const handler3 = (param1: string, param2: string) => {
console.log('从子组件接收到数据:', param1, param2);
};
</script>
子组件触发自定义事件:
在子组件内部,使用 defineEmits
宏声明需要触发的自定义事件,然后通过 $emit
方法触发事件并传递数据。
<template>
<div>
<h1>我是子组件2</h1>
<button @click="handler">点击我触发xxx自定义事件</button>
</div>
</template>
<script setup lang="ts">
let $emit = defineEmits(['xxx']); // 声明自定义事件 'xxx'
const handler = () => {
$emit('xxx', '法拉利', '茅台'); // 触发 'xxx' 事件并传递数据
};
</script>
Vue3 中原生 DOM 事件的特殊性:
在 Vue3 中,像 click
、dbclick
、change
这类原生 DOM 事件,无论是在普通 HTML 标签上还是在自定义组件标签上,默认都视为原生 DOM 事件。这与 Vue2 中需要 native
修饰符才能将组件上的事件变为原生 DOM 事件有所不同。但如果子组件内部通过 defineEmits
定义了同名的事件,那么它将优先被视为自定义事件。
1.3 全局事件总线 (mitt):实现任意组件通信
在 Vue2 中,我们常利用 Vue.prototype.$bus
实现全局事件总线。然而,Vue3 没有 Vue 构造函数,且组合式 API 中没有 this
上下文,因此传统的全局事件总线方式不再适用。在 Vue3 中,我们可以借助轻量级的第三方库 mitt
来实现全局事件总线功能,从而让任意组件之间进行通信。
mitt 官网: https://www.npmjs.com/package/mitt
1.4 v-model
:实现父子组件数据的双向绑定
v-model
指令不仅用于收集表单数据实现双向绑定,它也是实现父子组件数据同步的强大工具。在底层,v-model
实际上是 props
(modelValue
) 和自定义事件 (update:modelValue
) 的语法糖。
基本用法:
<!-- 父组件 -->
<template>
<Child v-model="msg"></Child>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import Child from './Child.vue';
const msg = ref('初始消息');
</script>
多 v-model 绑定:
Vue3 允许一个组件使用多个 v-model
,从而实现父子组件多个数据的同步。
<!-- 父组件 -->
<template>
<Child v-model:pageNo="msg" v-model:pageSize="msg1"></Child>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import Child from './Child.vue';
const msg = ref(1);
const msg1 = ref(10);
</script>
1.5 useAttrs
:获取组件的非 props
属性和事件
useAttrs
是 Vue3 提供的一个 Composition API,用于获取组件实例上未被 defineProps
声明的属性和事件(包括原生 DOM 事件和自定义事件)。这类似于 Vue2 中的 $attrs
和 $listeners
。
示例:
<!-- 父组件 -->
<template>
<my-button type="success" size="small" title="标题" @click="handler"></my-button>
</template>
<!-- 子组件 my-button.vue -->
<script setup lang="ts">
import { useAttrs } from 'vue';
let $attrs = useAttrs();
console.log($attrs); // 会包含 type, size, title, 和 click 事件
</script>
需要注意的是,如果 defineProps
已经接收了某个属性,那么 useAttrs
返回的对象中将不再包含该属性。
1.6 ref
与 $parent
:直接访问子/父组件实例
ref
允许我们在父组件中获取子组件的实例(VC),从而直接访问子组件的方法和响应式数据。而 $parent
则允许子组件获取其父组件的实例。
父组件通过 ref
访问子组件:
<!-- 父组件 -->
<template>
<div>
<h1>ref与$parent</h1>
<Son ref="son"></Son>
</div>
</template>
<script setup lang="ts">
import Son from './Son.vue';
import { onMounted, ref } from 'vue';
const son = ref();
onMounted(() => {
console.log(son.value); // 打印子组件实例
// 访问子组件暴露的数据和方法
console.log(son.value.money);
son.value.someMethod();
});
</script>
子组件通过 defineExpose
暴露数据和方法:
在 Vue3 中,组件内部的数据和方法默认不对外暴露。如果希望父组件通过 ref
访问,子组件必须使用 defineExpose
明确地暴露它们。
<!-- 子组件 Son.vue -->
<script setup lang="ts">
import { ref } from 'vue';
let money = ref(1000);
const someMethod = () => {
console.log('子组件的方法被调用');
};
defineExpose({
money,
someMethod,
});
</script>
子组件通过 $parent
访问父组件:
<!-- 子组件 -->
<template>
<button @click="handler($parent)">点击我获取父组件实例</button>
</template>
<script setup lang="ts">
const handler = (parent: any) => {
console.log(parent); // 打印父组件实例
// 访问父组件暴露的数据和方法
if (parent && parent.parentData) {
console.log(parent.parentData);
}
};
</script>
1.7 provide
与 inject
:实现跨层级组件通信
provide
和 inject
是 Vue3 提供的两个方法,用于实现“祖先组件提供数据,后代组件注入数据”的跨层级通信,无论组件层级有多深。
祖先组件提供数据:
<!-- 祖先组件 -->
<script setup lang="ts">
import { provide } from 'vue';
provide('token', 'admin_token'); // 提供一个名为 'token' 的数据
</script>
后代组件注入数据:
<!-- 后代组件 -->
<script setup lang="ts">
import { inject } from 'vue';
let token = inject('token'); // 注入名为 'token' 的数据
console.log(token); // 'admin_token'
</script>
1.8 Pinia:新一代集中式状态管理容器
Pinia 是 Vue3 推荐的集中式状态管理库,它类似于 Vuex,但更加轻量、直观,并且移除了 mutations
和 modules
等概念,使得状态管理更加扁平化和易于理解。
Pinia 官网: https://pinia.web3doc.top/
1.9 插槽 (slot
):灵活的内容分发
插槽是 Vue 组件提供内容分发能力的机制,它允许父组件向子组件的指定位置注入内容,从而实现更灵活的组件组合。插槽分为默认插槽、具名插槽和作用域插槽。
默认插槽:
子组件定义一个 <slot>
标签,父组件在使用子组件时,在双标签内部书写的内容将填充到这个插槽中。
<!-- 子组件 Todo.vue -->
<template>
<div>
<slot></slot>
</div>
</template>
<!-- 父组件 -->
<template>
<Todo>
<h1>我是默认插槽填充的结构</h1>
</Todo>
</template>
具名插槽:
子组件定义多个带有 name
属性的插槽,父组件通过 v-slot:
或 #
指令指定内容填充到哪个具名插槽。
<!-- 子组件 Todo.vue -->
<template>
<div>
<h1>todo</h1>
<slot name="a"></slot>
<slot name="b"></slot>
</div>
</template>
<!-- 父组件 -->
<template>
<div>
<h1>slot</h1>
<Todo>
<template v-slot:a>
<!-- 可以用 #a 替换 -->
<div>填入组件A部分的结构</div>
</template>
<template v-slot:b>
<!-- 可以用 #b 替换 -->
<div>填入组件B部分的结构</div>
</template>
</Todo>
</div>
</template>
作用域插槽:
作用域插槽允许子组件在渲染插槽内容时向父组件传递数据,父组件可以根据这些数据决定插槽内容的结构和样式。
<!-- 子组件 Todo.vue -->
<template>
<div>
<h1>todo</h1>
<ul>
<!-- 组件内部遍历数组 -->
<li v-for="(item, index) in todos" :key="item.id">
<!-- 作用域插槽将数据回传给父组件 -->
<slot :$row="item" :$index="index"></slot>
</li>
</ul>
</div>
</template>
<script setup lang="ts">
defineProps(['todos']); // 接受父组件传递过来的数据
</script>
<!-- 父组件 -->
<template>
<div>
<h1>slot</h1>
<Todo :todos="todos">
<template v-slot="{$row, $index}">
<!-- 父组件决定子组件的结构与外观 -->
<span :style="{ color: $row.done ? 'green' : 'red' }">{{ $row.title }}</span>
</template>
</Todo>
</div>
</template>
<script setup lang="ts">
import Todo from './Todo.vue';
import { ref } from 'vue';
// 父组件内部数据
let todos = ref([
{ id: 1, title: '吃饭', done: true },
{ id: 2, title: '睡觉', done: false },
{ id: 3, title: '打豆豆', done: true },
]);
</script>
二、项目初始化与规范化:构建高质量代码的基石
一个高质量的项目离不开严格的代码规范和统一的开发流程。“硅谷甄选运营平台”项目在初始化阶段就集成了多项工具,确保了代码的质量和团队协作的顺畅。
2.1 环境准备与项目初始化
项目采用 Vue3 和 Vite 进行构建,并强制使用 pnpm 作为包管理器,以确保依赖安装的一致性和高性能。
环境要求:
- • Node.js v16.14.2+
- • pnpm 8.0.0+
pnpm 安装:
npm i -g pnpm
项目初始化命令:
pnpm create vite
进入项目根目录后,运行 pnpm install
安装所有依赖,然后 pnpm run dev
即可启动项目。
2.2 项目配置:代码质量与开发效率的保障
2.2.1 ESLint 配置:JavaScript/TypeScript 代码质量检测
ESLint 是一个可插拔的 JavaScript/TypeScript 代码检测工具,用于发现和报告代码中的问题。
安装 ESLint 及其相关插件:
pnpm create @eslint/config@latest
pnpm install -D eslint-plugin-import eslint-plugin-vue eslint-plugin-node eslint-plugin-prettier eslint-config-prettier eslint-plugin-node @babel/eslint-parser
eslint.config.ts
配置文件示例:
import { globalIgnores } from 'eslint/config'
import { defineConfigWithVueTs, vueTsConfigs } from '@vue/eslint-config-typescript'
import pluginVue from 'eslint-plugin-vue'
import skipFormatting from '@vue/eslint-config-prettier/skip-formatting'
import pluginTs from '@typescript-eslint/eslint-plugin'
export default defineConfigWithVueTs(
// 配置要被 ESLint 检查的文件类型
{
name: 'app/files-to-lint',
files: ['**/*.{ts,mts,tsx,vue}'],
},
// 忽略不需要检查的目录,例如构建输出目录、覆盖率目录等
globalIgnores(['**/dist/**', '**/dist-ssr/**', '**/coverage/**']),
// 使用 Vue 提供的基础配置(等价于 vue3-essential 规则集)
pluginVue.configs['flat/essential'],
// 使用 Vue 官方提供的 TypeScript 推荐规则
vueTsConfigs.recommended,
// 禁用所有格式化相关规则,避免与 Prettier 冲突
skipFormatting,
// 自定义规则配置
{
plugins: {
vue: pluginVue, // 一定要声明 vue 插件
'@typescript-eslint': pluginTs,
},
rules: {
// JavaScript 基础规则
'no-var': 'error', // 禁止使用 var,强制使用 let/const
'no-multiple-empty-lines': ['warn', { max: 1 }], // 警告:最多允许一行空行
'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off', // 生产环境禁止使用 console
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off', // 生产环境禁止使用 debugger
'no-unexpected-multiline': 'error', // 防止由于自动分号插入引发的 bug
'no-useless-escape': 'off', // 允许多余的转义字符(某些正则场景需要)
// TypeScript 相关规则
'@typescript-eslint/no-unused-vars': 'error', // 禁止未使用的变量
'@typescript-eslint/prefer-ts-expect-error': 'error', // 更推荐使用 ts-expect-error 替代 ts-ignore
'@typescript-eslint/no-explicit-any': 'off', // 允许使用 any 类型
'@typescript-eslint/no-non-null-assertion': 'off', // 允许使用非空断言(!)
'@typescript-eslint/no-namespace': 'off', // 允许使用命名空间(namespace)
'@typescript-eslint/semi': 'off', // 关闭对分号的强制检查
// Vue 相关规则
'vue/multi-word-component-names': 'off', // 允许使用单词组件名(适用于页面组件等)
// 'vue/script-setup-uses-vars': 'error', // 新版插件可能移除了此规则,先注释避免错误
'vue/no-mutating-props': 'off', // 允许修改 props(某些业务场景可能需要)
'vue/attribute-hyphenation': 'off', // 关闭模板中 attribute 必须使用连字符的限制
},
},
)
同时,通过 .eslintignore
文件可以配置需要忽略的文件或目录,例如 dist
和 node_modules
。
在 package.json
中添加运行脚本,方便进行代码检查和修复:
"scripts": {
"lint": "eslint src",
"fix": "eslint src --fix"
}
2.2.2 Prettier 配置:代码格式化工具
Prettier 是一个强大的代码格式化工具,它专注于代码的美观和一致性,支持多种语言。ESLint 保证代码质量,而 Prettier 保证代码美观。两者结合,相得益彰。
安装 Prettier 相关依赖:
pnpm install -D eslint-plugin-prettier prettier eslint-config-prettier
.prettierrc.cjs
规则配置示例:
module.exports = {
"singleQuote": true, // 使用单引号代替双引号
"semi": false, // 禁止语句末尾自动添加分号
"bracketSpacing": true, // 对象字面量的大括号内添加空格(如:{ foo: bar })
"htmlWhitespaceSensitivity": "ignore", // 忽略 HTML 中的空格敏感性,避免格式混乱
};
通过 .prettierignore
同样可以忽略不需要格式化的文件。
2.2.3 Stylelint 配置:CSS/SCSS 代码规范
Stylelint 是 CSS 的 Lint 工具,用于格式化 CSS 代码、检查语法错误和不合理的写法,并指定 CSS 书写顺序等。项目中使用 SCSS 作为预处理器。
安装 Stylelint 及其相关依赖:
pnpm add sass sass-loader stylelint postcss postcss-scss postcss-html stylelint-config-prettier stylelint-config-recess-order stylelint-config-recommended-scss stylelint-config-standard stylelint-config-standard-vue stylelint-scss stylelint-order stylelint-config-standard-scss -D
stylelint.config.ts
配置文件示例:
// @see https://stylelint.io/
// @see https://github.com/stylelint-scss/stylelint-scss
// @see https://github.com/prettier-community/stylelint-prettier
import type { Config } from 'stylelint'
const config: Config = {
// 继承一系列共享配置
extends: [
'stylelint-config-standard', // Stylelint 官方推荐的基础配置
'stylelint-config-standard-scss', // SCSS 语法规则
'stylelint-config-html/vue', // 解析 Vue 文件中的 <style> 标签
'stylelint-config-recommended-vue/scss', // 推荐在 Vue 中使用的 SCSS 规则
'stylelint-config-recess-order', // CSS 属性排序规则
],
// 注册插件
plugins: [
'stylelint-prettier', // 将 Prettier 作为 Stylelint 的规则运行
],
// 为不同类型的文件指定自定义语法解析器
overrides: [
{
files: ['**/*.(scss|css|vue|html)'],
customSyntax: 'postcss-scss',
},
{
files: ['**/*.(html|vue)'],
customSyntax: 'postcss-html',
},
],
// 忽略检查特定文件
ignoreFiles: [
'**/*.js',
'**/*.jsx',
'**/*.tsx',
'**/*.ts',
'**/*.json',
'**/*.md',
'**/*.yaml',
'dist/**/*',
'public/**/*',
'node_modules/**/*',
'html/**/*', // <--- 已为您添加此行
],
// 自定义规则
rules: {
// -----------------------------------------------------------------
// @section 插件与集成 (Plugins & Integrations)
// -----------------------------------------------------------------
// [建议] 激活 stylelint-prettier 插件,使用 .prettierrc 文件进行格式化
'prettier/prettier': true,
// -----------------------------------------------------------------
// @section 规则关闭与放宽 (Rules Disabled & Relaxed)
// -----------------------------------------------------------------
// [关闭] 允许在 CSS 中使用 v-bind(),避免不必要的报错
'value-keyword-case': null,
// [关闭] 允许低优先级的选择器出现在高优先级选择器之后,在复杂场景下更灵活
'no-descending-specificity': null,
// [关闭] 允许空的源文件
'no-empty-source': null,
// [关闭] 不强制规定类名的格式,让开发者自由选择 BEM、pcss 等规范
'selector-class-pattern': null,
// [关闭] 允许使用未知的 CSS 属性(例如,为了兼容某些 CSS-in-JS 写法)
'property-no-unknown': null,
// [关闭] 允许在值和属性中使用浏览器引擎前缀(--webkit-, -moz- 等)
'value-no-vendor-prefix': null,
'property-no-vendor-prefix': null,
// -----------------------------------------------------------------
// @section 风格与约定增强 (Style & Convention Enhancements)
// -----------------------------------------------------------------
// [增强] 要求 URL 地址必须总是带引号
'function-url-quotes': 'always',
// [增强] 验证 SCSS 特有的 @ 规则(如 @use, @forward),防止拼写错误
'scss/at-rule-no-unknown': true,
// [增强] 不允许未知的伪类选择器,但忽略 Vue 特有的伪类
'selector-pseudo-class-no-unknown': [
true,
{
ignorePseudoClasses: ['global', 'v-deep', 'deep'],
},
],
// [最佳实践] 禁止在选择器中使用 ID,鼓励使用类来控制样式,提高可复用性
'selector-max-id': 0,
// -----------------------------------------------------------------
// @section 可选的最佳实践规则 (Optional Best Practices)
// 您可以根据项目需求取消注释来启用这些规则
// -----------------------------------------------------------------
// [可选] 强制使用 BEM 命名约定 (https://en.bem.info/methodology/naming-convention/)
// 'selector-class-pattern': [
// '^[a-z]([a-z0-9-]+)?(__([a-z0-9]+-?)+)?(--([a-z0-9]+-?)+){0,2}$',
// {
// message:
// 'Expected class selector to be written in BEM notation (e.g., block__element--modifier)',
// },
// ],
// [可选] 规定 font-size 只能使用 rem 或 em 单位,提升可访问性
// 'declaration-property-unit-allowed-list': {
// 'font-size': ['rem', 'em'],
// },
},
}
export default config
在 package.json
中添加 Stylelint 的运行脚本:
"scripts": {
"lint:style": "stylelint src/**/*.{css,scss,vue} --cache --fix"
}
最终,package.json
中的脚本将包含完整的 Lint 和格式化命令:
"scripts": {
"dev": "vite --open",
"build": "vue-tsc && vite build",
"preview": "vite preview",
"fix": "eslint src --fix",
"format": "prettier --write \"./**/*.{html,vue,ts,js,json,md}\"",
"lint": "run-p lint:eslint lint:style",
"lint:eslint": "eslint src/**/*.{ts,vue} --cache --fix",
"lint:style": "stylelint src/**/*.{css,scss,vue} --cache --fix",
}
2.2.4 Husky 配置:Git Hook 自动化
为了强制开发人员遵循代码规范,我们引入了 Husky。Husky 可以在 Git 提交(commit)前触发 Git 钩子,自动执行代码格式化和检查。
安装 Husky:
pnpm install -D husky
初始化 Husky:
npx husky-init
这会在项目根目录下生成 .husky
目录,其中包含 pre-commit
文件。
修改 .husky/pre-commit
文件:
pnpm run format # 在提交前自动执行格式化
这样,每次执行 git commit
时,代码都会自动格式化。
2.2.5 Commitlint 配置:统一 Git 提交信息规范
为了保持 Git 提交信息的清晰和一致性,我们使用 Commitlint 来强制执行提交规范。
安装 Commitlint:
pnpm add @commitlint/config-conventional @commitlint/cli -D
创建 commitlint.config.cjs
配置文件:
// commitlint.config.cjs
module.exports = {
// 继承社区推荐的 'config-conventional' 规范
extends: ['@commitlint/config-conventional'],
// 自定义规则 (0=禁用, 1=警告, 2=报错)
rules: {
// type 类型必须是以下之一
'type-enum': [
2,
'always',
[
'feat', // 新功能
'fix', // 修复bug
'docs', // 文档
'style', // 代码格式
'refactor', // 重构
'perf', // 性能优化
'test', // 测试
'chore', // 构建或辅助工具变动
'revert', // 回退
'build', // 打包
],
],
// type 大小写: 不作限制
'type-case': [0],
// type 是否为空: 不作限制
'type-empty': [0],
// scope 是否为空: 不作限制
'scope-empty': [0],
// scope 大小写: 不作限制
'scope-case': [0],
// subject 结尾标点: 不作限制
'subject-full-stop': [0, 'never'],
// subject 大小写: 不作限制
'subject-case': [0, 'never'],
// header 最大长度: 不作限制
'header-max-length': [0, 'always', 72],
},
};
在 package.json
中添加 Commitlint 脚本:
"scripts": {
"commitlint": "commitlint --config commitlint.config.cjs -e -V"
}
配置 Husky,在 commit-msg
钩子中执行 Commitlint 检查:
npx husky add .husky/commit-msg
在生成的 .husky/commit-msg
文件中添加:
pnpm commitlint
现在,提交信息必须符合 type: message
的格式,例如 fix: 修复登录问题
。
2.2.6 强制使用 pnpm 包管理器
为了避免不同包管理器(npm, yarn, pnpm)可能导致的依赖版本不一致问题,项目强制使用 pnpm。
在根目录创建 scripts/preinstall.js
文件:
if (!/pnpm/.test(process.env.npm_execpath || '')) {
console.warn(
`\u001b[33mThis repository must using pnpm as the package manager ` +
` for scripts to work properly.\u001b[39m\n`,
);
process.exit(1);
}
在 package.json
中配置 preinstall
脚本:
"scripts": {
"preinstall": "node ./scripts/preinstall.js"
}
这样,当尝试使用 npm
或 yarn
安装依赖时,将会报错并提示使用 pnpm
。
三、项目集成:提升开发效率与用户体验
在项目规范化之后,我们将核心的第三方库和工具集成到项目中,进一步提升开发效率和用户体验。
3.1 Element Plus 集成:美观高效的 UI 组件库
硅谷甄选运营平台采用 Element Plus 作为 UI 组件库,它提供了丰富的组件和友好的开发体验。
安装 Element Plus:
pnpm install element-plus @element-plus/icons-vue
在 main.ts
中全局安装并配置中文语言:
import { createApp } from 'vue';
import App from './App.vue';
import ElementPlus from 'element-plus';
import 'element-plus/dist/index.css';
//@ts-ignore 忽略当前文件ts类型的检测否则有红色提示(打包会失败)
import zhCn from 'element-plus/dist/locale/zh-cn.mjs';
const app = createApp(App);
app.use(ElementPlus, {
locale: zhCn, // 设置为中文
});
// Element Plus全局组件类型声明 (在 tsconfig.json 中配置)
// {
// "compilerOptions": {
// "types": ["element-plus/global"]
// }
// }
app.mount('#app');
3.2 src
别名配置:简化模块导入
为了简化文件路径,提高代码可读性,我们为 src
目录配置了 @
别名。
vite.config.ts
配置:
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import path from 'path';
export default defineConfig({
plugins: [vue()],
resolve: {
alias: {
"@": path.resolve("./src") // 相对路径别名配置,使用 @ 代替 src
}
}
});
tsconfig.json
TypeScript 编译配置:
{
"compilerOptions": {
"baseUrl": "./", // 解析非相对模块的基地址,默认是当前目录
"paths": { //路径映射,相对于baseUrl
"@/*": ["src/*"]
}
}
}
3.3 环境变量配置:灵活适应多环境部署
项目开发通常会经历开发、测试和生产等不同环境,每个环境的接口地址等配置可能不同。通过环境变量配置,我们可以轻松地在不同环境之间切换。
项目根目录创建环境变量文件:
- •
.env.development
(开发环境) - •
.env.production
(生产环境) - •
.env.test
(测试环境)
.env.development
示例:
NODE_ENV = 'development'
VITE_APP_TITLE = '硅谷甄选运营平台'
VITE_APP_BASE_API = '/dev-api' # 变量必须以 VITE_ 为前缀才能暴露给外部读取
package.json
配置运行命令:
"scripts": {
"dev": "vite --open",
"build:test": "vue-tsc && vite build --mode test",
"build:pro": "vue-tsc && vite build --mode production",
"preview": "vite preview"
}
在代码中通过 import.meta.env
对象获取环境变量。
3.4 SVG 图标配置与封装:轻量级矢量图方案
SVG 矢量图相比传统图片资源具有体积小、不失真、性能优越等优点。项目中通过 vite-plugin-svg-icons
插件集成 SVG 图标。
安装插件:
pnpm install vite-plugin-svg-icons -D
vite.config.ts
配置插件:
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons';
import path from 'path';
export default defineConfig(({ command }) => {
return {
plugins: [
vue(),
createSvgIconsPlugin({
// 指定需要缓存的图标文件夹
iconDirs: [path.resolve(process.cwd(), 'src/assets/icons')],
// 指定 symbolId 的格式
symbolId: 'icon-[dir]-[name]',
}),
],
};
});
入口文件 main.ts
导入:
import 'virtual:svg-icons-register';
SVG 图标全局组件封装 (src/components/SvgIcon/index.vue
):
为了方便使用,我们将 SVG 图标封装成一个全局组件 SvgIcon
。
<template>
<div>
<svg :style="{ width: width, height: height }">
<use :xlink:href="prefix + name" :fill="color"></use>
</svg>
</div>
</template>
<script setup lang="ts">
defineProps({
// xlink:href 属性值的前缀
prefix: {
type: String,
default: '#icon-'
},
// svg 矢量图的名字
name: String,
// svg 图标的颜色
color: {
type: String,
default: ""
},
// svg 宽度
width: {
type: String,
default: '16px'
},
// svg 高度
height: {
type: String,
default: '16px'
}
});
</script>
<style scoped></style>
全局组件注册 (src/components/index.ts
):
import SvgIcon from './SvgIcon/index.vue';
import type { App, Component } from 'vue';
const components: { [name: string]: Component } = { SvgIcon };
export default {
install(app: App) {
Object.keys(components).forEach((key: string) => {
app.component(key, components[key]);
});
}
};
在 main.ts
中安装全局组件:
import gloablComponent from './components/index';
app.use(gloablComponent);
3.5 集成 Sass:增强样式开发能力
项目已通过 Stylelint 配置安装了 sass
和 sass-loader
,可以直接在 Vue 组件中使用 SCSS 语法,只需在 <style>
标签上添加 lang="scss"
。
引入全局样式和变量:
在 src/styles
目录下创建 index.scss
(用于引入全局重置样式) 和 variable.scss
(用于定义全局 SCSS 变量)。
src/styles/index.scss
:
@import './reset.scss'; // 引入重置样式,npm官网复制
// 其他全局样式
main.ts
引入全局样式:
import '@/styles';
vite.config.ts
配置全局 SCSS 变量:
为了在所有组件中都能使用全局 SCSS 变量,需要在 Vite 配置中进行设置。
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import path from 'path';
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons';
export default defineConfig(({ command }) => {
return {
plugins: [
vue(),
createSvgIconsPlugin({
iconDirs: [path.resolve(process.cwd(), 'src/assets/icons')],
symbolId: 'icon-[dir]-[name]',
}),
],
resolve: {
alias: {
"@": path.resolve("./src")
}
},
css: {
preprocessorOptions: {
scss: {
javascriptEnabled: true,
additionalData: '@import "./src/styles/variable.scss";', // 引入全局变量
},
},
},
};
});
注意: @import "./src/styles/variable.scss";
后面的分号不能省略。
3.6 Mock 数据:前端独立开发利器
在后端接口尚未完成时,Mock 数据能够让前端独立进行开发和测试,提高开发效率。项目使用了 vite-plugin-mock
和 mockjs
。
安装依赖:
pnpm install -D vite-plugin-mock mockjs
vite.config.ts
启用插件:
import { UserConfigExport, ConfigEnv } from 'vite';
import { viteMockServe } from 'vite-plugin-mock';
import vue from '@vitejs/plugin-vue';
import path from 'path';
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons';
export default defineConfig(({ command })=> {
return {
plugins: [
vue(),
viteMockServe({
localEnabled: command === 'serve', // 仅在开发模式下启用 Mock
}),
createSvgIconsPlugin({
iconDirs: [path.resolve(process.cwd(), 'src/assets/icons')],
symbolId: 'icon-[dir]-[name]',
}),
],
resolve: {
alias: {
"@": path.resolve("./src")
}
},
css: {
preprocessorOptions: {
scss: {
javascriptEnabled: true,
additionalData: '@import "./src/styles/variable.scss";',
},
},
},
};
});
mock/user.ts
示例:
在 mock
文件夹下创建 user.ts
文件,定义模拟的用户登录和信息接口。
// mock/user.ts
// 用户信息数据
function createUserList() {
return [
{
userId: 1,
avatar:
'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif',
username: 'admin',
password: '111111',
desc: '平台管理员',
roles: ['平台管理员'],
buttons: ['cuser.detail'],
routes: ['home'],
token: 'Admin Token',
},
{
userId: 2,
avatar:
'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif',
username: 'system',
password: '111111',
desc: '系统管理员',
roles: ['系统管理员'],
buttons: ['cuser.detail', 'cuser.user'],
routes: ['home'],
token: 'System Token',
},
];
}
export default [
// 用户登录接口
{
url: '/api/user/login', // 请求地址
method: 'post', // 请求方式
response: ({ body }) => {
// 获取请求体携带过来的用户名与密码
const { username, password } = body;
// 调用获取用户信息函数,用于判断是否有此用户
const checkUser = createUserList().find(
(item) => item.username === username && item.password === password,
);
// 没有用户返回失败信息
if (!checkUser) {
return { code: 201, data: { message: '账号或者密码不正确' } };
}
// 如果有返回成功信息
const { token } = checkUser;
return { code: 200, data: { token } };
},
},
// 获取用户信息
{
url: '/api/user/info',
method: 'get',
response: (request) => {
// 获取请求头携带token
const token = request.headers.token;
// 查看用户信息是否包含有次token用户
const checkUser = createUserList().find((item) => item.token === token);
// 没有返回失败的信息
if (!checkUser) {
return { code: 201, data: { message: '获取用户信息失败' } };
}
// 如果有返回成功信息
return { code: 200, data: { checkUser } };
},
},
];
3.7 Axios 二次封装:统一网络请求与错误处理
在前端项目中,Axios 是常用的 HTTP 客户端。对其进行二次封装,可以实现请求拦截、响应拦截、统一错误处理等功能,极大地提升开发效率和代码健壮性。
安装 Axios:
pnpm install axios
src/utils/request.ts
Axios 二次封装:
import axios from 'axios';
import { ElMessage } from 'element-plus'; // 引入 Element Plus 的消息提示组件
// 创建 axios 实例
let request = axios.create({
baseURL: import.meta.env.VITE_APP_BASE_API, // 从环境变量获取基础 API 地址
timeout: 5000 // 请求超时时间
});
// 请求拦截器
request.interceptors.request.use(config => {
// 在这里可以处理一些业务逻辑,例如:
// 1. 开启进度条 (例如 NProgress)
// 2. 在请求头中携带公共参数 (例如 token)
// if (userStore.token) {
// config.headers.token = userStore.token;
// }
return config;
});
// 响应拦截器
request.interceptors.response.use((response) => {
// 响应成功,可以结束进度条,并简化服务器返回的数据结构
return response.data; // 返回实际的数据部分
}, (error) => {
// 处理网络错误
let msg = '';
let status = error.response.status;
switch (status) {
case 401:
msg = "token过期";
// TODO: 清除本地 token,跳转到登录页
break;
case 403:
msg = '无权访问';
break;
case 404:
msg = "请求地址错误";
break;
case 500:
msg = "服务器出现问题";
break;
default:
msg = "无网络";
}
// 使用 Element Plus 的 ElMessage 显示错误信息
ElMessage({
type: 'error',
message: msg
});
return Promise.reject(error); // 继续向下传递错误
});
export default request;
3.8 API 接口统一管理:清晰的接口结构
为了避免接口地址硬编码、提高代码可读性和维护性,项目采用了统一的 API 接口管理方式。
在 src
目录下创建 api
文件夹,并按模块(如 user
、product
、acl
)进行分类管理。
src/api/user/index.ts
示例:
// src/api/user/index.ts
// 统一管理项目用户相关的接口
import request from '@/utils/request'; // 引入二次封装的 axios 实例
import type {
loginFormData,
loginResponseData,
userInfoReponseData,
} from './type'; // 引入接口相关的数据类型定义
// 项目用户相关的请求地址枚举
enum API {
LOGIN_URL = '/admin/acl/index/login',
USERINFO_URL = '/admin/acl/index/info',
LOGOUT_URL = '/admin/acl/index/logout',
}
// 登录接口
export const reqLogin = (data: loginFormData) =>
request.post<any, loginResponseData>(API.LOGIN_URL, data);
// 获取用户信息接口
export const reqUserInfo = () =>
request.get<any, userInfoReponseData>(API.USERINFO_URL);
// 退出登录接口
export const reqLogout = () => request.post<any, any>(API.LOGOUT_URL);
src/api/user/type.ts
示例 (定义数据类型):
// src/api/user/type.ts
// 定义用户相关接口的数据类型
// 登录表单数据类型
export interface loginFormData {
username: string;
password: string;
}
// 登录接口响应数据类型
export interface loginResponseData {
code: number;
message: string;
data: {
token: string;
};
}
// 用户信息接口响应数据类型
export interface userInfoReponseData {
code: number;
message: string;
data: {
name: string;
avatar: string;
// 其他用户信息字段
};
}
这种模块化的接口管理方式,使得接口定义清晰、易于查找和维护,并且结合 TypeScript 提供了强大的类型检查,进一步提升了代码质量。
总结与展望
本次学习记录的是“硅谷甄选运营平台”的开发过程,主要基于 Vue3 + TypeScript 技术栈,结合模块化接口管理、统一的错误处理机制和严格的代码规范,实现了一个结构清晰、易于维护的后台管理系统。
在项目搭建与开发过程中,重点掌握了以下几个方面:
- • 组件通信与状态管理:通过 props、emit、pinia 等方式实现组件间的数据交互;
- • 项目结构设计:实现了合理的模块拆分和目录组织;
- • 权限管理与动态路由:掌握了基于 token 的路由控制思路;
- • 接口封装与异常处理:统一封装 API 请求逻辑,提高代码复用性和容错能力;
- • UI 组件库使用:熟练使用 Element Plus 提高开发效率;
- • TS 类型管理:通过类型定义提升代码健壮性与开发体验。