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

随笔3个月前发布 海军
39 0 0

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


目录

概览资源环境目录工具⭐pnpm 9⭐ESLint 8⭐Prettier 3⭐Husky 9⭐vite 5 构建⭐package.json
注释GIT常用命令类型说明上线流程
HTMLCSSmoduleJS依赖导入变量命名策略模式条件判断解构赋值模板字符串
TS前言文件声明组件引入
NodeVUE组件闭合Fragments 语法
React组件命名属性命名事件方法外部定义
微信小程序Taro 跨端工具函数HTTP防抖节流正则匹配日期格式
AI 辅助编码


概览

前端技术的迭代速度较快,相关的框架和知识点也越来越多,在开发新项目时可供选择的技术栈也越来越多。对于需要多人团队写作的场景下,如何保证大家共同的编码规范,用一套约定俗成的标准进行开发至关重要,可以更好的提升自己的开发体验。
接下来,主要讲解不同前端领域的编码规范以及格式化代码,本质上底层还是前端三件套的编程思想。
核心:语义化、代码清晰明确、注释完善,代码配置 < 沟通 < 约定 < 文档 < 体系

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

资源

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

https://guide.aotu.io/index.html(首选)https://github.com/airbnb/javascripthttps://github.com/airbnb/javascript/tree/master/reacthttps://github.com/ryanmcdermott/clean-code-javascripthttps://github.com/Microsoft/TypeScript/wiki/Coding-guidelines

环境

VUE 新项目推荐开发环境:Vue 3.4.1 + Node 20.11.1 + Pnpm 9.6 + Vite 5.2 + TypeScript 5.2.2Node 新项目推荐开发环境: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 进行编写不同模块的代码逻辑,下面是针对大型项目一些补充说明:

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

工具

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

⭐pnpm 9

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

// 1. 安装 pnpm 
npm install -g pnpm

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

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

12345678

⭐ESLint 8

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

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

// 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

1234567891011
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']
}


123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869

⭐Prettier 3

Prettier是一个代码格式化工具,专注于对代码进行格式化,使其符合一致的风格规范。它会自动调整代码的缩进、换行、引号等,确保代码在不同的编辑器和环境中具有一致的外观。
简单来说,ESLint更注重你的代码是否符合规范,Pretter则是为你提供了按照规范格式化代码的能力。

{
  "tabWidth": 2,
  "semi": false,
  "printWidth": 90,
  "endOfLine": "auto",
  "singleQuote": true,
  "trailingComma": "none"
}

12345678

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

# 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



12345678910111213141516171819202122232425262728

⭐Husky 9

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

// 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 命令配置


123456789101112131415161718192021
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]
  }
}



12345678910111213141516171819202122232425262728293031

⭐vite 5 构建

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'] : []
  }
})



1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465

⭐package.json

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

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

1234

注释

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

设计复杂业务逻辑的处理函数必须说明函数的含义模块化封装的代码需要说明该模块的使用说明和功能介绍团队成员的注释风格须保持一致性,以保证注释与代码的同步性

/**
 * 斐波那契数列(用于复杂逻辑功能的说明)
 * @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))


1234567891011121314151617

特殊注释标记如下:

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

123456789

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/

// 获取远程最新的分支代码
// 本地没影响,只是更新远程跟踪分支的列表
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 节点


1234567891011121314151617181920212223242526272829303132

类型说明

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

vscode 推荐插件:git-commit-lint-vscode

上线流程

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

HTML

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

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

CSS

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

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

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

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

module

https://juejin.cn/post/6844904080955932680
前端的底层编码风格分为两种,一种是 CommonJS、一种是 ES6 语法,这两种风格的区别如下:

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

JS

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

依赖导入

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'


12345678910111213141516171819

React 内置模块外部引入的模块自己编写的文件
样式文件图片资源文件TS编写的类型定义文件引入自定义的接口自己编写的组件 一行引入多个模块导致分行的,可以放在最后

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

变量命名

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

策略模式

// 为了解决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();


1234567891011121314151617181920212223

条件判断

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

if(isAuto) {
  xxx
  xxx
  xxx
} x

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


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

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


123456789101112131415161718

解构赋值

解构赋值 – JavaScript | MDN

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

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

1234567

模板字符串

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

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

return <div> {userDetails} </div>

12345

TS

前言

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

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

文件声明

// <reference types="vite/client" />

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

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

12345678910

组件引入

Node

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

VUE

组件闭合

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

123456

Fragments 语法

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

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

12345678910

React

组件命名

//map-version-dropdown.tsx
export default function MapVersionDropdown(){}

12

文件使用 – 连接,组件对应文件名称使用大驼峰命名

属性命名

const [isTruckTpra, setIsTruckTpra] = useState<boolean>(true)
const [streamOLGList, setStreamOLGList] = useState<Stream['streamOLGList']>(
    [],
  )

1234

响应式属性使用useState命名,使用小驼峰命名,至少2个字符以上数据状态不影响UI变化的,使用一般数据即可,不用响应式数据

事件方法外部定义

const submitData = () => dispatch(ACTION_TO_SEND_DATA);

return <button onClick={submitData}>This is a good example</button>;

123

复杂的事件方法需要在语法外部定义简单的如状态变化可以在内部定义,尽量控制在一行以内

微信小程序

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

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

Taro 跨端

工具函数

HTTP

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

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

参考代码如下所示:

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()


123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
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


1234567891011121314151617181920
import UserAccount from './modules/user'
import GlobalConfig from './modules/config'

export { UserAccount , GlobalConfig }

1234

防抖节流

// 深拷贝
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 }


12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364

正则匹配

// 去除首尾空格
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 }



123456789101112131415161718192021222324252627282930313233343536373839404142

日期格式

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 }



12345678910111213141516171819202122232425262728293031323334353637

AI 辅助编码

© 版权声明

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
暂无评论...