奕玖科技 > 新闻中心 > 技术文章

ASP.NET Core 审批表自动生成与电子签字系统

来源: 奕玖科技 Fly | 2025/11/14 10:04:29

根据需求,我们设计了一个功能完整的审批表生成系统,支持根据用户输入条件动态生成审批表,并提供手写签字功能。系统采用 ASP.NET Core 8 作为后端框架,结合 HTML5 Canvas 实现手写签字功能,使用 Bootstrap 构建响应式界面,此篇文章主要出于教学

20230116638094709862372372.jpg

下面是完整的实现方案,包含项目结构、核心代码和使用说明:

1、项目结构

ApprovalFormGenerator/
├── Controllers/
│   └── ApprovalController.cs       # 审批表生成与签字处理控制器
├── Models/
│   ├── ApprovalCriteria.cs         # 审批条件模型
│   └── ApprovalForm.cs             # 审批表数据模型
├── Views/
│   ├── Approval/
│   │   ├── Index.cshtml            # 条件输入页面
│   │   ├── Preview.cshtml          # 审批表预览与签字页面
│   │   └── CompLeted.cshtml        # 提交完成页面
│   └── Shared/
│       └── _Layout.cshtml          # 布局页面
├── wwwroot/
│   ├── css/
│   │   └── signature-pad.css       # 签字板样式
│   └── js/
│       └── signature-pad.js        # 手写签字功能脚本
└── Program.cs                      # 应用程序配置

2. 核心代码实现

2.1 模型定义(Models)

ApprovalCriteria.cs - 审批条件模型

using System.ComponentModel.DataAnnotations;

namespace ApprovalFormGenerator.Models;

public class ApprovalCriteria
{
    [Required(ErrorMessage = "请输入申请人姓名")]
    [Display(Name = "申请人姓名")]
    public string ApplicantName { get; set; } = string.Empty;

    [Required(ErrorMessage = "请输入申请部门")]
    [Display(Name = "申请部门")]
    public string Department { get; set; } = string.Empty;

    [Required(ErrorMessage = "请选择申请类型")]
    [Display(Name = "申请类型")]
    public string ApprovalType { get; set; } = string.Empty;

    [Required(ErrorMessage = "请输入申请事由")]
    [Display(Name = "申请事由")]
    [StringLength(500, ErrorMessage = "申请事由不能超过500字")]
    public string Reason { get; set; } = string.Empty;

    [Required(ErrorMessage = "请输入申请金额(如有)")]
    [Display(Name = "申请金额(元)")]
    [DataType(DataType.Currency)]
    public decimal Amount { get; set; }

    [Display(Name = "紧急程度")]
    public string UrgencyLevel { get; set; } = "普通";

    [Display(Name = "附加说明")]
    public string Remarks { get; set; } = string.Empty;
}

ApprovalForm.cs - 审批表数据模型

namespace ApprovalFormGenerator.Models;

public class ApprovalForm
{
    public string FormId { get; set; } = Guid.NewGuid().ToString("N");
    public string ApplicantName { get; set; } = string.Empty;
    public string Department { get; set; } = string.Empty;
    public string ApprovalType { get; set; } = string.Empty;
    public string Reason { get; set; } = string.Empty;
    public decimal Amount { get; set; }
    public string UrgencyLevel { get; set; } = string.Empty;
    public string Remarks { get; set; } = string.Empty;
    public DateTime ApplyDate { get; set; } = DateTime.Now;
    public string SignatureData { get; set; } = string.Empty; // 存储签字图片base64数据
}

2.2 控制器(Controllers)

ApprovalController.cs

using ApprovalFormGenerator.Models;
using Microsoft.AspNetCore.mvc;

namespace ApprovalFormGenerator.Controllers;

public class ApprovalController : Controller
{
    // 模拟存储已提交的审批表
    private static readonly List<ApprovalForm> _submittedForms = new();

    // 条件输入页面
    public IActionResult Index()
    {
        // 提供申请类型选项
        ViewBag.ApprovalTypes = new List<string>
        {
            "费用报销", "采购申请", "假期申请", "出差申请", "其他"
        };

        // 提供紧急程度选项
        ViewBag.UrgencyLevels = new List<string>
        {
            "普通", "紧急", "非常紧急"
        };

        return View(new ApprovalCriteria());
    }

