【团队建设】前端编码规范


目录


概览

前端技术的迭代速度较快,相关的框架和知识点也越来越多,在开发新项目时可供选择的技术栈也越来越多。对于需要多人团队写作的场景下,如何保证大家共同的编码规范,用一套约定俗成的标准进行开发至关重要,可以更好的提升自己的开发体验。

接下来,主要讲解不同前端领域的编码规范以及格式化代码,本质上底层还是前端三件套的编程思想。
核心:语义化、代码清晰明确、注释完善,代码配置 < 沟通 < 约定 < 文档 < 体系

语雀原文档:https://www.yuque.com/xixifusi-eicch/ma8xgr/skhybs2u0glzs5p5?singleDoc# 《编码规范》

资源

推荐几款代码规范文档库,建议收藏! - 掘金
TGideas文档库
Google Style Guides

环境

  1. VUE 新项目推荐开发环境:Vue 3.4.1 + Node 20.11.1 + Pnpm 9.6 + Vite 5.2 + TypeScript 5.2.2
  2. Node 新项目推荐开发环境:Node 20.11.1 + TypeScript 5.2.2

目录

我们平时开发新项目的时候,往往会利用第三方工具进行生成基础的项目模板,这些文件目录代表的意思如下:

名称 含义
.vscode vscode 调试程序、样式等本地化配置
public 公共文件夹,存放图标、全局配置
api 接口文件夹
assets 静态资源存放,包含字体图标、图片等
components 抽离的全局公共组件(页面逻辑过多,可分为一级、二级、三级)
config 相关的配置文件,一般是静态相关的
hooks 具有响应式数据特性/生命周期的方法抽离
layout 页面的布局组件,包含侧边栏、头部、底部、内容区,单页面应用较为常见
router 路由组件
script 脚本执行文件夹,存放常用的自动化脚本
store 当前页面的数据存储状态(可选是否持久化)
style 页面的样式,会包含 重置样式、组件样式、自定义样式 等
utils 工具函数,包含正则、http请求、工具函数等
views 项目的视图页面,具体可拆分
types TS 项目专有,包含 自动引入、路由声明等 .d.ts 文件
.etc 一些组件配置,包含 vite 环境、eslint、prettier 样式、.gitignore 等

上述标红的文件夹会有多个文件,一般的开发规范是统一通过 index.ts 暴露收口,在 modules 进行编写不同模块的代码逻辑,下面是针对大型项目一些补充说明:

  1. views 满足上面的命名规则外,页面入口可采用 index 进行命名
  2. 静态资源(图片、svg等)使用 下划线 '_' ,逻辑文件 JS/TS 使用 小驼峰命名(camelCase)
  3. 所有业务逻辑文件夹(包含 css 文件名 简单 vue 文件可以采用大驼峰命名)统一采用 **短横线 '-' **进行连接,原则上不超过 3 个单词

工具

前端工具的完善,使得部分样式都给我们规范好了,极大的提高了个人开发者的便利性,但是这种工具的限制链路不是越多越好,尽量把常用的挑出来进行开发,这里可以参考我之前的文章。
前端项目工程化之代码规范_扁平化 代码开发规范-CSDN博客

⭐pnpm 9

新项目开发统一采用 pnpm ( node 环境 18+ )安装包,并设置淘宝镜像源。

javascript 复制代码
// 1. 安装 pnpm 
npm install -g pnpm

// 2. 设置淘宝镜像源
pnpm config set registry https://registry.npmmirror.com/

// 3. 在项目中使用 pnpm 安装依赖包
pnpm install 

⭐ESLint 8

ESLint 是一个静态代码分析工具,主要用于查找和修复代码中的潜在问题、错误、不一致和不推荐的模式。是帮助你提高代码质量、避免常见的错误,以及确保团队成员遵循统一的编码约定。

这里需要根据不同的项目框架进行具体的配置,如:VUE React TS,这里网上有很多参考资料进行参考

javascript 复制代码
// 1. 安装 eslint 和 vue 插件
pnpm i eslint@8 eslint-plugin-vue -D

// 2. 安装 prettier 和 相关插件
pnpm i prettier@3 eslint-config-prettier eslint-plugin-prettier -D

// 3. TS 解析器安装
pnpm i @typescript-eslint/parser -D

