在小小的需求里挖呀挖呀挖,挖出大大的坑再填好它

近期加班加点的赶需求,有老项目的迭代,还有新项目的开发,反正亘古不变的道理就是,无论新老,坑是一直存在的,反正我们的宗旨就是,没有坑制造坑也要上,闲言少叙,开始填坑铺路吧。

一、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并不适用。通过文档我们发现有这样一句话:

11.png

这样我们可以这样设置,把需要靠右侧的都放到箭头后边就可以了。

<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打开,但是路径一定要是线上环境才可以,个人尝试过,打开是能打开,但是授权页就卡死了,完全没法正常跳转回来,不知道是我配置问题还是其他原因,有了解的小伙伴希望评论区给出解答~

好了,写了很多了,我继续挖坑去了……

© 版权声明
THE END
喜欢就支持一下吧
点赞0

Warning: mysqli_query(): (HY000/3): Error writing file '/tmp/MYhuJKIl' (Errcode: 28 - No space left on device) in /www/wwwroot/583.cn/wp-includes/class-wpdb.php on line 2345
admin的头像-五八三
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

图形验证码
取消
昵称代码图片