    // 生成审批表并显示预览
    [HttpPost]
    public IActionResult Preview(ApprovalCriteria criteria)
    {
        if (!ModelState.IsValid)
        {
            ViewBag.ApprovalTypes = new List<string> { "费用报销", "采购申请", "假期申请", "出差申请", "其他" };
            ViewBag.UrgencyLevels = new List<string> { "普通", "紧急", "非常紧急" };
            return View("Index", criteria);
        }

        // 根据条件生成审批表
        Var approvalForm = new ApprovalForm
        {
            ApplicantName = criteria.ApplicantName,
            Department = criteria.Department,
            ApprovalType = criteria.ApprovalType,
            Reason = criteria.Reason,
            Amount = criteria.Amount,
            UrgencyLevel = criteria.UrgencyLevel,
            Remarks = criteria.Remarks
        };

        // 存储到临时会话
        TempData["ApprovalForm"] = System.Text.Json.JsonSerializer.Serialize(approvalForm);

        return View(approvalForm);
    }

    // 提交签字后的审批表
    [HttpPost]
    public IActionResult Submit(string signatureData)
    {
        var formJson = TempData["ApprovalForm"] as string;
        if (string.IsNullOrEmpty(formJson) || string.IsNullOrEmpty(signatureData))
        {
            return RedirectToAction("Index");
        }

        // 反序列化审批表数据
        var approvalForm = System.Text.Json.JsonSerializer.Deserialize<ApprovalForm>(formJson)!;
        approvalForm.SignatureData = signatureData;

        // 保存到提交列表
        _submittedForms.Add(approvalForm);

        // 传递审批表ID到完成页面
        TempData["FormId"] = approvalForm.FormId;

        return RedirectToAction("Completed");
    }

    // 提交完成页面
    public IActionResult Completed()
    {
        var formId = TempData["FormId"] as string;
        if (string.IsNullOrEmpty(formId))
        {
            return RedirectToAction("Index");
        }

        ViewBag.FormId = formId;
        return View();
    }

    // 查看已提交的审批表(可选功能)
    public IActionResult ViewSubmittedForms()
    {
        return View(_submittedForms);
    }
}
2.3 视图页面(Views)

_Layout.cshtml - 布局页面

<!DOCTYPE html> <html> <head>     <meta charset="utf-8" />     <meta name="viewport" content="width=device-width, initial-scale=1.0" />     <title>@ViewData["Title"] - 审批表自动生成系统</title>     <!-- Bootstrap CSS -->     <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet" />     <!-- 自定义样式 -->     <link href="~/css/signature-pad.css" rel="stylesheet" /> </head> <body>     <header class="bg-primary text-white py-3">         <div>             <h1 class="h3 mb-0">审批表自动生成与电子签字系统</h1>         </div>     </header>     <main class="container py-4">         @RenderBody()     </main>     <footer class="bg-light py-3 mt-4">         <div class="container text-center text-muted">             <p>© @DateTime.Now.Year 审批表系统 - 版权所有</p>         </div>     </footer>     <!-- Bootstrap JS -->     <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>     <!-- 签字板脚本 -->     <script src="~/js/signature-pad.js"></script>     @RenderSection("Scripts", required: false) </body> </html>

Index.cshtml - 条件输入页面

@model ApprovalFormGenerator.Models.ApprovalCriteria

@{
    ViewData["Title"] = "填写审批条件";
}