// 4. TS 
pnpm i @typescript-eslint/eslint-plugin -D
javascript 复制代码
module.exports = {
  root: true,
  parser: 'vue-eslint-parser',

  // 环境设置
  env: {
    es6: true,
    node: true,
    browser: true
  },

  // 解析选项
  parserOptions: {
    sourceType: 'module',
    ecmaVersion: 'latest',

    // TS 解析器配置
    parser: '@typescript-eslint/parser'
  },

  // 插件
  plugins: ['vue'],

  // 引入的语法校验规则
  extends: [
    'plugin:vue/vue3-recommended',
    'plugin:@typescript-eslint/recommended',

    'prettier',
    'plugin:prettier/recommended' // eslint-plugin-prettier 规则放到最后
  ],

  // 自定义规则
  rules: {
    'no-console': 'off',
    'no-debugger': 'off',
    'no-unused-vars': 'warn',
    'prefer-rest-params': 'warn',

    'vue/html-indent': 'off',
    'vue/html-self-closing': 'off',
    'vue/max-attributes-per-line': 'off',
    'vue/multi-word-component-names': 'off',
    'vue/no-setup-props-destructure': 'warn',

    // TS 冲突规则
    '@typescript-eslint/no-unused-vars': 'off',

    // 强制组件在模板中使用 kebab-case
    'vue/component-name-in-template-casing': ['warn', 'kebab-case'],

    // 自闭和单标签元素
    'vue/html-self-closing': [
      'warn',
      {
        html: {
          void: 'always',
          normal: 'always',
          component: 'always'
        },
        svg: 'always',
        math: 'always'
      }
    ]
  },

  // 配置忽略文件
  ignorePatterns: ['node_modules/', 'dist/', 'public/', 'pnpm-lock.yaml']
}

⭐Prettier 3

Prettier是一个代码格式化工具,专注于对代码进行格式化,使其符合一致的风格规范。它会自动调整代码的缩进、换行、引号等,确保代码在不同的编辑器和环境中具有一致的外观。

简单来说,ESLint更注重你的代码是否符合规范,Pretter则是为你提供了按照规范格式化代码的能力。

json 复制代码
{
  "tabWidth": 2,
  "semi": false,
  "printWidth": 90,
  "endOfLine": "auto",
  "singleQuote": true,
  "trailingComma": "none"
}

忽略格式化的文件本质上和 .gitignore 大差不差(可以忽略)

javascript 复制代码
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

node_modules
dist
dist-ssr
*.local

# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

# build report file
report.html

⭐Husky 9

https://juejin.cn/post/7355535964612100108
https://juejin.cn/post/7041768022284976165
这个主要是限制提交的 commit 信息规范即可,不能随意提交,对此没要求的可以不做。

javascript 复制代码
// 1. 安装钩子触发工具 husky
pnpm i husky@9 -D

// 2. 执行配置脚本
npx husky init

// 3. 安装 commitlint 规范提交信息
pnpm i @commitlint/cli @commitlint/config-conventional -D

// 4. 新建 commitlint.config.ts 校验文件

// 5. 新建 commit-msg husky 规范提示消息
#!/bin/sh

npx --no-install commitlint -e

// 6. husky 新建 pre-commit
pnpm run lint

// 7. 提交前 eslint 检查代码
具体参考 package.json 命令配置
javascript 复制代码
module.exports = {
  extends: ['@commitlint/config-conventional'],

  rules: {
    'type-enum': [
      2,
      'always',
      [
        'feat', // 新功能(feature)
        'bug', // 此项特别针对bug号,用于向测试反馈bug列表的bug修改情况
        'fix', // 修补bug
        'ui', // 更新 ui
        'docs', // 文档(documentation)
        'style', // 格式(不影响代码运行的变动)
        'perf', // 性能优化
        'release', // 发布
        'deploy', // 部署
        'refactor', // 重构(即不是新增功能,也不是修改bug的代码变动)
        'test', // 增加测试
        'chore', // 构建过程或辅助工具的变动
        'revert', // feat(pencil): add 'graphiteWidth' option (撤销之前的commit)
        'merge', // 合并分支, 例如: merge(前端页面): feature-xxxx修改线程地址
        'build' // 打包
      ]
    ],

    // subject 不做大小写限制
    'subject-case': [0]
  }
}

