所有分类
  • 所有分类
  • Html5资源
  • React资源
  • Vue资源
  • Php资源
  • ‌小程序资源
  • Python资源

鸿蒙系统(HarmonyOS)应用开发之实现电子签名效果

项目概述

DevEco 电子签名应用是一款专为移动设备设计的高效电子签名解决方案,基于 OpenHarmony 平台开发。该应用旨在满足日益增长的电子签名需求,为用户提供便捷、安全且具有法律效力的电子签名功能。在数字化转型的时代背景下,我们的应用致力于简化签名流程,提高工作效率,减少纸质文档的使用,为绿色环保贡献力量。

通过直观的用户界面和流畅的操作体验,DevEco 电子签名应用让用户能够随时随地完成签名任务,无需打印文件或亲自到场。无论是商务合同、法律文件还是日常文档,我们的应用都能提供专业的签名解决方案。

功能特点

核心功能

  • 精准手写签名:采用先进的触控算法,确保签名流畅自然,完美还原用户的笔迹特点
  • 多样化绘制选项
  • 四种标准颜色选择(黑色、红色、蓝色、绿色)
  • 可调节的线条粗细(1-10 级调节)
  • 圆润的线条连接,提供专业签名效果
  • 操作管理功能
  • 一键清除画布,快速重新开始
  • 撤销功能,方便纠正误操作
  • 即时保存签名,防止工作丢失

用户体验优势

  • 简洁直观的界面设计:遵循现代 UI 设计原则,界面清晰易懂
  • 响应式布局:自适应不同屏幕尺寸,提供一致的用户体验
  • 操作反馈:通过 Toast 提示等方式,提供及时的操作反馈
  • 轻量级应用:占用资源少,运行流畅,适合各种设备

技术架构

开发框架

  • 基于 OpenHarmony 的 ArkTS/ETS 语言开发
  • 采用声明式 UI 编程范式,提高开发效率和代码可维护性

核心技术

  • Canvas 绘图技术:利用 CanvasRenderingContext2D 实现高精度绘图
  • 触控事件处理:精确捕捉并处理多点触控事件
  • 状态管理:使用@State 装饰器进行组件状态管理
  • 离屏渲染:通过 OffscreenCanvas 技术实现高效图像处理和导出

应用场景

  • 合同签署:远程签署商业合同、协议和备忘录
  • 报价确认:快速确认商业报价和订单
  • 会议纪要:为会议纪要和决议添加签名确认
  • 法律文件:签署法律文件和声明
  • 公证材料:为需要公证的材料添加电子签名
  • 授权确认:提供授权确认的电子签名
  • 快递签收:替代传统的纸质签收单
  • 考勤签到:用于员工考勤或活动签到
  • 个人认证:在各类需要身份确认的场景中使用

完整源码

import promptAction from '@ohos.promptAction';
import image from '@ohos.multimedia.image';
import fs from '@ohos.file.fs';
import common from '@ohos.app.ability.common';
import router from '@ohos.router';

// 定义点的接口
interface Point {
  x: number;
  y: number;
}

// 定义路径的接口
interface PathData {
  path: Point[];
  color: string;
  width: number;
}

