近期加班加点的赶需求,有老项目的迭代,还有新项目的开发,反正亘古不变的道理就是,无论新老,坑是一直存在的,反正我们的宗旨就是,没有坑制造坑也要上,闲言少叙,开始填坑铺路吧。
一、react使用antd的Modal.confirm如何异步关闭
关于Modal.confirm,在日常使用中,通常是用来进行提示信息的,但是对于某些需要二次确认的操作来说,再确认之后会触发接口,成功就隐藏提示框,失败可能不会隐藏提示框。但是插件的操作是,只要点击了确认就会直接隐藏提示框,这就需要进行异步操作。
// 表格操作
onClick: () => {
ConFirm({
title: '确认关闭吗?',
content: '',
onOk: () => {
return new Promise((resolve, reject)=>{
return this.close(resolve, reject)
})
},
onCancel () {
console.log('取消');
},
})
}
// 方法
openShop = (resolve, reject) => {
this.props.ajax.post('url', {
params
}).then(
res => {
if (res.code == 200) {
message.success('操作成功!')
resolve()
} else {
reject()
message.error(res.msg || '操作失败!')
}
}
).catch(()=>{
reject()
message.error('操作失败!')
})
}
二、Element-plus的一些问题
1、element-plus如何显示中文提示
使用过element-plus的小伙伴们都会发现,他默认的一些提示都是英文的,无论是时间控件、选择框、表格……都是英文提示,美其名曰国际化,但是对国内大部分小伙伴来说很不友好,因此需要进行汉化处理(天朝内使用天朝开发的框架,需要汉化?)。
有几种方法,可以进行某个组件的汉化,也可以全局进行汉化。
方法一、全局汉化
如果是全局引入element-plus,可以直接在main.js里操作
// main.js
import ElementPlus from 'element-plus'
import zhCn from 'element-plus/lib/locale/lang/zh-cn'
app.use(ElementPlus, {
locale: zhCn
})
如果是按需引入的方式,没办法直接挂载到app上,我们可以在最外层router-view外层汉化。
//App.vue
<template>
<el-config-provider :locale="zhCn">
<router-view></router-view>
</el-config-provider>
</template>
<script lang="ts" setup>
import zhCn from 'element-plus/lib/locale/lang/zh-cn'
</script>
方法二:局部汉化
所谓局部汉化,就是只对某些插件单独进行汉化,其实原理和上边全局是一样的,需要汉化那些组件,就在其外面包一层。
<el-config-provider :locale="zhCn">
<el-date-picker v-model="dataForm.timeArr" type="daterange" range-separator="至"
start-placeholder="开始时间" end-placeholder="结束时间" />
</el-config-provider>
<script lang="ts" setup>
import zhCn from 'element-plus/lib/locale/lang/zh-cn'
</script>
2、Pagination分页相关问题
分页左右对齐问题
小伙伴们会发现,plus的分页默认都是左对齐的,但是出于习惯问题,需要进行右对齐。对于原来的版本,直接设置align=right
就可以了,但是对于plus并不适用。通过文档我们发现有这样一句话:
这样我们可以这样设置,把需要靠右侧的都放到箭头后边就可以了。
<el-pagination
:current-page="pageIndex"
:page-sizes="[10, 20, 50, 100]"
:page-size="pageSize"
:total="totalPage"
layout="->, prev, pager, next, sizes, jumper"
@size-change="sizeChangeHandle"
@current-change="currentChangeHandle"
>
</el-pagination>
自定义分页
依旧是通过文档,我们发现,页码部分只有上一页、下一页、总页数、页码和跳转到第几页,其他内容是没有的,如果我们需要展示下面这种样子,就只能自定义了。
<el-pagination
:current-page="pageIndex"
:page-sizes="[10, 20, 50, 100]"
:page-size="pageSize"
:total="totalPage"
layout="slot, ->, prev, pager, next, sizes, jumper"
@size-change="sizeChangeHandle"
@current-change="currentChangeHandle"
>
<span>第{{pageIndex}}页 (共{{ Math.ceil(Number(totalPage)/pageSize) }}页,总计{{totalPage}}条)</span>
</el-pagination>
3、el-upload多图上传,以及回显删除问题
有一说一,图片上传功能还是很给力的,但是如果涉及到原有数据回显和新图上传混合到一起的情况,就有新问题出现了,当删除图片的时候,没办法知道删除的到底是哪一张图片,通过文档也发下,删除方法只会告诉你删除的图片的信息,并不会告诉你第几张图片,这就很不友好啊,没办法自己动手丰衣足食吧。
<el-form-item label="BANNER:" prop="bannerList">
<div v-for="(item, index) in dataObj.dataForm.bannerList" class="banner_item_box">
<!-- 图片展示 -->
<img v-if="item" :src="item" class="banner_item_box_img">
<!-- 删除图片按钮 -->
<div><img :src="getAssets('del.png')" @click="bannerHandleRemove(index)" /></div>
</div>
<el-upload
v-if="dataObj.dataForm.bannerList.length < 5"
class="banner_img_btn_normal"
:show-file-list="false" // 不展示默认的图片列表
action=""
:http-request="httpRequestImg" // 自定义图片上传方法
:on-remove="bannerHandleRemove"
>
<Icon class="banner_list_btn" :icon="'Plus'" />
</el-upload>
</el-form-item>
// 删除图片
const bannerHandleRemove = (index: number)=>{
if(dataObj.dataForm.bannerList.length){
dataObj.dataForm.bannerList.splice(index, 1)
}
}
三、vue3中引入@vueup/vue-quill编辑器
关于这个编辑器的引用,网上相关的文章很多了,就简单说一下吧,图片上传使用的是自定义上传,最最不好处理的就是数据回显问题,最终通过watch监听完成,直接上代码吧┓(;´_`)┏
// edit.vue 组件
<template>
<div class="edit_box">
<!-- 图片上传组件辅助-->
<el-upload
class="edit_img_upload"
:action='editObj.serverUrl'
:show-file-list="false"
name="file"
:http-request="httpRequestImg"
id="quill-upload">
</el-upload>
<!--富文本编辑器组件-->
<el-row v-loading="editObj.quillUpdateImg">
<QuillEditor
class="ql-editor"
contentType="html"
v-model:content='editObj.detailContent'
ref="myQuillEditor"
:options="editObj.editorOption"
@editorChange="onEditorChange()"
@ready="onEditorReady()"
>
</QuillEditor>
</el-row>
</div>
</template>
<script lang="ts" setup>
import { reactive, ref, watch, onBeforeMount, onMounted } from 'vue'
import { ElMessage } from 'element-plus'
import {QuillEditor} from '@vueup/vue-quill'
import sceneHttp from '@/serive/scene/http';
import '@vueup/vue-quill/dist/vue-quill.snow.css';
interface PropsType {
msg: any
}
const toolbarOptions = [
["bold", "italic", "underline", "strike"], // 加粗 斜体 下划线 删除线 -----['bold', 'italic', 'underline', 'strike']
["blockquote", "code-block"], // 引用 代码块-----['blockquote', 'code-block']
[{ list: "ordered" }, { list: "bullet" }], // 有序、无序列表-----[{ list: 'ordered' }, { list: 'bullet' }]
[{ 'header': 1 }, { 'header': 2 }],
[{ script: "sub" }, { script: "super" }], // 上标/下标-----[{ script: 'sub' }, { script: 'super' }]
[{ indent: "-1" }, { indent: "+1" }],
[{ size: ['small', false, 'large', 'huge']}], // 配置字号
[{ header: [1, 2, 3, 4, 5, 6, false] }], // 标题-----[{ header: [1, 2, 3, 4, 5, 6, false] }]
[{ color: [] }, { background: [] }], // 字体颜色、字体背景颜色-----[{ color: [] }, { background: [] }]
[{ font: []}], //显示字体选择
[{ align: [] }], // 对齐方式-----
["clean"], // 清除文本格式-----
['link', 'image'], // 链接、图片、视频-----
]
const props = defineProps<PropsType>()
const emits = defineEmits(['changeContent'])
const myQuillEditor = ref()
const editObj = reactive({
quillUpdateImg: false, // 根据图片上传状态来确定是否显示loading动画,刚开始是false,不显示
serverUrl: '', // 这里写你要上传的图片服务器地址
header: {}, // 有的图片服务器要求请求头需要有token之类的参数,写在这里
detailContent: '', // 富文本内容
editorOption: {
placeholder: '',
theme: 'snow', // or 'bubble'
modules: {
toolbar: {
container: toolbarOptions, // 工具栏
handlers: {
'image': function (value: any) {
if (value) {
(document as any).querySelector('#quill-upload input').click()
} else {
myQuillEditor.value.quill.format('image', false);
}
}
}
}
}
} // 富文本编辑器配置
})
const onEditorChange = () => {}
const onEditorReady = () => {}
// 图片上传
const httpRequestImg = (options: any) => {
console.log('[[[[[[', options)
if (options) {
const file = options.file
new Promise(function (resolve, reject) {
let _URL = window.URL || window.webkitURL;
let img = new Image();
img.onload = function () {
resolve(img)
}
img.src = _URL.createObjectURL(file);
}).then(() => {
let fromData = new FormData()
fromData.append('file', file)
sceneHttp.uploadImgFun(fromData).then((data) => {
if (data && data.code == 200) {
if (data.data) {
let quill = myQuillEditor.value.getQuill()
// 如果上传成功
console.log(data)
// 获取光标所在位置
let length = quill.getSelection().index;
// 插入图片
quill.insertEmbed(length, 'image', data.data)
// 调整光标到最后
quill.setSelection(length + 1)
} else {
ElMessage.error('图片插入失败')
}
} else {
ElMessage.error('图片插入失败')
}
})
}, () => {
ElMessage.error('请选择合适的图片');
return false;
})
}
}
// 监听当前内容,返回给父组件
watch(() => editObj.detailContent, (newVal, oldVal) => {
emits('changeContent', newVal)
}, { deep: true})
// 监听父组件传值,回显到子组件
watch(() => props.msg, (newVal, oldVal) => {
if(!oldVal){
myQuillEditor.value.setHTML(newVal)
setTimeout(()=>{
// 调整光标到最后
let quill = myQuillEditor.value.getQuill()
let length = quill.getLength();
quill.setSelection(length + 1)
}, 200)
} else {
editObj.detailContent = newVal
}
}, { deep: true})
</script>
//===== 父组件调用
<Edit :msg="dataObj.dataForm.graphicDetails"
@changeContent="changeContent" />
// 编辑器内容变化
const changeContent = (val: any)=>{
dataObj.dataForm.graphicDetails = val
}
四、vue+vant开发微信/支付宝公众号页面
这部分内容整体上没有太大难度,抛开微信和支付宝相关环境方法,它就是个h5页面,按照正常h5页面开发就好,遇到的最大问题就是如何获取授权code,最后会重点说下,先贴上两个比较小的问题。
1、公众号内嵌h5受微信字体大小影响问题
这部分是直接copy的,没啥说的,cv大法走起来~
;(function() {
if (typeof WeixinJSBridge == 'object' && typeof WeixinJSBridge.invoke == 'function') {
//判断程序运行环境是否是微信浏览器(微信内置的浏览器)
handleFontSize()
} else {
if (document.addEventListener) {
document.addEventListener('WeixinJSBridgeReady', handleFontSize, false)
} else if (document.attachEvent) {
document.attachEvent('WeixinJSBridgeReady', handleFontSize)
document.attachEvent('onWeixinJSBridgeReady', handleFontSize)
}
}
function handleFontSize() {
// 设置网页字体为默认大小
WeixinJSBridge.invoke('setFontSizeCallback', {
fontSize: 0
})
// 重写设置网页字体大小的事件
WeixinJSBridge.on('menu:setfont', function() {
WeixinJSBridge.invoke('setFontSizeCallback', {
fontSize: 0
})
})
}
})()
body {
/* IOS禁止微信调整字体大小 */
-webkit-text-size-adjust: 100% !important;
text-size-adjust: 100% !important;
-moz-text-size-adjust: 100% !important;
}
2、vant Dialog关闭无法进行前置校验问题
关于这一点,和第一个react Modal关闭前无法异步校验问题类似,这里需要自定义关闭前方法进行判断。
<van-dialog class="order_edit_dialog" v-model:show="orderData.orderIsEdit" title="修改" show-cancel-button :before-close="changeBeforeClose">
<p>请输入修改内容</p>
<van-field class="order_edit" v-model="orderData.mes" placeholder="请输入" />
</van-dialog>
// 关闭前
const changeBeforeClose = (action: string)=>{
console.log(action)
if(action === 'confirm'){
if(校验未通过){
showFailToast('校验失败提示')
return false
} else {
return true
}
} else {
return true
}
}
3、关于微信和支付宝h5授权
关于微信的授权问题,开发之前可以说是查了很多前辈的文章,毕竟第一次做,都无从下手。经过多方翻阅,了解大致过程就是,进入之后通过标识判断是否存在code,不存在就跳转到微信/支付宝的授权页面,授权成功后会返回到指定页面,成功拿到授权,流程上是比较简单的,实际开发中会有比较多的问题。
最先遇到的就是授权成功,回跳回来的地址并不是自己想要的样子(当然前提是已经在公众号后台配置好了跳转地址等内容)。此次开发使用的是hash模式,跳转前的地址是http://xxx.com/#/index
授权成功后,地址就变成了这个德行http://xxx.com/?code=xxxxxx&auth=xxx/#/index
。当然这也不是不能用,正常跳转路由都没问题,但是相关参数会一直存在在连接中,而且如果其中有自己自定义的参数,链接会变的非常长,并且也不确定在微信里会不会有其他莫名其妙的问题,因此在跳转之前和跳转授权之后,都需要对链接进行处理。
跳转路径只传域名和路径名字,跳转回来之后重新拼接路由,让路由变得正常。
const local = window.location.href
const origin = window.location.origin
const search = window.location.search
const pathname = window.location.pathname
const redirectUrl = origin + pathname
// 微信跳转路径
let wxCodeUrl = 'https://open.weixin.qq.com/connect/oauth2/authorize?appid=' + appId + '&redirect_uri=' + encodeURIComponent(redirectUrl) + '&response_type=code&scope=snsapi_base&state=STATE&connect_redirect=1#wechat_redirect'
// 支付宝跳转路径
let zfbCodeUrl = 'https://openauth.alipay.com/oauth2/publicAppAuthorize.htm?app_id=' + appId + '&scope=auth_base&redirect_uri=' + encodeURI(redirectUrl)
// 跳转回来之后拼接,简单代码
var urlHash = pathName + '#/index?orgId=' + sessionStorage.getItem('orgId') + '&'
const urlReplace = origin + urlHash + search.slice(1)
window.location.replace(urlReplace)
授权成功拿到了,接下来可能会引发的一个问题就是路由死循环了。因为获取授权是在路由拦截里增加的方法,可能对某些标识判断错误的话,就会陷入死循环,浏览器一直刷个不停,这里简单说下我的业务逻辑和处理方法。
业务逻辑上,用户进入的时候链接里会带一个id参数,首先这个id要存在才能正常进入流程,否则就给出提示;
然后就看路径是否是授权后返回的路径,如果是就按上边方法重新拼接路径,如果不是就看code是否存在,也就是是否授权过。当然这部分不能只依赖路径中的code,因为我们重新拼接路径,路由跳转后路径里的code是不存在的,所以这里把code缓存下来,通过两个code同时去判断。如果两者都不存在则说明未授权,跳转授权页;如果链接中有code,但是缓存里没有,则走接口请求,然后缓存code;其他情况直接走路由,不进行处理。
乍一看,逻辑可能觉得很复杂,直接上代码就清晰了,另外还需要说明一点,对于授权code,微信回跳链接里拼接的是code,支付宝回跳链接拼接的是auth_code
,取code值需要注意。
// 路由发生变化监听,根据自己需求可以适当删减修改
// isWxOrZfb为判断是微信环境还是支付宝环境方法,通过ua判断,网上方法很多
router.beforeEach((to, from, next) => {
// 修改标题
if (to.meta.title) {
(document as any).title = to.meta.title
}
let appId = isWxOrZfb() === 'wx' ? '微信appid' : isWxOrZfb() === 'zfb' ? '支付宝appid' : '' // 本地开发appid
const local = window.location.href
const origin = window.location.origin
const search = window.location.search
const pathname = window.location.pathname
const code = isWxOrZfb() === 'wx' ? to.query.code as string : isWxOrZfb() === 'zfb' ? to.query.auth_code as string : ''
const id = to.query.id as string
const sessionCode = sessionStorage.getItem('code')
const redirectUrl = origin + pathname
if(id){
sessionStorage.setItem('id', id)
}
if(id || sessionStorage.getItem('id')){
// 自带参数存在,去判断是否是授权后的链接,并重新拼接
// 因为授权后路径格式为 htttp://aaa.com/pathname/?code=xxxx,因此此处通过pathname和code进行判断
let urlCode = isWxOrZfb() === 'wx' ? 'code' : isWxOrZfb() === 'zfb' ? 'auth_code' : ''
if(local.includes( pathname + '?' + urlCode + '=')){
let urlHash = ''
let pathName = pathname.indexOf('/fck-web/') !== -1 ? '/fck-web/' : '/' // 路径有特殊路径存在,加以区分
if(sessionStorage.getItem('orgId')){
urlHash = pathName + '#/index?orgId=' + sessionStorage.getItem('orgId') + '&'
}
const urlReplace = origin + urlHash + search.slice(1)
window.location.replace(urlReplace)
} else {
if(!code && !sessionCode){
// 授权获取code
let zfbCodeUrl = 'https://openauth.alipay.com/oauth2/publicAppAuthorize.htm?app_id=' + appId + '&scope=auth_base&redirect_uri=' + encodeURI(redirectUrl)
let wxCodeUrl = 'https://open.weixin.qq.com/connect/oauth2/authorize?appid=' + appId + '&redirect_uri=' + encodeURIComponent(redirectUrl) + '&response_type=code&scope=snsapi_base&state=STATE&connect_redirect=1#wechat_redirect'
window.location.replace(isWxOrZfb() === 'wx' ? wxCodeUrl : isWxOrZfb() === 'zfb' ? zfbCodeUrl : '')
} else if(code && !sessionCode){
// 拿到code并存储,发送请求
sessionStorage.setItem('code', code)
sceneHttp.login({
code
}).then((res)=>{
if(res && res.code === 200 && res.data){
// 处理成功的业务逻辑,存储token等
next()
} else {
setTimeFailToast(res.message || '请求失败')
}
})
} else {
next()
}
}
} else {
// 如果id不存在,则关闭页面
setTimeFailToast('id不存在');
setTimeout(()=>{
if(isWxOrZfb() === 'wx'){
(wx as any).closeWindow()
document.addEventListener('WeixinJSBridgeReady', function() {
WeixinJSBridge.call('closeWindow');
}, false);
WeixinJSBridge.call('closeWindow');
} else if(isWxOrZfb() === 'zfb'){
AlipayJSBridge.call('closeWebview');
}
}, 2000)
}
})
最后说一下关于微信和支付宝公众号h5本地调试问题。
微信还好,可以申请一个本地测试的相关appid,后端也配置同样的appid,然后将相关的域名配置好了就可以本地开发调试了,测试号地址:公众号测试号
对于支付宝环境,网上说的办法是可以用小程序的web-view打开,但是路径一定要是线上环境才可以,个人尝试过,打开是能打开,但是授权页就卡死了,完全没法正常跳转回来,不知道是我配置问题还是其他原因,有了解的小伙伴希望评论区给出解答~
好了,写了很多了,我继续挖坑去了……