Business Front-end System Development Coding Standards Guide and Instructions
This guide aims to standardize the coding style and best practices of front-end business systems, improve code readability, maintainability, and team collaboration efficiency, and reduce the risk of potential defects.
1. Document Description
1.1 Compilation Notes
The rapid development of the software industry demands increasingly higher comprehensive qualities from software developers. It is not only programming knowledge that matters; knowledge in other dimensions also affects the final delivery quality. This document, from the perspective of developing frontend projects, details frontend coding standards, covering HTML, CSS, JavaScript, TypeScript, Vue, code comments, performance optimization, and other aspects. Each chapter is divided in detail to help readers quickly locate information, standardize their code, and improve project code quality.
1.2 Terminology Explanation
-
Cyclomatic Complexity: The cyclomatic complexity of a portion of a software source code is the number of linearly independent paths in that code. For example: if a section of source code contains no control flow statements (conditions or decision points), its cyclomatic complexity is 1, because there is only one path in this code; if a section of code contains only one if statement with just one condition, its cyclomatic complexity is 2; a code block containing two nested if statements, or an if statement with two conditions, has a cyclomatic complexity of 3.
-
Cognitive Complexity: Cognitive complexity score is evaluated according to three basic rules: ignoring cases where multiple statements can be easily simplified into one, each break in the linear flow of the code adds (+1), nesting of flow-breaking structures increases complexity.
2. HTML
2.1 Document Type
- [Mandatory] Use the HTML5 DOCTYPE.
<!-- good -->
<!DOCTYPE html>
<html>
</html>2.2 Document Language
- [Recommended] Specify the lang attribute on the html tag.
<html lang="zh-CN"></html>2.3 Resource Loading
- [Recommended] It is not necessary to specify the type when importing CSS and JavaScript. According to the HTML5 specification, it is generally unnecessary to specify the type when importing CSS and JavaScript, because text/css and text/javascript are their default values.
- [Recommended] Import CSS within the head tag and JS before the closing body tag. Specifying external stylesheets and embedded style blocks within the head may cause page reflow and repaint, affecting page rendering. Therefore, in general, CSS should be imported within the head tag.
2.4 Document Tags
- [Mandatory] Tag names should consistently use lowercase.
- [Mandatory] Self-closing HTML tags should use the self-closing syntax such as
<br />,<img />, etc., and should not be written as<br>or<img>. - [Recommended] When referencing custom components in Vue3, it is recommended to use the full closing tag form (not self-closing).
Example:
<!-- 不推荐:自闭合组件标签 -->
<CustomComponent />
<!-- 推荐:完整闭合标签 -->
<CustomComponent></CustomComponent>
<!-- HTML自闭合标签 -->
<br />
<img src="path" alt="description" />
<input type="text" />2.5 Project Supplement
In a Vue3 single-page application project, if you need to reference third-party resources, and other functional pages use third-party resources as follows, if the project uses printing control resources, they need to be included in index.html:
<head>
<!-- 下面是打印控件资源 -->
<script type="text/javascript" src="/js/jquery.min.js"></script>
<script type="text/javascript" src="/js/jquery.watermark.js"></script>
<script type="text/javascript" src="/js/socket.io.js"></script>
<script type="text/javascript" src="/js/finereport.js"></script>
</head>This method is fine, but it may affect the rendering speed of the homepage after packaging, causing an increase in white screen time. We can try placing it under the body to optimize rendering speed. (Note: analyze specific examples individually)
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
<script type="text/javascript" src="/js/jquery.min.js"></script>
<script type="text/javascript" src="/js/jquery.watermark.js"></script>
<script type="text/javascript" src="/js/socket.io.js"></script>
<!-- 下面是打印控件资源 -->
<script type="text/javascript" src="/js/finereport.js"></script>
</body>3. CSS
3.1. File Reference
- [Mandatory] Always use the link method to call external styles.
- [Recommended] Do not use @import in style; do not use style blocks in the page.
3.2. Component Elements
- [Mandatory] Names must consist of letters, hyphens, or numbers and cannot start with a number or a hyphen.
- [Mandatory] Do not use a mix of Pinyin and English for naming, and never use Chinese directly; prohibit having multiple different words or translations for the same meaning in the same application.
3.3. Vocabulary Standards
- [Reference] Do not name based on appearance.
- [Reference] Naming can be based on content or based on function.
3.4. Naming Abbreviations
- [Mandatory] Ensure that the abbreviation still clearly conveys the meaning of the original word.
- [Recommended] Use industry-recognized or conventionally accepted definitions for CSS class names.
Extension
Before frameworks like
Vue/React/Angularbecame popular, styles had aBEMnaming convention, which was generally accepted and recognized.However, with the popularity of
tailwind cssandunocss, this convention has also faded, and projects can decide on their own whether to use theBEMconvention.
3.5. Coding Style
- [Mandatory] All declarations should end with a semicolon and cannot be omitted.
- [Recommended] Use 2 spaces for indentation; do not use 4 spaces or tabs.
- [Recommended] Keep a space between the selector and {.
- [Recommended] No space before : in property names, but keep a space between : and the property value.
- [Recommended] Keep a space before and after combinators such as >, +, ~, ||.
- [Recommended] In comma-separated property values, leave a space after the comma.
- [Recommended] Leave a space between the comment content and the comment symbols.
- [Recommended] The closing brace } of a declaration block should be on its own line.
- [Recommended] Each property declaration should be on its own line.
- [Recommended] A single line of code should not exceed 100 characters.
- [Reference] When using multiple selectors, each selector should be on its own line.
- 【Reference】 Even when there is only one statement inside the declaration block, it should still be written in multiple lines.
- 【Reference】 Leave a blank line above comment lines, unless the previous line is also a comment or the top of a block.
3.6 Selectors
- 【Reference】 Do not use ID selectors. IDs create too high selector specificity, making it difficult to override styles later (which may cause style pollution).
- 【Recommended】 Prevent the vicious cycle of using !important to override styles.
- 【Reference】 Always wrap attribute selector values in double quotes.
3.7 Project Supplement
When developing styles using sass, it is important to distinguish between a key point: @import and @import url();
@import is a reference to the file content and requires sass loader preprocessing; @import url() is a path reference supported by css itself.
In general project development, use @import to include file content, but you can try @import url() for style reuse.
4 JavaScript
-
It is recommended to follow the Airbnb JavaScript style guide.
- Reference link: https://github.com/airbnb/javascript
-
You can also follow the Google JavaScript Style Guide.
- Reference link: https://google.github.io/styleguide/jsguide.html
Choose flexibly according to your actual business development needs.
5 TypeScript
-
It is recommended to follow the official TypeScript specifications.
-
You can also follow the Google TypeScript Style Guide.
- Reference URL: https://google.github.io/styleguide/tsguide.html
The project can choose according to the actual situation.
6. Vue
Matters to note and recommended practices during project development in Vue3. Beginners in front-end development or back-end developers writing front-end pages need to study these carefully.
6.1 Arrow Functions
It is recommended to use arrow functions (to keep the this context unchanged). This is mainly for code style consistency, aiming to avoid “three coding styles between two people” as much as possible. Remember, code readability is the primary concern in project development.
Example:
//【建议】业务开发中提倡的做法, 箭头函数配合const函数一起使用
const getTableListData = () => { // TODO }
//【反例】尽量不要出现混用,如下:
function getDomeData () {}
const getDome1Data = () => {}
// 【反例】this乱指向,乱用,如下:
const obj = {
name: 'Alice',
greet: function() {
// 普通函数,this指向obj
setTimeout(() => {
// 箭头函数内 this 已经指向 obj
// 这里却画蛇添足地再次保存 this
const that = this;
console.log(that.name); // 输出 Alice,但直接 this.name 即可
}, 100);
}
};
// 混用会导致可读性变差,而开发首要元素的可读性。6.2 Variable Hoisting
In projects or development processes, try to use let or const to define variables. This can effectively avoid problems of variable hoisting and will not be elaborated further. Note that const is generally used to declare constants or variables whose values are not allowed to change.
6.3 Data Requests
Data request classes and asynchronous operations need to use try…catch to catch exceptions. Try to avoid callback hell.
Example:
// 推荐写法
/**
* @description 获取列表数据
* @return void
*/
const getTableListData = async () => {
// 自己的业务处理TODO
try {
const res = await getTableListDataApi();
const res1 = await getTableListDataApi1();
// TODO
} catch (error) {
// 异常处理相关
} finally {
// 最终处理
}
};
//【提倡】推荐接口定义带着Api结尾,比如我的方法是getTableListData,
//【提倡】内部逻辑调用的后端接口,那我的接口便可以定位为getTableListDataApi。Of course, you can also use the following method: Example:
/**
* @description 获取列表数据
* @return void
*/
const getTableListData = () => {
getTableListDataApi({....}).then(() => {
// TODO
}).catch(() => {
// TODO
}).finally(() => {
// TODO
})
}
// 注意使用这种方式避免嵌套层级太深,如下反例:
const getTableListData1 = () => {
getTableListDataApi({....}).then(() => {
getTableListDataApi1({....}).then(() => {
getTableListDataApi2({....}).then(() => {
// TODO 这种就是典型的回调地狱,禁止出现这种
})
})
})
}Reasonable Use of Data Concurrent Requests: Example:
// 场景描述:表头和表格数据都需要请求接口获取,可以使用并发请求。
/**
* 查询列表数据
*/
const getTableList = async () => {
// TODO
try {
// 并行获取表格列数据和列表数据
const [resColumns, resData] = await Promise.all([
getTableColumnsApi({....}),
getTableListApi({...}),
]);
// TODO
} catch (error) {
// TODO
} finally {
// TODO
}
};
// Promise.all的一些执行细节不在赘述,但是注意区分和Promise.allSettled用法
//
// Promise.all()方法会在任何一个输入的 Promise 被拒绝时立即拒绝。
// 相比之下,Promise.allSettled() 方法返回的 Promise 会等待所有
// 输入的 Promise 完成,不管其中是否有 Promise 被拒绝。如果你需
// 要获取输入可迭代对象中每个 Promise 的最终结果,则应使用allSettled()方法。Reasonable Use of Data Race Requests: Example:
// 场景描述:某些业务需要请求多个接口,但是只要一个接口先返回便处理逻辑
let promise1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("数据请求1");
}, 1000);
});
let promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("数据请求2");
}, 500);
});
Promise.race([promise1, promise2]).then((result) => {
console.log(result); // 输出 "数据请求2"
});Note:
When requesting data, be sure to properly catch and handle exceptions. Catching and handling exceptions can increase the robustness of the program and improve the user experience.
The following counterexamples are to be prohibited:
/**
* 获取表格数据
*/
function getTableListData () {
getTableListData({
pageNum: 1,
pageSize: 10, // pageSize: 100000
}).then((res) => {
tableList.value = res.rows;
tableTotal.value = res.total;
//【提倡】
tableList.value = res?.code === 200 ? res.rows : [];
})
}
// 上面写法,界面可能没报错,功能也实现了,但是....6.4 Reactive Variables
Use reactive variables appropriately. For objects or arrays with large amounts of data, and when properties are nested objects, if your business scenario only requires the first-level properties to be reactive, it is recommended to use shallowRef and shallowReactive to define reactive variables. In this case, ref and reactive are no longer recommended.
6.5 Single Responsibility
When writing components or methods, you must follow the single responsibility principle (the concept will not be repeated here, understand it independently).
To expand a bit, there are four principles to follow in coding: open for extension, closed for modification, high cohesion, low coupling.
6.6 File Naming
Entry files for functional menus must include name, and other business components should generally include it as well. Use PascalCase names and keep them globally unique where possible so later troubleshooting does not become unnecessarily complex.
Regarding file name conventions, there are no mandatory rules in Vue. Here, we reference the rules from React, using PascalCase.
React component names must start with a capital letter, like StatusBar and SaveButton. React components also need to return something that React knows how to display, like a piece of JSX.
Example:
<script setup name='CustomName'> </script>
// 或者
export default defineComponent({
name: 'CustomName',
.......
})6.7 Using Listeners
When using listeners watchEffect and watch in Vue3, you need to pay attention to the usage method. Let's first look at watchEffect:
Example:
<script setup>
import { ref, watchEffect } from "vue"
const a = ref(true)
const b = ref(false)
watchEffect(() => {
if (a.value || b.value) {
console.log('执行了更新操作');
}
})
const test = () => b.value = !b.value;
</script>
<template>
<button @click="test">改变b的值</button>
<h2>当前b的值:{{ b }}</h2>
</template>Answer: When the value of b changes in the template, watchEffect cannot observe that an update operation occurred.
See the example below:
<script setup>
import { ref, watchEffect } from "vue"
const getInfo = async () => {
await new Promise((resolve, reject) => {
setTimeout(() => {
resolve(111)
}, 2000)
})
}
watchEffect(async () => {
// 请求信息
await getInfo()
if (b.value) console.log('执行了更新操作');
})
const test = () => b.value = !b.value;
</script>
<template>
<button @click="test">改变b的值</button>
<h2>当前b的值:{{ b }}</h2>
</template>Answer: When the value of b changes in the template, watchEffect cannot observe that an update operation occurred.
Let's continue to look at the example below:
<script setup>
import { ref, watchEffect } from "vue"
const a = ref(true)
const b = ref(true)
setTimeout(() => {
watchEffect(() => {
if (a.value) {
console.log('执行了更新操作');
}
})
}, 2000)
const test = () => b.value = !b.value;
</script>
<template>
<button @click="test">改变b的值</button>
<h2>当前b的值:{{ b }}</h2>
</template>Answer: When the value of b changes in the template, watchEffect cannot detect 'an update operation has been performed'. Using watchEffect requires attention to two points:
- To make watchEffect capture reactive variables immediately;
- Asynchronous operations that trigger microtasks will affect watchEffect's immediate capturing of reactive variables. If you are not very familiar with using watchEffect, it is recommended to use watch as much as possible.
Points to note about watch: When you use watch frequently inside your component or want to manually reduce the complexity of watch, it is recommended as follows:
<script setup>
....
const currentScope = effectScope();
currentScope.run(() => {
watch(
() => props.currentRow,
(newVal, oldVal) => {
// TODO
},
{ deep: true }
);
watchEffect(() => {
if (queryObj.visitId) {
// TODO
}
});
});
onBeforeUnmount(() => {
currentScope.stop();
});
</script>It should be noted that in Vue 3.5+, a new deep property has been added that can take a number directly, indicating to watch how many levels of reactive data to observe.
6.8 Using Hooks
In Vue 3 projects, it is strongly recommended to use hooks for splitting and reusing functionality. This is the way recommended by the official Vue team. Let's look at an example. For instance, if I want to implement a popup feature, a common approach is as follows. The first one leans towards a backend way of thinking:
const editModel = reactive({
isShow: false,
form: {
name: "ANDROID",
// ......
},
showFunc: () => {
// 显示逻辑
},
cancelFunc: () => {
// 取消逻辑
},
submitFunc: () => {
// 提交逻辑
},
});Or other similar writing styles, not elaborated here. Actually, they can all be replaced with the hooks approach:
Example:
const useEditModel = () => {
const isShow = ref(false);
/**
* 显示弹框
*/
const showModal = () => {};
/**
* 关闭弹框
*/
const cancelModal = () => {};
/**
* 提交操作
*/
const submitModal = () => {};
onBeforeMount(() => {
// TODO
});
return {
isShow,
showModal,
cancelModal,
submitModal,
};
};
// 其他地方使用
const { isShow, showModal, cancelModal, submitModal } = useEditModel();A simple summary of the idea behind writing hooks:
Define and use reactive/non-reactive state, variables, or states, variables, and methods obtained from multiple functions within the function scope, and combine them to handle complex problems.
6.9 Exposing Methods
When we want to expose all properties of a third-party component, how can we expose them quickly?
Using expose requires writing them one by one, which is obviously too troublesome. We can use the following method:
expose(
new Proxy(
{},
{
get(target, key) {
// CustomDomRef是定义的模板中的ref dom节点
return CustomDomRef.value?.[key];
},
has(target, key) {
return key in CustomDomRef.value;
},
},
),
);6.10 Picking Properties
In certain business scenarios, we need to pick some properties to pass to an interface. How to elegantly pick properties can be referenced as follows:
const obj = {
name: "张三",
age: 20,
sex: "男",
name1: "张三1",
};
// 当不需要name1传递时,怎么做呢?
// 方式1
delete obj.name1;
// 方式2
const newObj = {
name: obj.name,
age: obj.age,
sex: obj.sex,
};
// 方式3
const newObj = {
...obj,
name1: undefined,
};
// 其实可以使用一种更优雅的方式
const { name1, ...newObj } = obj;
// 或者使用lodash的omit或者pick方法6.11 Compositional API
The Compositional API itself is designed for flexibility, but in projects it has been used in various inconsistent ways. Some put expose at the very beginning and put component imports at the bottom. When you are unsure about the usage order under the setup syntax sugar, you can refer to the following order:
Example:
<script setup>
// import语句
// Props(defineProps)
// Emits(defineEmits)
// 响应性变量定义
// Computed
// Watchers
// 函数
// 生命周期
// Expose(defineExpose)
</script>6.12 Logical Branches
When we write business code, we often encounter the following kind of writing. The writing is not right or wrong, there are just better ways to optimize it:
Example:
// 场景一
if (type === 1) {
// TODO
} else if (type === 2) {
// TODO
} else if (type === 3) {
// TODO
} else if (type === 4) {
// TODO
} else if (type === 5) {
// TODO
} else {
// TODO
}
// 场景二
if (type === 1) {
if (type1 === 1) {
if (type2 === 1) {
if (type3 === 1) {
// TODO
}
}
}
}Scenario 1: Violates the Open-Closed Principle (open for extension, closed for modification) and the Single Responsibility Principle. Scenario 1 can be optimized as follows:
// 优化方式一:字典映射方式
const typeHandlers = {
1: handleType1,
2: handleType2,
3: handleType3,
4: handleType4,
5: handleType5,
default: handleDefault,
};
const handler = typeHandlers[type] || typeHandlers.default;
handler();
// 优化方式二:高阶函数方式
const handleType1 = () => {
/* TODO for type 1 */
};
const handleType2 = () => {
/* TODO for type 2 */
};
// 其他处理函数...
const handlers = [handleType1, handleType2 /*...*/];
const processType = (type) => {
if (handlers[type - 1]) handlers[type - 1]();
};
processType(type);Scenario 2: Violated the Cyclomatic Complexity Principle and the Single Responsibility Principle. Scenario 2 can be optimized as follows:
// 优化方式一
const isValidType = () => {
return type === 1 && type1 === 1 && type2 === 1 && type3 === 1;
};
if (isValidType()) {
}
// 优化方式二:使用"早返回原则"或者叫"错误前置原则"进行优化
if (type !== 1) return;
if (type1 !== 1) return;
if (type2 !== 1) return;
if (type3 !== 1) return;
// TODOThe above only briefly lists some optimization ideas; there are many solutions, as long as they are reasonable.
6.13 Delete Redundancy
During business development, we often comment out code in our programs. Some files may have many comments, and of course, some of these comments may be restored later. However, the officially recommended practice is to delete these commented-out codes as much as possible, and restore only the code that is truly needed.
Another common problem is: console logs and debuggers. Although these can be removed during packaging through plugin configuration, the official recommendation is to delete them immediately at the source code level once debugging is complete.
A single file should generally stay below 600 lines of code. This can be relaxed when circumstances justify it, but larger files should usually be split by component, method, style, configuration, or another coherent responsibility. Excessive splitting also creates fragmentation, so use judgment.
6.14 Asynchronous Components
Vue 3 provides the definition of asynchronous components (defineAsyncComponent). The advantages of asynchronous components are:
- They are lazy-loaded at runtime, which allows the browser to render other features more efficiently.
- They are beneficial for code splitting when packaging with Vite.
Simple example:
<template>
<AdminPage />
</template>
<script setup>
import { defineAsyncComponent } from "vue";
const AdminPage = defineAsyncComponent(() =>
import("./components/AdminPageComponent.vue")
);
</script>Complex Example:
<template>
<keep-alive>
<component :is="getCurrentComponents()"></component>
</keep-alive>
</template>
<script setup>
import { defineAsyncComponent } from "vue";
export const PreferenceItemComponents: any = {
Residence: defineAsyncComponent(() => import("./Residence.vue")),
PastHistory: defineAsyncComponent(() => import("./PastHistory.vue")),
AllergyHistory: defineAsyncComponent(() => import("./AllergyHistory.vue")),
Diagnose: defineAsyncComponent(() => import("./Diagnose.vue")),
};
/**
* 获取当前需要渲染的组件
*/
const getCurrentComponents = () => {
const projectType = activeName.value;
if (projectType && PreferenceItemComponents[projectType]) {
return PreferenceItemComponents[projectType];
}
return null;
};
</script>For splitting complex functions, you can consider using asynchronous components, especially for features that include a large number of components.
6.15 Route Lazy Loading
In existing frameworks, we usually do not need to deal with this because the menu and routes are already well encapsulated, but we also need to understand the concept of route lazy loading:
Example:
// 将
// import UserDetails from './views/UserDetails.vue'
// 替换成
const UserDetails = () => import('./views/UserDetails.vue')
const router = createRouter({
// ...
routes: [
{ path: '/users/:id', component: UserDetails }
// 或在路由定义里直接使用它
{ path: '/users/:id', component: () => import('./views/UserDetails.vue') },
],
})Lazy loading routes is beneficial for Vite to perform code splitting for different menu functions, reducing the code size after packaging, thereby increasing access speed. It should be noted that: do not use async components in routes. Async components can still be used within route components, but route components themselves are dynamically imported.
6.16 Operators
There are several new operators in ES that you need to understand because they can simplify your coding.
?? (Nullish coalescing operator)
?. (Optional chaining operator)
??= (Nullish coalescing assignment operator)
?= (Safe assignment operator)
Example
// ?? ( 空值合并运算符):这个运算符主要是左侧为null和undefined,直接返回右侧值
// 请在开发过程中合理使用||和??
let result = value ?? '默认值';
console.log('result', result);
// ?.(可选链运算符): 用于对可能为 null 或 undefined 的对象进行安全访问。
// 建议这个属性要用起来,防止数据不规范时控制台直接报错
const obj = null;
let prop = obj?.property;
console.log('prop', prop);
// ??= (空值合并赋值操作符): 用于在变量已有非空值,避免重复赋值。
let x = null;
x ??= 5; // 如果 x 为 null 或 undefined,则赋值为 5
// ?= (安全复制运算符):旨在简化错误处理。改运算符与 Promise、async 函数以及任何实现了 Symbol.result 方法的对象兼容,简化了常见的错误处理流程。
// 注意:任何实现了 Symbol.result 方法的对象都可以与 ?= 运算符一起使用,Symbol.result 方法返回一个数组,第一个元素为错误,第二个元素为结果。
const [error, response] ?= await fetch("https://blog.conardli.top");7. Code Comments
The readability and iterability of code are the primary considerations when writing code.
7.1 File Comments
Single file comment specifications: each independent VUE file can have a file comment at the beginning, indicating the file's description, author, creation time, etc.
Example:
<!--
* @FileDescription: 该文件的描述信息
* @Author: 作者信息
* @Date: 文件创建时间
* @LastEditors: 最后更新作者
* @LastEditTime: 最后更新时间
-->7.2 Method Comments
When developing functions, the relevant methods should be commented and explained. Comments should follow the JSDOC specification.
Method comment format:
/**
* @description: 方法描述 (可以不带@description)
* @param {参数类型} 参数名称
* @param {参数类型} 参数名称
* @return 没有返回信息写 void / 有返回信息 {返回类型} 描述信息
*/Example:
/**
* @description 获取解析统计相关数据
* @param {Object} userInfo
* @param {Array} lists
* @return void
*/
或者;
/**
* 获取解析统计相关数据
* @param {Object} userInfo 用户信息
* @param {Array} lists 用户列表
* @return void
*/7.3 Variable Comments
Key variables should be commented on, and variable comments generally include two types:
Example:
// 提倡(vscode可以给出提示的写法)
/* 描述信息 */
activeName: "first";
activeName: "first"; // 默认激活的Tab页
或者;
// 默认激活的Tab页
activeName: "first";7.4 Inline Comments
Key business code must have inline comments. It is recommended that inline comments follow the format below:
Example:
// 根据指定的属性对数据进行分类
或者;
// 根据指定的属性对数据进行分类,
// 分类之后按住时间进行降序排序
// ......
或者;
/**
* 根据指定的属性对数据进行分类,
* 分类之后按住时间进行降序排序
* ......
*/7.5 Code Folding
It is recommended to use code folding comments for variables or methods with very high coupling.
Example:
// #region 升序、降序处理逻辑
/**
* 升序、降序处理逻辑说明:
*
* 根据指定的属性对数据进行分类,
* 分类之后按住时间进行降序排序
* ......
*/
const asceOrderLists = []; // 升序数组
const descOrderLists = []; // 降序数组
/**
* @description 升序操作
* @param {Array} lists
* @return {Array} arrs
*/
const handleAsceOrder = (lists) => {
// .........
return arrs
}
/**
* @description 降序操作
* @param {Array} lists
* @return {Array} arrs
*/
const handleDescOrder = (lists) => {
// .........
return arrs
}
......
// #endregion7.6 Other Instructions
In daily development, common problem fixes and functional development suggestions are annotated as follows:
- 新功能点开发
// FEAT-001: 进行了XXXXX功能开发(LMX-2024-09-24)
- 问题修复
// BUGFIX-001: 进行了XXXXX功能修复(LMX-2024-09-24)
....Explanation:
格式说明:
[${a1}-${a2}]: 相关描述信息(${a3}-${a4})
- a1:类型描述,建议遵循git提交规范,但是使用全驼峰大写。(feat、fix、bugfix、docs、style、refactor、perf、chore)
- a2: 编号,可以使用bug单号、功能特性单号或者自增序号,建议使用bug单号、功能特性单号。
- a3: git账户或者能标识自己的账号即可。
- a4: 新增或者修改时间,建议精确到天。8. Directory Structure
For project feature development, how should one divide the directory structure of a feature? What kind of directory structure can improve code readability?
Below is a relatively complete business feature file directory for reference:
custom_module # Business module
│ ├── api # Private interfaces of the business module
│ ├── components/modules # Business components (involving business processing)
│ ├── composable # Business components (not involving specific business)
│ ├── functional # Business functional components
│ ├── methods/hooks # Business hooks
│ ├── config # Business configuration items
│ ├── styles # Business styles
│ └── utils # Private utilities for the business module
|── index.vue # Business entry file
|── .pubrc.js # Business late-stage module federation entry
└── README.md # Business documentationThe specific division of business functions can be defined according to your own specific business, as long as it is reasonable. If it is a common component, it may not be necessary to divide it according to the directory structure above.
8.1 State Management Specifications
It is recommended to use Pinia for state management, and the following specifications need to be noted:
// stores/user.ts
import { defineStore } from "pinia";
import type { UserInfo } from "@/types";
/**
* @description 用户信息store
*/
export const useUserStore = defineStore("user", () => {
// 【强制】使用 ref 定义响应式状态
const userInfo = (ref < UserInfo) | (null > null);
const isLoading = ref(false);
// 【推荐】使用 computed 定义派生状态
const isAdmin = computed(() => userInfo.value?.role === "admin");
// 【强制】使用函数定义 action,并使用 async/await 处理异步操作
/**
* @description 获取用户信息
*/
const fetchUserInfo = async () => {
isLoading.value = true;
try {
const res = await getUserInfoApi();
userInfo.value = res;
} catch (error) {
console.error("获取用户信息失败", error);
} finally {
isLoading.value = false;
}
};
// 【推荐】重置状态方法
const resetUserInfo = () => {
userInfo.value = null;
};
return {
userInfo,
isLoading,
isAdmin,
fetchUserInfo,
resetUserInfo,
};
});Standards Key Points:
- The Store module name should clearly describe its function
- [Mandatory] Store should be built on a modular basis and should not be too large
- [Recommended] Key actions should include error catching and handling
- [Recommended] Avoid performing complex business logic in the Store; it should be handled in hooks or service layers
8.2 Regular Expressions
Standardize the definition and use of regular expressions:
// ✓ 提倡做法:使用常量存储正则,并添加注释说明用途
const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; // 邮箱地址
const PHONE_REGEX = /^1[3-9]\d{9}$/; // 中国手机号码
const ID_CARD_REGEX =
/^[1-9]\d{5}(18|19|20|21)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[\dXx]$/; // 身份证号
// 【推荐】复杂的正则表达式应该使用 new RegExp 并添加详细注释
const complexRegex = new RegExp(
// 匹配 URL
"^(https?:\\/\\/)?" + // protocol
"((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|" + // domain
"((\\d{1,3}\\.){3}\\d{1,3}))" + // OR IP
"(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*" + // port and path
"(\\?[;&a-z\\d%_.~+=-]*)?" + // query string
"(\\#[-a-z\\d_]*)?$",
"i", // flags
);
// 【反例】避免动态生成正则表达式
const userInput = getUserInput();
const dynamicRegex = new RegExp(userInput); // 不安全,易被利用8.3 Request Management Specifications
Establish a unified API request layer to ensure the consistency and maintainability of requests:
// api/index.ts
import axios, { type AxiosInstance, type AxiosRequestConfig } from "axios";
/**
* @description API 响应通用格式
*/
interface ApiResponse<T = any> {
code: number;
message: string;
data: T;
timestamp?: number;
}
/**
* @description 创建 axios 实例
*/
const instance: AxiosInstance = axios.create({
baseURL: import.meta.env.VITE_API_BASE_URL,
timeout: 10000,
});
// 【推荐】请求拦截器:统一处理请求前置逻辑
instance.interceptors.request.use(
(config) => {
// 添加认证 token
const token = localStorage.getItem("token");
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
(error) => Promise.reject(error),
);
// 【推荐】响应拦截器:统一处理响应和错误
instance.interceptors.response.use(
(response) => {
const { code, message, data } = response.data as ApiResponse;
if (code !== 200) {
throw new Error(message);
}
return data;
},
(error) => {
// 统一错误处理
if (error.response?.status === 401) {
// 处理未授权
}
return Promise.reject(error);
},
);
export default instance;API Naming Conventions:
// api/user.ts
/**
* @description 获取用户完整信息
* @param {string} userId 用户ID
* @returns {Promise<UserInfo>}
*/
export const getUserInfoApi = (userId: string) => {
return request.get(`/user/${userId}`);
};
/**
* @description 创建新用户
* @param {CreateUserPayload} payload 创建用户请求体
* @returns {Promise<UserInfo>}
*/
export const createUserApi = (payload: CreateUserPayload) => {
return request.post("/user", payload);
};
/**
* @description 批量获取用户列表
* @param {QueryParams} params 查询参数
* @returns {Promise<PaginatedResponse<UserInfo[]>>}
*/
export const getUserListApi = (params: QueryParams) => {
return request.get("/users", { params });
};8.4 Unit Testing Standards
When writing unit tests and integration tests, the following standards should be followed:
// src/utils/__tests__/formatter.test.ts
import { describe, it, expect, beforeEach } from "vitest";
import { formatDate, formatPrice } from "../formatter";
describe("Formatter Utils", () => {
/**
* 【强制】每个测试用例应该有清晰的描述
*/
describe("formatDate", () => {
it("should format date correctly", () => {
const result = formatDate(new Date("2024-01-01"));
expect(result).toBe("2024-01-01");
});
it("should handle invalid date", () => {
expect(() => formatDate(null as any)).toThrow();
});
});
describe("formatPrice", () => {
/**
* 【推荐】使用专业的测试用例描述,不要使用"test 1"、"test 2"
*/
it("should format price with 2 decimal places", () => {
expect(formatPrice(99.9)).toBe("99.90");
});
it("should add thousand separator", () => {
expect(formatPrice(10000)).toBe("10,000.00");
});
it("should handle negative price", () => {
expect(formatPrice(-50.5)).toBe("-50.50");
});
});
});
/**
* @description 【强制】类型定义测试
*/
describe("Type Safety", () => {
it("should have correct return type", () => {
const result: string = formatDate(new Date());
expect(typeof result).toBe("string");
});
});Test Coverage Requirements:
- [Mandatory] Key business logic coverage should not be less than 80%
- [Recommended] Common utility function coverage should not be less than 90%
- [Recommended] Use vitest for unit testing and Playwright for E2E testing
8.5 Internationalization Standards
Internationalization implementation standards in Vue3 projects:
// locales/en.ts
export default {
'common.save': 'Save',
'common.delete': 'Delete',
'common.cancel': 'Cancel',
'user.login': 'User Login',
'user.logout': 'User Logout',
'user.profile': 'User Profile',
'error.networkFailed': 'Network request failed',
'error.unauthorized': 'Unauthorized access',
};
// locales/zh-CN.ts
export default {
'common.save': '保存',
'common.delete': '删除',
'common.cancel': '取消',
'user.login': '用户登录',
'user.logout': '用户登出',
'user.profile': '用户资料',
'error.networkFailed': '网络请求失败',
'error.unauthorized': '未授权访问',
};
// 在组件中使用
<script setup lang="ts">
import { useI18n } from 'vue-i18n';
const { t, locale } = useI18n();
</script>
<template>
<button>{{ t('common.save') }}</button>
<div>{{ t('error.networkFailed') }}</div>
</template>Key Standards:
- [Mandatory] Use dot-separated namespaces for internationalization keys, e.g.,
module.feature.key - [Recommended] Translations should be stored in separate locale files
- [Recommended] Avoid hardcoding text in the code; consistently use i18n
8.6 Accessibility
Develop accessible components that meet the WCAG 2.1 standards:
<!-- ✓ 提倡做法 -->
<form>
<label for="username">Username</label>
<input id="username" type="text" required />
</form>
<!-- ✓ 提倡做法:使用 aria-label 和 aria-describedby -->
<button aria-label="Close dialog" @click="handleClose">×</button>
<input aria-describedby="password-hint" type="password" />
<p id="password-hint">Password must be at least 8 characters</p>
<!-- ✓ 提倡做法:使用 role 属性明确 DOM 角色 -->
<div role="status" aria-live="polite">{{ message }}</div>
<!-- ✗ 反例:跳过标题级别 -->
<h1>Main Title</h1>
<h3>Subtitle</h3>
<!-- 应该是 h2 -->
<!-- ✗ 反例:没有标签的表单输入 -->
<input type="text" placeholder="Username" />9. Performance Optimization
9.1. Reduce Code Bundle Size
- Reduce duplication in source code, reuse functionality by extracting shared components or methods
- Optimize front-end dependencies to prevent the addition of new dependencies from increasing bundle size, for example, lodash-es is preferred over lodash
- Code splitting (ESM dynamic imports, route lazy loading)
- Reasonable configuration of options in vite.config.ts, such as output.manualChunks and sourceMap in rollupOptions
- Remove unused polyfills and shims
- Use dynamic imports instead of static imports to achieve code splitting
9.2. Optimize Resource Loading Speed
- Some static resources or dependencies can consider using a CDN to increase access speed
- Enable browser gzip compression to reduce bandwidth requests
- Consider preloading some critical resources
- Consider lazy loading some images and videos
- Use HTTP/2 server push for key resources
- Configure caching strategies for commonly used third-party libraries
9.3. Business Code Layer Optimization
- Reduce the number of API requests and optimize time-consuming interfaces
- Handle large data scenarios (pagination, virtual scrolling)
- Reduce unnecessary updates (updates between parent and child components, do not use index as key)
- Reduce reactivity overhead under large quantities
- Reduce manual memory leaks and overflow operations
- Optimize tasks in JS that take a long time (for example, consider async, requestAnimationFrame, requestIdleCallback)
- Use Web Worker to handle CPU-intensive tasks
- Use virtual scrolling technology to handle large lists
9.4. Proper Use of Caching
- Browser conditional caching
- Browser strong caching
- Browser local storage (whether localStorage, sessionStorage, indexedDB can be used)
10. Code Style and Tools
Use unified code formatting and code linting plugin tools for standardization.
10.1. Prettier
Use a unified code formatting tool to format the code and maintain consistency in the project. In future projects, unified prettierrc and prettierignore files will be added.
.prettierrc
{
// 最大的行长度
"printWidth": 150,
// 每个缩进级别的空格数
"tabWidth": 2,
// 使用制表符还是空格缩进行
"useTabs": false,
// 是否使用分号
"semi": true,
// 是否使用单引号
"singleQuote": true,
// 遵循对象属性中引号的输入使用
"quoteProps": "as-needed",
// 在jsx中是否使用单引号替代双引号
"jsxSingleQuote": false,
// > 是否单独一行,false默认单独一行
"bracketSameLine": false,
// "all" "es5" "none" --》更换为all
"trailingComma": "all",
// 在对象,数组括号与属性之间加空格 "{ foo: bar }"
"bracketSpacing": true,
// 是否进行嵌入代码的格式化
"embeddedLanguageFormatting": "auto",
// 箭头函数只有一个参数时是否要"()"
"arrowParens": "always",
// 是否需要Pragma
"requirePragma": false,
// 是否需要插入Pragma
"insertPragma": false,
// 什么都不做保持散文原格式
"proseWrap": "preserve",
// 标签周围的空格处理方式
"htmlWhitespaceSensitivity": "css",
// Vue 文件中的 <script> 和 <style> 标签内缩进代码。
"vueIndentScriptAndStyle": false,
// 结尾是 \n \r \n\r auto lf...
"endOfLine": "auto"
}.prettierignore
/dist/*
.local
.output.js
/node_modules/**
**/*.svg
**/*.png
**/*.jpg
**/*.jpeg
**/*.sh
/public/*10.2 ESLint
Use a unified code linting tool to maintain the correctness of the project's code. Later projects will add unified eslintrc.cjs and eslintignore files.
.eslintrc.cjs
module.exports = {
env: {
browser: true,
node: true,
es6: true,
},
parser: "vue-eslint-parser",
extends: [
"plugin:vue/vue3-recommended",
"plugin:@typescript-eslint/recommended",
"./.eslintrc-auto-import.json",
"prettier",
"plugin:prettier/recommended",
],
parserOptions: {
ecmaVersion: "2020",
sourceType: "module",
project: "./tsconfig.*?.json",
parser: "@typescript-eslint/parser",
},
plugins: [
"vue",
"@typescript-eslint",
"import",
"promise",
"node",
"prettier",
],
rules: {
// 关闭空方法校验
"@typescript-eslint/no-empty-function": "off",
// 关闭类型限制不允许使用any
"@typescript-eslint/no-explicit-any": "off",
// 关闭没有使用变量校验
"@typescript-eslint/no-unused-vars": "off",
// 关闭不允许复制this给变量校验
"@typescript-eslint/no-this-alias": "off",
// 关闭不允许使用ts的关闭注释等
"@typescript-eslint/ban-ts-comment": "off",
// 关闭要求组件名称始终为多个单词
"vue/multi-word-component-names": "off",
// 关闭强制执行有效的defineProps编译器宏
"vue/valid-define-props": "off",
// 关闭禁止在自定义组件中使用带有参数的v-model
"vue/no-v-model-argument": "off",
// 关闭禁止使用 v-html
"vue/no-v-html": "off",
// 关闭标记arguments变量提示通知
"prefer-rest-params": "off",
// 把Prettier的规则设为错误级别
"prettier/prettier": "error",
// 开启圈复杂度检查, 最大圈复杂度为10
complexity: ["warn", { max: 10 }],
},
globals: {
DialogOption: "readonly",
OptionType: "readonly",
defineEmits: "readonly",
defineProps: "readonly",
},
};.eslintignore
*.sh
node_modules
*.md
*.woff
*.ttf
.vscode
.idea
dist
/public
/docs
.husky
.local
/bin
.eslintrc.cjs
prettier.config.js
src/assets
tailwind.config.js10.3 Stylelint
Use unified style linting rules to maintain correct and consistent project styles. In future projects, stylelint.config.js will be added uniformly.
stylelint.config.js
export default {
extends: ["stylelint-config-standard"],
ignoreFiles: [
"src/**/*.js",
"src/**/*.jsx",
"src/**/*.tsx",
"src/**/*.ts",
"src/**/*.json",
"src/**/*.md",
"src/**/*.{jpg,png,gif,jpge,svg,webp}",
],
overrides: [
{
customSyntax: "postcss-html",
files: ["*.(html|vue)", "**/*.(html|vue)"],
rules: {
"selector-pseudo-class-no-unknown": [
true,
{
ignorePseudoClasses: ["deep"],
},
],
"selector-pseudo-element-no-unknown": [
true,
{
ignorePseudoElements: ["v-deep", "v-slotted", ":deep", ":slotted"],
},
],
},
},
{
customSyntax: "postcss-scss",
extends: [
"stylelint-config-recommended-scss",
"stylelint-config-recommended-vue/scss",
],
files: ["*.scss", "**/*.scss"],
},
],
plugins: ["stylelint-prettier"],
rules: {
"prettier/prettier": true,
// 自定义未知规则
"at-rule-no-unknown": [
true,
{
ignoreAtRules: [
"extends",
"ignores",
"include",
"mixin",
"if",
"else",
"media",
"for",
"at-root",
"tailwind",
"apply",
"variants",
"responsive",
"screen",
"function",
"each",
"use",
"forward",
"return",
],
},
],
// 不校验兜底字体
"font-family-no-missing-generic-family-keyword": null,
// 不校验自定义函数
"function-no-unknown": null,
// 不校验import引入方式
"import-notation": null,
// 不校验媒体查询范围
"media-feature-range-notation": null,
// 不校验网格布局
"named-grid-areas-no-invalid": null,
// 不校验同类选择器的优先级
"no-descending-specificity": null,
// 不校验空文件
"no-empty-source": null,
// 校验类规则之间的空行
"rule-empty-line-before": [
"always",
{
ignore: ["after-comment", "first-nested"],
},
],
// 校验类命名
"selector-class-pattern":
"^(?:(?:o|c|u|t|s|is|has|_|js|qa)-)?[a-zA-Z0-9]+(?:-[a-zA-Z0-9]+)*(?:__[a-zA-Z0-9]+(?:-[a-zA-Z0-9]+)*)?(?:--[a-zA-Z0-9]+(?:-[a-zA-Z0-9]+)*)?(?:[.+])?$",
// 不校验 not 语法
"selector-not-notation": null,
// 不校验自定义属性
"custom-property-pattern": null,
// 不校验伪类未知属性
"selector-pseudo-class-no-unknown": null,
// 不校验自定义export属性
"property-no-unknown": null,
// 不校验浏览器前缀
"property-no-vendor-prefix": null,
// 不校验动画命名规则
"keyframes-name-pattern": null,
},
};10.4 Commitlint
Using commitlint to enforce rules for git commits.
commitlint.config.js
export default {
extends: ["@commitlint/config-conventional"],
rules: {
"type-case": [0],
"type-enum": [
2,
"always",
[
"feat",
"fix",
"docs",
"style",
"refactor",
"perf",
"test",
"build",
"ci",
"revert",
"other",
],
],
"scope-case": [0],
"scope-empty": [2, "never"],
"subject-case": [0, "never"],
"subject-full-stop": [0, "never"],
"header-max-length": [0, "always", 120],
},
};10.5 Code-spell-checker
Use code-spell-checker to perform code spell checking.
cspell.json
{
// 版本
"version": "0.2",
// 检查的语言
"language": "en,en-US",
// 允许使用复合词
"allowCompoundWords": true,
// 被认为是正确的单词列表
"words": [
"axios",
"paratera",
"blsc",
"bscc",
"iconfont",
"pinia",
"wechat",
"nprogress",
"istio",
"pkce",
"echarts",
"weixin",
"vite",
"cros",
"jsencrypt",
"dompurify",
"mockjs",
"remixicon",
"vuex",
"exceljs",
"matomo",
"Piwik",
"brotli",
"npmmirror",
"monoplasty",
"rushstack",
"argb",
"cascader",
"backtop",
"popconfirm",
"msgbox",
"acmr",
"antd",
"antdv",
"astro",
"brotli",
"clsx",
"defu",
"demi",
"echarts",
"ependencies",
"esno",
"etag",
"execa",
"iconify",
"iconoir",
"intlify",
"ipaddr",
"lockb",
"logininfor",
"lucide",
"mkdist",
"vitejs",
"noopener",
"noreferrer",
"nprogress",
"nuxt",
"oper",
"operlog",
"prefixs",
"publint",
"Qqchat",
"qrcode",
"shadcn",
"sonner",
"sortablejs",
"styl",
"taze",
"ui-kit",
"uicons",
"unref",
"vitepress",
"vnode",
"vueuse",
"yxxx",
"unocss",
"attributify",
"svgicon",
"amfe"
],
// 要忽略检测的单词列表
"ignoreWords": [
"userinfo",
"userid",
"readed",
"unplugin",
"mcode",
"persistedstate",
"selfservice",
"accountapply",
"sess",
"jobname",
"jobid",
"preinstall",
"cusers",
"pcas",
"invoicable",
"supercomputing",
"cmscp",
"headerbtn",
"qrcode-mpdefault",
"realname",
"resultv2",
"VVIP",
"hasPermi",
"checkPermi",
"clientid",
"Relogin",
"ctyk",
"yjk",
"unmatch",
"strs",
"gitee",
"auths",
"jpge",
"webp",
"pxtorem"
],
// 总是被认为是不正确单词的列表
"flagWords": [],
// 忽略iconfont相关目录的拼写检测
"ignorePaths": ["**/node_modules/**", "**/dist/**", "**/icons/**", "npm-lock.yaml", "**/*.log", "src/assets/**"]
}10.6 Husky
Consider using Husky in the future to perform pre-commit code validation fixes and submission description standardization checks.
Husky and lint-staged (lint-staged calls prettier and eslint) are used for code validation and fixes. Husky and commitlint are used for checking commit message descriptions.
Discussion
Support the author