当前位置: 首页 > news >正文

《硅谷甄选》项目笔记

《硅谷甄选》项目笔记

📁 个人仓库地址

👉 我的 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.vueconst 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.vueconst 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 中,像 clickdbclickchange 这类原生 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 provideinject:实现跨层级组件通信

provideinject 是 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,但更加轻量、直观,并且移除了 mutationsmodules 等概念,使得状态管理更加扁平化和易于理解。

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 环境准备与项目初始化

项目采用 Vue3Vite 进行构建,并强制使用 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 文件可以配置需要忽略的文件或目录,例如 distnode_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-prettierimport 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"
}

这样,当尝试使用 npmyarn 安装依赖时,将会报错并提示使用 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 配置安装了 sasssass-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-mockmockjs

安装依赖:

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) => {// 获取请求头携带tokenconst 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 文件夹,并按模块(如 userproductacl)进行分类管理。

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 类型管理:通过类型定义提升代码健壮性与开发体验。
http://www.sczhlp.com/news/6847/

相关文章:

  • 腾讯游戏安全2023安卓初赛题解
  • Linux系统入门指南第二章 -- 安装及管理程序
  • 8.6总结
  • 20250806 HT-071
  • 博客园头像 - Charon
  • 【通信模型】你想知道的关于 actor 模型但可能不敢问的所有信息(译文)
  • 第二十三篇
  • ZROJ3265 猜数游戏
  • 安装 PVE
  • 003-存储读取数据的方案
  • 004-多线程
  • 安科瑞分布式光伏监控系统:筑牢10kV光伏电站的智能监测防线 - 实践
  • 什么是供应链 - 智慧园区
  • 泛微e8获取当前操作者并与明细行的项目负责人对比,不同就隐藏明细行
  • 25.8.5python模块
  • 在K8S中,同⼀个Pod的不同容器互相可以访问是怎么做到的?
  • 基于图像识别与分类的中国蛇类识别系统 - 教程
  • 对于项目调用方法的解析
  • 在K8S中,不同的Pod之间互相可以访问是怎么做到的?
  • 在K8S中,如果容器没有bash命令,如何进入容器排查问题?
  • 在K8S中,如果是因为开发写的镜像问题导致pod起不来该怎么排查?
  • 在K8S中,在服务上线的时候Pod起不来怎么进行排查?
  • 在K8S中,cronjob的使用场景?
  • 在K8S中,Deployment支持扩容吗?它与HPA有什么区别?
  • [浅谈数学]浅谈博弈论
  • mysql 定位慢sql - xiao-jie
  • 在K8S中,K8S⼆进制和kubeadm安装的方式有什么区别?
  • 在运维工作中,k8s中service和ingress的有什么区别?
  • 8.4 NOIP 模拟赛
  • panic、defer、recover