⭐vite 5 构建

javascript 复制代码
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import svgLoader from 'vite-svg-loader'
import { fileURLToPath, URL } from 'node:url'
import { visualizer } from 'rollup-plugin-visualizer'
import viteCompression from 'vite-plugin-compression'

// Element-Plus 按需引入
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'

export default defineConfig({
  base: process.env.VITE_PUBLIC_PATH,
  plugins: [
    vue(),
    svgLoader(),
    viteCompression(),
    // TODO 性能优化原则在出问题时候去做动作,不然效果不明显,没有成就感
    visualizer({
      title: '打包性能分析报告',
      filename: 'report.html'
    }),
    AutoImport({
      dts: 'types/auto-imports.d.ts',
      resolvers: [ElementPlusResolver()]
    }),
    Components({
      dts: 'types/components.d.ts',
      extensions: ['vue'],
      resolvers: [ElementPlusResolver()]
    })
  ],

  // 路径优化
  resolve: {
    alias: {
      '@': fileURLToPath(new URL('./src', import.meta.url)),
      '#': fileURLToPath(new URL('./types', import.meta.url))
    }
  },

  // 开发服务器配置
  server: {
    // 端口号
    port: 5202,
    // 本地跨域代理 https://cn.vitejs.dev/config/server-options.html#server-proxy
    proxy: {
      '/api': {
        target: 'http://www.xxxx.com',
        changeOrigin: true
      },
    },
    // 预热文件以提前转换和缓存结果,降低启动期间的初始页面加载时长并防止转换瀑布
    warmup: {
      clientFiles: ['./index.html', './src/{views,components}/*']
    }
  },

  // TODO 后台项目不用配置,方便排查问题
  esbuild: {
    drop: process.env.MODE === 'development' ? ['console', 'debugger'] : []
  }
})

⭐package.json

这里是全局 安装包与命令执行的入口文件,常用的配置如下所示:

javascript 复制代码
"scripts": {
  "format": "prettier --write src/",
  "lint": "eslint . --ext .vue,.js,.ts,.jsx,.tsx,.cjs,.mjs --fix --ignore-path .gitignore",
},

注释

https://segmentfault.com/a/1190000044789971

代码注释是软件开发中的重要组成部分,它帮助开发者理解代码的功能和目的,同时也是代码维护和团队协作的基础,一个清晰的注释规范能够提高代码的可读性和维护性

  1. 设计复杂业务逻辑的处理函数必须说明函数的含义
  2. 模块化封装的代码需要说明该模块的使用说明和功能介绍
  3. 团队成员的注释风格须保持一致性,以保证注释与代码的同步性
javascript 复制代码
/**
 * 斐波那契数列(用于复杂逻辑功能的说明)
 * @param {number} n 斐波那契数列的第n项
 * @returns {number} 返回第n项的值
 */

const fibonacciDP = (n) => {
  let fib = [0, 1]
  for (let i = 2; i <= n; i++) {
    fib[i] = fib[i - 1] + fib[i - 2]
  }
  return fib[n]
}

// 斐波那契传入的数字(描述字段、函数、配置...的简短说明)
const num = 10
console.log(fibonacciDP(10))

特殊注释标记如下:

javascript 复制代码
// TODO:标记尚未实现或需要进一步工作的代码部分,提醒开发者将来需要完成的功能
// FIXME:指出代码中已知的问题或错误,这些代码可能存在问题或不稳定,需要修复
// HACK:指示代码中的临时解决方案或不够优雅的实现,通常是为了快速解决问题,
// 但需要更好的长期解决方案
// NOTE:用于强调代码中重要的信息或解释,如复杂逻辑的解释或特定实现方式的原因
// OPTIMIZE:提示代码性能可以优化,虽然代码可能没有问题,但存在提升效率的空间
// REVIEW:表示代码需要进一步审查,以确保满足需求或寻找更好的实现方法
// DEPRECATED:表明代码已经过时,不推荐使用,可能在未来会被移除或替换
// NOCOMMIT:警告标记,表示代码不应该提交到版本控制系统,通常用于临时改动,如调试代码

GIT

虽然现在有很多的可视化工具能够帮助我们进行分支操作,这也很方便,从原有的命令行操作中解放出来,这里也可以大概了解一下相关操作。