@Entry
@Component
struct SignaturePage {
  // Canvas相关状态
  @State canvasWidth: number = 0;
  @State canvasHeight: number = 0;
  @State isDrawing: boolean = false;
  @State lineColor: string = '#000000';
  @State lineWidth: number = 3;
  @State lastX: number = 0;
  @State lastY: number = 0;
  // 用于保存绘制历史,支持撤销功能
  @State drawHistory: PathData[] = [];
  @State currentPath: Point[] = [];
  // 画布上下文
  private settings: RenderingContextSettings = new RenderingContextSettings(true);
  private context: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings);
  // 获取应用上下文,用于保存图片
  private context16: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings);
  private appContext: common.UIAbilityContext = getContext(this) as common.UIAbilityContext;

  aboutToAppear() {
    // 页面加载时的初始化操作
  }

  // 开始绘制
  startDrawing(event: TouchEvent) {
    this.isDrawing = true;
    this.lastX = event.touches[0].x;
    this.lastY = event.touches[0].y;

    // 开始新路径
    this.context.beginPath();
    this.context.moveTo(this.lastX, this.lastY);

    // 记录当前路径的起点
    this.currentPath = [{ x: this.lastX, y: this.lastY }];
  }

  // 绘制中
  drawing(event: TouchEvent) {
    if (!this.isDrawing) {
      return;
    }

    const currentX = event.touches[0].x;
    const currentY = event.touches[0].y;

    // 绘制线条
    this.context.lineWidth = this.lineWidth;
    this.context.strokeStyle = this.lineColor;
    this.context.lineJoin = 'round';
    this.context.lineCap = 'round';

    this.context.lineTo(currentX, currentY);
    this.context.stroke();

    // 更新最后位置
    this.lastX = currentX;
    this.lastY = currentY;

    // 记录当前路径点
    this.currentPath.push({ x: currentX, y: currentY });
  }

  // 结束绘制
  endDrawing() {
    if (!this.isDrawing) {
      return;
    }

    this.isDrawing = false;

    // 保存当前路径到历史记录
    if (this.currentPath.length > 1) {
      this.drawHistory.push({
        path: [...this.currentPath],
        color: this.lineColor,
        width: this.lineWidth
      });
    }

    this.currentPath = [];
  }

  // 清除画布
  clearCanvas() {
    this.context.clearRect(0, 0, this.canvasWidth, this.canvasHeight);
    this.drawHistory = [];
    this.currentPath = [];
  }

  // 撤销上一步
  undo() {
    if (this.drawHistory.length === 0) {
      promptAction.showToast({
        message: '没有可撤销的操作',
        duration: 2000
      });
      return;
    }

    // 移除最后一个路径
    this.drawHistory.pop();

    // 重新绘制所有路径
    this.context.clearRect(0, 0, this.canvasWidth, this.canvasHeight);

    for (let item of this.drawHistory) {
      this.context.beginPath();
      this.context.lineWidth = item.width;
      this.context.strokeStyle = item.color;
      this.context.lineJoin = 'round';
      this.context.lineCap = 'round';

      if (item.path.length > 0) {
        this.context.moveTo(item.path[0].x, item.path[0].y);

        for (let i = 1; i < item.path.length; i++) {
          this.context.lineTo(item.path[i].x, item.path[i].y);
        }

        this.context.stroke();
      }
    }
  }

  // 保存签名
  async saveSignature() {
    try {
      // 创建离屏Canvas
      let offscreenCanvas = new OffscreenCanvas(this.canvasWidth, this.canvasHeight);
      let offContext = offscreenCanvas.getContext("2d");

      // 将当前画布内容绘制到离屏Canvas
      offContext.fillStyle = '#FFFFFF';
      offContext.fillRect(0, 0, this.canvasWidth, this.canvasHeight);

      // 重新绘制所有路径到离屏Canvas
      for (let item of this.drawHistory) {
        offContext.beginPath();
        offContext.lineWidth = item.width;
        offContext.strokeStyle = item.color;
        offContext.lineJoin = 'round';
        offContext.lineCap = 'round';

        if (item.path.length > 0) {
          offContext.moveTo(item.path[0].x, item.path[0].y);

          for (let i = 1; i < item.path.length; i++) {
            offContext.lineTo(item.path[i].x, item.path[i].y);
          }

          offContext.stroke();
        }
      }

      // 转换为图片 - 使用transferToImageBitmap替代toDataURL
      let imageBitmap = offscreenCanvas.transferToImageBitmap();

      // 显示成功消息
      promptAction.showToast({
        message: '签名已保存',
        duration: 2000
      });

      return imageBitmap;
    } catch (error) {
      console.error('保存签名失败:', error);
      promptAction.showToast({
        message: '保存签名失败',
        duration: 2000
      });
      return null;
    }
  }

  build() {
    Column() {
      // 标题栏
      Row() {
        Text('电子签名')
          .fontSize(20)
          .fontWeight(FontWeight.Medium)
          .margin({ left: 16 })

        Blank()

        Button('保存')
          .fontSize(16)
          .height(36)
          .backgroundColor('#007DFF')
          .borderRadius(18)
          .onClick(() => {
            this.saveSignature();
          })
      }
      .width('100%')
      .height(56)
      .padding({ left: 16, right: 16 })
      .backgroundColor('#FFFFFF')

      // 签名区域
      Column() {
        Text('请在下方区域签名')
          .fontSize(16)
          .fontColor('#666666')
          .margin({ top: 20, bottom: 10 })

        // 签名画布
        Canvas(this.context)
          .width('100%')
          .layoutWeight(4) // 增加Canvas的权重,给予更多空间
          .backgroundColor('#F5F5F5')
          .borderRadius(8)
          .margin({ bottom: 16 }) // 添加底部间距
          .onAreaChange((oldArea: Area, newArea: Area) => {
            // 使用新区域的尺寸
            this.canvasWidth = newArea.width as number;
            this.canvasHeight = newArea.height as number;
          })
          .onReady(() => {
            // Canvas准备就绪
          })
          .onTouch((event: TouchEvent) => {
            if (event.type === TouchType.Down) {
              this.startDrawing(event);
            } else if (event.type === TouchType.Move) {
              this.drawing(event);
            } else if (event.type === TouchType.Up) {
              this.endDrawing();
            }
          })

        // 工具栏容器
        Column() {
          // 按钮和颜色选择器行
          Row() {
            Button('清除')
              .fontSize(16)
              .height(40)
              .backgroundColor('#F5F5F5')
              .fontColor('#333333')
              .borderRadius(20)
              .onClick(() => {
                this.clearCanvas();
              })

            Button('撤销')
              .fontSize(16)
              .height(40)
              .backgroundColor('#F5F5F5')
              .fontColor('#333333')
              .borderRadius(20)
              .margin({ left: 16 })
              .onClick(() => {
                this.undo();
              })

            Blank()

            // 颜色选择器
            Row() {
              ForEach(['#000000', '#FF0000', '#0000FF', '#008000'], (color: string) => {
                Column() {
                  Circle({ width: 24, height: 24 })
                    .fill(color)
                    .stroke(this.lineColor === color ? '#007DFF' : 'transparent')
                    .strokeWidth(2)
                    .margin(4)
                    .onClick(() => {
                      this.lineColor = color;
                    })
                }
              })
            }
          }
          .width('100%')
          .height(48)
          .margin({ top: 16 })

          // 线宽选择器行
          Row() {
            Text('线条粗细:')
              .fontSize(16)
              .fontColor('#666666')

            Slider({
              value: this.lineWidth,
              min: 1,
              max: 10,
              step: 1,
              style: SliderStyle.OutSet
            })
              .width('60%')
              .onChange((value: number) => {
                this.lineWidth = value;
              })

            Text(this.lineWidth.toFixed(0))
              .fontSize(16)
              .fontColor('#666666')
              .width(24)
              .textAlign(TextAlign.Center)
          }
          .width('100%')
          .height(48)
          .margin({ top: 8 })
        }
        .width('100%')
        .layoutWeight(1)
        .padding({ left: 16, right: 16 })
      }
      .width('100%')
      .padding(16)
      .backgroundColor('#FFFFFF')
      .layoutWeight(1) // 使用layoutWeight而不是固定高度
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#F5F5F5')
  }
}
资源下载
下载价格免费
注意:本网站资源属于虚拟产品,不支持退款。请谨慎购买! 购买后资源无法下载,请联系客服QQ:844475003,微信号:th844475003。
原文链接:https://code.ifrontend.net/archives/649,转载请注明出处。
0

评论0

显示验证码
没有账号?注册  忘记密码?