《硅谷甄选》项目笔记

 

《硅谷甄选》项目笔记

📁 个人仓库地址

👉 我的 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 中,像 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 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 文件夹,并按模块(如 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 类型管理:通过类型定义提升代码健壮性与开发体验。

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

对不起初见i

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值