万有斥力 发表于 2024-11-29 04:49:47

鸿蒙(Harmony)实现滑块验证码

在Android和ios两头已经使用的滑块验证码框架还未适配鸿蒙版,于是需要自己去实现类似如下的滑块验证码:
https://i-blog.csdnimg.cn/direct/9d646c5e2d2c45e4b752d53e0b526bb2.jpeg
那么实现这样的验证码重要涉及到几个内容:
1、自界说弹窗

2、base64图片转换

3、滑动组件与滑块的联动,以及横移间隔转换等

自界说弹窗:

自界说一个可导出的弹窗组件CustomDialog,最重要是使用 @CustomDialog 修饰符。
@CustomDialog
export struct BlockPuzzleDialog {

phoneNum: number | string = ''

controller: CustomDialogController = new CustomDialogController({
    builder: BlockPuzzleDialog({}),
})

    build() {
         Column(){
      
      }
    }
   
   
   // 验证码校验回调给使用页面
blockCheckCallback: (token: string) => void = (token: string) => {

}
}
在使用页面创建构造器与弹窗绑定
@Entry
@Component
struct LoginPage {
   dialogController: CustomDialogController = new CustomDialogController({
    builder: BlockPuzzleDialog({
      phoneNum: this.phoneNum, blockCheckCallback: (token: string) => {
      this.blockPuzzleSuccessCallback(token)
      }
    }),
    autoCancel: false,//弹窗是否自动取消
    alignment: DialogAlignment.Center,// 弹窗位置
    cornerRadius: 8,
    width: '90%'// 弹窗宽度
})
   
    build(){
      ...
    }
} 弹窗UI组件的实现:核心组件就一个预先挖孔的底图上面叠加滑块图片再加上一个slider组件
build(){

......


Stack() {
      Image(this.coverUri).width('100%').margin({ top: 10 }).objectFit(ImageFit.Auto).onComplete((event) => {
          this.scaleRatio = event!!.componentWidth / event?.width!!
      })
      Image(this.blockUri)
          .width(this.blockW + "px")
          .height(this.blockH + "px")
          .margin({ top: 10 })
          .objectFit(ImageFit.Auto)
          .onComplete((event) => {
            this.blockW = event?.width!! * this.scaleRatio
            this.blockH = event?.height!! * this.scaleRatio
            this.slideMax = Const.mWidth * 0.9 - 24 - px2vp(this.blockW)
          })
          .translate({ x: this.bolckTranslateX + "px" })

      this.loading()

      }.width('100%').alignContent(Alignment.Start)

      RelativeContainer() {
      Text('向右拖动滑动填充拼图')
          .fontSize(18)
          .fontColor($r('app.color.C_BEBEC6'))
          .id('blockTip')
          .alignRules({
            "top": {
            "anchor": "slider",
            "align": VerticalAlign.Top
            },
            "bottom": {
            "anchor": "slider",
            "align": VerticalAlign.Bottom
            },
            "left": {
            "anchor": "slider",
            "align": HorizontalAlign.Start
            },
            "right": {
            "anchor": "slider",
            "align": HorizontalAlign.End
            },
          })
          .textAlign(TextAlign.Center)
      Slider({
          style: SliderStyle.InSet,
          value: $$this.sliderValue,
          step: 1,
          max: vp2px(this.slideMax)
      })
          .trackColor(this.sliderConfig.trackColor)
          .selectedColor(this.sliderConfig.selectedColor)
          .blockSize({ height: 40, width: 44 })
          .blockStyle({
            type: SliderBlockType.IMAGE,
            image: this.sliderConfig.blockImg
          })// .sliderInteractionMode(SliderInteraction.SLIDE_ONLY)
          .trackBorderRadius(Const.BORDER_RADIUS_4)
          .trackThickness(40)
          .width('100%')
          .onChange((value: number, mode: SliderChangeMode) => {
            // this.bolckTranslateX = this.slideMax * (value / this.slideMax)
            this.bolckTranslateX = value
            console.info('滑块滑动:滑块滑动数值==' + value + " 图片位移==" + this.bolckTranslateX)
            if (mode == SliderChangeMode.End) {
            // this.sliderValue = value
            let point = new Point()
            point.x = parseFloat((this.bolckTranslateX / this.scaleRatio).toFixed(0))
            console.info('滑动结束:滑动数值 this.sliderValue==' + this.sliderValue + " this.bolckTranslateX==" +
            this.bolckTranslateX + " 转像素==" + point.x)
            this.checkCaptcha(point)
            }
          })
          .id('slider')
      }.width('100%').height(40).margin({ top: 10 })

......

}
滑块图片translate的值就是Slider组件的滑动值。使用
this.dialogController.open() 弹窗 Base64图片的下载与转换

aboutToAppear(): void {
    this.getSlideImage()
}

......

// 获取底图和滑块图片的base64数据并保存到本地,同时获取到滑块校验相关信息。
getSlideImage() {
    this.sliderConfig.showLoading = true
    HttpUtil.getData<BlockResult>(Const.URL_BLOCK_IMG).then((result) => {
      if (result !== undefined && result !== null) {
      this.blockResult = result
      this.coverBase64 = this.blockResult.repData?.originalImageBase64!!
      this.blockBase64 = this.blockResult.repData?.jigsawImageBase64!!
      console.info("滑块:获取到base64 ==" + this.coverBase64)
      let coverName = "coverBase64_" + Date.now().toString() + ".png"
      let blockName = "blockBase64_" + Date.now().toString() + ".png"
      this.coverPath = this.context.filesDir + "/temp/" + coverName;
      this.blockPath = this.context.filesDir + "/temp/" + blockName;
      this.coverUri =
          Utils.saveBase64Image(this.coverBase64, this.context, coverName)
      this.blockUri =
          Utils.saveBase64Image(this.blockBase64, this.context, blockName)
      this.sliderConfig.showLoading = false
      this.reset()
      }
    })
} 可以参考官网示例 通过buffer.from的方法,将base64编码格式的字符串创建为新的Buffer对象,接着用fileIo.writeSync方法将转换好的Buffer对象写入文件。
let context = getContext(this) as common.UIAbilityContext;
let filesDir = context.filesDir;

