前言
项目中有些代码功能经常使用,但是零零碎碎,项目大了之后,查找起来比较费时耗力。为了加快查找效率,决心把这些功能整理一下,用的时候直接粘贴复制,小改一下,就能立刻投入使用。即便是做CV工程师,也要做一个有修为的CV工程师。
10.企微常用api
企业微信客户端api,有几个api容易搞混。
- 分享消息到当前会话
- 群发信息给客户
- 群发消息给客户群
分享消息到当前会话,只能在聊天工具栏使用。使用之前,需要调用getContext
接口判断是否从 单聊或群聊会话进入H5页面
js
wx.invoke('getContext', {
}, function(res){
if(res.err_msg == "getContext:ok"){
entry = res.entry ; //返回进入H5页面的入口类型,目前有normal、contact_profile、single_chat_tools、group_chat_tools、chat_attachment
shareTicket = res.shareTicket; //可用于调用getShareInfo接口
}else {
//错误处理
}
});
判断entry等于single_chat_tools或者group_chat_tools,就可向单聊窗口和群聊窗口发送文本,图片,视频,文件,链接,小程序。
js
wx.invoke('sendChatMessage', {
msgtype:"text", //消息类型,必填
enterChat: true, //为true时表示发送完成之后顺便进入会话,仅移动端3.1.10及以上版本支持该字段
text: {
content:"你好", //文本内容
},
image:
{
mediaid: "", //图片的素材id
},
video:
{
mediaid: "", //视频的素材id
},
file:
{
mediaid: "", //文件的素材id
},
news:
{
link: "", //H5消息页面url 必填
title: "", //H5消息标题
desc: "", //H5消息摘要
imgUrl: "", //H5消息封面图片URL
},
miniprogram:
{
appid: "wx8bd80126147df384",//小程序的appid,企业已关联的任一个小程序
title: "this is title", //小程序消息的title
imgUrl:"https://search-operate.cdn.bcebos.com/d054b8892a7ab572cb296d62ec7f97b6.png",//小程序消息的封面图。必须带http或者https协议头,否则报错 $apiName$:fail invalid imgUrl
page:"/index/page.html", //小程序消息打开后的路径,注意要以.html作为后缀,否则在微信端打开会提示找不到页面
},
}, function(res) {
if (res.err_msg == 'sendChatMessage:ok') {
//发送成功
}
})
群发消息给客户shareToExternalContact
,群发消息给客户群shareToExternalChat
, 没有必须从单聊或群聊窗口进入h5页面的限制,只要选择了客户或客户群,就能将文本,图片,视频,文件,链接,小程序发送出去。群发消息给客户,群发消息给客户群的限制是:群发消息给客户,每天只能给同一个客户发送一条消息,群发消息给客户群,也是每天只能给一个群发一条。另外再说一个api功能的区别,企微的转发功能shareAppMessage
,与群发消息给客户/群发消息给客户群的区别是转发只能发送消息给企微群内部的成员或群,每天没有转发限制,而群发消息给客户或客户群,只能是外部客户或外部客户群。
9.Vue3表单校验
因为antd没有vue版的移动端UI框架,所以移动端的UI框架,项目中使用的是Vant,由于Vue是数据双向绑定,所以设置和获取表单,比较方便。Vant中将Button设置为native-type="submit"类型,点击后就会触发表单校验。
html
<Form class="feedback-form">
<Field
v-model.trim="formState.contactContent"
class="contact-content-row"
rows="12"
autosize
name="contactContent"
required
label-width="7em"
label="详细沟通信息"
:formatter="formatter"
type="textarea"
maxlength="500"
show-word-limit
placeholder="请输入内容"
:rules="[{ validator:validContent, message: '请输入详细沟通信息' }]"
/>
</Form>
<div class="bottom-btn one-px-top">
<Button
@click="onSubmit"
round
type="primary"
native-type="submit"
class="flex-center submit-btn"
:disabled="disableSubmit"
>
确定
</Button>
</div>
<script setup>
import { Form, Field, Button} from 'vant';
const formState = ref({
feedback: null,
contactContent: '',
});
// 只能输入中文 true 表示校验通过,false 表示不通过
const validContent = (val) => /^\u4e00-\u9fa5\w]/g/.test(val);
</script>
8. React表单校验
Vue项目写得久了,忽然切换到React项目,一时有点手生,不知道表单操作的一些常用api的名称了,比如说如何取值,设置值,写校验规则..., 对表单校验,需要重新看一下antd的官方文档。这次看完之后,记录在这里,下次就不去官方文档找了,直接来这里看。
hooks组件
jsx
import { Form, message } from 'antd';
const [form] = Form.useForm();
// 设置值
form.setFieldsValue({ auditMode: 1});
// 表单校验
const values = await form.validateFields();
{auditMode === 1 && (
<Form.Item
label="初审人"
name="firstAudit"
rules={[
{ required: true, message: '请搜索初审人' },
{
validator: async (_, value): Promise<any> => {
// console.log(value, form.getFieldValue('secondAudit'));
// 获取值
if (value && value === form.getFieldValue('secondAudit')) {
return Promise.reject(new Error('初审人与复审人不可相同'));
}
},
},
]}
>
<GetUserList
multiple={false}
userIdsArr={form.getFieldValue('firstAudit')}
userListArr={firstAuditListArr}
handleChangeUserList={(values, options) => handleChangeUserList(values, options, 'firstAudit')}
placeholder="输入名称搜索,仅1人"
/>
</Form.Item>
)}
class组件
类组件和函数钩子组件操作表单的区别是表单操作对象的获取方式,api是相同的。
jsx
// 创建表单操作对象
formRef = React.createRef();
this.formRef.current.validateFields().then((values) => {
// console.log(values);
this.submitData(values.materialNo);
});
// 清除表单校验信息
this.formRef.current?.resetFields.();
<Form ref={this.formRef}>
<Form.Item
label="素材编号"
name="materialNo"
rules={[
{ required: true, message: '素材编号必填,请先填写内容' },
{ pattern: /^\d{12}$/, message: '输入编号有误,请检查后重新输入' },
]}
>
<Input maxLength={12} placeholder="请输入素材编号" />
</Form.Item>
</Form>
7. 仅在本地显示新增菜单
这段代码的使用场景是:管理后台根据不同的角色,展示不同的菜单功能。要展示的菜单是后端接口返回的,后端偷懒,开发和测试环境共用一套SQL表存储菜单路由。这就造成一个问题,页面功能还没有联调测试通过的时候,如果提前把没开发完成的页面路由添加到后端的菜单路由表里,那么测试环境也会显示还在开发中的页面,吹毛求疵的测试就会提缺陷。 此种条件下,就需要前端在后端返回的菜单路由数据中,追加正在开发的页面的路由配置,这样就能在本地让正在开发的页面显示出来。
js
// 后端返回的菜单路由列表
menu = menu.map((item) => {
// 正在开发的新页面路由
if (item.resName === '小站管理') {
item.resources.unshift({
icon: '',
level: 2,
owner: true,
parentId: 60,
resCode: 'station_quick_entry_config',
resId: 9506,
resName: '快捷入口配置',
resShortCode: 'stationQuickEntryConfig',
sort: 1
});
}
return item;
})
6.复制文本功能
业务中有个功能,需要把营销话术,发送给客户。这个功能是在vue3项目中使用的,基于vue3-clipboard
工具实现,代码很简单,就是每次查找起来,比较费事。
html
<div @click="handleCopy(detail.tips)" class="flex-center copy-topic">一键复制话术</div>
<script>
import { copyText } from 'vue3-clipboard';
const handleCopy = (text = '') => {
copyText(text, undefined, () => {
Toast('复制成功');
});
};
</script>
5. 折叠收起
这段代码的功能是: 文字长度默认只显示两行,超出部分显示省略号。点击展开图标后,显示完整内容且原来的展开图标也变成折叠图标,点击折叠图标后,文字收起,只显示两行。收集这段代码的原因是:折叠收起字体图标的名称,每次用的时候,总是要查找好大一会儿。
html
<div class="info">
<span class="label">参考话术</span>
<span :class="['value', isShowDetail && 'text-ellipsis-line-2']">{{ detail.tips }}</span>
</div>
<div class="show-more" @click="showMore">
<template v-if="isShowDetail">
<BaseIconFont type="czmingpian-gengduo" class="show-more-icon" />
</template>
<template v-else>
<BaseIconFont type="czmingpian-gengduo-shouqi" class="show-more-icon" />
</template>
</div>
<script>
const isShowDetail = ref(true);
const showMore = () => (isShowDetail.value = !isShowDetail.value);
</script>
<style>
/* 文字超出两行省略号 */
.text-ellipsis-line-2 {
display: -webkit-box;
overflow: hidden;
text-overflow: ellipsis;
-webkit-line-clamp: 2;
line-clamp: 2;
-webkit-box-orient: vertical;
}
</style>
4. 浮动按钮
这段代码实现的功能,是在移动端H5页面上,悬浮一个+号的创建任务按钮,初始化的时候会固定在页面右下方,可以手动拖拽移动位置。这是一段通用性较强的代码,不仅是创建任务按钮,像联系我,分享等其它快捷操作,也能复用。
html
<div
class="task-add"
ref="addBtn"
:style="{ left: x + 'px', top: y + 'px' }"
@touchend="touchend"
@touchstart="touchstart"
@touchmove="touchmove"
>
<div class="tips">点击新增任务</div>
</div>
<script>
// 添加任务按钮相关
const x = ref(window.screen.availWidth - 50);
const y = ref(window.screen.availHeight * 0.7);
const addBtn = ref<null | HTMLElement>(null);
const touchStartTime = ref(new Date().getTime());
const xOffset = ref(0);
const yOffset = ref(0);
const touchstart = (e: TouchEvent) => {
e.preventDefault();
e.stopPropagation();
touchStartTime.value = new Date().getTime();
const { targetTouches, target } = e;
const { clientX, clientY } = targetTouches[0];
const { top, left } = target.getBoundingClientRect();
xOffset.value = clientX - left;
yOffset.value = clientY - top;
};
const touchmove = (e: TouchEvent) => {
e.preventDefault();
e.stopPropagation();
const { targetTouches } = e;
const { clientX, clientY } = targetTouches[0];
const { width, height } = addBtn.value.getBoundingClientRect();
let newX = clientX - xOffset.value;
let newY = clientY - yOffset.value;
const maxX = window.screen.availWidth - width;
const maxY = window.screen.availHeight - height;
x.value = newX < 0 ? 0 : newX > maxX ? maxX : newX;
y.value = newY < 0 ? 0 : newY > maxY ? maxY : newY;
};
const touchend = () => {
if (new Date().getTime() - touchStartTime.value <= 300) {
// do something
}
};
</script>
3 可以输入中文,英文,数字和特定的中英文标点符号,禁止输入其它字符
复杂一点的正则,理解起来比较烧脑。众所周知,中英文标点符号是不相同的。这段正则表达式的特点是兼容中英文标点符合。也是在项目中用的比较多,但查找起来比较费时。
js
value = value?.replace(/[^\u4E00-\u9FA5A-Za-z0-9\s,\.\?\!:\"\-\(\);,。?! : " " - ();]/g, '');
2.下载功能
这段代码的功能是实现文件下载。有时候,后端返回的下载数据,不是个URL资源地址,而是实时生成的二进制数据,这个时候,就需要使用 window.URL.createObjectURL方法,将二进制数据转化成一个浏览器可以使用的下载地址。然后创建一个a标签dom元素,自动触发点击下载。
js
// 导出
const downloadFile = async () => {
let url = '';
const res = await api.exportExcelUrl({ deptName: state.searchKey });
if (res.ret === 0) {
const blob = res.retdata;
const href = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = href;
a.click();
URL.revokeObjectURL(url);
message.success('下载成功!');
} else {
message.error('下载失败');
}
};
1.动态改变输入框最多允许输入的字符数
这段代码的功能是: 输入框默认会有一个输入长度限制,当插入一些模版变量时,要动态修改输入框的最大限制输入长度,还是只允许输入原来那么长的有效文字,但要把插入的模版字符串内容考虑进去。另外就是光标停留在什么位置,就在什么位置插入模版变量。
html
<div class="formInput">
<Input
id="lbs-newHonorific"
v-model:value="state.formData.newHonorific"
:maxlength="calcLimitLen(state.formData.newHonorific, CUSTOMER_NAME, 10)"
:placeholder="`不超过${calcLimitLen(state.formData.newHonorific, CUSTOMER_NAME, 10)}个字`"
/>
<Button class="button" @click="addCustomerName('newHonorific', 'lbs-newHonorific')">
插入客户昵称
</Button>
</div>
js
// 在光标位置插入模版字符
const addCustomerName = (type: TFormDataType, vialId: string) => {
const vDom = document.getElementById(vialId) as HTMLTextAreaElement;
state.formData[type] =
vDom.selectionStart !== undefined
? state.formData[type].slice(0, vDom.selectionStart) +
CUSTOMER_NAME +
state.formData[type].slice(vDom.selectionStart)
: state.formData[type] + CUSTOMER_NAME;
};
// 动态计算限制长度
const calcLimitLen = computed(() => (text: string, insertText: string, limit: number) => {
if (text.includes(insertText)) {
return insertText.length + limit;
}
return limit;
});