参考文献
规范目的
提高团队协作效率 便于后期优化维护 输出高质量的产品
目录文件夹及子文件规范
src 源码目录
|-- api 接口目录
| |-- xxx1.js 以业务大模块命名的js
| |-- xxx2.js 以业务大模块命名的js
|-- assets 静态资源目录
| |-- imgs 放置图片的文件目录
| |-- css 放置样式或字体图标的文件目录
| |-- |-- common.scss 一些通用的样式
| |-- fonts 放置字体图标的文件目录
|-- components 公用组件目录
| |-- Table 公用Table文件目录(PascalCase)
| |-- |-- index.vue 公用Table入口文件
| |-- Pagination 公用Pagination文件目录
| |-- |-- index.vue 公用Pagination入口文件
| |-- index.js 注册全局公用组件的入口js文件
|-- constant 常量目录
| |-- index.js
|-- enums 枚举目录
| |-- index.js 枚举入口
| |-- enums.js
|-- directives 指令目录
| |-- permission 权限指令相关目录
| |-- |-- index.js 注册指令
| |-- |-- permission.js
| |-- debounce 防抖指令相关目录
| |-- |-- index.js 注册指令
| |-- |-- debounce.js
|-- filters 过滤器目录(模板插值和v-bind表达式)(vue3中config.globalProperties和provide)
| |-- index.js
|-- mixins 混入目录(vue3中更推荐使用组合式函数composables)
| |-- xxxMixin.js
|-- request 对axios或fetch封装目录
| |-- index.js
|-- utils 工具集合目录
| |-- index.js 引入并输出各个工具
| |-- validate.js
| |-- format.js
|-- mock 模拟接口,临时存放
|-- router 路由目录
| |-- index.js 路由入口文件
| |-- modules 各个业务模块目录
| |-- |-- xxxa.js
| |-- |-- xxxb.js
|-- store vuex目录(pina的话store目录建平铺xxx.js即可,命名usexxxStore)
| |-- index.js 入口文件
| |-- modules 各个业务模块目录
| |-- |-- xxxa.js
| |-- |-- xxxb.js
|-- views 视图目录
| |-- staffWorkbench 视图模块名目录
| |-- |-- index.vue 模块入口页面
| |-- |-- components 模块页面级组件文件夹
| |-- |-- |-- AaBb.vue 模块页面级组件文件夹
|-- App.vue 根组件
|-- main.js 应用入口
- src/api/xxx1.js 代码示例 (每个导出的 api 以 Request 结尾)
import request from "@/request";
/**
* @desc 登录 [接口地址]
*/
export function loginRequest(data) {
return request({
url: "/vue-element-admin/user/login",
method: "post",
data,
});
}
- src/components/index.js 代码示例 (待测试)
// webpack项目中 require.context(需要读取模块的文件的所在目录, 是否遍历子目录, 匹配的规则(正则表达式))
import Vue from "vue";
const components = require.context("./components", true, /\/index.vue$/);
// const model = require.context('./components', true, /\.vue$/)
components.keys().forEach((componentKey) => {
const component = components(componentKey).default;
Vue.component(component.name, component);
});
for (const key in components) {
const component = components[key].default;
Vue.component(component.name, component);
}
// vite项目中
import Vue from "vue";
const components = import.meta.globEager("./components/*.vue");
for (const key in components) {
const component = components[key].default;
Vue.component(component.name, component);
}
- src/store/modules/user.js 代码示例
import { loginRequest } from "@/api/user";
import { getToken } from "@/utils/auth";
/**
* @desc 使用示例 computed: {...mapState(['token']), ...mapState('moduleName', ['aa', 'bb']),}
*/
const state = {
token: getToken(),
roles: [],
};
/**
* @desc 使用示例 computed: {...mapGetters(['getRolesByType']), ...mapGetters('moduleName', ['aaGetter', 'bbGetter']),}
*/
const getters = {
getRolesByType: (state) => (type) => {
return state.roles?.filter((role) => role.type === type);
},
};
/**
* @desc 使用示例 methods: {...mapMutations(['SET_TOKEN']), ...mapMutations('moduleName', ['setAa', 'setBb']),}
*/
const mutations = {
SET_TOKEN: (state, token) => {
state.token = token;
},
SET_ROLES: (state, roles) => {
state.roles = roles;
},
};
/**
* @desc 使用示例 methods: {...mapActions(['login']), ...mapActions('moduleName', ['setAaAsync', 'setBbAsync']),}
*/
const actions = {
async login({ commit }, userInfo) {
const { token, roles } = await loginRequest({ ...userInfo });
commit("SET_TOKEN", token);
commit("SET_ROLES", roles);
},
};
export default {
namespaced: true,
state,
getters,
mutations,
actions,
};
- src/store/index.js 代码示例
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
const modulesFiles = require.context("./modules", true, /\.js$/);
const modules = modulesFiles.keys().reduce((modules, modulePath) => {
// set './user.js' => 'user'
const moduleName = modulePath.replace(/^\.\/(.*)\.\w+$/, "$1");
const value = modulesFiles(modulePath);
modules[moduleName] = value.default;
return modules;
}, {});
const store = new Vuex.Store({
modules,
});
export default store;
- src/enums/index.js 代码示例
class EnumFactory {
constructor(configs) {
this.configs = Object.freeze(configs)
const options = []
Object.keys(configs).forEach(key => {
options.push(configs[key])
Object.defineProperty(this, key, {
get: () => this.configs[key].value,
});
});
this.options = Object.freeze(options)
}
getLabelByVal(val) {
return this.options.find(item => item.value === val).label
}
getAllVals() {
return this.options.map(item => item.value);
}
}
export function createEnumFactory(configs) {
return new EnumFactory(configs)
}
- src/enums/enums.js 代码示例
import { createEnumFactory } from "./index.js";
const TodoEnum = createEnumFactory({
PENDING: { label: '进行中', value: 'pending' },
DONE: { label: '已完成', value: 'done' },
CANCELED: { label: '已取消', value: 'cancel' }
})
const StausEnum = createEnumFactory({
PENDING: { label: '审批中', value: 'pending' },
DONE: { label: '审批通过', value: 'approve' },
CANCELED: { label: '审批拒绝', value: 'refuse' }
})
console.log('todoEnum', TodoEnum.options, TodoEnum.PENDING, TodoEnum.getLabelByVal('done'), TodoEnum.getAllVals())
命名规范(让团队当中其他人看你的代码能一目了然)
- 常量文件命名规范 A_B
const MAX_COUNT = 10;
const URL = "https://www.baidu.com/";
- 增强代码可读性,推荐使用枚举或英文代替数字等类型
- 变量命名规范 小驼峰 aaBbCc
// 数字类型 count total amount 等跟数量相关的
// 布尔类型 isxxx、canxx、hasxxx、disabledxxx、needxxx、showxxx、
// 数组类型 xxxs、xxxList、
// api方法 xxxRequest
// 获取列表 getList getxxxList
// 设置 setxxx
// 获取 getxxx getxxxByxxx
// mixin xxxMixin
// ref xxxRef
// 动作相关的 add delete update search view save reset change
- class 命名
aa-bb
// 容器 xxx-container sidebar-container main-container breadcrumb-container app-container
// 导航:nav 子导航:subnav 菜单:menu 面包屑:crumb 选项卡:tab 标题区:header/title 列表:list 表格: table 表单: form
// 提示:tips 新闻:news 下载:download
日志规范
console.error("xxx---error:", error);
console.log("xxx---start");
console.log("xxx---end");
console.log("data:", data);
编码规范(使用 ES6 风格编码)
- 能使用 const 的地方使用
const - 静态字符串一律使用单引号或反引号,动态字符串使用反引号 ``
- 使用数组成员对变量赋值时,优先使用解构赋值。
- 使用扩展运算符(...)拷贝数组。
- 优先使用 Map 结构
- 使用
===和!== - 字符串 includes(), startsWith(), endsWith()
- 函数参数的默认值 + 箭头函数
- 数组 includes()
- 对象 Object.is() Object.hasOwn()
- 运算符 ?. ??
- Reflect.ownKeys(target)
- for...of 循环
- async await 取代 then
vue 项目规范
- 组件命名规范
<my-component-name>和<MyComponentName>都是可接受的 (个人推荐<MyComponentName>) - 事件
// onXxx
<button @click="onLogin">click me</button>
// 自定义事件key @kebab-case
<MyComponent @some-event="onXxx" />
// 触发 camelCase
<button @click="$emit('some-event')">click me</button>
- 引号
// template中使用双引号(属性等)
// js中推荐使用单引号
- props 最好带有类型 默认值 和 校验器, .vue文件中使用的是字符串模板,可以统一命名prop方式为小驼峰
props: {
propD: {
type: Number,
default: 100
},
propF: {
validator: function (value) {
// 这个值必须匹配下列字符串中的一个
return ['success', 'warning', 'danger'].includes(value)
}
}
}
- 模板中表达式尽可能简单
- 组件命名
大驼峰命名(index.vue 除外) - 文件目录名
aaBbCc - 避免 v-if 和 v-for 同时用在一个元素上(性能问题),建议通过 computed 等转化
- v-if 和 v-show 使用要有原则
- v-for 上的 key 不要使用索引
- keep-alive
- 事件代理
- 列表 Object.freeze()
- 减少 watch 使用
- 优先使用 computed
- 第三方插件的按需引入
- 组件按需加载
prettier eslint(前提:vscode 安装 ESLint 和 Prettier 插件 和 设置 Format On Save + vscode配置默认格式化工具选择prettier)
- .prettierrc | .prettierrc.json (不要出现格式错误)
{
"printWidth": 200,
"semi": true,
"singleQuote": true,
"useTabs": false,
"tabWidth": 2,
"trailingComma": "es5",
"arrowParens": "avoid",
"proseWrap": "preserve",
"endOfLine": "auto",
"bracketSpacing": true,
"stylelintIntegration": false,
"eslintIntegration": false,
"tslintIntegration": false,
"disableLanguages": [
"vue"
],
"htmlWhitespaceSensitivity": "ignore",
"ignorePath": ".prettierignore"
}
- .prettierignore
dist/
node_modules/
package-lock.json
- .eslintrc
// off: 允许(关闭检测规则:0) warn:提醒级别(1) error:错误级别(2)
rules: {
'no-alert': 2, // 禁止使用alert confirm prompt
'no-debugger': 1, // 禁止使用debugger
'no-console': 0, // 允许使用console
'prefer-const': 1, // 建议使用 const
'no-dupe-keys': 2, // 在创建对象字面量时不允许键重复 {a:1,a:1}
'no-dupe-args': 2, // 函数参数不能重复
'no-duplicate-imports': [2, { 'includeExports': true }], // 不允许重复导入
'no-duplicate-case': 2, // switch中的case标签不能重复
'padded-blocks': 0, // 块语句内行首行尾是否要空行
'space-after-keywords': [0, 'always'], // 关键字后面是否要空一格
'space-before-blocks': [0, 'always'], // 不以新行开始的块{前面要不要有空格
'space-before-function-paren': [0, 'always'], // 函数定义时括号前面要不要有空格
'space-in-parens': [0, 'never'], // 小括号里面要不要有空格
'space-infix-ops': 0, // 中缀操作符周围要不要有空格
'eqeqeq': 2, // 必须使用全等
'no-var': 2, // 禁用var,用let和const代替
'no-unused-vars': 1, // 提醒没有用到的变量
'no-extra-boolean-cast': 2, // 禁止不必要的bool转换
'valid-jsdoc': 0,
'one-var': 0, // 允许连续声明
'semi-spacing': [0, { before: false, after: true }], // 分号前后空格
'no-new': 0, // 禁止在使用new构造一个实例后不赋值
'no-extra-semi': 0, // 禁止多余的冒号
'keyword-spacing': 0,
'arrow-parens': 0, // 箭头函数用小括号括起来 - 关闭
'generator-star-spacing': 0, // 生成器函数*的前后空格
'no-mixed-operators': 0,
'eol-last': 0, // 文件以单一的换行符结束 - 关闭
'object-curly-spacing': 0, // 大括号内是否允许不必要的空格
'vue/no-deprecated-v-bind-sync': 1, // 提醒 v-bind-sync
'vue/no-deprecated-slot-attribute': 1, // 提醒 deprecated-slo
'vue/multi-word-component-names': 1, // 提醒 multi-word-component
'vue/no-deprecated-v-on-native-modifier': 1, // 提醒 .native
'no-undef': 0, // 关闭 未定义的
}
- .eslintignore
dist/
node_modules/
package-lock.json
vscode 插件
auto close tab
auto rename tag
chinese
eslint
gitlens
es6 code snippets
markdown preview enhanced
open in browser
path autocomplete
prettier
prettify json
relative path
todo highlight
vetur
volar
copilot codewhisper codegeex
前端开发插件
(谷歌浏览器 + nvm + node(.msi) + git + postman + sourcetree + http-server)