// data为需要转换的base64字符串,返回沙箱路径uri
export async function writeFile(data: string): Promise<string> {
let uri = ''
try {
    let filePath = filesDir + "/1.png";
    uri = fileUri.getUriFromPath(filePath);
    let file = fileIo.openSync(filePath, fileIo.OpenMode.READ_WRITE | fileIo.OpenMode.CREATE);
    console.info("file fd: " + file.fd);
    const reg = new RegExp("data:image/\\w+;base64,")
    const base64 = data.replace(reg, "");
    console.log("base64flag", base64)
    const dataBuffer = buffer.from(base64, 'base64')
    let writeLen = fileIo.writeSync(file.fd, dataBuffer.buffer);
    hilog.info(0xA0c0d0,'uri',uri)
    fileIo.closeSync(file);
}
catch (Error) {
    hilog.error(0xA0c0d0,'Error',Error.code)
}
return uri;
} 当然你还可以直接将Base64转换成PiexlMap.先将base64字符串剖析成arraybuffer,然后使用这个arraybuffer构建新PixelMap,需要注意的是,使用decodeSync对base64字符串解码时,传入的base64字符串不能有'data:image/jpeg;base64,'这样的前缀。
import CommonConstants from '../common/constants/CommonContants';
import { util } from '@kit.ArkTS';
import { image } from '@kit.ImageKit';

@Entry
@Component
struct Index {
@State message: string = 'Base64ToPixelMap';
private base64: string = CommonConstants.Image_Base64_String; // 该变量为图片的base64格式字符串
@State private pixelMap: PixelMap | null = null;

build() {
    Row() {
      Column() {
      Text(this.message)
          .fontSize(50)
          .fontWeight(FontWeight.Bold)
          .onClick(async () => {
            let helper = new util.Base64Helper();
            let buffer: ArrayBuffer = helper.decodeSync(this.base64, util.Type.MIME).buffer as ArrayBuffer;
            let imageSource = image.createImageSource(buffer);
            let opts: image.DecodingOptions = { editable: true };
            this.pixelMap = await imageSource.createPixelMap(opts);
          })
      Image(this.pixelMap)
          .width(200).height(200).margin(15)
      }
      .width('100%')
    }
    .height('100%')
}
} 将得到的图片当地保存地点uri大概转换成的piexlMap设置给底图和滑动图片。
滑动值校验

上面已经说过,滑块的移动值就是Slider滑动值。其中slider 步长设置为1,滑动的最大值slideMax=底图的宽度-滑块图片的宽度。这样滑动值转换更方便,联动结果也更好。这里注意下 底图在填满控件的时间有肯定的缩放,滑动图片组件也需要按照这个缩放比例设置宽高。
step: 1,
max: vp2px(this.slideMax) 最后在slider的onchange回调中校验滑动值是不是正确,注意滑动值要除以上面的底图缩放比例。
将滑动值加上校验token传给校验接口获取校验结果。
.onChange((value: number, mode: SliderChangeMode) => {

this.bolckTranslateX = value
console.info('滑块滑动:滑块滑动数值==' + value + " 图片位移==" + this.bolckTranslateX)
if (mode == SliderChangeMode.End) {
    // this.sliderValue = value
    let point = new Point()
    point.x = parseFloat((this.bolckTranslateX / this.scaleRatio).toFixed(0))
    console.info('滑动结束:滑动数值 this.sliderValue==' + this.sliderValue + " this.bolckTranslateX==" +
    this.bolckTranslateX + " 转像素==" + point.x)
    this.checkCaptcha(point)
}
}) checkFail() {
    this.sliderConfig.showLoading = false
    this.sliderConfig.trackColor = $r('app.color.C_0DF32222')
    this.sliderConfig.selectedColor = $r('app.color.C_F32222')
    this.sliderConfig.blockImg = $r('app.media.drag_btn_error')
    this.sliderValue = 0
    this.bolckTranslateX = 0
    setTimeout(() => {
      // 删掉滑块图片
      FileUtil.delFile(this.coverPath)
      FileUtil.delFile(this.blockPath)
      this.getSlideImage()
    }, 300)
}

checkSuccess() {
    this.sliderConfig.showLoading = false
    this.sliderConfig.trackColor = $r('app.color.C_0D1264E0')
    this.sliderConfig.selectedColor = $r('app.color.C_1264E0')
    this.sliderConfig.blockImg = $r('app.media.drag_btn_success')

    setTimeout(() => {
      this.controller.close()
      // 删掉滑块图片
      FileUtil.delFile(this.coverPath)
      FileUtil.delFile(this.blockPath)
      if (this.blockCheckCallback !== undefined) {
      this.blockCheckCallback(this.blockResult?.token!!)
      }
    }, 300)
} 调用刚刚界说的回调方法将校验结果回调给登录页面this.blockCheckCallback(this.blockResult?.token!!)
至此导致流程已结束,当然还有一些细节需要自己根据业务实现。最后完成结果如下:
https://i-blog.csdnimg.cn/direct/3cbe8d8ab027462c93662091e48d9dff.gif

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: 鸿蒙(Harmony)实现滑块验证码