<div class="card shadow-sm">
    <div class="card-header bg-secondary text-white">
        <h2 class="h4 mb-0">审批条件输入</h2>
    </div>
    <div class="card-body">
        <form asp-action="Preview" method="post">
            <div class="row mb-3">
                <div class="col-md-6">
                    <label asp-for="ApplicantName" class="form-label"></label>
                    <input asp-for="ApplicantName" class="form-control @(ModelState["ApplicantName"].Errors.Any() ? "is-invalid" : "")" />
                    <span asp-validation-for="ApplicantName" class="text-danger"></span>
                </div>
                <div class="col-md-6">
                    <label asp-for="Department" class="form-label"></label>
                    <input asp-for="Department" class="form-control @(ModelState["Department"].Errors.Any() ? "is-invalid" : "")" />
                    <span asp-validation-for="Department" class="text-danger"></span>
                </div>
            </div>

            <div class="row mb-3">
                <div class="col-md-6">
                    <label asp-for="ApprovalType" class="form-label"></label>
                    <select asp-for="ApprovalType" class="form-select @(ModelState["ApprovalType"].Errors.Any() ? "is-invalid" : "")">
                        <option value="">请选择申请类型</option>
                        @foreach (var type in ViewBag.ApprovalTypes as List<string>)
                        {
                            <option value="@type">@type</option>
                        }
                    </select>
                    <span asp-validation-for="ApprovalType" class="text-danger"></span>
                </div>
                <div class="col-md-6">
                    <label asp-for="UrgencyLevel" class="form-label"></label>
                    <select asp-for="UrgencyLevel" class="form-select">
                        @foreach (var level in ViewBag.UrgencyLevels as List<string>)
                        {
                            <option value="@level">@level</option>
                        }
                    </select>
                </div>
            </div>

            <div class="mb-3">
                <label asp-for="Amount" class="form-label"></label>
                <input asp-for="Amount" class="form-control @(ModelState["Amount"].Errors.Any() ? "is-invalid" : "")" type="number" step="0.01" min="0" />
                <span asp-validation-for="Amount" class="text-danger"></span>
            </div>

            <div class="mb-3">
                <label asp-for="Reason" class="form-label"></label>
                <textarea asp-for="Reason" class="form-control @(ModelState["Reason"].Errors.Any() ? "is-invalid" : "")" rows="3"></textarea>
                <span asp-validation-for="Reason" class="text-danger"></span>
            </div>

            <div class="mb-3">
                <label asp-for="Remarks" class="form-label"></label>
                <textarea asp-for="Remarks" class="form-control" rows="2"></textarea>
            </div>

            <div class="d-grid gap-2 d-md-flex justify-content-md-end">
                <button type="submit" class="btn btn-primary btn-lg">生成审批表</button>
            </div>
        </form>
    </div>
</div>

@section Scripts {
    <script src="~/lib/jquery-validation/dist/jquery.validate.min.js"></script>
    <script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"></script>
}

Preview.cshtml - 审批表预览与签字页面

@model ApprovalFormGenerator.Models.ApprovalForm

@{
    ViewData["Title"] = "审批表预览与签字";
}

<div class="card shadow-sm mb-4">
    <div class="card-header bg-primary text-white">
        <h2 class="h4 mb-0">@Model.ApprovalType - 审批表</h2>
    </div>
    <div class="card-body">
        <div class="row mb-3">
            <div class="col-md-6">
                <strong>申请人:</strong> @Model.ApplicantName
            </div>
            <div class="col-md-6">
                <strong>申请部门:</strong> @Model.Department
            </div>
        </div>
        <div class="row mb-3">
            <div class="col-md-6">
                <strong>申请日期:</strong> @Model.ApplyDate.ToString("yyyy-MM-dd HH:mm")
            </div>
            <div class="col-md-6">
                <strong>紧急程度:</strong> 
                <span class="badge @(Model.UrgencyLevel == "普通" ? "bg-secondary" : Model.UrgencyLevel == "紧急" ? "bg-warning" : "bg-danger")">
                    @Model.UrgencyLevel
                </span>
            </div>
        </div>
        <div class="mb-3">
            <strong>申请事由:</strong>
            <p class="card-text">@Model.Reason</p>
        </div>
        <div class="row mb-3">
            <div class="col-md-6">
                <strong>申请金额:</strong> ¥@Model.Amount.ToString("N2")
            </div>
        </div>
        @if (!string.IsNullOrEmpty(Model.Remarks))
        {
            <div class="mb-3">
                <strong>附加说明:</strong>
                <p class="card-text">@Model.Remarks</p>
            </div>
        }
    </div>
</div>

<!-- 签字区域 -->
<div class="card shadow-sm mb-4">
    <div class="card-header bg-secondary text-white">
        <h3 class="h5 mb-0">电子签字</h3>
    </div>
    <div class="card-body">
        <div class="alert alert-info">
            请在下方签字区域手写签名,支持鼠标或触摸屏操作
        </div>
        
        <!-- 签字板 -->
        <div class="signature-pad-container">
            <canvas id="signaturePad" class="signature-pad border rounded"></canvas>
        </div>
        
        <div class="d-flex justify-content-between mt-3">
            <button id="clearSignature" class="btn btn-outline-danger">清除签名</button>
            <button id="submitForm" class="btn btn-success">确认签字并提交</button>
        </div>
    </div>
