web前端代码指南(vue3)

一、命名规范

市面上常用的命名规范:

  • camelCase(小驼峰式命名法 —— 首字母小写)
  • PascalCase(大驼峰式命名法 —— 首字母大写)
  • kebab-case(短横线连接式)
  • Snake(下划线连接式)

1.1 项目文件命名

1.1.1 项目命名

全部采用小写方式, 以短横线分隔。

  • 正例:mall-management-system
  • 反例:mall_management-system / mallManagementSystem

1.1.2 目录名

参照项目命名规则,有复数结构时,要采用复数命名法。

  • 正例:docs、assets、images、icons、components、directives、enums、hooks、utils、views
  • 反例:script / style / demo_scripts / demoStyles

1.VUE 的项目中的组件components 中的组件使用 PascalCase 命名

  • 正例: HeadSearch / PageLoading / Authorized / NoticeIcon
  • 反例: headSearch / page-loading

2. VUE 的项目中的页面目录使用 camelCase 命名

  • 正例:pageOne / shoppingCar / userManagement
  • 反例: shopping-car / UserManagement

1.1.3 图像文件名: Snake方式

全部采用Snake命名方式, 优先选择单个单词命名,多个单词命名以下划线连接。

banner_sina.gif
menu_aboutus.gif
menutitle_news.gif
logo_police.gif
logo_national.gif
pic_people.jpg
pic_TV.jpg

1.1.4 HTML 文件名: camelCase方式

优先选择单个单词命名,多个单词命名以camelCase方式。

|- errorReport.html
|- successReport.html

1.1.5 CSS 文件名: camelCase方式

全部采用camelCase方式, 优先选择单个单词命名,多个单词命名以短横线分隔。

|- index.scss
|- userCenter.scss

1.1.6 JavaScript 文件名: camelCase方式

全部采用camelCase写方式,优先选择单个单词命名,多个单词命名以短横线分隔。

|- index.js
|- util.js
|- userCenter.js

1.1.6 TypeScript 文件名: camelCase方式

全部采用camelCase写方式,优先选择单个单词命名,多个单词命名以短横线分隔。

|- index.ts
|- util.ts
|- userCenter.ts

除特定配置文件和依赖除外

1.2 Vue组件名

1.2.1 单文件组件名

文件扩展名为 .vuesingle-file components (单文件组件)。单文件组件名应该PascalCase方式。

components/
|- MyComponent.vue

1.2.2 紧密耦合的组件名

和父组件紧密耦合的子组件应该以父组件名作为前缀命名。 因为编辑器通常会按字母顺序组织文件,所以这样做可以把相关联的文件排在一起。

components/
|- RoleList.vue
|- RoleListItem.vue
|- RoleListItemButton.vue

1.2.3 组件名中单词顺序

组件名应该以高级别的 (通常是一般化描述的) 单词开头,以描述性的修饰词结尾。 因为编辑器通常会按字母顺序组织文件,所以现在组件之间的重要关系一目了然。如下组件主要是用于搜索和设置功能。

components/
|- SearchButtonClear.vue
|- SearchButtonRun.vue
|- SearchInputQuery.vue
|- SearchInputExcludeGlob.vue
|- SettingsCheckboxTerms.vue
|- SettingsCheckboxLaunchOnStartup.vue

1.2.4 完整单词的组件名

组件名应该倾向于完整而不是缩写。 编辑器中的自动补全已经让书写长命名的代价非常之低了,而其带来的明确性却是非常宝贵的。不常用的缩写尤其应该避免。

components/
|- StudentDashboardSettings.vue
|- RoleAuthorization.vue

1.2.5 全局组件命名

**全局组件应采用文件夹加index.vue格式。文件夹名称采用PascalCase格式命名。**其内部组件和页面内部组件采取组件规范。

全局组件
components/
|- ProTable
|  |- index.vue
|  |- components/
|  |  |- ProTableHeader.vue
|  |  |- ProTableBody.vue

1.2.8 api文件

  • api 目录
    文件、变量命名要与模块保持一致,注释明确。
  • 示例:Role模块
// api目录
api/modules/role.ts:

// 添加角色  
addRole: (data) => { 
  return postAxios('/role/add', data)  
}, 

// 删除角色 
deleteRole: (roleId) => {   
  return postAxios('/role/delete/' + roleId)   
},

1.2.9 Vue3项目文件资源目录示例:

my-project-name/
├─ .husky                             # husky 配置文件
├─ .vscode                            # VSCode 推荐配置
├─ build                              # Vite 配置项
├─ public                             # 静态资源文件
├─ docs                               # 项目的细化文档目录(可选)
├─ node_modules                       # 下载的依赖包
├─ src                                # 源码目录
│  ├─ api                             # http 请求目录
│  │  ├─ config                       # http 配置项
│  │  ├─ helper                       # http 封装
│  │  ├─ interface                    # http 接口类型
│  │  │  ├─ userCenter.ts             # http 用户接口
│  │  ├─ modules                      # http 接口模块
│  │  │  ├─ userCenter.ts             # http 用户api模块
│  │  └─ index.ts                     # http 请求入口
│  ├─ assets                          # 静态资源目录
│  ├─ components                      # 全局组件
│  │  ├─ ProTable                     # ProTable组件文件夹
│  │  │  ├─ index.vue                 # ProTable组件
│  │  │  ├─ interface                 # ProTable公共接口
│  │  │  │  ├─ index.ts               # ProTable公共接口文件
│  │  │  ├─ components                # ProTable内部组件
│  │  │  │  ├─ Pagination.vue         # ProTable内部分页组件
│  │  │  │  ├─ TableColumn.vue        # ProTable内部列组件
│  ├─ config                          # 全局配置项
│  ├─ constants                       # 全局常量
│  ├─ enums                           # 项目常用枚举
│  ├─ hooks                           # 常用 Hooks 封装
│  ├─ languages                       # 语言国际化 i18n
│  ├─ directives                      # 全局指令文件
│  ├─ mockApi                         # 模拟接口,临时存放
│  ├─ mockData                        # 模拟数据,临时存放
│  ├─ plugins                         # 插件
│  ├─ routers                         # 路由管理
│  ├─ stores                          # pinia store
│  ├─ styles                          # 全局样式文件
│  ├─ utils                           # 常用工具库
│  ├─ views                           # 页面存放目录
│  │  ├─ role                         # role模块名
│  │  │  ├─ userRole                  # 用户role
│  │  │  │  ├─ index.vue              # 用户role首页/列表
│  │  │  │  ├─ index.scss             # 用户role css
│  │  │  │  └─ detail.vue             # role详情页面
│  │  │  ├─ systemRole                # 系统role
│  │  │  ├─ components                # role模块通用组件文件夹
│  │  │  │  └─ RoleAuthorization.vue  # role授权组件
│  │  │  └─ interface                 # role公共接口文件夹
│  │  └─ user                         # user模块名
│  ├─ App.vue                         # 项目根组件
│  ├─ main.ts                         # 入口文件
│  └─ vite-env.d.ts                   # 指定 ts 识别 vue
├─ .editorconfig                      # 统一不同编辑器的编码风格
├─ .env                               # vite 常用配置
├─ .env.development                   # 开发环境配置
├─ .env.production                    # 生产环境配置
├─ .env.test                          # 测试环境配置
├─ .eslintignore                      # 忽略 Eslint 校验
├─ .eslintrc.cjs                      # Eslint 校验配置文件
├─ .gitignore                         # 忽略 git 提交
├─ .prettierignore                    # 忽略 Prettier 格式化
├─ .prettierrc.cjs                    # Prettier 格式化配置
├─ .stylelintignore                   # 忽略 stylelint 格式化
├─ .stylelintrc.cjs                   # stylelint 样式格式化配置
├─ CHANGELOG.md                       # 项目更新日志
├─ commitlint.config.cjs              # git 提交规范配置
├─ index.html                         # 入口 html
├─ LICENSE                            # 开源协议文件
├─ lint-staged.config.cjs             # lint-staged 配置文件
├─ package-lock.json                  # 依赖包包版本锁
├─ package.json                       # 依赖包管理
├─ postcss.config.cjs                 # postcss 配置
├─ README.md                          # README 介绍
├─ tsconfig.json                      # typescript 全局配置
└─ vite.config.ts                     # vite 全局配置文件