常用命令

git rebase详解(图解+最简单示例,一次就懂)-CSDN博客
图解 Git 基本命令 merge 和 rebase - Michael翔 - 博客园
https://segmentfault.com/a/1190000044038056
介绍 - 《阮一峰 Git 教程》 - 书栈网 · BookStack
https://pure-admin.github.io/pure-admin-doc/pages/git/

javascript 复制代码
// 获取远程最新的分支代码
// 本地没影响,只是更新远程跟踪分支的列表
git remote update origin --prune

// 代码添加到缓存区
git add .

// 代码添加到 commit
git commit -m "feat: 提交信息"

// 创建分支
git checkout -b xxx

// git reset(移动 head 指针
// 参数 soft 不改变工作区和缓存区、默认 mixed 改变缓存区、hard 改变缓存区和工作区

// 修复错误提交 push,可指定Head,不然默认当前状态
git reset Head^ --hard
git push -f


// git revert(选择一个 commit 重新制作并提交,新加一个 commmit 多了一条记录)
git revert xxx 

// 后续合错了分支优先推荐 git reset

// git merge(新增/基准线更改)
// 本质是合并代码,主分支拉取分支后提前的直接转移指针,未提前的需要新增 提交信息结点


// git rebase(变基修改)
// 将目标元素 rebase 到当前分支上,注意拉齐水平线,当前分支代码在最新 commit 节点

类型说明

类型 描述
feat 新增开发分支提交 feature
fix 修复 bug
docs 仅仅是修改了文档,比如 readme、changelog 等
style 仅仅是修改了空格、格式缩进、逗号等等,不改变代码逻辑
refactor 代码重构,没有新功能或者修复 bug
perf 优化相关,比如提升性能、体验
test 测试用例,包括单元测试、集成测试等
chore 改变构建流程、或者增加依赖库、工具等
revert 回滚到上一个版本
  • vscode 推荐插件:git-commit-lint-vscode

上线流程

  1. 首先需要在 **Matrix (需求管理平台)**创建当前开发任务的描述信息
  2. 获取 Matrix Id ,拉取最新 master 分支的代码到本地并创建分支,如:dev_1803329addProduct_1
  3. 开发完成之后拉取最新的 release_qa 分支,并合并代码
  4. 发布到 qa 测试环境转交测试,没问题 合并到 **release **分支发布预发
  5. 预发测试没问题,代码合并到 master ,打上 tag ,并推送到远程 origin
  6. 利用公司的自动化部署平台 Jean 进行部署发布,发布完成注意监控异常

HTML

  1. DOCTYPE 声明一般默认,注意 编码** 'UTF-8'**,语言 'zh-CN'
  2. 使用语义化标签,空元素使用单个自闭和标签,其余不可省略
  3. HTML标签名、类名、标签属性和大部分属性值统一用小写
  4. 层级结构块级元素独立一行,内联元素视场景处理,内联元素不支持嵌套块级
  5. 不使用 < 和 > 等特殊元素,防止标签相关层级失效,浏览器解析错误

CSS

CSS 目前发展出了多个分支,包含:

  1. 原生 css ,也包含不同平台推出的语言(wxss、wxs)
  2. 预处理 css,对原生样式进行增强,最终转换为 css (sass、scss、less等)
  3. 组件框架,开箱即用的第三方组件库(Element、Antd)
  4. 原子化框架,响应式、高度集成的类库(Tailwind)

在开发大型项目过程中,上述样式多少会用到,对于具体的编码规范来说,总结如下:

  1. class 名称小写,id 主要表明特殊的 dom 元素,**短斜杠 '-' 分割,**不超过3个,子元素前面需跟随父元素
  2. 自定义 css 变量使用 -- 作为首部,在全局 :root 进行声明定义,具体的类别后面出一篇前端设计规范文章
  3. 原子类 css 注意类名的顺序、是否重复、抽离公共样式等,可以借助格式化插件或者第三方库实现
  4. 组件样式的重写首先根据官方文档进行修改,或者** :deep() 样式穿透 优先级 !important(少用)**修改

module

https://juejin.cn/post/6844904080955932680

前端的底层编码风格分为两种,一种是 CommonJS 、一种是 ES6 语法,这两种风格的区别如下:

  1. 首先推荐统一采用 **ES6 模块化 **语法作为首选编程范式
  2. 待补充...

