26种不良事件表单的通用设计模式与实现
前言
医院不良事件种类繁多,从输血反应到手术并发症,从药品不良反应到职业暴露,需要覆盖医疗活动的各个环节。本文将详细介绍26种不良事件表单的通用设计模式,包括动态路由、JSON内容存储、表单提交验证等核心实现。
一、事件表单的动态路由设计
1.1 RESTful风格的URL设计
系统采用 {EventType}{Action} 的URL命名规范,例如:
HarmfulEvent01Add - 输血不良反应-新增
HarmfulEvent01Edit - 输血不良反应-编辑
HarmfulEvent01Show - 输血不良反应-查看
HarmfulEvent06Add - 药品不良反应-新增
HarmfulEvent13Edit - 跌倒坠床-编辑
1.2 动态Action映射
csharp
[HttpPost]
public JsonResult HarmfulEventAddShow(int repTypeId)
{
// 将数字类型ID转换为两位字符串: 1 -> "01", 6 -> "06"
string typeId = repTypeId.ToString().Length == 1 ? "0" + repTypeId.ToString() : repTypeId.ToString();
string url = $"HarmfulEvent{typeId}Add";
return Json(new MsgModel { code = 0, msg = url });
}
[HttpPost]
public JsonResult HarmfulEventEditShow(int repTypeId)
{
string typeId = repTypeId.ToString().Length == 1 ? "0" + repTypeId.ToString() : repTypeId.ToString();
string url = $"HarmfulEvent{typeId}Edit";
return Json(new MsgModel { code = 0, msg = url });
}
前端调用:
javascript
// 新增事件
$.post('/HarmfulEvent/HarmfulEventAddShow', field, function (data) {
var url = '/HarmfulEvent/' + data.msg;
layer.open({
title: "新增不良事件",
type: 2,
content: url,
area: ['100%', '100%']
});
});
// 编辑事件
$.post('/HarmfulEvent/HarmfulEventEditShow', field, function (data) {
var url = '/HarmfulEvent/' + data.msg + "?repId=" + field.repId;
layer.open({
title: "编辑不良事件",
type: 2,
content: url,
area: ['100%', '100%']
});
});
二、统一的事件内容模型
2.1 基类模型设计
所有26种事件都继承自统一的基础模型:
csharp
public class HarmfulEventBaseModel
{
public string deptid { get; set; } // 发生科室ID
public string occutime { get; set; } // 发生时间
public string occuaddr { get; set; } // 发生地点
public string pname { get; set; } // 患者姓名
public string psex { get; set; } // 性别
public string age { get; set; } // 年龄
public string diagnose { get; set; } // 临床诊断
public string mrn { get; set; } // 病历号
public string eventtype { get; set; } // 事件类型
public string effect { get; set; } // 损害程度
public string process { get; set; } // 发生过程
public string analysis { get; set; } // 原因分析
public string advise { get; set; } // 改进建议
public string discoverer { get; set; } // 发现人员
}
2.2 各类型专用模型
csharp
// 输血不良反应 (01)
public class HarmfulEvent01Model : HarmfulEventBaseModel
{
public string bloodtype { get; set; } // 血型
public string bloodmatchresult { get; set; } // 配血结果
public string transfusionstarttime { get; set; } // 开始输血时间
public string transfusionamount { get; set; } // 输血量
}
// 药品不良反应 (06)
public class HarmfulEvent06Model : HarmfulEventBaseModel
{
public string drugsNo { get; set; } // 药品记录号
public string impact { get; set; } // 对原患疾病影响
public string evaluation { get; set; } // 关联性评价
public string stopdrug { get; set; } // 停药或未停药
public string usedagain { get; set; } // 再次使用可疑药
public string eventresult { get; set; } // 不良反应结果
}
// 跌倒坠床事件 (13)
public class HarmfulEvent13Model : HarmfulEventBaseModel
{
public string fallscore { get; set; } // 跌倒评分
public string falladdr { get; set; } // 跌倒地点
public string iscompanion { get; set; } // 有无陪客
public string fallpart { get; set; } // 跌倒部位
public string firstreason { get; set; } // 首要原因
public string falllevel { get; set; } // 严重程度
public string defect { get; set; } // 缺陷分类
}
// 压力性损伤 (20)
public class HarmfulEvent20Model : HarmfulEventBaseModel
{
public string pressure { get; set; } // 压疮分期
public string firstreason { get; set; } // 首要原因
public string defect { get; set; } // 缺陷分类
}
// 管路事件 (11)
public class HarmfulEvent11Model : HarmfulEventBaseModel
{
public string linetype { get; set; } // 管路类型
public string linedropmode { get; set; } // 滑脱方式
public string firstreason { get; set; } // 首要原因
public string lineredo { get; set; } // 是否重新插管
public string linelevel { get; set; } // 风险等级
}
三、表单控制器实现
3.1 新增表单视图
以药品不良反应为例:
csharp
public IActionResult HarmfulEvent06Add()
{
EmployeeModel employee = JsonConvert.DeserializeObject<EmployeeModel>(
HttpContext.Session.GetString("Employee"));
string drugsNo = RandomNumber.GetRandomNumber("99"); // 生成药品记录号
string ChartNo = Request.Query["ChartNo"].ToString(); // 病历号
string ItemNo = Request.Query["ItemNo"].ToString(); // 药品编码
string system_name = Request.Query["system_name"].ToString();
// 从HIS系统获取患者信息
ViewData["patient"] = string.IsNullOrEmpty(ChartNo) ? null :
DI.QueryPatientByChartNo(_mssqlService, _sihis, _pgsqlService, _lyradb, ChartNo, system_name);
ViewData["employee"] = employee;
// 如果是接口调用,自动填充药品信息
if (!string.IsNullOrEmpty(ChartNo) || !string.IsNullOrEmpty(ItemNo))
{
AddMedreportDrugsFromMedOrder(drugsNo, ItemNo, system_name);
}
// 判断是否接口调用
string IsSelf = "0";
if (!string.IsNullOrEmpty(ChartNo))
{
IsSelf = "1";
}
ViewData["title"] = "药品不良反应";
ViewData["explain"] = "事件说明:合格药品在正常用法、用量情况下出现的与用药目的无关的反应...";
ViewData["repTypeId"] = 6;
ViewData["depts"] = DI.QueryDepts(_mysqlService, _qasystem);
ViewData["sexs"] = DI.QuerySex();
ViewData["eventtypes"] = DI.QueryEventType();
ViewData["effects"] = DI.QueryEffect();
ViewData["discoverers"] = DI.QueryDiscoverer();
ViewData["drugsNo"] = drugsNo;
ViewData["impact"] = DI.QueryImpct();
ViewData["evaluation"] = DI.QueryEvaluation();
ViewData["stopdrug"] = DI.QueryStopDrug();
ViewData["usedagain"] = DI.QueryUsedAgain();
ViewData["eventResult"] = DI.QueryEventResult();
ViewData["IsSelf"] = IsSelf;
return View();
}
3.2 编辑表单视图
csharp
public IActionResult HarmfulEvent06Edit(string repId)
{
EmployeeModel employee = JsonConvert.DeserializeObject<EmployeeModel>(
HttpContext.Session.GetString("Employee"));
ViewData["title"] = "药品不良反应";
// 获取事件内容
string sql = "SELECT * FROM `medreportdt` WHERE `repId`=?repId";
MedreportdtModel medreportdt = _mysqlService.DBFind<MedreportdtModel>(_harmfulevent, sql,
new MedreportdtModel { repId = repId });
ViewData["repId"] = medreportdt.repId;
ViewData["repContent"] = JsonConvert.DeserializeObject<HarmfulEvent06Model>(medreportdt.repContent);
// 传递字典数据
ViewData["depts"] = DI.QueryDepts(_mysqlService, _qasystem);
ViewData["sexs"] = DI.QuerySex();
ViewData["eventtypes"] = DI.QueryEventType();
ViewData["effects"] = DI.QueryEffect();
ViewData["discoverers"] = DI.QueryDiscoverer();
ViewData["status"] = DI.QueryMedRepStatusByRepId(_mysqlService, _harmfulevent, repId);
ViewData["impact"] = DI.QueryImpct();
ViewData["evaluation"] = DI.QueryEvaluation();
ViewData["stopdrug"] = DI.QueryStopDrug();
ViewData["usedagain"] = DI.QueryUsedAgain();
ViewData["eventResult"] = DI.QueryEventResult();
ViewData["roleId"] = employee.roleId;
return View();
}
3.3 查看表单视图
csharp
public IActionResult HarmfulEvent06Show(string repId)
{
string sql = "SELECT * FROM `v_medreport_show` WHERE `repId`=?repId";
List<MedreportShowModel> medreportshows = _mysqlService.DBQuery<MedreportShowModel>(_harmfulevent, sql,
new MedreportShowModel { repId = repId });
ViewData["repContent"] = medreportshows.Count > 0 ?
JsonConvert.DeserializeObject<HarmfulEvent06Model>(medreportshows[0].repContent) : null;
ViewData["occrDept"] = medreportshows.Count > 0 ?
DI.QueryDeptNameById(_mysqlService, _qasystem,
Convert.ToInt32(JsonConvert.DeserializeObject<HarmfulEvent06Model>(medreportshows[0].repContent).deptid)) : null;
ViewData["medreportshow"] = medreportshows.Count > 0 ? medreportshows[0] : null;
return View();
}
四、事件新增与编辑
4.1 新增事件
csharp
[HttpPost]
public JsonResult HarmfulEventAdd(HarmfulEventModel model)
{
MsgModel msgObj = null;
try
{
EmployeeModel employee = JsonConvert.DeserializeObject<EmployeeModel>(
HttpContext.Session.GetString("Employee"));
// 生成唯一事件编号
model.repId = RandomNumber.GetRandomNumber(model.repTypeId.ToString());
// 创建主表记录
MedreportmtModel medreportmt = new MedreportmtModel
{
repId = model.repId,
repTypeId = model.repTypeId,
repTypeId1 = model.repTypeId,
status = model.status, // 0=暂存, 1=提交
empId = employee.id,
empDeptId = employee.deptId,
createDate = DateTime.Now,
submitDate = model.status == 1 ? DateTime.Now : model.submitDate
};
// 创建明细记录
MedreportdtModel medreportdt = new MedreportdtModel
{
repId = model.repId,
repContent = model.repContent // JSON格式的事件内容
};
// 解析事件内容判断来源
HarmfulEventModel harmfulEventModel = JsonConvert.DeserializeObject<HarmfulEventModel>(model.repContent);
// 插入主表
string mtSql = @"INSERT INTO `medreportmt` (`id`, `repId`, `repTypeId`, `repTypeId1`,
`status`, `empId`, `empDeptId`, `createDate`, `submitDate`)
VALUES (NULL, ?repId, ?repTypeId, ?repTypeId1, ?status, ?empId, ?empDeptId, ?createDate, ?submitDate)";
_mysqlService.DBExecute(_harmfulevent, mtSql, medreportmt);
// 插入明细表
string dtSql = @"INSERT INTO `medreportdt` (`id`, `repId`, `repContent`)
VALUES (NULL, ?repId, ?repContent)";
_mysqlService.DBExecute(_harmfulevent, dtSql, medreportdt);
// 发送钉钉消息
if (_SendDingTalkMessageEnable == "1" && model.status == 1)
{
sendDingTalkMsgToEventManager(medreportmt);
sendDingTalkMsgToDeptManager(model.repId);
}
if (harmfulEventModel.IsSelf == "0")
{
msgObj = new MsgModel { code = 0, msg = "新增成功" };
}
else
{
msgObj = new MsgModel { code = 3, msg = "新增成功" };
}
}
catch (Exception ex)
{
msgObj = new MsgModel { code = 1, msg = ex.Message };
_logger.LogError(ex.Message);
}
return Json(msgObj);
}
4.2 编辑事件
csharp
[HttpPost]
public JsonResult HarmfulEventEdit(HarmfulEventModel model)
{
MsgModel msgObj = null;
try
{
EmployeeModel employee = JsonConvert.DeserializeObject<EmployeeModel>(
HttpContext.Session.GetString("Employee"));
// 更新主表
MedreportmtModel medreportmt = new MedreportmtModel
{
repId = model.repId,
status = model.status,
empDeptId = employee.deptId,
createDate = DateTime.Now,
submitDate = DateTime.Now,
repTypeId = Convert.ToInt32(model.repId.Substring(0, 2))
};
// 更新明细表
MedreportdtModel medreportdt = new MedreportdtModel
{
repId = model.repId,
repContent = model.repContent
};
string mtSql = "";
if (model.status == 0)
{
mtSql = @"UPDATE `medreportmt` SET `empDeptId`=?empDeptId, `status`=?status,
`createDate`=?createDate WHERE `repId`=?repId";
}
else
{
mtSql = @"UPDATE `medreportmt` SET `empDeptId`=?empDeptId, `status`=?status,
`submitDate`=?submitDate WHERE `repId`=?repId";
}
_mysqlService.DBExecute(_harmfulevent, mtSql, medreportmt);
// 如果是提交状态,发送钉钉消息
if (model.status == 1)
{
sendDingTalkMsgToEventManager(medreportmt);
sendDingTalkMsgToDeptManager(model.repId);
}
string dtSql = @"UPDATE `medreportdt` SET `repContent`=?repContent WHERE `repId`=?repId";
_mysqlService.DBExecute(_harmfulevent, dtSql, medreportdt);
msgObj = new MsgModel { code = 0, msg = "编辑成功" };
}
catch (Exception ex)
{
msgObj = new MsgModel { code = 1, msg = ex.Message };
_logger.LogError(ex.Message);
}
return Json(msgObj);
}
五、前端表单实现
5.1 Layui表单结构
以药品不良反应为例:
html
<div class="layui-fluid">
<div class="layui-card">
<div class="layui-card-header" id="cardHeader">@ViewData["title"]</div>
<div class="layui-card-body">
<div class="layui-form" lay-filter="component-form-group">
<!-- 患者信息区域 -->
<div class="layui-form-item">
<div class="layui-inline">
<label class="layui-form-label">科室:</label>
<div class="layui-input-inline">
<select name="deptid" lay-verify="required">
@foreach (DeptModel dept in ViewData["depts"] as List<DeptModel>)
{
<option value="@dept.id">@dept.deptName</option>
}
</select>
</div>
</div>
<div class="layui-inline">
<label class="layui-form-label">发生时间:</label>
<div class="layui-input-inline">
<input type="text" name="occutime" class="layui-input date-picker"
lay-verify="required" placeholder="yyyy-MM-dd HH:mm:ss">
</div>
</div>
</div>
<!-- 患者信息 -->
<div class="layui-form-item">
<div class="layui-inline">
<label class="layui-form-label">患者姓名:</label>
<div class="layui-input-inline">
<input type="text" name="pname" class="layui-input" lay-verify="required">
</div>
</div>
<div class="layui-inline">
<label class="layui-form-label">性别:</label>
<div class="layui-input-inline">
<select name="psex" lay-verify="required">
@foreach (DicModel sex in ViewData["sexs"] as List<DicModel>)
{
<option value="@sex.key">@sex.value</option>
}
</select>
</div>
</div>
<div class="layui-inline">
<label class="layui-form-label">年龄:</label>
<div class="layui-input-inline">
<input type="text" name="age" class="layui-input">
</div>
</div>
</div>
<!-- 药品信息 -->
<div class="layui-form-item">
<label class="layui-form-label">对原患疾病影响:</label>
<div class="layui-input-inline">
<select name="impact" lay-verify="required">
@foreach (DicModel item in ViewData["impact"] as List<DicModel>)
{
<option value="@item.key">@item.value</option>
}
</select>
</div>
</div>
<!-- 事件过程 -->
<div class="layui-form-item layui-form-text">
<label class="layui-form-label">发生过程:</label>
<div class="layui-input-block">
<textarea name="process" class="layui-textarea" lay-verify="required"></textarea>
</div>
</div>
<!-- 提交按钮 -->
<div class="layui-form-item">
<div class="layui-input-block">
<button class="layui-btn" lay-submit lay-filter="saveDraft">保存草稿</button>
<button class="layui-btn layui-btn-normal" lay-submit lay-filter="submitForm">提交</button>
</div>
</div>
</div>
</div>
</div>
</div>
5.2 表单提交处理
javascript
layui.use(['form', 'layer', 'laydate'], function(){
var form = layui.form;
var layer = layui.layer;
var laydate = layui.laydate;
// 日期时间选择器
laydate.render({
elem: 'input[name="occutime"]'
,type: 'datetime'
,format: 'yyyy-MM-dd HH:mm:ss'
});
// 获取URL参数
var repId = getQueryString('repId');
var repTypeId = '@ViewData["repTypeId"]';
var drugsNo = '@ViewData["drugsNo"]';
var status = '@ViewData["status"]';
// 加载已有数据
if (repId) {
var repContent = '@Html.Raw(ViewData["repContent"] ?? "{}")';
var data = JSON.parse(repContent.replace(/"/g, '"'));
form.val('component-form-group', data);
}
// 表单验证
form.verify({
required: function(value, elem) {
if (!value) {
return '此字段必填';
}
}
});
// 保存草稿
form.on('submit(saveDraft)', function(data){
var field = data.field;
field.status = 0; // 暂存状态
field.repTypeId = repTypeId;
field.repId = repId || '';
field.repContent = JSON.stringify(field);
field.drugsNo = drugsNo;
$.post('/HarmfulEvent/HarmfulEventAdd', field, function(res) {
if (res.code == 0) {
layer.msg('保存成功', function() {
var index = parent.layer.getFrameIndex(window.name);
parent.layer.close(index);
});
} else {
layer.msg(res.msg);
}
});
return false;
});
// 提交表单
form.on('submit(submitForm)', function(data){
var field = data.field;
field.status = 1; // 提交状态
field.repTypeId = repTypeId;
field.repId = repId || '';
field.repContent = JSON.stringify(field);
field.drugsNo = drugsNo;
$.post('/HarmfulEvent/HarmfulEventAdd', field, function(res) {
if (res.code == 0 || res.code == 3) {
layer.msg('提交成功', function() {
var index = parent.layer.getFrameIndex(window.name);
parent.layer.close(index);
});
} else {
layer.msg(res.msg);
}
});
return false;
});
});
六、特殊类型表单处理
6.1 药品不良反应-自动获取药品信息
csharp
public void AddMedreportDrugsFromMedOrder(string drugsNo, string ItemNo, string system_name = "siapp")
{
List<MedOrderModel> medreportDrugs = null;
if (system_name == "siapp")
{
// 从HIS获取药品医嘱信息
string sql = @"select ItemName, WayName, Dose, DoseUnit, Usage,
b.OrderTime, a.TreatTime from MedItem a, MedOrder b
where a.ItemNo=@ItemNo and a.OrderNo=b.OrderNo";
medreportDrugs = _mssqlService.DBQuery<MedOrderModel>(_sihis, sql,
new MedOrderModel { ItemNo = ItemNo });
}
else if (system_name == "lyra")
{
// 从HIS获取药品医嘱信息
string sql = @"SELECT itemname as ""ItemName"", wayname as ""WayName"",
dose as ""Dose"", doseunit as ""DoseUnit"", usage as ""Usage"",
ordertime as ""OrderTime"", treattime as ""TreatTime""
FROM ""interface"".""v_rmyy_blsj_ypyzxx"" where ""itemno""=@ItemNo";
medreportDrugs = _pgsqlService.DBQuery<MedOrderModel>(_lyradb, sql,
new DynamicParameters { { "@ItemNo", ItemNo } });
}
foreach (MedOrderModel MedOrder in medreportDrugs)
{
DrugModel drug = new DrugModel
{
drugsNo = drugsNo,
drugName = MedOrder.ItemName,
dusage = MedOrder.WayName,
dosage = MedOrder.Dose.ToString("0.##") + MedOrder.DoseUnit,
frequency = MedOrder.Usage,
startDate = Convert.ToDateTime(MedOrder.OrderTime),
endDate = Convert.ToDateTime(MedOrder.TreatTime)
};
string isql = @"INSERT INTO `medreportdrugs`(`id`, `drugsNo`, `drugName`, `reason`,
`dusage`, `dosage`, `frequency`, `startDate`, `endDate`, `manufacturer`, `drugsnumber`)
VALUES (NULL, ?drugsNo, ?drugName, ?reason, ?dusage, ?dosage, ?frequency,
?startDate, ?endDate, ?manufacturer, ?drugsnumber)";
_mysqlService.DBExecute(_harmfulevent, isql, drug);
}
}
6.2 手术并发症-自动获取患者信息
csharp
public IActionResult HarmfulEvent03Add()
{
string ChartNo = Request.Query["ChartNo"].ToString();
// 从多个HIS系统获取患者信息
ViewData["patient"] = string.IsNullOrEmpty(ChartNo) ? null :
DI.QueryPatientByChartNo(_mssqlService, _sihis, _pgsqlService, _lyradb, ChartNo);
ViewData["title"] = "手术并发症上报";
ViewData["explain"] = "事件说明:各类手术并发症,如术后出血、切口感染、切口裂开...";
ViewData["repTypeId"] = 3;
ViewData["depts"] = DI.QueryDepts(_mysqlService, _qasystem);
ViewData["users"] = DI.QueryUsers(_mysqlService, _qasystem);
ViewData["sexs"] = DI.QuerySex();
ViewData["eventtypes"] = DI.QueryEventType();
ViewData["effects"] = DI.QueryEffect();
ViewData["discoverers"] = DI.QueryDiscoverer();
return View();
}
七、事件列表展示
csharp
public IActionResult Index()
{
ViewData["medreptypes"] = DI.QueryMedreptypes(_mysqlService, _harmfulevent);
ViewData["canTrack"] = HttpContext.Session.GetString("CanTrack");
return View();
}
前端列表:
html
<table id="HarmfulEventsTable" lay-filter="HarmfulEventsTable"></table>
<script type="text/html" id="status">
{{# if(d.status == 0){ }}
<button class="layui-btn layui-btn-warm layui-btn-xs">暂存</button>
{{# }else if(d.status == 1){ }}
<button class="layui-btn layui-btn-normal layui-btn-xs">上报</button>
{{# }else if(d.status == 2){ }}
<button class="layui-btn layui-btn-xs">审核</button>
{{# } }}
</script>
<script type="text/html" id="isTrack">
{{# if(d.isTrack == 0){ }}
<button class="layui-btn layui-btn-primary layui-btn-xs">未追踪</button>
{{# }else if(d.isTrack == 1){ }}
<button class="layui-btn layui-btn-normal layui-btn-xs">可追踪</button>
{{# }else if(d.isTrack == 2){ }}
<button class="layui-btn layui-btn-normal layui-btn-xs">追踪中</button>
{{# }else if(d.isTrack == 3){ }}
<button class="layui-btn layui-btn-xs">已追踪</button>
{{# } }}
</script>
总结
本文详细介绍了26种不良事件表单的实现:
- 动态路由设计:统一的URL命名规范,灵活支持新事件类型
- JSON内容存储:灵活的事件内容模型,易于扩展
- 主从表结构:medreportmt主表 + medreportdt明细表
- 多系统集成:从HIS、LIS自动获取患者和药品信息
- 状态管理:暂存、提交、审核多种状态流转
- 前端Layui:统一的表单组件和交互体验
下一篇文章将详细介绍审核追踪流程与SAC分级算法。