简介:本资源是一套面向高校计算机专业本科生的毕业设计级电子病历系统实现方案,基于ASP.NET Web Forms技术栈开发,聚焦医疗信息化场景下的患者管理、医生信息维护、病历录入与药品目录等核心业务模块。压缩包共119个文件,包含22个ASPX前端页面(如病历列表、患者新增、医生信息管理等)、20个C#后台逻辑文件、13张JPG/GIF界面截图与图标资源、6个DLL依赖库及数据库文件(MDF/LDF),辅以CSS、JS和配置文件,完整覆盖前后端与数据层,总大小仅1.34MB,轻量易部署。已有136人学习下载,适合课程设计、毕设参考或ASP.NET入门实践者快速掌握Web表单开发、SQL Server集成及三层架构落地思路。读者可直接运行调试,深入理解电子病历系统的权限划分、数据增删改查流程与典型页面跳转逻辑。 拿到"基于ASP.NET的电子病历系统毕业设计实现+源码.rar"这个题目,很多同学的第一反应是:这不就是一个信息管理系统吗?患者增删改查、病历增删改查、用户登录登出,做完界面写个数据库,就完事了。
如果你真的这么想,那大概率会做出一个答辩时被评委十分钟问懵的系统。
电子病历这个领域,真正的难点从来不是"能不能录入数据",而是"录进去之后怎么保证它可信、可控、可追溯"。病历是法律文书,是医疗纠纷里要拿出来当证据的东西。它有着编辑锁定、签名确认、版本留存、分级授权、全程留痕这些特殊要求。把这一层业务逻辑想明白了,你的系统才算真正"懂行",而不是一堆页面拼在一起。
这篇文章,我基于自己做过的项目经验和帮人审毕设代码的经历,把一条从技术选型、领域建模、数据库设计、功能实现、安全加固到答辩演示的完整路径拆给你。全文围绕ASP.NET这套技术栈来展开,最后一个"源码.rar"解压之后怎么让它真的变成你答辩时拿得出手的东西,也在里面。
1. 技术栈选型:三代ASP.NET怎么选,既稳又能加分
1.1 为什么这个题目值得用ASP.NET做
先说一个可能让很多人意外的结论:在毕业设计这个赛道上,选ASP.NET其实是"错位竞争"。
大多数同学的首选往往是Java SSM/Spring Boot,或者Python Django/Flask。这些方向确实是主流,但也意味着评委老师看得太多,答辩时审得也细。而ASP.NET这边,尤其是ASP.NET Core,会的人反而少一些。选它有几个实际的收益:
- C#这门语言的工程性是真的强。强类型、nullable引用类型、LINQ、async/await,写出来的代码规范度天然比动态语言好,答辩讲代码时也更有得讲。
- ASP.NET Core框架本身内置了大量生产级能力:依赖注入(DI)、身份认证框架(ASP.NET Core Identity)、结构化日志(ILogger)、配置系统、环境变量管理。这些不是"第三方插件",而是框架自带的东西,你在代码里随手用上,论文里就多出很多"设计亮点"。
- 电子病历系统天然适合Windows/IIS生态,部署演示时最容易跑通的组合就是.NET + SQL Server。
一句话:同样一个系统,用ASP.NET Core写,你在"架构合理性"和"工程规范度"上的得分空间,比用传统三层JSP要高。
1.2 Web Forms、MVC、Core 到底该选哪个
"基于ASP.NET的电子病历系统"这个题目,网上能找到的老源码,绝大多数是ASP.NET Web Forms(也就是.aspx那套)写的,甚至还有ASP.NET MVC 5写在.NET Framework 4.x上的。如果你从这种源码起步,务必想清楚三件事:
| 框架 | 生命周期 | 适不适合作为新毕设 | 原因 |
|---|---|---|---|
| ASP.NET Web Forms | 已停止重大演进 | 不推荐 | 服务器控件和视图状态机制老旧,写出的代码难维护,答辩时容易被评委质疑技术选型落后 |
| ASP.NET MVC 5 (.NET Framework) | 维护模式 | 勉强可用 | 只能跑Windows,无法跨平台,新特性支持有限,但架构相对Web Forms清晰 |
| ASP.NET Core (8/9) | 积极演进 | 推荐 | 跨平台、内置DI、性能高、生态新,能体现选题的前瞻性 |
我给你的结论非常明确:不要因为网上搜到的老源码是Web Forms,就跟着用Web Forms。选ASP.NET Core 8(当前LTS长期支持版本),如果你做毕设的年份.NET 9已经发布且稳定,也可以用9,但考虑到答辩环境的稳定性,8更稳妥。
1.3 配套选型:EF Core、数据库、前端怎么搭
技术选型不是框架一个点就完了,整条链路要能自洽:
- 数据访问层:直接用EF Core(Entity Framework Core)。它的LINQ查询体验比手写ADO.NET或拼接SQL好一个时代,而且自动参数化查询能帮你挡掉SQL注入。答辩时被问到"怎么防止SQL注入",答"数据访问框架层面自动参数化,自定义SQL全部用FromSqlInterpolated",这已经比很多毕设高一个档次。
- 数据库:首选SQL Server Express或LocalDB。它是微软生态的标准搭配,安装简单,LocalDB甚至不用配置实例。若你电脑内存紧张,MySQL 8也完全没问题。
- 页面技术:如果你项目时间紧、重点是业务逻辑,直接用Razor Pages或MVC + Razor视图 + Bootstrap,简单且够用。如果想显得更"现代",前端可以局部引入Vue 3,通过API和后端对接,但这会明显增加工作量,通常不建议作为毕设的默认选择。
- 认证方案:传统页面应用用Cookie认证,SPA式前后端分离用JWT。毕设如果以页面为主,选前者。
我见过太多同学把时间浪费在"前端框架选型纠结"上,最后页面写得稀碎。作为毕设,核心是系统完整度和业务逻辑自洽程度。前端做到整洁、响应式、够用,就很好。
2. 领域建模先行:电子病历系统最值钱的部分不是CRUD
2.1 病历的状态机:草稿、待签名、已签名、已归档
电子病历和一般表单最大的区别在于:一份病历存在"生命周期",而且这个生命周期是不可逆推进的。
| 状态 | 含义 | 允许的操作 |
|---|---|---|
| 草稿(Draft) | 医生正在编辑,还没提交 | 编辑、保存、删除 |
| 待签名(Pending) | 已提交但医生或上级医生未签名 | 查看、签名、退回修改 |
| 已签名(Signed) | 医生已签名,具有法律效力 | 查看、打印,原则上禁止修改 |
| 已归档(Archived) | 患者出院后病历封存 | 查看、打印,禁止一切编辑 |
这个状态机用代码实现,最简单的做法是在MedicalRecord实体上加一个Status字段,同时用状态枚举约束业务层的修改操作。稍微讲究一点,可以把状态流转封装成方法,比如:
public class MedicalRecord { public Guid Id { get; set; } public Guid PatientId { get; set; } public string Status { get; private set; } public DateTime? SubmitTime { get; private set; } public DateTime? SignTime { get; private set; } public bool CanEdit() => Status == RecordStatus.Draft || Status == RecordStatus.Pending; public bool Submit() { if (Status != RecordStatus.Draft) return false; Status = RecordStatus.Pending; SubmitTime = DateTime.Now; return true; } public bool Sign() { if (Status != RecordStatus.Pending) return false; Status = RecordStatus.Signed; SignTime = DateTime.Now; return true; } }从业务角度说清楚这套状态流转,比写一百个CRUD接口都更能体现你对"病历"这个事物的理解。答辩时评委问"病历能不能随意改",你直接引状态机:已签名的病历编辑访问直接返回HTTP 403,数据库层再配合Status字段过滤,双重保证。
2.2 权限模型:三角色与三级数据范围
电子病历的权限,往下细说有三重维度:角色维度(你是谁)、数据范围维度(你能看谁的数据)、操作维度(你能做什么)。毕设里做到这三重,基本就很扎实了。
| 角色 | 数据范围 | 核心权限 |
|---|---|---|
| 医生 | 本人创建 + 本科室患者 | 病历书写、编辑、签名、查看本科病历 |
| 护士 | 本科室患者 | 查看病历、执行医嘱、记录护理信息 |
| 系统管理员 | 全院数据 + 配置权限 | 用户管理、角色分配、日志审计、数据备份 |
实现上用ASP.NET Core Identity,角色存AspNetRoles,用户角色关系存AspNetUserRoles,然后在控制器或页面处理器上打特性:
[Authorize(Roles="Doctor")] public IActionResult Edit(Guid id) { ... }但注意,光有角色是不够的。竖着看,医生能不能编辑别的科室医生写的病历?不能。所以业务查询语句里必须带上数据范围条件,比如"WHERE DoctorId = 当前登录人 OR DepartmentId = 当前登录人科室"。
这里有个原则值得记住:授权分两层——第一层是'你有没有权限做这个动作'(角色),第二层是'你具体能做哪一条数据'(数据范围)。两个都过了,才放行。
2.3 结构化与模板化:病历不是一个大文本框
很多新手把病历设计成"一个大文本域",这基本会在答辩时被一击即溃。真实的病历是有结构的:主诉、现病史、既往史、体格检查、辅助检查、初步诊断、治疗意见,每个章节各有其格式和必填项。
毕设里至少做到两步:
- 模板机制:每种病历类型(门诊病历、入院记录、出院小结、病程记录)对应一个模板,模板用JSON描述各个章节的结构和顺序。
- 内容存储:病历正文采用"章节片段"方式存,每段存标题和HTML内容,而不是一整个长字符串。这样后续做检索、做打印、做统计分析都方便。
模板的JSON结构大概是:
{ "templateId": "admission", "name": "入院记录", "sections": [ { "code": "chiefComplaint", "label": "主诉", "type": "textarea", "required": true }, { "code": "presentIllness", "label": "现病史", "type": "textarea", "required": true }, { "code": "diagnosis", "label": "初步诊断", "type": "text", "required": true } ] }页面渲染时根据模板动态生成表单,保存时按章节存JSON或分表存。这套"模板驱动"的设计,说出去是能打动评委的。
3. 数据库设计:按这套结构来搭,后期基本不用返工
3.1 核心表:七张表解决90%的业务
我直接给你一份可以当蓝图的表清单,具体字段按你的项目裁剪:
| 表名 | 用途 | 关键字段 |
|---|---|---|
| Users | 用户(继承IdentityUser) | UserName, RealName, DepartmentId |
| Patients | 患者基本信息 | Name, Gender, BirthDate, Phone, IdCard, Address |
| MedicalRecords | 病历主表 | PatientId, DoctorId, DepartmentId, RecordType, Status, SubmitTime, SignTime |
| MedicalRecordContents | 病历内容 | MedicalRecordId, SectionCode, SectionTitle, Content, Version |
| MedicalTemplates | 病历模板 | TemplateName, Type, StructureJson, IsActive |
| Orders | 医嘱 | PatientId, DoctorId, OrderType, Content, Status, StartTime, EndTime |
| AuditLogs | 操作日志 | UserId, UserName, Action, TargetType, TargetId, Detail, IpAddress, CreateTime |
"七张表"不是什么硬性标准,但它确实覆盖了电子病历系统的全部主干。实际项目里再补充一些关联表,比如用户-角色关联表(Identity自动生成)、模板-科室映射表等。
3.2 为什么病历要拆"主表"和"内容表"
这是我在帮人审毕设时最常强调的一个设计决策。
如果不拆,病历主表里直接塞一个超长的Content字符串,会带来三个问题:查询列表时每次都要把大字段捞出来、无法做章节级别的版本管理、跨科室分享和统计时无从下手。拆成MedicalRecords和MedicalRecordContents后:
- 主表负责状态流转、归属关系、签名时间,列表页只查主表,性能很好。
- 内容表按章节存储,天然支持"显示某个章节"和"局部修改"。
- 配合Version字段,可以做到"每次签名前保存历史版本",这个直接对应病历的防篡改需求。
public class MedicalRecordContent { public Guid Id { get; set; } public Guid MedicalRecordId { get; set; } public string SectionCode { get; set; } public string SectionTitle { get; set; } public string Content { get; set; } public int Version { get; set; } public DateTime UpdatedAt { get; set; } }3.3 常用查询与索引设计
电子病历系统最常跑的三类查询:
- 按患者查所有病历(患者详情页):
WHERE PatientId = @p ORDER BY CreateTime DESC。索引:(PatientId, CreateTime)。 - 按科室查近期病历(科室工作台):
WHERE DepartmentId = @p AND CreateTime BETWEEN @start AND @end。索引:(DepartmentId, CreateTime)。 - 模糊搜病历内容(全文检索):早期可以做
WHERE Content LIKE '%关键词%',数据量大了建议用倒排索引,但毕设到LIKE已经够用。
你需要记住的另一个原则:永远不要写无索引的粗粒度全表扫描查询。比如"统计所有已签名病历"这类查询,在状态字段上建索引,效率会好非常多。这不是性能调优的高级课题,这是在答辩现场用SQL Profiler稍微演示一下就能让评委点头的东西。
4. 关键功能模块的实现要点:每一块都能讲出设计逻辑
4.1 登录与身份认证:别用Session存用户,那是十年前的事
我在看学生代码时最痛心的一个习惯是:登录成功,Session["username"]="张三",然后到处读这个Session,甚至用Session里存的用户名来判断角色。这不叫"实现了登录"。
用ASP.NET Core Identity + Cookie认证的完整流程是:
- 注册Identity服务并配置DbContext:
builder.Services.AddDbContext<AppDbContext>(options => options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection"))); builder.Services.AddIdentity<ApplicationUser, IdentityRole>() .AddEntityFrameworkStores<AppDbContext>() .AddDefaultTokenProviders(); builder.Services.ConfigureApplicationCookie(options => { options.LoginPath = "/Account/Login"; options.AccessDeniedPath = "/Account/Denied"; });- 登录控制器:
[HttpPost] public async Task<IActionResult> Login(LoginViewModel model) { var user = await _userManager.FindByNameAsync(model.UserName); if (user == null || !await _userManager.CheckPasswordAsync(user, model.Password)) return View(model); await _signInManager.SignInAsync(user, isPersistent: true); return RedirectToAction("Index", "Home"); }- 在需要鉴权的地方用
[Authorize]+ 角色特性。
这套方案的好处:密码哈希(默认PBKDF2)、防暴力破解的登录锁定、防CSRF的令牌机制,框架全给你处理好了,不用自己造轮子。
4.2 病历书写与保存:事务和版本是核心
病历保存这个动作,在代码层面至少包含三步:更新主表状态、插入内容章节、写入操作日志。这三步必须放在同一个数据库事务里,否则中途失败会留下"主表说已保存,内容表却是空的"这种脏数据。
public async Task<Result> SaveRecordContentAsync(Guid recordId, List<SectionViewModel> sections, string currentUser) { using var transaction = await _db.Database.BeginTransactionAsync(); try { var record = await _db.MedicalRecords.FindAsync(recordId); if (record == null || !record.CanEdit()) return Result.Fail("病历不存在或当前状态不可编辑"); var currentVersion = await _db.MedicalRecordContents .Where(c => c.MedicalRecordId == recordId) .MaxAsync(c => (int?)c.Version) ?? 0; foreach (var section in sections) { var content = await _db.MedicalRecordContents .FirstOrDefaultAsync(c => c.MedicalRecordId == recordId && c.SectionCode == section.Code); if (content == null) { _db.MedicalRecordContents.Add(new MedicalRecordContent { MedicalRecordId = recordId, SectionCode = section.Code, SectionTitle = section.Title, Content = section.Content, Version = currentVersion + 1 }); } else { content.Content = section.Content; content.Version = currentVersion + 1; content.UpdatedAt = DateTime.Now; } } await _auditLogService.LogAsync(currentUser, "SaveRecordContent", "MedicalRecord", recordId, sectionJson); await _db.SaveChangesAsync(); await transaction.CommitAsync(); return Result.Ok(); } catch { await transaction.RollbackAsync(); return Result.Fail("保存失败,所有改动已回滚"); } }注意两点。第一,查询版本号用MaxAsync后转int?,避免空集合报错;第二,每次保存日志记录的是"改前和改后"的差异,这个留给审计服务去做,不要在业务代码里散落日志逻辑。
4.3 医嘱管理:状态机和科室协作
医嘱不是简单的"开药"。医嘱有类型(长期医嘱、临时医嘱、出院带药医嘱),有状态(待执行、已执行、已停止),还有执行时间约束。
建议的字段设计可以这样:
public class Order { public Guid Id { get; set; } public Guid PatientId { get; set; } public Guid DoctorId { get; set; } public string OrderType { get; set; } // LongTerm / Temporary / Discharge public string Content { get; set; } // 医嘱内容 public string Status { get; set; } // Pending / Executed / Cancelled / Stopped public DateTime? StartTime { get; set; } public DateTime? EndTime { get; set; } public string ExecutorName { get; set; } public DateTime? ExecutedTime { get; set; } }在护士端,列表里过滤Status == Pending的医嘱,执行后置为Executed并记录执行人和执行时间。长期医嘱到期后自动或手动置为Stopped。这套逻辑跟病历状态机一样,把业务规则固化在代码里,千万不要在页面上自由填状态。
4.4 模板管理与动态渲染
模板表存JSON结构,那页面端怎么渲染?
Razor Pages里可以用一个局部视图接收模板JSON,然后循环生成表单控件:
@model TemplateRenderModel <form id="recordForm" method="post"> @foreach (var section in Model.TemplateSections) { <div class="form-group"> <label>@section.Label</label> @if (section.Type == "textarea") { <textarea class="form-control" name="sections[@section.Code]" rows="4">@Model.GetContent(section.Code)</textarea> } else { <input class="form-control" name="sections[@section.Code]" value="@Model.GetContent(section.Code)" /> } </div> } <button type="submit" class="btn btn-primary">保存</button> </form>提交时用IFormCollection或一个字典接收key = sectionCode,再走4.2节的保存逻辑。这个"模板驱动"的架构有个好处:新增一种病历类型,只要在后台配置模板,不需要改代码。
4.5 操作日志:让每个动作都有迹可循
我对所有毕设系统的一个硬性要求就是:至少要有"用户管理"和"日志管理"两个模块,并且日志要真实记录到数据库,不是打印到控制台就完事。
日志表的字段至少包含:操作人、操作时间、动作(登录/退出/保存/签名/删除/权限修改)、目标对象、目标ID、详情(JSON格式)、IP地址。每次关键操作,用第三方库Serilog或自封装的AuditLogService插入一条记录。
有了这套日志,答辩时可以现场演示:"我刚把一份已签名病历的编辑请求提交了,现在到日志里看有没有记录。"这一手展示,比你在台上说半小时"这个系统很安全"都有效。
5. 安全合规:在毕业设计尺度上,医疗数据安全要做到什么程度
5.1 医疗数据的特殊性:为何安全不是选修课
电子病历系统处理的是患者隐私数据,这在真实行业里受到严格监管。作为毕业设计,不要求你做到生产环境的等保合规,但必须体现你的"安全意识"。安全意识的呈现,三个点是基本盘:
- 密码不能明文存储(Identity自动做了,但你要能讲清楚用了什么算法)。
- 操作必须全程可审计(上面的AuditLogs)。
- 敏感数据(身份证号、手机号)应做加密存储或脱敏展示。
5.2 三道基础防线:SQL注入、XSS、CSRF
- SQL注入:用EF Core的LINQ查询,默认参数化,自然免疫。如果你有手写SQL的场景,务必用参数化。
- XSS:Razor视图默认对输出内容进行HTML编码。但要注意,如果你用
@Html.Raw()输出富文本,一定要做白名单过滤,否则病历内容可能被注入脚本。 - CSRF:ASP.NET Core表单默认带AntiForgeryToken,
<form method="post">里加@Html.AntiForgeryToken(),配合[ValidateAntiForgeryToken],基本就防住了。
5.3 审计日志与数据备份意识
数据备份在单机毕设里不用做复杂方案,但系统里可以留一个"备份/恢复"模块,用BACKUP DATABASE命令或导出JSON的方式实现。这又是个答辩加分项。
public async Task<IActionResult> Backup() { var dbPath = _config.GetConnectionString("DefaultConnection"); var backupCmd = $"BACKUP DATABASE EMRDb TO DISK = 'D:\\Backups\\EMRDb_{DateTime.Now:yyyyMMdd_HHmm}.bak'"; await _db.Database.ExecuteSqlRawAsync(backupCmd); return Ok("备份成功"); }6. 实战踩坑清单:这些坑我全踩过,你直接跳过
6.1 EF Core迁移相关的两个大坑
第一个坑:dotnet ef命令找不到。报错信息通常是"无法执行,因为找不到指定的命令"。解决办法:项目里必须安装Microsoft.EntityFrameworkCore.Design包,并且dotnet-ef工具版本要匹配;用的命令是dotnet ef migrations add Init和dotnet ef database update。
第二个坑:迁移时自动生成的内容把Program.cs搞乱了。如果你用的是较新版本的EF Core,迁移的DesignTimeDbContextFactory可能不会自动生成,反倒会要求程序启动时能创建DbContext。这时候老老实实把连接字符串配置到appsettings.json并注册服务,确保dotnet ef能拿到配置。
6.2 中文乱码与数据库排序规则
中文乱码这个问题,一半出现在页面编码,一半出现在数据库排序规则。标准的做法是:Razor页面文件保存为UTF-8(VS默认就是),数据库建库时指定Chinese_PRC_CI_AS排序规则,连接字符串里加上Character Set=utf8mb4;(MySQL场景)。
SQL Server场景建议建库语法:
CREATE DATABASE EMRDb COLLATE Chinese_PRC_CI_AS;6.3 异步编程:别用.Result卡死线程
ASP.NET Core是全异步模型,但很多同学不习惯。出现"线程卡死、页面转圈"的头号元凶,是用同步方式调异步方法:_db.SaveChanges().Wait()或.Result。这在某些同步上下文里会引起死锁。
正确写法全是await:
public async Task<IActionResult> Detail(Guid id) { var record = await _db.MedicalRecords .Include(r => r.Patient) .FirstOrDefaultAsync(r => r.Id == id); return View(record); }6.4 部署与发布:IIS里跑不起来怎么办
毕设最终提交时通常要现场演示。如果你要在本机IIS上部署,记住两个前置条件:
- 安装对应版本的.NET Hosting Bundle(包含ASP.NET Core运行时和IIS模块)。
- 发布时选择"框架依赖"模式,保留web.config;把发布目录直接作为IIS站点物理路径。
- 如果报500.30错误,多半是启动失败。先看Windows事件查看器里的.NET Runtime日志,绝大多数能定位到"连接字符串错误"或"服务注册缺失"。
如果你不想折腾IIS,直接用dotnet run启动访问localhost也完全可以,但提前用无头浏览器或真实浏览器跑一遍演示流程,别到答辩现场才知道端口被占用。
7. 答辩演示与源码组织:怎么把"做了"讲成"做得好"
7.1 演示脚本:一条主线贯穿到底
答辩时间通常5-10分钟,不要每个页面都点一遍。我建议的演示主线:
- 管理员登录 → 创建角色/用户(体现权限管理)。
- 医生登录 → 选择患者 → 新建病历 → 选择模板 → 填写关键章节 → 保存草稿(体现模板驱动)。
- 提交病历 → 状态变为待签名 → 签名 → 状态变为已签名(体现状态机)。
- 尝试编辑已签名病历 → 系统阻止(体现防篡改)。
- 切换到护士账号 → 查看该患者医嘱 → 执行(体现协作)。
- 切回管理员 → 查看操作日志,指出刚才的操作全部有记录(体现审计)。
这套流程讲完,系统的主干和设计亮点全部落地,评委基本不会觉得"这个系统没深度"。
7.2 评委常问的七个问题
- "为什么选ASP.NET Core而不是Java?"——答生态、跨平台、自带DI和认证框架、C#强类型工程性好。
- "病历怎么防篡改?"——答状态机锁定 + 版本号记录 + 审计日志留存。
- "权限是怎么控制的?"——答RBAC + 数据范围两重过滤。
- "数据量大了会卡吗?"——答分页查询 + 索引设计 + 内容表与主表分离。
- "这个系统离真实产品还差什么?"——答消息推送/级联复制/全文检索/高可用,这些是刻意留白。
- "密码安全怎么做的?"——答Identity默认PBKDF2哈希,加盐存储。
- "设计上最满意的一个点?"——答病历模板驱动和状态机设计。
7.3 源码组织与文档
源码.rar解压后,建议内部结构按标准的Clean/分层结构组织:
EMR.sln ├── src/ │ ├── EMR.Domain # 实体、枚举、领域逻辑 │ ├ <p> <a href="https://download.csdn.net/download/Mmnnnbb123/85189233" style="color:#ec7500;font-size:14px;"> 本文还有配套的精品资源,点击获取 </a> <img alt="menu-r.4af5f7ec.gif" src="https://csdnimg.cn/release/wenkucmsfe/public/img/menu-r.4af5f7ec.gif" style="width:16px;margin-left:4px;vertical-align:text-bottom;cursor:text;"> </p>