JS

JS 是前端的灵魂所在,也是处理一切业务逻辑的入口,所以这部分的规范至关重要。

依赖导入

javascript 复制代码
import React from 'react'
import { useQuery } from 'react-query'

import { PropTypes } from 'prop-types'
import styled from 'styled-components/native'

import colors from '@/styles/colors'
import { fetchData } from '@/lib/api'
import ErrorImg from '@/assets/images/error.png'
import { RouteData, RouteOLG } from '@/types/route'
import MapVersionDropdown from '@/components/map-version-dropdown'

import {
  generateRandomColor,
  generateStreamPoint,
  generateStreamPolyline,
  isInputStreamValid,
  generateStreamLink,
} from '@/lib/utils'
  1. React 内置模块
  2. 外部引入的模块
  3. 自己编写的文件
    1. 样式文件
    2. 图片资源文件
    3. TS编写的类型定义文件
    4. 引入自定义的接口
    5. 自己编写的组件
  4. 一行引入多个模块导致分行的,可以放在最后

这里以 React 的项目工程举例,相关等级如上述说明,总体来说在不脱离大类的前提下,要保证长度阶梯性递增,符合开发人员的视觉规范。

变量命名

  1. 变量采用 **小驼峰 **命名,一般为名词居多,原则上不超过 3个单词
  2. 属性值为互斥关系的统一采用 is 开头,如:isOpenDialog
  3. 全局静态变量采用 大写单词命名 下划线分割,如:const TY_EBK_CONFIG
  4. 命名的单词尽量通俗易懂,原则上使用英文单词(可缩写),避免汉字拼音(特殊情况除外)
  5. 方法命名使用动词,描述事件逻辑,一般场景推荐使用 **箭头函数书写 ( ) => { } **,一般不受this影响,遵循函数时编程:https://juejin.cn/post/6844903936378273799
  6. 方法与数据相关的常用 get post 开头,与逻辑处理相关的常用 handle(最常用) update delete pre generate close 开头,条件处理/判断的常用 **isValidate judge isSatify condition **

策略模式

javascript 复制代码
// 为了解决3层以上的if/else、switch判断,可以使用对象的形式存储

const { role } = { role: "ADMIN" };

function AdminUser() {
  console.log("ADMIN");
}

function EmployeeUser() {
  console.log("EMPLOYEE");
}

function NormalUser() {
  console.log("NormalUser");
}
const components = {
  ADMIN: AdminUser,
  EMPLOYEE: EmployeeUser,
  USER: NormalUser,
};

const Component = components[role];
Component();

条件判断

javascript 复制代码
// 函数处理逻辑,可以提前结束,而不是包裹在正确的 {} 内部
if(!isAuto) return √ 

if(isAuto) {
  xxx
  xxx
  xxx
} x

// 单条件渲染
{ isAuto || 'default' }
{streamOLGList.length > 0 && <StreamResultList />}


// 函数执行判断,简单逻辑优先采用三元运算符
const { role } = user;

return role === ADMIN ? <AdminUser /> : <NormalUser />;

解构赋值

解构赋值 - JavaScript | MDN

javascript 复制代码
// 对象的解构赋值
const obj = { a: 1, b: 2 };
const { a, b } = obj;

// 数组的解构赋值
const x = [1, 2, 3, 4, 5];
const [y, z] = x;

模板字符串

javascript 复制代码
// 避免使用字符串 + 号 连接,会造成不必要的栈内存消耗

const userDetails = `${user.name}'s profession is ${user.proffession}`

return <div> {userDetails} </div>

TS

前言

https://juejin.cn/post/6872111128135073806

TS 本质上是为了规范编码,利用强类型进行预编译,提前检查编码的错误。但是由于需要限制字段的类型,占用一部分开发时间,对于功能来说增益不大,会压缩原本的工期,下面是适用的场景:

  • 工具类、基础建设的团队内部工具
  • 业务交互涉及少、前端展示多的网页
  • 团队要求规范严格、成员基础好、开发时间充足

文件声明

typescript 复制代码
// <reference types="vite/client" />

// 找不到模块"xxx.vue"或其相应的类型声明