</div>

<form id="submitForm" action="@Url.Action("Submit")" method="post">
    <input type="hidden" name="signatureData" id="signatureData" />
</form>

@section Scripts {
    <script>
        document.addEventListener('DOMContentLoaded', function() {
            // 初始化签字板
            Const canvas = document.getElementById('signaturePad');
            const signatureDataInput = document.getElementById('signatureData');
            const submitButton = document.getElementById('submitForm');
            const clearButton = document.getElementById('clearSignature');
            
            // 设置签字板尺寸
            function resizeCanvas() {
                const container = canvas.parentElement;
                canvas.width = container.clientWidth - 30; // 减去内边距
                canvas.height = 200;
            }
            
            window.addEventListener('resize', resizeCanvas);
            resizeCanvas();
            
            // 签字板绘制功能
            let isDrawing = false;
            let lastX = 0;
            let lastY = 0;
            const ctx = canvas.getContext('2d');
            
            // 设置画笔样式
            ctx.lineWidth = 2;
            ctx.lineCap = 'round';
            ctx.strokeStyle = '#000';
            
            // 开始绘制
            function startDrawing(e) {
                isDrawing = true;
                [lastX, lastY] = getCanvasCoordinates(e);
            }
            
            // 绘制中
            function draw(e) {
                if (!isDrawing) return;
                
                ctx.beginPath();
                ctx.moveTo(lastX, lastY);
                [lastX, lastY] = getCanvasCoordinates(e);
                ctx.lineTo(lastX, lastY);
                ctx.stroke();
            }
            
            // 结束绘制
            function endDrawing() {
                isDrawing = false;
            }
            
            // 获取鼠标/触摸在画布上的坐标
            function getCanvasCoordinates(e) {
                const rect = canvas.getBoundingClientRect();
                let x, y;
                
                if (e.type.includes('touch')) {
                    x = e.touches[0].clientX - rect.left;
                    y = e.touches[0].clientY - rect.top;
                } else {
                    x = e.clientX - rect.left;
                    y = e.clientY - rect.top;
                }
                
                return [x, y];
            }
            
            // 绑定事件
            canvas.addEventListener('mousedown', startDrawing);
            canvas.addEventListener('mousemove', draw);
            canvas.addEventListener('mouseup', endDrawing);
            canvas.addEventListener('mouseout', endDrawing);
            
            // 支持触摸设备
            canvas.addEventListener('touchstart', function(e) {
                e.preventDefault();
                startDrawing(e);
            });
            canvas.addEventListener('touchmove', function(e) {
                e.preventDefault();
                draw(e);
            });
            canvas.addEventListener('touchend', function(e) {
                e.preventDefault();
                endDrawing(e);
            });
            canvas.addEventListener('touchcancel', function(e) {
                e.preventDefault();
                endDrawing(e);
            });
            
            // 清除签名
            clearButton.addEventListener('click', function() {
                ctx.clearRect(0, 0, canvas.width, canvas.height);
                signatureDataInput.value = '';
            });
            
            // 提交表单
            submitButton.addEventListener('click', function() {
                // 检查是否有签名
                const isEmpty = ctx.getImageData(0, 0, canvas.width, canvas.height).data.every(channel => channel === 0);
                
                if (isEmpty) {
                    alert('请先完成电子签名!');
                    return;
                }
                
                // 将签名转换为Base64格式
                const signatureData = canvas.toDataURL('image/png');
                signatureDataInput.value = signatureData;
                
                // 提交表单
                document.getElementById('submitForm').submit();
            });
        });
    </script>
}

Completed.cshtml - 提交完成页面

@{
    ViewData["Title"] = "提交成功";
}

<div class="alert alert-success text-center">
    <i class="bi bi-check-circle-fill" style="font-size: 3rem; color: #198754;"></i>
    <h2 class="h3 mt-3">审批表提交成功!</h2>
    <p class="mt-2">您的审批表编号:<strong>@ViewBag.FormId</strong></p>
    <p class="mt-2">请妥善保存此编号,以便后续查询</p>
    <div class="mt-4">
        <a href="@Url.Action("Index")" class="btn btn-primary btn-lg">创建新审批表</a>
        <a href="@Url.Action("ViewSubmittedForms")" class="btn btn-outline-secondary btn-lg ms-2">查看已提交审批表</a>
    </div>