1.3 代码参数命名

1.3.1 name

组件名应该始终是多个单词,应该始终是 PascalCase 的。 根组件 App 以及 <transition>、<component> 之类的 Vue 内置组件除外。

<script setup lang="ts" name="ProTable"></script>

1.3.2 prop

在声明 prop 的时候,其命名应该始终使用 camelCase,而在模板和 JSX 中应该始终使用 kebab-case。我们单纯的遵循每个语言的约定,在 JavaScript 中更自然的是 camelCase。而在 HTML 中则是 kebab-case

<TableColumn col-setting="colSetting"/>

<script>
  defineProps<{ colSetting: ColumnProps[] }>();
</script>

1.3.3 router

**Vue Router Path 命名采用 camelCase 格式。**和views页面尽量保持一致。

// good
{
  "path": "/proTable",
  "name": "proTable",
  "redirect": "/proTable/useProTable",
  "meta": {
    "icon": "MessageBox",
  },
  "children": [
    {
      "path": "/proTable/useProTable",
      "name": "useProTable",
      "component": "/proTable/useProTable/index",
      "meta": {
        "icon": "Menu",
      }
  ]
},
// bad
{
  path: '/user_info', // user_info 当成一个单词
  name: 'UserInfo',
  meta: {
    title: ' - 用户',
    desc: ''
  },
  component: () => import('@/views/file/user-Info.vue')
},

path除了采用camelCase命名规范以外,必须以 / 开头,即使是children里的path也要以 / 开头

目的:
经常有这样的场景:某个页面有问题,要立刻找到这个vue文件,如果不用以/开头,pathparentchildren组成的,可能经常需要在router文件里搜索多次才能找到,而如果以/开头,则能立刻搜索到对应的组件

1.3.4 模板中组件

对于绝大多数项目来说,在 单文件组件 和 字符串模板 中 组件名 应该总是 PascalCase 的 ,
但是在 DOM 模板中总是 kebab-case 的。

<!-- 在单文件组件、字符串模板和 JSX 中 -->
<MyComponent /><Row><table :column="data"/></Row>

<!-- 在 DOM 模板中 --> 
<my-component></my-component>

1.3.5 变量

  • 命名方法:camelCase
  • 命名规范:类型 + 对象描述或属性的方式
// good
let tableTitle = "LoginTable"
let mySchool = "我的学校"

// bad
var getTitle = "LoginTable"

1.3.6 常量

  • 命名方法:全部大写下划线分割
  • 命名规范:使用大写字母和下划线来组合命名,下划线用以分割单词
const MAX_COUNT = 10
const URL = 'http://test.host.com'

1.3.7 方法

  • 命名方法:camelCase
  • 命名规范:统一使用动词或者动词 + 名词形式
// 1、普通情况下,使用动词 + 名词形式
// good
jumpPage、openCarInfoDialog

// bad
go、nextPage、show、open、login

// 2、请求数据方法,以 data 结尾
// good
getListData、postFormData

// bad
takeData、confirmData、getList、postForm

// 3、单个动词的情况
init、refresh

动词含义返回值
can判断是否可执行某个动作 (权 )函数返回一个布尔值。true:可执行;false:不可执行;
has判断是否含有某个值函数返回一个布尔值。true:含有此值;false:不含有此值;
is判断是否为某个值函数返回一个布尔值。true:为某个值;false:不为某个值;
get获取某个值函数返回一个非布尔值
set设置某个值无返回值、返回是否设置成功或者返回链式对象

附: 函数方法常用的动词:

get 获取/set 设置,
add 增加/remove 删除
create 创建/destory 移除
start 启动/stop 停止
open 打开/close 关闭,
read 读取/write 
load 写入/save 保存,
create 创建/destroy 销毁
begin 开始/end 结束,
backup 备份/restore 恢复,
import 导入/export 导出,
split 分割/merge 合并,
inject 注入/extract 提取,
attach 附着/detach 脱离,
bind 绑定/separate 分离,
view 查看/browse 浏览,
edit 编辑/modify 修改,
select 选取/mark 标记,
copy 复制/paste 粘贴,
undo 撤销/redo 重做,
insert 插入/delete 移除,
add 加入/append 添加,
clean 清理/clear 清除,
index 索引/sort 排序,
find 查找/search 搜索,
increase 增加/decrease 减少,
play 播放/pause 暂停,
launch 启动/run 运行,
compile 编译/execute 执行,
debug 调试/trace 跟踪,
observe 观察/listen 监听,
build 构建/publish 发布,
input 输入/output 输出,
encode 编码/decode 解码,
encrypt 加密/decrypt 解密,
compress 压缩/decompress 解压缩,
pack 打包/unpack 解包,
parse 解析/emit 生成,
connect 连接/disconnect 断开,
send 发送/receive 接收,
download 下载/upload 上传,
refresh 刷新/synchronize 同步,
update 更新/revert 复原,
lock 锁定/unlock 解锁,
check out 签出/check in 签入,
submit 提交/commit 交付,
push 推/pull 拉,
expand 展开/collapse 折叠,
begin 起始/end 结束,
start 开始/finish 完成,
enter 进入/exit 退出,
abort 放弃/quit 离开,
obsolete 废弃/depreciate 废旧,
collect 收集/aggregate 聚集

1.3.8 自定义事件

自定义事件应始终使用 kebab-case 的事件名。

不同于组件和 prop,事件名不存在任何自动化的大小写转换。而是触发的事件名需要完全匹配监听这个事件所用的名称。

// 父组件中调用
<MyComponent @my-event="handleDoSomething" />

<script>
// 子组件中定义
const emit = defineEmits<{
  "my-event": [];
}>();

emit("my-event", data);
</script>

1.3.10 事件方法

  • 命名方法:camelCase
  • 命名规范:handle + 名称(可选)+ 动词
<template>
  <div @click="handleClick(data)" >
  </div>
</template>

<script>
/**
 * @description 点击事件
 * @param data 所需要的参数
 * */
const handleClick = (data: DataInterface) => {
  ...
};
</script>

1.3.11 api命名

变量命名要与尽可能与模块保持一致,写好注释。

// 添加角色  
addRole: (data) => { 
  return postAxios('/role/add', data)  
}, 

// 删除角色 
deleteRole: (roleId) => {   
  return postAxios('/role/delete/' + roleId)   
},

1.3.12 TS接口命名

  • 类型使用 PascalCase 命名。不要使用I做为接口名前缀。接口成员使用 camelCase 方式命名
// Good
interface Foo {
  bar: number;
  baz(): number;
}

// Bad
interface IFoo {
  Bar: number;
  Baz(): number;
}
  • 枚举值使用 PascalCase 命名。
// 推荐
enum StatusEnum {
	SUCCESS = 'success'
}

// Good
enum StatusEnum {
	Success = 'success'
}

// Bad
enum Status {
	success = 'success'
}

二、代码规范

2.1 Vue

2.1.1 代码结构

  1. script标签内部加上name属性, 用于定义组件名称
  2. script 标签内部结构顺序:
    1. import引入顺序
      1. 官方/生态 方法、类型
      2. 全局方法、类型、组件
      3. 依赖方法、类型、组件
      4. 局部方法、类型、组件
    2. 接收参数
      1. 定义类型接口
      2. 设置props
    3. 功能代码(推荐将统一功能的代码写在聚集在一起)
      1. 功能一
        1. 定义接口类型
        2. 定义数据
        3. 定义计算属性
        4. 定义方法
        5. 定义数据监视
      2. 功能二
    4. 生命周期
    5. 其他方法
    6. 导出内容

示例:

<script setup lang="ts" name="home">
{/* 1.引入 */}
  {/* 引入官方方法 */}
  import { reactive, ref, withDefaults } from "vue";
  {/* 引入路由 */}
  import { useRouter } from "vue-router";

  {/* 全局方法 */}
  import { useGlobalStore } from "@/stores/modules/global";
  {/* 引入工具类 */}
  import { globalUtils } from "@/utils";
  {/* {引入api} */}
  import { loginApi } from "@/api/modules/login";
  {/* 引入全局组件 */}
  import { DefaultComponents } from "@/components/DefaultComponents";

  {/* 引入依赖类型 */}
  import type { FormInstance, FormRules } from "element-plus";
  {/* 引入依赖组件 */}
  import { ElMessage } from "element-plus";

  {/* 引入自定义组件 */}
  import { TableComponents } from "./components/TableComponents";
  {/* 引入自定义接口类型 */}
  import { TableData } from "./interface/index.ts";

{/* 2.接收传参 */}
  interface Props {}

  const props = withDefaults(defineProps<Props>(), {...默认值});

{/* 3.功能代码 */}
  {/* 功能一 */}
  type StringType = 'string'
  const stringData: StringType = 'string';
  const stringDataComputed = computed(() => {stringData});
  const setStringData = () => {console.log(stringDataComputed)};
  watch(props, () => {setStringData()});

  {/* 功能二 */}
  interface RuleForm {
    name: string;
    date1: string;
    date2: string;
  }

  const ruleForm = reactive<RuleForm>({});
  const rules = reactive<FormRules<RuleForm>>({});

  {/* 功能三 */}
  const globalStore = useGlobalStore();
  const {isLoading} = globalStore;
  const isLoadingSync = () => {console.log(isLoading)};

{/* 4.生命周期 */}
  onMounted(() => {init()});

{/* 5.其他方法 */}
  const init = () => {};

  const submitForm = async (formEl: FormInstance | undefined) => {};

  const resetForm = (formEl: FormInstance | undefined) => {};

{/* 6.导出内容 */}
  defineExpose({})
</script>

2.1.2 data

  1. 若需要一个基本类型的响应式数据,必须使用ref
  2. 若需要一个响应式对象,层级不深,ref、reactive都可以
  3. 若需要一个响应式对象,且层级较深,推荐使用reactive,(form表单数据推荐使用reactive
<script>
const num = ref<number>(0);

const ruleForm = reactive<RuleForm>({});
</script>

2.1.3 computed

应该把复杂计算属性分割为尽可能多的更简单的属性。 小的、专注的计算属性减少了信息使用时的假设性限制,所以需求变更时也用不着那么多重构了。

<script>
// good
const basePrice = computed(() => manufactureCost / (1 - profitMargin))
const discount = computed(() => basePrice * (discountPercent || 0))
const finalPrice = computed(() => basePrice - discount)

// bad
const price = computed(() => { 
  var basePrice = manufactureCost / (1 - profitMargin) 
  return ( 
    basePrice - basePrice * (discountPercent || 0) 
  ) 
})
</script>

2.1.4 为 v-for 设置键值

在组件上必须用 key 搭配 v-for,以便维护内部组件及其子树的状态。

<ul>
  <li
    v-for="todo in todos"
    :key="todo.id">
      {{ todo.text }}
  </li>
</ul>

2.1.5 禁止v-ifv-for 放在同一个元素上

永远不要把 v-ifv-for 同时用在同一个元素上。因为这可能导致渲染逻辑变得复杂且难以预测。
Vue3中: 当它们同时存在于一个节点上时,v-if 比 v-for 的优先级更高。这意味着 v-if 的条件将无法访问到 v-for 作用域内定义的变量别名:

<!--
 这会抛出一个错误,因为属性 todo 此时
 没有在该实例上定义
-->
<li v-for="todo in todos" v-if="!todo.isComplete">
  {{ todo.name }}
</li>

在外先包装一层 <template> 再在其上使用 v-for 可以解决这个问题 (这也更加明显易读):

<template v-for="todo in todos">
  <li v-if="!todo.isComplete">
    {{ todo.name }}
  </li>
</template>

一般我们在两种常见的情况下会倾向于这样做:

  • 为了过滤一个列表中的项目 (比如 v-for="user in users" v-if="user.isActive")。在这种情形下,请将 users 替换为一个计算属性 (比如 activeUsers),让其返回过滤后的列表。
<ul>
  <li
    v-for="user in activeUsers"
    :key="user.id">
      {{ user.name }}
  </li>
</ul>
const activeUsers = computed(() => users.filter(user => user.isActive))
  • 为了避免渲染本应该被隐藏的列表 (比如 v-for="user in users" v-if="shouldShowUsers")。这种情形下,请将v-if移动至容器元素上 (比如 ul, ol)。
// good
<ul v-if="shouldShowUsers">
  <li
    v-for="user in users"
    :key="user.id">
      {{ user.name }}
  </li>
</ul>

// bad
<ul>
  <li
    v-for="user in users"
    v-if="shouldShowUsers"
    :key="user.id">
      {{ user.name }}
  </li>
</ul>

2.1.6 多个 attribute 的元素

多个 attribute 的元素占满一行之后应该分多行撰写,每个 attribute 一行。

//good
<img 
  src="https://vuejs.org/images/logo.png" 
  alt="Vue Logo" 
  title="vue" 
  width="100" 
  height="100" 
  align="right"
  loading="lazy">

<MyComponent foo="a" bar="b" baz="c"/>

//bad
<img src="https://vuejs.org/images/logo.png" alt="Vue Logo" title="vue" width="100" 
height="100" align="right" loading="lazy">
<MyComponent 
  foo="a" 
  bar="b" 
  baz="c"
/>

2.1.7 模板中简单的表达式

组件模板应该只包含简单的表达式,复杂的表达式则应该重构为计算属性或方法。

复杂表达式会让你的模板变得不那么声明式。我们应该尽量描述应该出现的是什么,而非如何计算那个值。而且计算属性和方法使得代码可以重用。

// good
// 在模板中
{{ normalizedFullName }}

// 复杂表达式已经移入一个计算属性
const normalizedFullName = computed(() => {
  return fullName.split(' ').map(function (word) {
      return word[0].toUpperCase() + word.slice(1)
    }).join(' ')
})

// bad
{{
  fullName.split(' ').map((word) => {
    return word[0].toUpperCase() + word.slice(1)
  }).join(' ')
}}

2.1.8 指令缩写

  • : 表示 v-bind:
  • @ 表示 v-on:
  • # 表示 v-slot:
<input
  :value="newTodoText"
  :placeholder="newTodoInstructions">

<input
  @input="onInput"
  @focus="onFocus">

<template #header>
  <h1>Here might be a page title</h1>
</template>

<template #footer>
  <p>Here's some contact info</p>
</template>

<Child #default="params">
  ...
</Child>

2.2 HTML

2.2.1 文件模板

HTML5 文件模板:

<!DOCTYPE html>
  <html lang="zh-CN">
  <head>
    <meta charset="UTF-8">
    <title>HTML5标准模版</title>
  </head>
  <body>
  </body>
</html>

移动端:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
        content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, shrink-to-fit=no">
    <meta name="format-detection" content="telephone=no">
    <title>移动端HTML模版</title>

    <!-- S DNS预解析 -->
    <link rel="dns-prefetch" href="">
    <!-- E DNS预解析 -->

    <!-- S 线上样式页面片,开发请直接取消注释引用 -->
    <!-- #include virtual="" -->
    <!-- E 线上样式页面片 -->

    <!-- S 本地调试,根据开发模式选择调试方式,请开发删除 -->
    <link rel="stylesheet" href="css/index.css">
    <!-- /本地调试方式 -->

    <link rel="stylesheet" href="http://srcPath/index.css">
    <!-- /开发机调试方式 -->
    <!-- E 本地调试 -->

</head>
<body>
</body>
</html>

PC 端:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="keywords" content="your keywords">
    <meta name="description" content="your description">
    <meta name="author" content="author,email address">
    <meta name="robots" content="index,follow">
    <meta http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1">
    <meta name="renderer" content="ie-stand">
    <title>PC端HTML模版</title>

    <!-- S DNS预解析 -->
    <link rel="dns-prefetch" href="">
    <!-- E DNS预解析 -->

    <!-- S 线上样式页面片,开发请直接取消注释引用 -->
    <!-- #include virtual="" -->
    <!-- E 线上样式页面片 -->

    <!-- S 本地调试,根据开发模式选择调试方式,请开发删除 -->
    <link rel="stylesheet" href="css/index.css">
    <!-- /本地调试方式 -->

    <link rel="stylesheet" href="http://srcPath/index.css">
    <!-- /开发机调试方式 -->
    <!-- E 本地调试 -->
</head>
<body>
</body>
</html>

2.2.2 元素及标签闭合

HTML 元素共有以下5种:

  • 空元素:area、base、br、col、command、embed、hr、img、input、keygen、link、meta、param、source、track、wbr
  • 原始文本元素:script、style
  • RCDATA 元素:textarea、title
  • 外来元素:来自 MathML 命名空间和 SVG 命名空间的元素
  • 常规元素:其他 HTML 允许的元素都称为常规元素

为了能让浏览器更好的解析代码以及能让代码具有更好的可读性,有如下约定:

  • 所有具有开始标签和结束标签的元素都要写上起止标签,某些允许省略开始标签或和束标签的元素亦都要写上。
  • 空元素标签都不加 / 字符。
<!-- good -->
<div>
    <h1>我是h1标题</h1>
    <p>我是一段文字,我有始有终,浏览器能正确解析</p>
</div>
 
<br data-tomark-pass>


<!-- bad -->
<div>
    <h1>我是h1标题</h1>
    <p>我是一段文字,我有始无终,浏览器亦能正确解析
</div>

<br/>

2.2.3 代码嵌套

元素嵌套规范,每个块状元素独立一行,内联元素可选。

<!-- good -->
<div>
    <h1></h1>
    <p></p>
</div> 
<p><span></span><span></span></p>

<!-- bad -->
<div>
    <h1></h1><p></p>
</div> 
<p> 
    <span></span>
    <span></span>
</p>

段落元素与标题元素只能嵌套内联元素。

<!-- good -->
<h1><span></span></h1>
<p><span></span><span></span></p>

<!-- bad -->
<h1><div></div></h1>
<p><div></div><div></div></p>

2.3 CSS

2.3.1 样式文件

样式文件必须写上 @charset 规则,并且一定要在样式文件的第一行首个字符位置开始写,编码名用 “UTF-8”

Vite构建工具的编码设置通常是基于项目的配置文件和构建过程中的默认设置。在大多数情况下,Vite默认使用UTF-8编码,这是现代Web开发中广泛使用的字符编码标准

  • 推荐:
@charset "UTF-8";
.demo {}
  • 不推荐:
/* @charset规则不在文件首行首个字符开始 */
@charset "UTF-8";
.demo {}

/* @charset规则没有用小写 */
@CHARSET "UTF-8";
.demo {}

/* 无@charset规则 */
.demo {}

2.3.2 代码格式化

样式书写一般有两种:一种是紧凑格式 (Compact),一种是展开格式(Expanded)。

  • 推荐:展开格式(Expanded
.demo {
  display: block;
  width: 50px;
}
  • 不推荐:紧凑格式 (Compact
.demo { display: block; width: 50px;}

2.3.3 代码大小写

样式选择器,属性名,属性值关键字全部使用小写字母书写,属性字符串允许使用大小写。

  • 推荐:
.demo {
  display: block;
}
  • 不推荐:
.demo {
  DISPLAY: BLOCK;
}

2.3.4 代码易读性

  1. 左括号与类名之间一个空格,冒号与属性值之间一个空格。
  • 推荐:
.demo {
  width: 100%;
}
  • 不推荐:
.demo{
  width:100%;
}
  1. 逗号分隔的取值,逗号之后一个空格。
  • 推荐:
.demo {
  box-shadow: 1px 1px 1px #333, 2px 2px 2px #ccc;
}
  • 不推荐:
.demo {
  box-shadow: 1px 1px 1px #333,2px 2px 2px #ccc;
}
  1. 为单个 CSS 选择器或新声明开启新行。
  • 推荐:
.demo, .demo_logo, .demo_hd {
  color: #ff0;
}

.nav{
  color: #fff;
}
  • 不推荐:
.demo, .demo_logo, .demo_hd {
  color: #ff0;
}.nav{
  color: #fff;
}
  1. 颜色值 rgb() rgba() hsl() hsla() rect() 中不需有空格,且取值不要带有不必要的 0
  • 推荐:
.demo {
  color: rgba(255,255,255,.5);
}
  • 不推荐:
.demo {
  color: rgba( 255, 255, 255, 0.5 );
}
  1. 属性值十六进制数值不推荐使用简写。
  • 推荐:
.demo {
  color: #ffffff;
}
  • 不推荐:
.demo {
  color: #fff;
}
  1. 不要为 0 指明单位。
  • 推荐:
.demo {
  margin: 0 10px;
}
  • 不推荐:
.demo {
  margin: 0px 10px;
}

2.3.5 属性值引号

CSS 属性值需要用到引号时,统一使用双引号。

  • 推荐:
.demo {
  font-family: "Hiragino Sans GB";
}
  • 不推荐:
.demo {
  font-family: 'Hiragino Sans GB';
}

2.3.6 属性书写顺序建议

样式建议遵循以下顺序:

  1. 位置属性: position、top、right、z-index
  2. 显示和框模型 box-sizing、display、float、width、height、margin、padding
  3. 字体,行间距,颜色,文本 font、line-height、color、text-align
  4. 光标属性 cursor
  5. 背景和边框 background、border、border-radius、box-shadow
  6. 其他一些视觉或动画属性 opacity、transition、transform、animation
.demo {
  position,
  top,
  right,
  bottom,
  left,
  z-index,
  box-sizing,
  display,
  flex,
  flex-align,
  flex-basis,
  flex-direction,
  flex-wrap,
  flex-flow,
  flex-shrink,
  flex-grow,
  flex-order,
  flex-pack,
  align-content,
  align-items,
  align-self,
  justify-content,
  order,
  float,
  width,
  min-width,
  max-width,
  height,
  min-height,
  max-height,
  padding,
  padding-top,
  padding-right,
  padding-bottom,
  padding-left,
  margin,
  margin-top,
  margin-right,
  margin-bottom,
  margin-left,
  overflow,
  overflow-x,
  overflow-y,
  -webkit-overflow-scrolling,
  -ms-overflow-x,
  -ms-overflow-y,
  -ms-overflow-style,
  columns,
  column-count,
  column-fill,
  column-gap,
  column-rule,
  column-rule-width,
  column-rule-style,
  column-rule-color,
  column-span,
  column-width,
  orphans,
  widows,
  clip,
  clear,
  font,
  font-family,
  font-size,
  font-style,
  font-weight,
  font-variant,
  font-size-adjust,
  font-stretch,
  font-effect,
  font-emphasize,
  font-emphasize-position,
  font-emphasize-style,
  font-smooth,
  src,
  hyphens,
  line-height,
  color,
  text-align,
  text-align-last,
  text-emphasis,
  text-emphasis-color,
  text-emphasis-style,
  text-emphasis-position,
  text-decoration,
  text-indent,
  text-justify,
  text-outline,
  -ms-text-overflow,
  text-overflow,
  text-overflow-ellipsis,
  text-overflow-mode,
  text-shadow,
  text-transform,
  text-wrap,
  -webkit-text-size-adjust,
  -ms-text-size-adjust,
  letter-spacing,
  -ms-word-break,
  word-break,
  word-spacing,
  -ms-word-wrap,
  word-wrap,
  overflow-wrap,
  tab-size,
  white-space,
  vertical-align,
  direction,
  unicode-bidi,
  list-style,
  list-style-position,
  list-style-type,
  list-style-image,
  pointer-events,
  -ms-touch-action,
  touch-action,
  cursor,
  visibility,
  zoom,
  table-layout,
  empty-cells,
  caption-side,
  border-spacing,
  border-collapse,
  content,
  quotes,
  counter-reset,
  counter-increment,
  resize,
  user-select,
  nav-index,
  nav-up,
  nav-right,
  nav-down,
  nav-left,
  background,
  background-color,
  background-image,
  filter,
  background-repeat,
  background-attachment,
  background-position,
  background-position-x,
  background-position-y,
  background-clip,
  background-origin,
  background-size,
  border,
  border-color,
  border-style,
  border-width,
  border-top,
  border-top-color,
  border-top-style,
  border-top-width,
  border-right,
  border-right-color,
  border-right-style,
  border-right-width,
  border-bottom,
  border-bottom-color,
  border-bottom-style,
  border-bottom-width,
  border-left,
  border-left-color,
  border-left-style,
  border-left-width,
  border-radius,
  border-top-left-radius,
  border-top-right-radius,
  border-bottom-right-radius,
  border-bottom-left-radius,
  border-image,
  border-image-source,
  border-image-slice,
  border-image-width,
  border-image-outset,
  border-image-repeat,
  outline,
  outline-width,
  outline-style,
  outline-color,
  outline-offset,
  box-shadow,
  opacity,
  -ms-interpolation-mode,
  page-break-after,
  page-break-before,
  page-break-inside,
  transition,
  transition-delay,
  transition-timing-function,
  transition-duration,
  transition-property,
  transform,
  transform-origin,
  perspective,
  appearance,
  animation,
  animation-name,
  animation-duration,
  animation-play-state,
  animation-timing-function,
  animation-delay,
  animation-iteration-count,
  animation-direction,
  animation-fill-mode,
  fill,
  stroke
}

3.3.7 CSS3 浏览器私有前缀

CSS3 浏览器私有前缀在前,标准前缀在后。

.demo {
  -webkit-border-color: #ffffff;
  -moz-border-color: #ffffff;
  -o-border-color: #ffffff;
  -ms-border-color: #ffffff;
  border-color: #ffffff;
}

3.3.8 scss、lesscss预处理语言

  1. 代码组织

    1. @import
    2. 变量声明
    3. 样式声明
    @import "mixins/size.less"; 
    @default-text-color: #ffffff; 
    .page { 
      width: 960px; 
      margin: 0 auto;
    }
    
  2. 避免嵌套层级过多

    将嵌套深度限制在3级。对于超过4级的嵌套,给予重新评估。这可以避免出现过于详实的CSS选择器。避免大量的嵌套规则。当可读性受到影响时,将之打断。推荐避免出现多于20行的嵌套规则出现

  • 推荐
.main-title{   
  .demo{      
    ...
  }
}

.demo{
  .title{    
    .name{       
      color:#fff
    }  
  }
} 
  • 不推荐
.main{ 
  .demo{
    .title{    
      .name{       
        color:#fff
      }  
    }
  } 
}

2.4 JavaScript

2.4.1 单行代码块

在单行代码块中使用空格。

  • 推荐:
const a = () => true
if (condition) return;
  • 不推荐:
function foo () {return true}
if (foo) {bar = 0}

2.4.2 大括号风格

在编程过程中,大括号风格与缩进风格紧密联系,用来描述大括号相对代码块位置的方法有很多。在 JavaScript 中,主要有三种风格,如下:

  • 【推荐】One True Brace Style
if (foo) {
  bar()
} else {
  baz()
}
  • Stroustrup
if (foo) {
  bar()
}
else {
  baz()
}
  • Allman
if (foo)
{
  bar()
}
else
{
  baz()
}

下列关键字后必须有大括号:if, else, for, while, do, switch, try, catch, finally, with。,若是单行代码块,则不写大括号。

  • 推荐:
const condition = Math.random() > 0.5;
if (condition) {
  return;
}
或者
if (condition) return;
  • 不推荐:
if (condition) { return };

2.4.3 代码中的空格

  1. 逗号前后的空格可以提高代码的可读性,团队约定在逗号后面使用空格,逗号前面不加空格。
  • 推荐:
let foo = 1, bar = 2
  • 不推荐:
let foo = 1,bar = 2

let foo = 1 , bar = 2

let foo = 1 ,bar = 2
  1. 对象字面量的键和值之间不能存在空格,且要求对象字面量的冒号和值之间存在一个空格。
  • 推荐:
let obj = { "foo": "haha" }
  • 不推荐:
let obj = { "foo" : "haha" }
  1. 代码块前要添加空格。
  • 推荐:
if (a) {
  b()
}

function a () {}
  • 不推荐:
if (a){
  b()
}

function a (){}
  1. 函数声明括号前要加空格。
  • 推荐:
function func (x) {
  // ...
}
  • 不推荐:
function func(x) {
  // ...
}
  1. 在函数调用时,禁止使用空格。
  • 推荐:
func()
  • 不推荐:
func ()

func
()
  1. 在操作符前后都需要添加空格。
  • 推荐:
let sum = 1 + 2
  • 不推荐:
let sum = 1+2

2.4.4 undefined 判断

永远不要直接使用 undefined 进行变量判断;使用 typeof 和字符串'undefined'对变量进行判断。

  • 推荐:
if (typeof person === 'undefined') {  
  ...
}
  • 不推荐:
if (person === undefined) {
  ...
}

2.4.5 函数参数不能超过 3 个

超过 3 个的情况使用对象的方式进行传递。

  • 推荐
const foo = args => {
    const { a, b, c, d } = args;
    console.log(a, b, c, d);
};
  • 不推荐
const foo = (a, b, c, d) => {
    console.log(a, b, c, d);
};

2.4.6 注册事件与卸载事件的代码必须同时存在

  • 推荐
<script>
onMounted(() => {
 window.addEventListener('resize', resizeHandler);
});
onUnmounted(() => {
  window.removeEventListener("resize", resize);
});
</script>

2.4.7 条件判断和循环最多三层

条件判断能使用三目运算符和逻辑运算符解决的,就不要使用条件判断,但是谨记不要写太长的三目运算符。如果超过 3 层请抽成函数,并写清楚注释。

2.4.8 避免无意义的条件判断

优先使用 ||&& 运算符。

  • 推荐
function createMicrobrewery(name) {
    let breweryName = name || 'Hipster Brew Co.';
}
  • 不推荐
function createMicrobrewery(name) {
    let breweryName;
    if (name) {
        breweryName = name;
    } else {
        breweryName = 'Hipster Brew Co.';
    }
}

2.4.9 使用模板字符串

避免直接拼接字符串带来的代码可读性降低、易出现 bug 等问题。

  • 推荐
const date = '2011-01-01';
const time = '12:00';

const fullTime = `${date} ${time}`;
  • 不推荐
const fullTime = date + ' ' + time;

三、代码注释

注释的目的:

  • 提高代码的可读性,从而提高代码的可维护性

注释的原则:

  • 如无必要,勿增注释 ( As short as possible )
  • 如有必要,尽量详尽 ( As long as necessary )

3.1 HTML 文件注释

3.1.1 单行注释

一般用于简单的描述,如某些状态描述、属性描述等。
注释内容前后各一个空格字符,注释位于要注释代码的上面,单独占一行。

  • 推荐:
<!-- Comment Text -->
<div>...</div>
  • 不推荐
<div>...</div><!-- Comment Text -->

<div><!-- Comment Text -->
  ...
</div>

3.1.2 模块注释

一般用于描述模块的名称以及模块开始与结束的位置。

注释内容前后各一个空格字符, <!-- S Comment Text -->表示模块开始, <!-- E Comment Text -->表示模块结束,模块与模块之间相隔一行。

  • 推荐:
<!-- S Comment Text A --> 
<div class="mod_a">
  ...
</div>
<!-- E Comment Text A -->
 
<!-- S Comment Text B --> 
<div class="mod_b">
  ...
</div>
<!-- E Comment Text B -->
  • 不推荐
<!-- S Comment Text A -->
<div class="mod_a">
  ...
</div>
<!-- E Comment Text A -->
<!-- S Comment Text B --> 
<div class="mod_b">
  ...
</div>
<!-- E Comment Text B -->

3.1.3 嵌套模块注释

当模块注释内再出现模块注释的时候,为了突出主要模块,嵌套模块不再使用。

<!-- S Comment Text -->
<!-- E Comment Text -->

而改用

<!-- /Comment Text -->

注释写在模块结尾标签底部,单独一行。

<!-- S Comment Text A -->
<div class="mod_a">
  
    <div class="mod_b">
        ...
    </div>
    <!-- /mod_b -->
     
    <div class="mod_c">
     ...
    </div>
    <!-- /mod_c -->
  
</div>
<!-- E Comment Text A -->

3.2 CSS 文件注释

3.2.1 单行注释

注释内容第一个字符和最后一个字符都是一个空格字符,单独占一行,行与行之间相隔一行。

  • 推荐:
/* Comment Text */ 
.demo {} 

/* Comment Text */ 
.demo {}
  • 不推荐:
/*Comment Text*/
.demo {
  display: block;
}

.demo {
  display: block;/*Comment Text*/
}

3.2.2 模块注释

注释内容第一个字符和最后一个字符都是一个空格字符,/* 与 模块信息描述占一行,多个横线分隔符 -*/ 占一行,行与行之间相隔两行。

  • 推荐:
/* Module A
---------------------------------------------------------------- */
.mod_a {}


/* Module B
---------------------------------------------------------------- */
.mod_b {}
  • 不推荐:
/* Module A ---------------------------------------------------- */
.mod_a {}
/* Module B ---------------------------------------------------- */
.mod_b {}

3.2.3 文件注释

在样式文件编码声明 @charset 语句下面注明页面名称、作者、创建日期等信息。

@charset "UTF-8";
/**
 * @description File Info
 * @author Author Name
 * @date 2015-10-10
 */

3.3 JavaScript 文件注释

3.3.1 单行注释

单行注释使用 //,注释应单独一行写在被注释对象的上方,不要追加在某条语句的后面。

  • 推荐:
// is current tab
const active = true
  • 不推荐:
const active = true // is current tab

注释行的上方需要有一个空行(除非注释行上方是一个块的顶部),以增加可读性。

  • 推荐:
function getType () {  
  console.log('fetching type...')
  
  // set the default type to 'no type'
  const type = this.type || 'no type'
  return type
}
// 注释行上面是一个块的顶部时不需要空行
function getType () {  
  // set the default type to 'no type'
  const type = this.type || 'no type'   
  return type
}
  • 不推荐:
function getType () {  
  console.log('fetching type...')
  // set the default type to 'no type'
  const type = this.type || 'no type'
  return type
}

3.3.2 多行注释

多行注释使用 /** ... */,而不是多行的 //

  • 推荐:
/**
 * @name make() returns a new element
 * @description based on the passed-in tag name
 * @param { Boolean } tag tag name
 * @returns { String } element name
 */
function make (tag) {
  // ...

  return element
}
  • 不推荐:
// make() returns a new element
// based on the passed in tag name
function make (tag) {
  // ...

  return element
}

3.3.3 注释空格

注释内容和注释符之间需要有一个空格,以增加可读性。eslint: spaced-comment。

  • 推荐:
// is current tab
const active = true

/**
 * @name make() returns a new element
 * @description based on the passed-in tag name
 * @param { Boolean } tag tag name
 * @returns { String } element name
 */
function make(tag) {  
  // ...

  return element
}
  • 不推荐:
//is current tab
const active = true

/**
 *make() returns a new element
 *based on the passed-in tag name
 */
function make(tag) {  
  // ...

  return element
}

3.3.4 特殊标记

有时我们发现某个可能的 bug,但因为一些原因还没法修复;或者某个地方还有一些待完成的功能,这时我们需要使用相应的特殊标记注释来告知未来的自己或合作者。常用的特殊标记有两种:

  • // FIXME : 说明问题是什么

  • // TODO : 说明还要做什么或者问题的解决方案

class Calculator extends Abacus {
  constructor () {
    super ()

      // FIXME: shouldn’t use a global here
      total = 0

      // TODO: total should be configurable by an options param
      this.total = 0
  }
}

3.3.5 文档类注释

文档类注释,如函数、类、文件、事件等;尽量使用 jsdoc 规范。

/**
 * Book类,代表一个书本.
 * @constructor
 * @param {string} title - 书本的标题.
 * @param {string} author - 书本的作者.
 */
function Book (title, author) {
  this.title = title
  this.author = author
}

Book.prototype = {
  /**
   * 获取书本的标题
   * @returns {string|*}
   */
  getTitle: function () {
    return this.title
  },

  /**
   * 设置书本的页数
   * @param pageNum {number} 页数
   */
  setPageNum: function (pageNum) {
    this.pageNum=pageNum
  }
}

四、编码风格

4.1 禁止使用var定义变量,使用let定义变量,const定义常量
  • 推荐
const bar = 1;
  • 不推荐
var a = 1;
bar = 1;
4.2 缩进换行请使用两个空格
4.3 结尾使用分号
const sortTable = () => {
  console.log(newIndex, oldIndex);
  console.log(proTable.value?.tableData);
  ElMessage.success("修改成功");
};
4.4 使用双引号
<div class="loading-wrap"></div>

<script setup lang="ts">
const ruleForm = reactive({
  name: "",
  sex: "",
  grade: "",
  desc: ""
});
</script>
4.5 将 > 多行元素放在最后一行的末尾,而不是放在下一行
  • 推荐:
<MyComponent 
  title="vue" 
  width="100" 
  height="100" 
  loading="lazy">
  ...#slot
</MyComponent>
  • 不推荐:
<MyComponent 
  title="vue" 
  width="100" 
  height="100" 
  loading="lazy"
>
  ...#slot
</MyComponent>
4.6 箭头函数参数只有一个时省略括号
const handleClick = param => {
  ...
}

const getArrList = param => param * 2
4.7 代码块之间不允许多个空行
  • 推荐
const myArray = ref(['item1', 'item2', 'item3']) 

const address = reactive<Address>({
  province: "",
  city: "",
  district: "",
  address: "",
  lnglat: [114.525918, 38.032612],
});

const handleClick = () => {
  ...
}
  • 不推荐
const myArray = ref(['item1', 'item2', 'item3'])  
const address = reactive<Address>({
  ...
});


const handleClick = () => {
  ...
}
  • 若相邻两个同功能的表达式,第一个表达式一行内完成,与第二个表达式中间可不需要空格
// 推荐
const treeRef = ref<InstanceType<typeof ElTree>>();
const treeData = ref<{ [key: string]: any }[]>([]);
const treeAllData = ref<{ [key: string]: any }[]>([]);

// 栅格间隔与样式
const gutter = 10;
const gutterStyle = computed(() => {
  return {
    width: `calc(100% + ${gutter}px)`,
    "margin-bottom": `${gutter}px`
  };
});

// 亦可
const amapFlag = ref<boolean>(false);

const handleAmapChange = () => {
  amapFlag.value = true;
};

defineExpose({
  iMap
});
4.8 禁止定义未使用的变量和空函数
const unused = 1;
const empty = () => {};
4.9 禁止使用 @ts-ignore
  • 不推荐
// @ts-ignore: Object is possibly 'null'.  
const value = possiblyNullValue.someProperty;

五、GIT提交规范

  • commit 规范:(类型和描述之间加空格

    1. 提交类型 + 描述
    feat: 新功能
    
    1. 提交类型 + 图标 + 描述
    feat: 🚀 新功能
    
  • types: 提交类型

  { value: "feat", name: "特性:   🚀  新增功能", emoji: "🚀" },
  { value: "fix", name: "修复:   🧩  修复缺陷", emoji: "🧩" },
  { value: "docs", name: "文档:   📚  文档变更", emoji: "📚" },
  { value: "style", name: "格式:   🎨  代码格式(不影响功能,例如空格、分号等格式修正)", emoji: "🎨" },
  { value: "refactor", name: "重构:   ♻️  代码重构(不包括 bug 修复、功能新增)", emoji: "♻️" },
  { value: "perf", name: "性能:    ⚡️  性能优化", emoji: "⚡️" },
  { value: "test", name: "测试:   ✅  添加疏漏测试或已有测试改动", emoji: "✅" },
  { value: "build", name: "构建:   📦️  构建流程、外部依赖变更(如升级 npm 包、修改 webpack 配置等)", emoji: "📦️" },
  { value: "ci", name: "集成:   🎡  修改 CI 配置、脚本", emoji: "🎡" },
  { value: "revert", name: "回退:   ⏪️  回滚 commit", emoji: "⏪️" },
  { value: "chore", name: "其他:   🔨  对构建过程或辅助工具和库的更改(不影响源文件、测试用例)", emoji: "🔨" },
  { value: "wip", name: "开发:   🕔  正在开发中", emoji: "🕔" },
  { value: "workflow", name: "工作流:   📋  工作流程改进", emoji: "📋" },
  { value: "types", name: "类型:   🔰  类型定义文件修改", emoji: "🔰" }

六、 写在最后

规范的目的在于确保一致性、减少错误、提高工作效率,让大家能编写出高质量的代码,更好促进沟通和协作。

Web前端开发中,编写规范代码是非常重要的,可以提高代码的可读性和维护性。对于Vue3项目的代码规范,可以参考以下步骤: 1. 配置ESLint:ESLint是一个用于检测和报告JavaScript代码错误的工具。在Vue3项目中,可以通过在项目根目录下的.eslintrc.js文件中进行配置。其中,extends后面的"@vue/standard"表示使用Vue官方提供的标准方式进行代码检测。 2. 安装相关插件:在Vue3项目中,可以使用一些lint插件来辅助编写规范代码。例如,可以使用"plugin:vue/vue3-essential"来对Vue3模板编写进行检测。这些插件可以在.eslintrc.js文件中配置。 3. 遵循Vue官方指南Vue官方提供了一份详细的代码规范指南,建议开发者遵循这些指南来编写Vue3项目的前端代码。该指南包含了Vue3的最佳实践、命名规范、组件设计等方面的内容,可以帮助开发者编写更规范代码。 总结:对于Vue3项目的前端代码规范,可以通过配置ESLint,安装相关插件,并遵循Vue官方指南来实现。这样可以确保代码的质量和可读性,提高开发效率。请参考以上步骤进行操作。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [前后端分离vue.js+spring boot 计算机软件工程课程设计毕业设计 前端 后端](https://download.csdn.net/download/Amzmks/88275824)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *2* *3* [前端实战项目:前端框架Vue3.0项目教程(一)Vue3.0环境的搭建](https://blog.csdn.net/play_big_knife/article/details/119912738)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值