// vite-env.d.ts 防止引入.vue 文件标红未找到模块
declare module '*.vue' {
  import { ComponentOptions } from 'vue'
  const componentOptions: ComponentOptions
  export default componentOptions
}

组件引入

Node

【超多代码、超多图解】Node.js一文全解析_node技术分析图示-CSDN博客

VUE

组件闭合

vue 复制代码
// 组件内部没有子节点,使用自闭合标签,提高了可读性
    
<follow-track
  v-model:currentTab="currentTab"
  :shop-id="shopId"
/>

Fragments 语法

javascript 复制代码
// 始终使用 Fragment 而不是 Div。
// 它可以保持代码整洁,并且也有利于性能,因为在虚拟 DOM 中创建的节点少了一个

return (
  <>
  <component-a />
  <component-b />
  <component-c />
  </>
);

React

组件命名

javascript 复制代码
//map-version-dropdown.tsx
export default function MapVersionDropdown(){}
  • 文件使用 - 连接,组件对应文件名称使用大驼峰命名

属性命名

javascript 复制代码
const [isTruckTpra, setIsTruckTpra] = useState<boolean>(true)
const [streamOLGList, setStreamOLGList] = useState<Stream['streamOLGList']>(
    [],
  )
  • 响应式属性使用useState命名,使用小驼峰命名,至少2个字符以上
  • 数据状态不影响UI变化的,使用一般数据即可,不用响应式数据

事件方法外部定义

javascript 复制代码
const submitData = () => dispatch(ACTION_TO_SEND_DATA);

return <button onClick={submitData}>This is a good example</button>;
  • 复杂的事件方法需要在语法外部定义
  • 简单的如状态变化可以在内部定义,尽量控制在一行以内

微信小程序

微信小程序设计指南 | 微信开放文档
【第二周】基础语法学习-CSDN博客

  1. 小程序设计突出重点,页面尽量简洁清晰
  2. 组件在单页面按需引入,尽量不用全局组件

Taro 跨端

工具函数

HTTP

**展示前端 **主要是获取数据和前台页面的交互,所以网络模块的封装至关重要,步骤如下:

  1. 封装 axios 构建 TyHttp
  2. 生成实例,构造通用请求工具函数
  3. 新建 api 文件夹、index.ts 作为主入口 modules 放置各模块的请求
  4. 其中 modules 文件小写命名,里面使用 类 收口,静态方法隔离不同请求

参考代码如下所示:

javascript 复制代码
import { router } from '@/router/'
import { ElMessage } from 'element-plus'
import { RequestMethods, TyHttpRequestConfig } from './types.d'
import { urlConfigResolver } from '@/utils/microAppConfigResolver'
import Axios, { type AxiosInstance, type AxiosRequestConfig } from 'axios'

const defaultConfig: AxiosRequestConfig = {
  // 请求超时时间
  timeout: 30000,
  headers: {
    Accept: 'application/json, text/plain, */*',
    'Content-Type': 'application/json',
    'X-Requested-With': 'XMLHttpRequest'
  },
  baseURL: urlConfigResolver()
}

class TyHttp {
  constructor() {
    this.httpInterceptorsRequest()
    this.httpInterceptorsResponse()
  }

  /** 默认控制台格式化输出数据 */
  private static formatData(method: string, url: string, data: any): void {
    console.log(`%c${method} %c${url}`, 'color: #00f', 'color: #f00', data)
  }

  /** 初始化配置对象 */
  private static initConfig: TyHttpRequestConfig = {
    beforeRequestCallback(config) {
      // console.log('beforeRequestCallback', config)
    },
    beforeResponseCallback(response) {
      const { code, message } = response.data
      // TyHttp.formatData(response.config.method!, response.config.url!, response.data)
      if (code == 200) {
        return response.data
      }
      if (code == 401) {
        localStorage.clear()
        // 强制重新登录
        if (window.__MICRO_APP_ENVIRONMENT__) {
          const baseRouter = window.microApp.router.getBaseAppRouter()
          baseRouter.push('/login')
        } else {
          router.push('/login')
        }
      }
      // 错误处理
      ElMessage.error({
        type: 'error',
        grouping: true,
        message: message
      })
    }
  }

  /** 保存当前Axios实例对象 */
  private static axiosInstance: AxiosInstance = Axios.create(defaultConfig)

