杀鸡焉用牛刀 发表于 2024-9-12 02:00:28

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

https://i-blog.csdnimg.cn/direct/9970a7482d8a46a18c8792bb40090254.png


概览

前端技术的迭代速率较快,相干的框架和知识点也越来越多,在开发新项目时可供选择的技术栈也越来越多。对于必要多人团队写作的场景下,怎样保证各人共同的编码规范,用一套约定俗成的尺度举行开发至关重要,可以更好的提升本身的开发体验。
接下来,重要解说不同前端领域的编码规范以合格式化代码,本质上底层照旧前端三件套的编程头脑。
焦点:语义化、代码清楚明确、注释美满,代码配置 < 沟通 < 约定 < 文档 < 体系
语雀原文档:https://www.yuque.com/xixifusi-eicch/ma8xgr/skhybs2u0glzs5p5?singleDoc# 《编码规范》
资源

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


[*]https://guide.aotu.io/index.html(首选)
[*]https://github.com/airbnb/javascript
[*]https://github.com/airbnb/javascript/tree/master/react
[*]https://github.com/ryanmcdermott/clean-code-javascript
[*]https://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.2
[*]Node 新项目推荐开发情况:Node 20.11.1 + TypeScript 5.2.2
目录

我们平常开发新项目标时候,往往会利用第三方工具举行生成基础的项目模板,这些文件目录代表的意思如下:
名称寄义.vscodevscode 调试步伐、样式等当地化配置public公共文件夹,存放图标、全局配置api接口文件夹assets静态资源存放,包含字体图标、图片等components抽离的全局公共组件(页面逻辑过多,可分为一级、二级、三级)config相干的配置文件,一样平常是静态相干的hooks具有响应式数据特性/生命周期的方法抽离layout页面的布局组件,包含侧边栏、头部、底部、内容区,单页面应用较为常见router路由组件script脚本执行文件夹,存放常用的主动化脚本store当前页面的数据存储状态(可选是否持久化)style页面的样式,会包含 重置样式、组件样式、自定义样式 等utils工具函数,包含正则、http请求、工具函数等views项目标视图页面,详细可拆分typesTS 项目专有,包含 主动引入、路由声明等 .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
⭐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
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则是为你提供了按照规范格式化代码的能力。
{
"tabWidth": 2,
"semi": false,
"printWidth": 90,
"endOfLine": "auto",
"singleQuote": true,
"trailingComma": "none"
}
忽略格式化的文件本质上和 .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

⭐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 命令配置
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':
}
}

⭐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:
    }),
    Components({
      dts: 'types/components.d.ts',
      extensions: ['vue'],
      resolvers:
    })
],

// 路径优化
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

这里是全局 安装包与命令执行的入口文件,常用的配置如下所示:
"scripts": {
"format": "prettier --write src/",
"lint": "eslint . --ext .vue,.js,.ts,.jsx,.tsx,.cjs,.mjs --fix --ignore-path .gitignore",
},
注释

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

[*]设计复杂业务逻辑的处置惩罚函数必须说明函数的寄义
[*]模块化封装的代码必要说明该模块的使用说明和功能先容
[*]团队成员的注释风格须保持划一性,以保证注释与代码的同步性
/**
* 斐波那契数列(用于复杂逻辑功能的说明)
* @param {number} n 斐波那契数列的第n项
* @returns {number} 返回第n项的值
*/

const fibonacciDP = (n) => {
let fib =
for (let i = 2; i <= n; i++) {
    fib = fib + fib
}
return fib
}

// 斐波那契传入的数字(描述字段、函数、配置...的简短说明)
const num = 10
console.log(fibonacciDP(10))
特殊注释标记如下:
// 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/
// 获取远程最新的分支代码
// 本地没影响,只是更新远程跟踪分支的列表
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新增开发分支提交 featurefix修复 bugdocs仅仅是修改了文档,比如 readme、changelog 等style仅仅是修改了空格、格式缩进、逗号等等,不改变代码逻辑refactor代码重构,没有新功能或者修复 bugperf优化相干,比如提升性能、体验test测试用例,包括单位测试、集成测试等chore改变构建流程、或者增加依靠库、工具等revert回滚到上一个版本

[*]vscode 推荐插件:git-commit-lint-vscode
上线流程


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

https://img-blog.csdnimg.cn/img_convert/2c381cf505ccd2b35fb71de72944afd8.jpeg

[*]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'

[*]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;
Component();
条件判定

// 函数处理逻辑,可以提前结束,而不是包裹在正确的 {} 内部
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
// 对象的解构赋值
const obj = { a: 1, b: 2 };
const { a, b } = obj;

// 数组的解构赋值
const x = ;
const = x;
模板字符串

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

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

return <div> {userDetails} </div>
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
}
组件引入

Node

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

组件闭合

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

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

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

组件定名

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


[*]文件使用 - 连接,组件对应文件名称使用大驼峰定名
属性定名

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


[*]响应式属性使用useState定名,使用小驼峰定名,至少2个字符以上
[*]数据状态不影响UI变化的,使用一样平常数据即可,不用响应式数据
变乱方法外部定义

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

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


[*]复杂的变乱方法必要在语法外部定义
[*]简单的如状态变化可以在内部定义,尽量控制在一行以内
微信小步伐

微信小步伐设计指南 | 微信开放文档
【第二周】基础语法学习-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()
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
import UserAccount from './modules/user'
import GlobalConfig from './modules/config'

export { UserAccount , GlobalConfig }
防抖节流

// 深拷贝
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 = deepClone(target, 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 }
正则匹配

// 去除首尾空格
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 === '') {
    return result
}

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

return parseFloat(result)
}

export { trim, matchCount, removeNonNumeric }

日期格式

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
}

// 判断凌晨 上午 中午 下午 晚上
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 辅助编码


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: 【团队建立】前端编码规范