14. Ant-Design-Vue Form校验自定义组件校验方法
首先在Form.Item中定义name属性,然后写一个表单项自定义校验函数, 这里有一点要注意一下,如果自定义校验函数的校验值与表单数据某个响应式key值相对应,自定义表单项校验时机是实时的,表单项响应式数据发生改变,校验结果也会随之改变,如果自定义表单项校验函数中的校验值不是表单数据的某个响应式值,则需要用watch函数,手动触发表单项实时校验。
html
<!-- 群聊 -->
<Form.Item
v-else-if="formData.pushType === 2"
label=""
name="groupChatList"
:rules="[{ required: true, validator: validateGroupChatList }]"
>
<FormSelectStaffGroupButton
v-model:value="formData.groupChatList"
:maxCount="200"
:disabled="isOnlyRead"
btnClassName="select-group-btn"
>
<template #button> 选择群聊</template>
<template #list>
<div v-if="formData.groupChatList?.length" style="margin: 10px 0 10px 0">
<div><span class="text">已选员工:</span>{{ Object.keys(staffGroupChatList.groups).length }}个</div>
<div><span class="text">已选群聊:</span>{{ formData.groupChatList.length }}个</div>
<div
v-for="(chatNames, groupOwnerName) in staffGroupChatList.groups"
:key="groupOwnerName"
class="staff-group-chat-list"
>
<span class="name">{{ groupOwnerName }}: </span>
<div>{{ chatNames.join('、') }}</div>
</div>
</div>
</template>
</FormSelectStaffGroupButton>
</Form.Item>
<script>
// 选择群聊校验
const validateGroupChatList = (rule: any, value: any, callback: any) => {
if (formData.groupChatList.length === 0) {
return Promise.reject(new Error('请至少选择一个聊天群'));
} else {
return Promise.resolve();
}
};
</script>
手动触发表单项实时校验的写法
ts
watch(
staffGroupChatList,
() => {
eventFormRef.value.validateFields(['groupChatList']);
},
{ deep: true }
);
// 选择群聊校验
const validateGroupChatList = (rule: any, value: any, callback: any) => {
if (Object.keys(staffGroupChatList.value.groups).length === 0) {
return Promise.reject(new Error('请至少选择一个聊天群'));
} else {
return Promise.resolve();
}
};
13. 一些时间禁选函数
js
// 禁选某个时间段范围之外的日期
const disableOutsideRanage = (current: Dayjs) => {
const {startDate,endDate}=props;
return (
(current && current < dayjs(startDate).startOf('day')) ||
current > dayjs(.endDate).endOf('day')
);
};
// 只能选择未来的时间 且 结束与起始时间跨度不能超过100天
const disabledPastDate = (current: Dayjs) => {
const [startDate] = formData.strategyRange;
if (
current < dayjs().startOf('day') ||
current.endOf('day').diff(startDate.startOf('day'), 'day') > 99
) {
return true;
}
return false;
};
// 查找某个时间段内是否存包含每周某一天
function findDayOfWeek(start: Dayjs, end: Dayjs, findDayOfWeek: number) {
const dayOfWeek = findDayOfWeek === 7 ? 0 : findDayOfWeek;
let current = start;
while (current <= end) {
if (current.day() === dayOfWeek) {
return true;
}
current = current.add(1, 'day');
}
return false;
}
// 查找某个时间段内是否存包含每月某一天
function findDayOfMonth(start: Dayjs, end: Dayjs, findDay: number) {
let current = start;
while (current <= end) {
if (current.date() === findDay) {
return true;
}
current = current.add(1, 'day');
}
return false;
}
// 禁选当前时间10分钟之后的小时
const disabledHours = () => {
const today = dayjs();
const selectDay = formData.date;
// 如果是当天
if (dayjs(selectDay).isSame(today, 'day')) {
let baseMin = dayjs().minute();
// 预留10分钟缓冲时间
baseMin = dayjs().add(10, 'minute').minute();
// 计算出来的当前小时分钟数超过当前的50分钟时,禁选当前小时
let curHour = dayjs().hour();
if (baseMin > 50) {
curHour = curHour + 1;
}
// 禁止当前小时之前的小时
return range(0, 24).filter((hour) => hour < curHour);
}
return [];
};
// 禁选当前时间10分钟之后的分钟选择
const disabledMinutes = (selectedHour: number) => {
const selectDay = dayjs(formData.date);
// 如果是当天
if (selectDay.isSame(dayjs(), 'day')) {
let baseMin = dayjs().minute();
// 预留10分钟缓冲时间
baseMin = dayjs().add(10, 'minute').minute();
// 禁选当前小时之前分钟
if (selectedHour < dayjs().hour()) {
return range(0, 59);
} else if (selectedHour === dayjs().hour()) {
// 禁选当前小时当前分钟之前的分钟
return range(0, 59).filter((minute) => minute < baseMin);
}
}
// 如果是当天之后的时间,每小时的0-59都可选择
return [];
};
// 生成时间范围
const range = (start: number, end: number) => {
const result = [];
for (let i = start; i < end; i++) {
result.push(i);
}
return result;
};
12. 四个交互优化点
- 表单元素的placeholder写一些套话,不如把校验规则写出来,减少用户犯错,更好一些
- 动态展示图标时,让图标和文字在垂直方向对齐,更美观。
- 二次确认弹窗, 非强制用户选择的,如果用户点击了页面上别的地方, 要自行进行关闭,不要妨碍用户的视线。
- 灵活的设置弹窗位置,有些弹窗位置适合固定,有些弹窗位置,现在在浏览器窗口的固定位置,操作更方便
11. Ant-Design-Vue RangePicker组件将值设置成字符串类型,取用更方便
如果不需要设置初始时间值的话,推荐加上valueFormat="YYYY-MM-DD"
属性,
html
<RangePicker
format="YYYY-MM-DD"
valueFormat="YYYY-MM-DD"
:placeholder="['开始时间', '结束时间']"
v-model:value="formData.taskClientRule.addTime"
/>
定义的时候可以直接赋空字符串
ts
const formData = reactive({
taskClientRule: {
// ...
addTime: ['', ''],
},
}
提交数据的时候, 可以直接取出YYYY-MM-DD
字符串类型的日期数值提交给后端服务器, 省去了用dayjs().format('YYYY-MM-DD')
来回转。
10. 前端要引导后端,定义合理的接口数据类型
至于为什么要这么做,请看下面的反例展示,excludeTags
原本应该定义成一个string[]
, materials
也应该定义成一个Item[]
, 而前端未告知后端,后端也没仔细看需求文档,定义接口数据类型怎么省事怎么来,把这两个字段都定义成了字符串,结果就是:
js
// 在编辑和详情页面,需要把字符串转成数组
formData.taskClientRule.excludeTags =
excludeTags === '' ? [] : excludeTags?.split(',').map((item) => Number(item)) || [];
formData.materials = isValidJson(materialsStr) ? JSON.parse(materialsStr) : [];
// 在提交数据时, 又得将数组转换成字符串
params.excludeTags= formData.taskClientRule.excludeTags.join(','),
params.materialsStr = JSON.stringify(materialContent.materials || []);
不合理的数据类型设计, 既多了一些额外的转换,也增加了产生bug的概率,上例中的Number(item)
和isValidJson(materialsStr)
都是因为自测出bug,才添加上去的。
9. Vue3 watch监听ref定义的对象的两种写法差别
ts
let obj = ref({
name:'juejin',
age:19
})
// 带.value的写法监视的是RefImpl对象,不用写{deep:true}
watch(obj.value,(new,old)=>{
console.log('obj发生了变化',new,old)
})
// 不带.value的写法,需要开启{deep:true}参数
watch(obj,(new,old)=>{
console.log('obj发生了变化',new,old)
},{deep:true})
在这里特别说明一下RefImpl,RefImpl是 reference+implement,翻译为引用实现。也就是说在vue3里面,如果想把数据变成响应式的,应该用ref函数包裹,也就是把该数据变成引用实现的实例对象。
8. 空值合并运算符一个需要注意的小点
js
let obj={};
// 如果a是一个属性,下面的写法没问题
obj?.a
// 如果a是一个函数,下面的写法就会报错
obj?.a()
// 正确的写法是
obj?.a?.()
// 如下的写法也不会报错
obj?.a?.b()
也就是说判断a是否存在和运行a()是同时执行的, 遇到这种情况,要进行两次判空。
7.容易产生bug的地方?
- 异常流程处理欠周 比如说上传清单文件,遗漏了上传文件失败清空数据
- 接口数据定义不合理,比如说前端的多选组件,值的类型为数组,数组中的每个值为数字,后端将接收多选数据的字段定义成字符串,在处理字符串转数组的过程中,有可能忽略数据类型的转换
- 联动逻辑,比如说要判断某个时间点是否已过,要结合选择的日期和小时分钟做判断,如果改变了 小时分钟的禁选逻辑,那么就要考虑一下,选择日期的逻辑是否也会引起变更。
- 遗漏了一些场景的测试,比如说有多个tab, 某个tab与其它的tab展示逻辑不一样, 只看了一下展示相同的tab的逻辑,遗漏了展示不同的tab逻辑。
- 合并代码解决冲突时, 当文件中的代码出现大段的冲突时, 可能会由于不仔细,产生代码合并错误,造成bug。
6.企业微信中external_userid,userid和unionId三者的关系?
企业微信下的corpid, userid, external_userid的概念可查询此文
- 其中企业微信中的账号ID(即userid)如果是由系统自动生成的,将允许被修改一次。
- external_userid由企业微信系统生成,企业无法在管理端页面中查看,只能通过 API 获取到,同一个客户在企业的不同自建应用,获取到的external_userid是相同的。
- unionid的用途是微信中为了区分同一个用户在不同应用下的唯一身份。可以用unionid查询出客户的external_userid.
5.eslint在package.json中设置忽略对某个文件校验的方法
比如说想设置对src/jimo-tracker-sdk/lib/index.js
,语法如下:
json
"lint-staged": {
"src/**/*.{js,ts,jsx,tsx}": [
"eslint --fix --ignore-pattern 'src/jimo-tracker-sdk/lib/index.js'"
],
"**/*.{js,jsx,ts,json,html,css,less,scss,md}": [
"prettier --write"
]
},
4.postman中的Authorization字段,对应的是fetch请求中哪个字段?
对应关系如下:
js
fetch('https://example.com/data', {
method: 'GET',
headers: {
'Authorization': 'Bearer ' + token
}
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error(error));
3.数组快速填充方法
js
// 方法一
let obj = Array(10).fill({name:'王五',age:18});
// 方法二 用于只想生成一个用来遍历10次,执行其它逻辑的数,组
const arr = [...Array(10).keys()];
//[ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ]
// 方法三
const arr = Array.from(Array(10), (item, index)=>({name:'王五',age:18}));
// 方法四
const arr = Array.from({length:10},(item,index)=>({name:'王五',age:18}));
2.Vue3对标React-DnD的库是哪个?
试了好几个vue3的拖拽库,不是使用文档不详尽,就是只支持vue2
- vue-draggable
- vue3-dnd
- vue-smooth-dnd
- vue-easy-dnd
- v-drag
最后找个这个库vue-draggable-plus,按照文章描述,很Easy的就把拖拽效果实现了,如果大家在vue3项目需要用到拖拽功能的话,推荐大家使用这个库。
1.企业微信的大坑
首先说一下Webview的概念。webview 是一个基于webkit的引擎,可以解析DOM 元素,展示html页面的控件,它和浏览器展示页面的原理是相同的,类似浏览器。webview通常用于原生应用加载html页面,这样做的好处是:应用内容如果采用的是webview+从服务器端获取html+css+js文件的话, 更新应用内容无需用户下载安装最新版本的应用安装包,只要新功能部署完成,用户重新进入应用之后,应用会自动刷新,就可以看到最新的功能。企业微信内置浏览器内核在IOS是webkit,在Android上以前是x5,现在是Chromium。
企业微信App v4.1.6版本,内置的WebView版本是107, 自建H5应用可以正常打开,而v4.1.6以上版本,内置的WebView版本是110,自建H5应用无法打开。我从v4.1.16逐个版本往下降,降到v4.1.6才正常了。还有一点比较诡异,就是v4.1.6以上的企业微信版本,刚开始安装完,内置的WebView显示的版本是70,自建H5应用打开正常,过个十几分钟后,内置的WebView版本会变成110,这个时候自建H5应用就打不开了。