  /** 设置请求拦截器 */
  private httpInterceptorsRequest(): void {
    TyHttp.axiosInstance.interceptors.request.use(
      async (config: TyHttpRequestConfig): Promise<any> => {
        // 支持单个接口传入请求拦截判断
        if (typeof config.beforeRequestCallback === 'function') {
          config.beforeRequestCallback(config)
          return config
        }
        // 支持全局请求拦截判断
        if (TyHttp.initConfig.beforeRequestCallback) {
          TyHttp.initConfig.beforeRequestCallback(config)
          return config
        }
      },
      (error) => {
        return Promise.reject(error)
      }
    )
  }

  /** 设置响应拦截器 */
  private httpInterceptorsResponse(): void {
    TyHttp.axiosInstance.interceptors.response.use(
      (response: any) => {
        // 支持单个接口传入请求拦截判断
        const $config = response.config
        if (typeof $config.beforeResponseCallback === 'function') {
          $config.beforeResponseCallback(response)
          return response.data
        }
        // 支持全局请求拦截判断
        if (TyHttp.initConfig.beforeResponseCallback) {
          return TyHttp.initConfig.beforeResponseCallback(response)
        }
        return response
      },
      (error) => {
        const { status } = error.response
        console.log(error, status)
        if (status == 401) {
          ElMessage.error('登录状态失效,请重新登录')
          localStorage.clear()
          // 强制重新登录
          if (window.__MICRO_APP_ENVIRONMENT__) {
            const baseRouter = window.microApp.router.getBaseAppRouter()
            baseRouter.push('/login')
          } else {
            console.log(router)
            router.push('/login')
          }
          throw new Error('登录状态失效,请重新登录')
        } else {
          ElMessage.error('网络失效')
          return Promise.reject(error)
        }
      }
    )
  }

  /** 通用请求工具函数 */
  public request<T>(
    method: RequestMethods,
    url: string,
    param?: AxiosRequestConfig,
    axiosConfig?: TyHttpRequestConfig
  ): Promise<T> {
    const config = {
      method,
      url,
      ...param,
      ...axiosConfig
    } as TyHttpRequestConfig

    // 单独处理自定义请求/响应回调
    return new Promise((resolve, reject) => {
      TyHttp.axiosInstance
        .request(config)
        .then((response: any) => {
          resolve(response)
        })
        .catch((error) => {
          reject(error)
        })
    })
  }

  /** 单独抽离的post工具函数 */
  public post<T, P>(
    url: string,
    params?: AxiosRequestConfig<T>,
    config?: TyHttpRequestConfig
  ): Promise<P> {
    return this.request<P>('post', url, params, config)
  }

  /** 单独抽离的get工具函数 */
  public get<T, P>(
    url: string,
    params?: AxiosRequestConfig<T>,
    config?: TyHttpRequestConfig
  ): Promise<P> {
    return this.request<P>('get', url, params, config)
  }
}

export const http = new TyHttp()
javascript 复制代码
import { http } from '@/utils/http'

class UserAccount {
  // 登录接口
  static async login(data) {
    return http.post('/xxx', { data })
  }

  // 登出接口
  static async logout(data) {
    return http.post('/xxx', { data })
  }

  // 获取信息
  static async getUserInfo() {
    return http.get('/xxx')
  }
}

export default UserAccount
javascript 复制代码
import UserAccount from './modules/user'
import GlobalConfig from './modules/config'

export { UserAccount , GlobalConfig }

防抖节流

javascript 复制代码
// 深拷贝
const deepClone = (target: any, map = new WeakMap()) => {
  if (typeof target !== 'object' || target === null) return target

  if (map.get(target)) return map.get(target)

  const constructor = target.constructor
  if (/^(Function|RegExp|Date|Map|Set)$/i.test(constructor.name))
    return new constructor(target)

  const cloneTarget = Array.isArray(target) ? [] : {}
  map.set(target, cloneTarget)

  for (const key of Object.keys(target)) {
    // @ts-ignore
    cloneTarget[key] = deepClone(target[key], map)
  }
  return cloneTarget
}

