用户签名功能(canvas)

用户可以在web上签名,然后保存为图片
阿里千问AI查到的

前端HTML代码:

@{
    Layout = null;
}

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>用户签名</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            display: flex;
            flex-direction: column;
            align-items: center;
            padding: 20px;
            background-color: #f9f9f9;
        }

        h1 {
            margin-bottom: 20px;
        }

        #signatureCanvas {
            border: 2px solid #333;
            background-color: white;
            cursor: crosshair;
            touch-action: none; /* 禁用触摸滚动/缩放 */
        }

        .controls {
            margin-top: 15px;
        }

        button {
            padding: 10px 20px;
            margin: 0 10px;
            font-size: 16px;
            cursor: pointer;
        }

            button:hover {
                opacity: 0.9;
            }
    </style> 
    <script src="~/lib/jquery/dist/jquery.min.js"></script> 
</head>
<body>

    <h1>请在此签名</h1>
    <canvas id="signatureCanvas" width="500" height="200"></canvas>

    <div class="controls">
        <button id="clearBtn">清除</button>
        <button id="saveBtn">保存签名</button>
    </div>
    <div id="res"></div>
    <script>
  const canvas = document.getElementById('signatureCanvas');
  const ctx = canvas.getContext('2d');
  let isDrawing = false;

  // 设置画笔样式
  ctx.lineWidth = 2;
  ctx.lineCap = 'round';
  ctx.strokeStyle = '#000';

  // 鼠标事件
  canvas.addEventListener('mousedown', startDrawing);
  canvas.addEventListener('mousemove', draw);
  canvas.addEventListener('mouseup', stopDrawing);
  canvas.addEventListener('mouseout', stopDrawing);

  // 触摸事件(移动端)
  canvas.addEventListener('touchstart', handleTouchStart);
  canvas.addEventListener('touchmove', handleTouchMove);
  canvas.addEventListener('touchend', handleTouchEnd);

  function getMousePos(e) {
    const rect = canvas.getBoundingClientRect();
    return {
      x: e.clientX - rect.left,
      y: e.clientY - rect.top
    };
  }

  function getTouchPos(touch) {
    const rect = canvas.getBoundingClientRect();
    return {
      x: touch.clientX - rect.left,
      y: touch.clientY - rect.top
    };
  }

  function startDrawing(e) {
    isDrawing = true;
    const pos = getMousePos(e);
    ctx.beginPath();
    ctx.moveTo(pos.x, pos.y);
  }

  function draw(e) {
    if (!isDrawing) return;
    const pos = getMousePos(e);
    ctx.lineTo(pos.x, pos.y);
    ctx.stroke();
  }

  function stopDrawing() {
    isDrawing = false;
  }

  // 触摸事件处理
  function handleTouchStart(e) {
    e.preventDefault();
    const touch = e.touches[0];
    const pos = getTouchPos(touch);
    isDrawing = true;
    ctx.beginPath();
    ctx.moveTo(pos.x, pos.y);
  }

  function handleTouchMove(e) {
    e.preventDefault();
    if (!isDrawing) return;
    const touch = e.touches[0];
    const pos = getTouchPos(touch);
    ctx.lineTo(pos.x, pos.y);
    ctx.stroke();
  }

  function handleTouchEnd(e) {
    e.preventDefault();
    isDrawing = false;
  }

  // 清除画布
  document.getElementById('clearBtn').addEventListener('click', () => {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
  });

  // 保存签名
        document.getElementById('saveBtn').addEventListener('click', () => {
            const dataURL = canvas.toDataURL('image/png'); // 获取 base64 字符串

            // 显示图片预览(可选)
            // const img = new Image();
            // img.src = dataURL;
            // document.body.appendChild(img);

            // 发送 AJAX 到后端
            $.ajax({
                url: '/home/index', // 根据实际情况调整 URL 路径
                type: 'POST',
                contentType: 'application/json;charset=UTF-8',
                data: JSON.stringify({
                    Signature: dataURL // 传递给后端的数据
                }),
                success: function (response) {
                    if (response.success) {
                        alert("签名上传成功!\n文件地址:" + response.url);
                        // 可以在此处处理返回的文件URL,比如显示在页面上等
                        $('#res').html("<a href='"+response.url+"'>"+response.url+"</a>")
                    } else {
                        alert("签名上传失败!");
                    }
                },
                error: function (xhr, status, error) {
                    console.error('签名上传时发生错误:', error);
                    alert("签名上传时发生错误,请重试!");
                }
            });
  });
    </script>

</body>
</html>


后端C#代码:

using Microsoft.AspNetCore.Mvc;
using System.Diagnostics;
using System.Text.RegularExpressions;
using userqianming.Models;

namespace userqianming.Controllers
{
    public class HomeController : Controller
    {
  
        public IActionResult Index()
        {
            return View();
        }

        [HttpPost]
        public async Task<IActionResult> Index([FromBody] SignatureRequest request)
        {
            if (string.IsNullOrEmpty(request?.Signature))
            {
                return BadRequest("签名数据为空");
            }

            try
            {
                // 提取 base64 部分(去掉 data URL 前缀)
                var base64Data = request.Signature;
                if (base64Data.StartsWith("data:image"))
                {
                    // 使用正则或 Split 移除前缀
                    var match = Regex.Match(base64Data, @"^data:image\/\w+;base64,(.*)$");
                    if (match.Success)
                    {
                        base64Data = match.Groups[1].Value;
                    }
                    else
                    {
                        return BadRequest("无效的 Base64 图片格式");
                    }
                }

                // 解码 Base64
                byte[] imageBytes = Convert.FromBase64String(base64Data);

                // 设置保存路径(例如:wwwroot/signatures/)
                var folderPath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "signatures");
                Directory.CreateDirectory(folderPath); // 确保目录存在

                // 生成唯一文件名
                string fileName = $"signature_{DateTime.Now:yyyyMMdd_HHmmss}_{Guid.NewGuid().ToString("N")[..8]}.png";
                string filePath = Path.Combine(folderPath, fileName);

                // 保存文件
                await System.IO.File.WriteAllBytesAsync(filePath, imageBytes);

                // 可选:返回保存的 URL 或成功信息
                var fileUrl = $"/signatures/{fileName}";
                return Ok(new { success = true, url = fileUrl });
            }
            catch (Exception ex)
            {
                // 记录日志(可集成 ILogger)
                Console.WriteLine($"保存签名失败: {ex.Message}");
                return StatusCode(500, "保存签名时发生错误");
            }
        }

 
    }

    // DTO 用于接收 JSON 数据
    public class SignatureRequest
    {
        public string? Signature { get; set; }
    }
}