</div>
2.4 静态资源(wwwroot)

css/signature-pad.css

.signature-pad-container {
    position: relative;
    width: 100%;
    margin: 0 auto;
}

.signature-pad {
    width: 100%;
    height: 200px;
    border: 2px dashed #ccc;
    border-radius: 8px;
    background-color: #f9f9f9;
    cursor: crosshair;
}

.signature-pad:hover {
    border-color: #666;
}

/* 适配移动端 */
@media (max-width: 768px) {
    .signature-pad {
        height: 180px;
    }
}

js/signature-pad.js

// 空文件,主要逻辑已内联在Preview视图中,此处预留扩展空间

3. 应用程序配置(Program.cs)

var builder = WebApplication.CreateBuilder(args);

// 添加MVC服务
builder.Services.AddControllersWithViews();

// 添加会话支持(用于临时存储审批表数据)
builder.Services.AddSession(options =>
{
    options.Cookie.Name = ".ApprovalForm.Session";
    options.IdleTimeout = TimeSpan.FromMinutes(30); // 会话超时时间
    options.Cookie.IsEssential = true;
});

var app = builder.Build();

// 配置HTTP请求管道
if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Home/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseSession(); // 使用会话

app.UseAuthorization();

// 配置路由
app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Approval}/{action=Index}/{id?}");

app.Run();

4. 功能特点与使用说明

4.1 核心功能
  1. 条件驱动的审批表生成:用户输入申请人信息、申请类型、事由等条件,系统自动生成标准审批表

  2. 电子手写签字:支持鼠标(PC 端)和触摸(移动端)签字,Canvas 实时绘制

  3. 响应式设计:适配 PC、平板和手机等不同设备

  4. 表单验证:对必填项进行前端 + 后端双重验证

  5. 审批表存储:模拟存储已提交的审批表,支持查看历史记录

4.2 使用流程
  1. 访问系统首页(默认跳转到条件输入页面)

  2. 填写审批条件(标 * 为必填项),点击 "生成审批表"

  3. 预览生成的审批表,确认信息无误后在签字区域手写签名

  4. 点击 "确认签字并提交",完成审批表提交

  5. 提交成功后可获取审批表编号,或创建新审批表 / 查看历史记录

4.3 技术亮点
  1. 使用 ASP.NET Core 8 构建高效后端,支持跨平台部署

  2. 采用 HTML5 Canvas 实现无插件电子签字,支持多种设备

  3. 结合 Bootstrap 5 实现响应式 UI,提升用户体验

  4. 使用 TempData 和 Session 临时存储审批表数据,确保数据安全

  5. 前端表单验证与后端模型验证结合,保证数据完整性

5. 扩展建议

  1. 持久化存储:将提交的审批表存储到数据库(如 SQL Server、MySQL),替换当前的内存存储

  2. 审批流程管理:增加审批节点设置、审批人分配、流程跟踪等功能

  3. 导出功能:支持将审批表导出为 PDF 或 Word 格式

  4. 签名验证:集成数字签名或生物识别技术,增强签名安全性

  5. 批量生成:支持导入 Excel 数据批量生成审批表

  6. 通知功能:审批状态变更时通过邮件或短信通知相关人员

6. 部署要求

  • .NET 8 SDK

  • 浏览器支持:Chrome 88+、Firefox 85+、Edge 88+、Safari 14+

  • 如需持久化存储,需配置相应数据库(SQL Server、MySQL 等)

此系统提供了完整的审批表生成与电子签字解决方案,代码结构清晰,易于扩展,可根据实际业务需求进一步定制化开发。


栏目导航
相关文章
文章标签
关于我们
公司简介
企业文化
资质荣誉
服务项目
高端网站定制
微信小程序开发
SEO排名推广
新闻动态
行业新闻
技术学院
常见问题
联系我们
联系我们
人才招聘
联系方式
Q Q:24722
微信:24722
电话:13207941926
地址:江西省抚州市赣东大道融旺国际3栋
Copyright©2008-2022 抚州市奕玖科技有限公司 备案号:赣ICP备2022010182号-1