// 防抖
let timer: NodeJS.Timeout | null = null
function debounce(fn: any, delay: number, immediate: boolean) {
  return function () {
    // @ts-ignore
    const context = this
    const args = arguments

    if (timer) clearTimeout(timer)

    if (immediate) {
      const callNow = !timer
      timer = setTimeout(() => {
        timer = null
      }, delay)

      if (callNow) fn.apply(context, args)
    } else {
      timer = setTimeout(() => {
        fn.apply(context, args)
      }, delay)
    }
  }
}

// 节流
function throttle(fn: any, delay: number) {
  let preTime = 0

  return function () {
    // @ts-ignore
    const context = this
    const args = arguments

    const nowTime = +new Date()
    if (nowTime - preTime >= delay) {
      fn.apply(context, args)

      preTime = nowTime
    }
  }
}

export { deepClone, debounce, throttle }

正则匹配

javascript 复制代码
// 去除首尾空格
const trim = (str) => {
  return str.replace(/(^\s*)|(\s*$)/g, '')
}

// 匹配某个字符出现的个数
const matchCount = (str, char) => {
  // 需要转义的字符
  const specialChars = ['.', '*', '+', '?', '|', '(', ')', '[', ']', '{', '}', '\\']
  if (specialChars.includes(char)) {
    char = '\\' + char
  }
  return (str.match(new RegExp(char, 'g')) || []).length
}

// 移除输入字符串 value 中所有非数字和非点号的字符,点号最多只能出现一次
const removeNonNumeric = (value) => {
  if (value === '') {
    return ''
  }

  // 移除点开头的点号和0开头的数字
  let result = value.replace(/^\./, '').replace(/^0+/, '0')

  result = result.replace(/[^\d.]/g, '').replace(/(\d+\.\d*).*/, '$1')

  // 字符串存在一个点号并且后面没有数字,则不判断
  const array = result.split('.')
  if (array.length > 1 && array[1] === '') {
    return result
  }

  // 字符串最后一个字符是0,不判断
  if (result.endsWith('0')) {
    return result
  }

  return parseFloat(result)
}

export { trim, matchCount, removeNonNumeric }

日期格式

javascript 复制代码
import dayjs from 'dayjs'

// 格式化特定日期
const formatDate = (date, formatStr = 'YYYY-MM-DD') => {
  return dayjs(date).format(formatStr)
}

// 获取当前日期并格式化
const getCurrentDate = (formatStr = 'YYYY-MM-DD HH:mm') => {
  return dayjs().format(formatStr)
}

// 获取是周几
const weekDays = ['周日', '周一', '周二', '周三', '周四', '周五', '周六']
const getWeekDay = () => {
  const index = dayjs().day()
  return weekDays[index]
}

// 判断凌晨 上午 中午 下午 晚上
const getDayPeriod = () => {
  const hour = dayjs().hour()
  if (hour >= 0 && hour < 6) {
    return '凌晨'
  } else if (hour >= 6 && hour < 12) {
    return '上午'
  } else if (hour >= 12 && hour < 14) {
    return '中午'
  } else if (hour >= 14 && hour < 18) {
    return '下午'
  } else {
    return '晚上'
  }
}

export { formatDate, getCurrentDate, getWeekDay, getDayPeriod }

AI 辅助编码

相关推荐
古希腊掌管学习的神15 分钟前
[机器学习]XGBoost(3)——确定树的结构
人工智能·机器学习
ZHOU_WUYI43 分钟前
4.metagpt中的软件公司智能体 (ProjectManager 角色)
人工智能·metagpt
迷雾漫步者1 小时前
Flutter组件————FloatingActionButton
前端·flutter·dart
向前看-1 小时前
验证码机制
前端·后端
靴子学长1 小时前
基于字节大模型的论文翻译(含免费源码)
人工智能·深度学习·nlp
AI_NEW_COME2 小时前
知识库管理系统可扩展性深度测评
人工智能
燃先生._.2 小时前
Day-03 Vue(生命周期、生命周期钩子八个函数、工程化开发和脚手架、组件化开发、根组件、局部注册和全局注册的步骤)
前端·javascript·vue.js
海棠AI实验室3 小时前
AI的进阶之路:从机器学习到深度学习的演变(一)
人工智能·深度学习·机器学习
hunteritself3 小时前
AI Weekly『12月16-22日』:OpenAI公布o3,谷歌发布首个推理模型,GitHub Copilot免费版上线!
人工智能·gpt·chatgpt·github·openai·copilot