文件编码
统一使用utf-8无BOM格式;
命名规范
● 文件、类型、枚举类型、Interface使用PascalCase命名法, 如果文件内有且只有一个成员,则文件名应和该成员相同。
● 常量、枚举值使用UPPER_CASE 命名法
● 本地变量、函数、对象属性、函数形参使用camelCase命名法
● 资源文件命名使用camelCase命名法
● css的class命名名用纯小写单词, 多单词间用减号-隔开, 如下
js
<templete>
<view class="test">
<text class="test-class">aaa</text>
<img class="test-img"></img>
<view>
</templete>
● Vue组件页面级别使用camelCase, 普通级别使用PascalCase
● 文件夹统一使用camelCase命名法
属性定义规范
● 禁止使用var声明变量, 推荐使用let和const分别声明可变和不可变的变量
ts
class Demo {
test() {
var a: number = 1; // 禁止
let a: number = 1; // 正确
const a: number = 1; // 正确
}
}
● 定义属性时必须有类型
ts
class Demo {
num = 1; // 禁止
num: number = 1; // 正确
}
● 避免使用any类型
除底层框架以外, 禁止在业务层代码中出现使用any类型定义的属性
ts
interface DrugDetail {
drugName: any; // 禁止
images: any[]; // 禁止
drugName: string; // 正确
images: DrugImage[]; // 正确
}
interface DrugImage {
id: number;
url: string;
action: string;
}
● 简单且确认不会被共用的类型可以使用匿名类型定义, 其他的必须用显示类型
ts
interface PrescriptionDetail {
patientInfo: { name: string, age: number }; // 禁止, 因为跟OrderDetail中的patientInfo类型重复
patientInfo: PatientInfo; // 正确
}
interface OrderDetail {
patientInfo: { name: string, age: number }; // 禁止, 因为跟PrescriptionDetail中的patientInfo类型重复
patientInfo: PatientInfo; // 正确
}
interface PatientInfo {
name: string;
age: number;
}
● 业务上有可能为空的字段必须使用联合类型定义, 并注明什么场景下会为空
ts
interface PatientInfo {
/** 患者信息备注, 有可能为null */
remark: string; // 禁止
/** 患者可不填写自身备注信息 */
remark: string|null; // 正确
}
● 函数有返回值的必须声明返回值类型, 禁止使用类型推断特性
js
class Demo {
test() { // 禁止
return 1;
}
test(): number { // 正确
return 1;
}
}
注释
注释不允许出现在包含代码的行内, 必须单独占用空行
● 类, 枚举类型, Interface必须声明注释, 使用/** */包裹注释内容, 注释内容包含: 作用、使用方法、作者等信息
js
/**
* 作用: 用来封装项目内的三级缓存策略流程
* 用法: 继承Cache类, 定义泛型T为数据具体类型, 实现其中abstract方法, 建议完整子类都是单例模式
* 作者: zhangdi
*/
class abstract Cache<T> {
}
● 函数(方法)必须声明注释, 使用/** */包裹注释内容, 注释内容包含: 作用、参数列表说明(如果有)、返回值说明(如果有)、作者等信息
js
/**
* ...
/*
class LoginService {
/**
* 根据患者id获取患者用户信息
* @param patientId 患者id
* @return 如果从缓存中能查到就返回PatientInfo对象, 否则返回null
*/
private async getPatientInfo(patientId: number): Promise<PatientInfo|null> {
...
}
/**
* 根据患者id获取患者用户信息
* @return 需要展示返回true, 否则返回false
*/
private shouldVisible(): boolean {
...
}
}
● 类、Interface成员变必须声明注释, 使用/** */包裹注释内容, 注释内容为字段用途, 特殊字段需详细说明取值及作用
js
interface OrderDetail {
/** 订单id */
id: number;
/** 订单状态 1.未支付 2.已支付 3.已取消 4.已结束 */
status: number;
}
class HomePage extends Vue {
/** banner数据, 展示在页面顶部 */
private banner: Banner[]|null;
}
● 代码块内, 如有较复杂逻辑需要特殊说明, 可以用单行注释// 内容或多行注释/* 内容 */声明
js
class MergeSort {
public static async login(): Promise<UserInfo> {
let userInfo = await UserInfoCache.getInstance().get();
// 登录接口的用户信息分了2级返回, 以前有的场景只保存了第一层数据, 为了兼容, 只保存第一层数据的账号让他重新登录下
if (userInfo && userInfo.userId) {
return userInfo;
}
return new Promise(async (resolve, reject) => {
/*
* 1. 先获取session, 如果获取不到直接抛出异常并返回
* 2. 获取session成功后判断是否可以自动登陆, 如需自动登陆直接调接口登陆并返回用户信息
* 3. 如果不能自动登录则跳转到登陆页面, 并把resolve和reject函数保存到LoginService.resolve和LoginService.reject中, 登陆成功后由登陆页面调用LoginService#loginSuccess方法触发promise返回值
*/
// 获取session
let session = await SessionCache.getInstance().get();
if (!session) {
reject(new UnknownError('获取Session失败'));
return;
}
// 判断是否可以自动登录
if (!session.autoLogin) {
Tips.hideLoading();
LoginService.resolve = resolve;
LoginService.reject = reject;
DNavigator.navigateTo(`/pages/login/index`);
} else {
Tips.showLoading();
let userInfo = await Service.autoLogin(session.sessionId);
UserInfoCache.getInstance().update(userInfo);
Tips.hideLoading();
resolve(userInfo);
LoginObservable.notify(userInfo);
}
});
}
}
TODO注释
因联调导致需要要提交功能未完善的代码, 必须在未完成的代码区块添加TODO注释, 署名并写清楚未来需要完善什么东西, 建议在写代码的过程中随手写TODO, 防止遗忘功能:
js
startDrugDetail() {
// TODO zhangdi: 等药品详情页面搞完实现跳转逻辑
}
相等条件
禁止使用和!=做相等条件判断, 应使用=和!==判断
js
a == 1; // 禁止
1 != 2; // 禁止
a === 1; // 正确
1 !== 2; // 正确
空格
● 在单目运算符之间不追加空格:
js
-b
!!b
● 如果单目运算符是一个单词,则添加一个空格:
js
new Func
void 0
typeof x
● 在双目运算符左右追加一个空格:
js
x + y * 3
a = b + 1;
-x + ++y
● 在冒号、逗号、分号后追加一个空格(末尾除外):
js
a = 1, b = 2
var a: number = 1;
● 在 if、for、while、switch、with 语句的括号前后追加空格:
js
if (a > 1) {
}
for (var i = 1; i < 2; i++) {
}
do {
} while (a < 1);
● 在函数声明的签名前后追加空格:
js
function fn(a, b) {
}
● 在匿名函数的签名前不追加空格
js
var fn = function(a, b) {
};
● 不建议为了对齐某些代码而手动添加空格,如下代码是不建议的:
js
var hello = 1;
var hi = 2; // 不建议在此次故意追加空格以保持 = 对齐。
var not = 3; // 不建议在此次故意追加空格以保持 = 对齐。
● 但是可以为了对齐注释而添加空格。
js
var hello = 1; // 对齐的注释。
var hi = 2; // 对齐的注释。
var not = 3; // 对齐的注释。
新行
● 建议将 else、catch、finally 等子段写在一行:
js
if (a > 1) {
} else if (b > 1) {
} else {
}
try {
} catch(e) {
} finally {
}
● 我们允许将空且永久为空的 {} 写在一行如:
js
var obj = {};
function empty() {}
● 如果 {} 和 [] 内包含多个复杂的项,应分多行书写。
js
var a = {
b: 1,
c: 2,
d: [],
e: [1, 2, 3],
f: [
"aaa",
"bbb",
"ccc",
"ddd",
"eee",
]
};
逗号
建议为{} 中的每个键值对末尾添加逗号。如果这个对象未来可扩展,在最后一个键值对也应追加逗号。
js
var obj = {
a: 1,
b: 2, // 保留此逗号
};
enum A {
a = 1,
b = 2,
}
其他
语句应固定以 } 或 ; 结尾。一行内最多只能有一行语句。
不建议使用 with 语法。
对于无限循环,应使用 true 作为循环条件。
如果字符串内部是一段 HTML,则应该确保 HTML 使用双引号,外部字符串使用单引号。
允许使用 ?:、&& 和 || 代替 if 语句。
建议使用 `` 字符串书写多行字符串。
建议保持较低闭包嵌套级别。当闭包嵌套级别超过 5 时,建议您重新整理代码。
不建议使用 [] 获取常量属性。
js
a["x"] // 不建议
a.x // 建议
git提交规范
commit日志:commit日志必须以add: , update: , delete: , fixed: 开头
● add: 用于开发新需求的提交, 如
add: 开发登陆页面, 逻辑未完善
● update: 用于文件修改或需求变更
update: **产品要求登陆时需要保存手机号, 登陆后把手机号保存到localstorage
● delete: 用于删除重要文件或页面
delete: **产品要求删除登录页面
● fixed:
fixed: 修复***bug, (有链接贴链接, 没链接写清楚bug描述), bug原因
日志内容尽量写为什么做, 而不是做了什么
js
#推荐
update: 因服务端接口会在不同的接口上对patientId字段返回string和number类型, patientId不能再单纯的使用number
#不推荐
update: patientId兼容string类型
分支管理
● 纯净环境分支
- 测试环境: dev
- 生产环境: master/main
● 功能分支
所有的功能分支统一使用feature/ 开头
正确代码合并规范流程应该下面这样:
功能个人分支 => 功能总分支 => 不同的纯净环境分支
- 功能总分支
统一使用 feature/xx 开头, 从master创建
部署不同的环境时,统一使用功能分支合并到对应的纯净环境分支上面去(避免多余功能提前携带到生产) - 功能个人分支
建议使用 feature/xx_个人名字缩写 (方便排查开发者)
● 改bug分支
线上bug从master创建fixbug/开头的分支进行修复
● 提测流程
需要提测的feature或fixbug分支统一合并至dev分支, 如有并行提测需求但上线时间不同可按照dev/xx格式创建单独的提测分支, 同时新建流水线部署到不同的子文件夹进行测试
● 上线流程
测试通过的dev分支合并到master/main, 通过云效流水线发布后在master/main分支最后一次提交上打tag, 名称格式为release_功能名称, 打完tag删除feature/*分支
vue文件规范
● 文件里面需要统一在script标签上设置name,符合UPPER_CASE 命名法,作为当前文件的命名唯一性,方便开发者使用vue插件排查定位问题
js
<script lang="ts" name="RevisitPreoperRevisitJob" setup>
● 响应式创建:
鉴于reaction只能基于集合类型进行响应式创建,对解构操作不友好,替换对象响应式容易丢失等问题
建议优先使用ref() 作为声明响应式状态的主要 API;
● 响应式标注类型:两种方法
js
import { ref } from 'vue'
import type { Ref } from 'vue'
const year: Ref<string | number> = ref('2020')
// ********或者使用泛型直接声明***********
const year = ref<string | number>('2020')
菜单-路由配置
通过新增/编辑菜单,对菜单进行修改,其中常用的属性配置说明如下:
● 访问路径:当前菜单对应的访问前端路由;
(通常与前端项目目录访问地址保持一致,命名规范见"项目文件目录规范")
● 前端组件:对应@src/views目录下的默认页面对应的访问路径地址;
(通常与前端访问路径地址保持一致,例如:路由/revisit/customList,通常对应组件 地址:revisit/customList/index;命名规范见"项目文件目录规范")
● 组件名称:根据访问路径自动生成(前端组件name必须与此保持一致)
● 菜单图标:(可选),对应左侧Menus组件展示图标
● 排序:左侧菜单展示顺序(如1,2,3)配置
● 是否路由菜单:是否生成前端对应路由地址,非末级菜单可选择关闭(通常一级菜单不需要链接地址,只有末级菜单需要点击访问页面)
页面功能模块-权限配置
前端配置方向,主要分为两个方面,可见性和可编辑性
目前已封装统一函数针对两种不同场景进行处理
可见性:
按钮/权限名称:以"XXX的可见性"命名;
授权标识:使用蛇形命名法,命名规则 "一级菜单名(即功能模块名)_页面菜单名_字段名_show",如:revisit_reList_revisitTime_show
可通过hasPermission('权限编码')进行判断展示
可编辑性:
按钮/权限名称:以"XXX的可编辑性"命名
授权标识:使用蛇形命名法,命名规则 "一级菜单名(即功能模块名)_页面菜单名_字段名_edit",如:revisit_reList_revisitTime_edit
可通过isDisabledAuth('权限编码')来进行是否可编辑的配置
角色/用户配置
正确的权限规则流向:权限编码=》角色=》个人用户;
第一步:角色管理页面,对角色进行权限编码授权
第二步:用户管理页面,搜索用户,进行角色分配
第三步:刷新页面,完成当前用户的权限绑定关系
数据字典配置
新增/编辑字典
字典名称:使用蛇形命名规范,"一级菜单名(即功能模块名)_字典名"(中文)
字典编码:使用蛇形命名规范,"一级菜单名(即功能模块名)_字典名"(英文)
描述:对字段必要的解释说明
常用项:
列表可通过render.renderDict()实现对数据字典的引用,完成对应关系
下拉表单可通过设置dictCode属性,实现对数据字典的引用,完成对应关系
项目文件目录规范
紫色A:
● 业务一级菜单,为大功能模块前端路由一级路径命名,同时也是关联本次功能的总文件夹命名,使用camelCase命名法法
绿色B:
● 对应业务二级菜单,为大功能模块前端路由二级路径命名,使用camelCase命名法(若有三级菜单,以此类推......)
蓝色C:
● 为当前二级菜单对应的页面文件目录
● 同级放置对应二级菜单默认进入的页面,默认进入页面命名为index.vue(菜单对应页面)
● 同级放置非对应二级菜单默认进入的页面,例如edit.vue、detail.vue(若有:大多由默认页面跳转进入,非菜单对应页面)
● 同级放置当前功能页面的api文件,要求以XXX.api.ts格式命名,XXX符合UPPER_CASE 命名法;
● 同级放置当前功能页面的表格数据配置信息以及查询数据信息,配置信息多以大json为主;剩余包括页面对应的 Ts声明类型(interface、type、enum)等声明类型,要求以XXX.data.ts格式命名,XXX符合UPPER_CASE 命名法;
● 同级放置components文件夹,为页面提取的公共组件,以高复用性,高扩展性为基本原则进行提取,符合UPPER_CASE 命名法;