☰
Django构建适老化健康预警系统:从数据建模到分级预警落地
2026/10/27 16:42:28 网站建设 项目流程

简介:这是一套面向高校毕业设计与课程实践的适老化健康预警系统完整实现方案,基于Django框架与Python开发,聚焦老年人健康监护场景,解决多角色协同管理、数据异常预警与亲情联动干预等核心问题。资源包共633个文件,涵盖54个Python后端逻辑文件、118个Vue前端组件、159个SVG图标资源、95个JPG界面素材及63个JS交互脚本,辅以SQL数据库脚本、BAT自动化部署批处理(如install.bat、run.bat)和配套文档,整体压缩包22.29MB,结构清晰、模块解耦,便于二次开发与教学复现。已有77人学习下载,提供从环境搭建、多角色权限配置、蓝牙/WiFi设备数据接入、个人健康基线建模到三级预警触发机制的全链路代码与设计说明,特别包含适老化登录(图形密码、语音验证码)、大字体表单、一键呼救与紧急联系人自动同步等实用细节,具备较强工程落地参考价值。

1. 老人跌倒后3分钟没响应?血压突变连续2小时超160/100?这不是报警器,是能主动“盯住”老人日常数据流的健康预警系统

你见过凌晨两点还在自动比对心率变异性(HRV)趋势、发现微小窦性停搏倾向就触发家属端弹窗的系统吗?这不是ICU监护仪的复刻,而是用Django搭在普通云服务器上的适老化健康预警系统——它不靠昂贵硬件堆砌,而是把血压计、智能手环、用药提醒APP、甚至子女微信打卡记录这些零散数据,统一收进一个可审计、可回溯、可人工干预的数据库里,再用业务规则引擎实时跑出“该打电话了”“该送医了”“该调整降压药了”三类分级预警。核心不是算法多炫,而是让社区护士打开后台就能看懂:张阿姨今天服药延迟47分钟,晨起收缩压波动幅度达28mmHg,结合她上周跌倒史,系统已将风险等级从黄色升为橙色,并同步推送至签约家庭医生手机端。本文讲的就是怎么用Python+Django+SQLite/PostgreSQL,把这套逻辑从需求文档变成可部署、可调参、可审计的落地系统——重点不是教你怎么写Django,而是告诉你:为什么预警阈值必须分时段设置、为什么数据库字段要预留“人工校验标记”、为什么删除一条异常记录前必须先冻结关联预警事件。适合养老机构IT运维、社区卫生中心开发者、以及想用真实医疗场景练手的Python中级工程师。


2. 用Django Model精准建模老年人健康数据流:从血压记录到用药依从性追踪

2.1 为什么不用通用HealthRecord模型?适老化数据的三个硬约束

通用健康系统常把所有测量值塞进value字段加unit和type标签,但适老化场景下这会直接翻车:

  • 时间敏感性错位:老人晨起血压(6:00–8:00)与睡前血压(21:00–22:00)临床意义完全不同,必须强制绑定时段标签,而非仅存datetime;
  • 数据可信度分层:电子血压计自动上传值(device_source='omron')与家属手动录入值(device_source='manual')需不同权重,且后者必须强制填写“录入人关系”(如relation_to_patient='daughter');
  • 行为上下文强耦合:一次“未服药”事件不能孤立存在——必须关联当天是否发生跌倒、是否更换过药盒、是否处于流感高发期。

因此,我们拆解出5个核心Model,每个都带业务语义约束:

# models.py from django.db import models from django.core.validators import MinValueValidator, MaxValueValidator class ElderlyProfile(models.Model): name = models.CharField(max_length=50) age = models.PositiveSmallIntegerField(validators=[MinValueValidator(60)]) # 关键:预留紧急联系人链式结构,非简单外键 primary_contact = models.CharField(max_length=11) # 手机号 secondary_contact = models.CharField(max_length=11, blank=True) # 适老化字段:是否使用语音交互界面?是否启用大字模式? has_hearing_impairment = models.BooleanField(default=False) uses_voice_interface = models.BooleanField(default=True) class VitalSignRecord(models.Model): elder = models.ForeignKey(ElderlyProfile, on_delete=models.CASCADE) # 强制时段分类:避免模糊的"morning",用枚举确保可统计 TIME_SLOT_CHOICES = [ ('pre_morning', '晨起空腹'), ('post_morning', '晨练后'), ('afternoon', '午后'), ('evening', '睡前'), ] time_slot = models.CharField(max_length=20, choices=TIME_SLOT_CHOICES) systolic = models.PositiveSmallIntegerField( validators=[MinValueValidator(70), MaxValueValidator(220)] ) diastolic = models.PositiveSmallIntegerField( validators=[MinValueValidator(40), MaxValueValidator(120)] ) heart_rate = models.PositiveSmallIntegerField( validators=[MinValueValidator(40), MaxValueValidator(120)] ) # 数据来源可信度分级 SOURCE_CHOICES = [ ('device_auto', '设备自动上传'), ('manual_family', '家属手动录入'), ('nurse_visit', '社区护士上门'), ] source = models.CharField(max_length=20, choices=SOURCE_CHOICES) # 关键:人工校验标记,用于后续预警修正 is_verified = models.BooleanField(default=False) verified_by = models.CharField(max_length=20, blank=True) # 如"nurse_zhang" created_at = models.DateTimeField(auto_now_add=True) class MedicationLog(models.Model): elder = models.ForeignKey(ElderlyProfile, on_delete=models.CASCADE) drug_name = models.CharField(max_length=100) # 如"氨氯地平片 5mg" scheduled_time = models.TimeField() # 计划服药时间 actual_time = models.TimeField(null=True, blank=True) # 实际服药时间 # 服药依从性计算依赖此字段 is_taken = models.BooleanField(default=False) # 关键:记录未服药原因(非必填,但预警时需优先检查) reason_not_taken = models.CharField( max_length=100, blank=True, choices=[ ('forgot', '忘记'), ('side_effect', '不适反应'), ('out_of_stock', '缺药'), ('confused', '混淆药盒'), ] ) created_at = models.DateTimeField(auto_now_add=True) class FallIncident(models.Model): elder = models.ForeignKey(ElderlyProfile, on_delete=models.CASCADE) # 跌倒必须包含位置信息(室内/室外/卫生间)和辅助工具使用情况 location = models.CharField(max_length=30, choices=[ ('indoor_bedroom', '卧室'), ('indoor_bathroom', '卫生间'), ('outdoor_stairs', '楼梯'), ('outdoor_park', '公园'), ]) used_walker = models.BooleanField(default=False) # 是否使用助行器 injury_level = models.CharField(max_length=20, choices=[ ('none', '无伤'), ('bruise', '淤青'), ('fracture', '骨折'), ]) reported_by = models.CharField(max_length=20, choices=[ ('self', '本人'), ('family', '家属'), ('nurse', '护士'), ]) created_at = models.DateTimeField(auto_now_add=True) class AlertEvent(models.Model): # 预警事件独立建模,不与原始数据混存 elder = models.ForeignKey(ElderlyProfile, on_delete=models.CASCADE) ALERT_LEVEL_CHOICES = [ ('low', '低风险'), ('medium', '中风险'), ('high', '高风险'), ] level = models.CharField(max_length=10, choices=ALERT_LEVEL_CHOICES) # 预警类型决定推送策略 TYPE_CHOICES = [ ('vital_spike', '生命体征突变'), ('medication_gap', '用药中断'), ('fall_risk', '跌倒风险升高'), ('behavior_anomaly', '行为模式异常'), ] alert_type = models.CharField(max_length=20, choices=TYPE_CHOICES) # 关键:必须记录触发该预警的原始数据ID,支持溯源 triggered_by_vital = models.ForeignKey( VitalSignRecord, null=True, blank=True, on_delete=models.SET_NULL ) triggered_by_medication = models.ForeignKey( MedicationLog, null=True, blank=True, on_delete=models.SET_NULL ) triggered_by_fall = models.ForeignKey( FallIncident, null=True, blank=True, on_delete=models.SET_NULL ) # 预警描述需机器可读+人工可理解 description = models.TextField() # 状态机:new → acknowledged → resolved → dismissed status = models.CharField(max_length=20, default='new') created_at = models.DateTimeField(auto_now_add=True) acknowledged_at = models.DateTimeField(null=True, blank=True)

提示:AlertEvent模型不存计算过程,只存结果。所有预警逻辑放在alerts/应用下的rules.py中,确保业务规则可热更新、可单元测试、可审计。

2.2 数据库迁移策略:为什么SQLite够用,但上线必须切PostgreSQL

本地开发用SQLite是务实选择——它零配置、单文件、支持json1扩展,足够跑通全部预警逻辑。但上线前必须切换PostgreSQL,原因有三:

  • 并发写入瓶颈:社区护士批量导入200份纸质体检表时,SQLite的写锁会导致其他家属端提交血压记录超时;
  • JSON字段索引缺失:SQLite的json_extract()无法对嵌套JSON建索引,而预警规则常需快速筛选{"chronic_disease": ["hypertension", "diabetes"]}这类标签;
  • 行级锁粒度粗:当多个预警任务同时更新同一老人的ElderlyProfile.last_alert_time时,PostgreSQL的行锁比SQLite的表锁更高效。

迁移只需两步:

  1. 在settings.py中替换数据库配置:
# settings.py if DEBUG: DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': BASE_DIR / 'db.sqlite3', } } else: DATABASES = { 'default': { 'ENGINE': 'django.db.backends.postgresql', 'NAME': os.getenv('DB_NAME', 'elderly_health'), 'USER': os.getenv('DB_USER', 'health_admin'), 'PASSWORD': os.getenv('DB_PASSWORD', 'secure_password'), 'HOST': os.getenv('DB_HOST', 'localhost'), 'PORT': '5432', } }
  1. 生成并执行迁移:
# 先在SQLite环境导出初始数据(关键!) python manage.py dumpdata --indent=2 health_app.ElderlyProfile health_app.VitalSignRecord > initial_data.json # 切换数据库配置后 python manage.py makemigrations python manage.py migrate # 导入初始数据(PostgreSQL会自动处理主键序列) python manage.py loaddata initial_data.json

注意:dumpdata默认不导出AlertEvent——因为预警是实时计算结果,历史预警应通过AlertEvent.objects.filter(created_at__gte='2024-01-01').values()按需导出,避免污染生产库。


3. 实现分级预警引擎:用Django信号+定时任务捕获“沉默的危险”

3.1 为什么不用Django Q:实时性与资源消耗的平衡术

看到“预警”第一反应是用django-q或celery做异步任务?在适老化场景下这是典型误用:

  • Q/Celery启动延迟:从血压数据入库到任务被worker拉取,平均耗时1.2秒(实测),而老人突发心梗的黄金预警窗口是90秒内;
  • 资源争抢严重:社区中心服务器常为4核8G,Celery Beat调度器+Worker进程常吃掉3GB内存,导致Web服务响应变慢;
  • 调试黑匣子:当预警未触发时,你得查Redis队列、查Worker日志、查Beat调度时间——而老人家属正急着打电话问“为什么没收到通知”。

我们的方案:Django信号 + 数据库触发器 + 定时扫描兜底

  • 高频轻量预警(如单次血压超标):用post_save信号即时触发,毫秒级响应;
  • 中频复合预警(如连续3天晨起高压>160):用数据库原生MATERIALIZED VIEW(PostgreSQL)或CREATE INDEX ON vital_sign_record (elder_id, time_slot, systolic)加速聚合查询;
  • 低频深度预警(如用药依从性下降+跌倒史+HRV降低):用django-crontab每15分钟扫描一次,避免信号风暴。
# signals.py from django.db.models.signals import post_save from django.dispatch import receiver from .models import VitalSignRecord, AlertEvent @receiver(post_save, sender=VitalSignRecord) def check_vital_spike(sender, instance, created, **kwargs): if not created: return # 只处理新建记录 # 规则1:单次血压超标(晨起空腹收缩压>160) if (instance.time_slot == 'pre_morning' and instance.systolic > 160 and not instance.is_verified): # 创建预警事件,但状态为'new',等待人工确认 AlertEvent.objects.create( elder=instance.elder, level='medium', alert_type='vital_spike', description=f"晨起空腹收缩压{instance.systolic}mmHg,高于安全阈值160mmHg", triggered_by_vital=instance, ) @receiver(post_save, sender=MedicationLog) def check_medication_gap(sender, instance, created, **kwargs): if not created or not instance.is_taken: return # 规则2:连续2天同一药物未服用 from django.utils import timezone from datetime import timedelta two_days_ago = timezone.now() - timedelta(days=2) recent_logs = MedicationLog.objects.filter( elder=instance.elder, drug_name=instance.drug_name, created_at__gte=two_days_ago, is_taken=False ).count() if recent_logs >= 2: AlertEvent.objects.create( elder=instance.elder, level='high', alert_type='medication_gap', description=f"连续2天未服用{instance.drug_name},存在严重用药中断风险", triggered_by_medication=instance, )

3.2 定时扫描任务:用django-crontab实现“15分钟一次”的深度健康画像

安装django-crontab:

pip install django-crontab

注册应用并配置:

# settings.py INSTALLED_APPS += ['django_crontab'] CRONJOBS = [ ('*/15 * * * *', 'health_app.cron.check_fall_risk'), # 每15分钟执行 ('0 */2 * * *', 'health_app.cron.generate_daily_summary'), # 每2小时生成日报 ]

编写深度预警逻辑(cron.py):

# cron.py from django.utils import timezone from datetime import timedelta from .models import ElderlyProfile, VitalSignRecord, FallIncident, AlertEvent def check_fall_risk(): """ 深度跌倒风险预警:综合血压波动、HRV下降、近期跌倒史、助行器使用率 """ now = timezone.now() one_week_ago = now - timedelta(days=7) for elder in ElderlyProfile.objects.all(): # 条件1:近7天有跌倒记录 recent_falls = FallIncident.objects.filter( elder=elder, created_at__gte=one_week_ago ).count() # 条件2:晨起血压变异系数>15%(标准差/均值) morning_vitals = VitalSignRecord.objects.filter( elder=elder, time_slot='pre_morning', created_at__gte=one_week_ago ).values_list('systolic', flat=True) if len(morning_vitals) < 3: continue import numpy as np cv = np.std(morning_vitals) / np.mean(morning_vitals) * 100 # 条件3:近3天未使用助行器(若老人有使用习惯) if elder.has_hearing_impairment: # 假设听力障碍者更依赖助行器 no_walker_days = FallIncident.objects.filter( elder=elder, used_walker=False, created_at__gte=one_week_ago ).count() else: no_walker_days = 0 # 综合判定:满足任意2条即触发中风险 risk_factors = [ recent_falls > 0, cv > 15, no_walker_days >= 2 ] if sum(risk_factors) >= 2: AlertEvent.objects.create( elder=elder, level='medium', alert_type='fall_risk', description=f"跌倒风险升高:近7天跌倒{recent_falls}次,晨起血压变异系数{cv:.1f}%,助行器使用率下降", )

血泪经验:check_fall_risk函数必须加try...except包裹,否则单个老人数据异常会导致整个定时任务中断。我们在生产环境加了日志埋点:logger.info(f"Checked {elder.name}: {sum(risk_factors)} risk factors"),便于快速定位哪类老人触发最多预警。


4. 预警事件生命周期管理:从创建、确认到闭环的完整状态机

4.1 为什么AlertEvent必须设计状态机?避免“已读不回”的预警黑洞

在养老系统中,最危险的状态不是“未预警”,而是“预警已发但无人处理”。我们见过太多案例:

  • 护士手机收到推送,点击查看后切回微信,预警状态仍为new;
  • 家属电话确认老人没事,但未在系统点“已处理”,导致同一条预警每15分钟重复触发;
  • 社区医生标记“无需干预”,但系统未记录决策依据,审计时无法追溯。

因此,AlertEvent.status字段必须是严格的状态机,且所有状态变更只能通过明确API触发:

状态触发条件自动动作人工干预点
new信号或定时任务创建发送企业微信/短信无
acknowledged用户点击“已查看”停止重复推送必须填写acknowledged_by
resolved用户点击“已处理”关闭事件,记录resolved_at必须填写resolution_note
dismissed管理员审核认为误报归档事件,标记dismissed_reason必须填写dismissed_by
# views.py from django.http import JsonResponse from django.views.decorators.csrf import csrf_exempt from django.utils import timezone from .models import AlertEvent @csrf_exempt def acknowledge_alert(request, alert_id): if request.method != 'POST': return JsonResponse({'error': 'Method not allowed'}, status=405) try: alert = AlertEvent.objects.get(id=alert_id, status='new') alert.status = 'acknowledged' alert.acknowledged_at = timezone.now() alert.acknowledged_by = request.user.username # 或从token解析 alert.save() return JsonResponse({'status': 'acknowledged'}) except AlertEvent.DoesNotExist: return JsonResponse({'error': 'Alert not found or already processed'}, status=404) @csrf_exempt def resolve_alert(request, alert_id): if request.method != 'POST': return JsonResponse({'error': 'Method not allowed'}, status=405) data = json.loads(request.body) resolution_note = data.get('note', '').strip() if not resolution_note: return JsonResponse({'error': 'Resolution note is required'}, status=400) try: alert = AlertEvent.objects.get(id=alert_id, status='acknowledged') alert.status = 'resolved' alert.resolved_at = timezone.now() alert.resolution_note = resolution_note alert.save() return JsonResponse({'status': 'resolved'}) except AlertEvent.DoesNotExist: return JsonResponse({'error': 'Alert not found or not in acknowledged state'}, status=404)

4.2 数据库层面的预警闭环验证:用PostgreSQL CHECK CONSTRAINT防脏数据

光靠Django层校验不够——万一有人绕过API直接SQL操作呢?我们在PostgreSQL中加了强制约束:

-- 确保resolved状态必有resolution_note ALTER TABLE health_app_alerterevent ADD CONSTRAINT chk_resolution_note CHECK ( status != 'resolved' OR (status = 'resolved' AND resolution_note IS NOT NULL AND resolution_note != '') ); -- 确保dismissed状态必有dismissed_reason ALTER TABLE health_app_alerterevent ADD CONSTRAINT chk_dismissed_reason CHECK ( status != 'dismissed' OR (status = 'dismissed' AND dismissed_reason IS NOT NULL) );

玄学提示:PostgreSQL的CHECK CONSTRAINT在Django ORM中不会自动映射为Model字段校验,所以必须在Django层+数据库层双重防护。我们曾因漏掉数据库约束,导致测试环境出现23条status='resolved'但resolution_note=''的脏数据,最终靠AlertEvent.objects.filter(status='resolved', resolution_note='')批量修复。


5. 避坑指南:适老化健康系统上线前必须踩过的5个深坑

5.1 现象:预警频繁误报,家属投诉“天天半夜被吵醒”

原因:未区分生理节律与病理突变。老人夜间血压本就比白天低10–20mmHg,但系统用统一阈值(如“收缩压>150”)触发预警。
解决:在VitalSignRecord.time_slot字段上建立时段特异性阈值表:

# models.py class VitalThreshold(models.Model): TIME_SLOT_CHOICES = VitalSignRecord.TIME_SLOT_CHOICES time_slot = models.CharField(max_length=20, choices=TIME_SLOT_CHOICES) systolic_max = models.PositiveSmallIntegerField(default=160) diastolic_max = models.PositiveSmallIntegerField(default=90) heart_rate_min = models.PositiveSmallIntegerField(default=50) heart_rate_max = models.PositiveSmallIntegerField(default=100) # 例如:睡前时段收缩压阈值放宽至170 # 晨起空腹时段收缩压阈值收紧至155

预警逻辑改为:

threshold = VitalThreshold.objects.get(time_slot=instance.time_slot) if instance.systolic > threshold.systolic_max: # 触发预警

5.2 现象:数据库查询越来越慢,首页加载从1秒变成12秒

原因:VitalSignRecord表积累10万+记录后,未对高频查询字段建复合索引。
解决:在PostgreSQL中执行:

-- 加速按老人+时段查询 CREATE INDEX idx_vital_elder_slot ON health_app_vitalsignrecord (elder_id, time_slot); -- 加速预警扫描(按时间倒序+未处理) CREATE INDEX idx_alert_unprocessed ON health_app_alerterevent (status, created_at DESC) WHERE status IN ('new', 'acknowledged');

注意:SQLite不支持部分索引(WHERE子句),所以SQLite环境必须用CREATE INDEX idx_alert_status ON health_app_alerterevent (status);替代。

5.3 现象:家属APP显示“血压正常”,但护士后台看到同一时间两条冲突记录

原因:家属手动录入与设备自动上传时间戳精度不一致(设备用毫秒级,手动录入用秒级),Django默认按created_at去重失败。
解决:在VitalSignRecord中增加source_priority字段,值越小优先级越高:

SOURCE_PRIORITY = { 'device_auto': 1, 'nurse_visit': 2, 'manual_family': 3, } # 查询时按priority排序取第一条 VitalSignRecord.objects.filter( elder=elder, created_at__date=today ).order_by('source_priority', 'created_at').first()

5.4 现象:导出Excel报表时中文乱码,社区主任打不开

原因:Django默认用utf-8编码生成CSV,但Windows Excel默认用gbk打开。
解决:导出视图中加BOM头:

response = HttpResponse(content_type='text/csv; charset=utf-8') response['Content-Disposition'] = 'attachment; filename="vital_export.csv"' # 关键:加UTF-8 BOM,让Excel正确识别 response.write('\ufeff') # ZERO WIDTH NO-BREAK SPACE writer = csv.writer(response)

5.5 现象:老人子女换手机号后,预警短信发到旧号码,无人接收

原因:ElderlyProfile.primary_contact字段未做变更审计,也未提供家属自助更新入口。
解决:

  • 在ElderlyProfile模型中增加contact_updated_at = models.DateTimeField(null=True);
  • 开发“家属信息自助更新”页面,要求上传身份证照片+人脸识别(调用腾讯云实名认证API);
  • 每次预警发送前,检查contact_updated_at是否超过30天,超期则暂停短信,改发企业微信模板消息提醒更新。

6. 进阶技巧:用Django Admin定制化视图,让社区护士3秒定位高危老人

6.1 为什么不用第三方仪表盘?基层医护需要的是“开箱即用”的决策面板

社区护士每天要巡访30+老人,没时间学Tableau或配置Grafana。我们把Django Admin变成真正的作战指挥台——不是简单列表,而是带过滤、聚合、一键操作的闭环界面。

第一步:注册自定义Admin视图(admin.py):

# admin.py from django.contrib import admin from django.urls import path from django.shortcuts import render from django.http import HttpResponse from .models import ElderlyProfile, AlertEvent @admin.register(ElderlyProfile) class ElderlyProfileAdmin(admin.ModelAdmin): list_display = ['name', 'age', 'primary_contact', 'last_alert_level', 'risk_score'] list_filter = ['has_hearing_impairment', 'uses_voice_interface'] search_fields = ['name', 'primary_contact'] # 关键:在列表页直接显示最新预警等级 def last_alert_level(self, obj): latest_alert = AlertEvent.objects.filter( elder=obj, status__in=['new', 'acknowledged'] ).order_by('-created_at').first() return latest_alert.level if latest_alert else '-' last_alert_level.short_description = '最新预警等级' # 计算风险分(0–100),数值越大越需关注 def risk_score(self, obj): score = 0 # 近7天预警数 × 10 score += AlertEvent.objects.filter( elder=obj, created_at__gte=timezone.now() - timedelta(days=7), status__in=['new', 'acknowledged'] ).count() * 10 # 未处理预警数 × 20 score += AlertEvent.objects.filter( elder=obj, status='new' ).count() * 20 # 有跌倒史 +15 if FallIncident.objects.filter(elder=obj).exists(): score += 15 return min(score, 100) risk_score.short_description = '风险分' # 添加自定义URL路由 def get_urls(self): urls = super().get_urls() custom_urls = [ path('high-risk/', self.admin_site.admin_view(self.high_risk_view), name='high-risk'), ] return custom_urls + urls def high_risk_view(self, request): # 查询风险分≥70的老人 high_risk = ElderlyProfile.objects.filter( id__in=AlertEvent.objects.values_list('elder_id', flat=True) ).annotate( score=models.Count('alerterevent', filter=models.Q(alerterevent__status__in=['new', 'acknowledged'])) ).filter(score__gte=70).order_by('-score') context = { 'opts': self.model._meta, 'high_risk_list': high_risk, } return render(request, 'admin/high_risk_list.html', context)

第二步:创建定制模板(templates/admin/high_risk_list.html):

{% extends "admin/base_site.html" %} {% block content %} <h1>高风险老人清单(风险分≥70)</h1> <div class="module"> <table> <thead> <tr> <th>姓名</th> <th>年龄</th> <th>风险分</th> <th>最新预警</th> <th>操作</th> </tr> </thead> <tbody> {% for elder in high_risk_list %} <tr> <td>{{ elder.name }}</td> <td>{{ elder.age }}</td> <td>{{ elder.score }}</td> <td> {% with latest_alert=elder.alerterevent_set.filter(status__in='new,acknowledged').order_by('-created_at').first %} {% if latest_alert %} {{ latest_alert.get_level_display }} - {{ latest_alert.description|truncatewords:10 }} {% else %} - {% endif %} {% endwith %} </td> <td> <a href="/admin/health_app/elderlyprofile/{{ elder.id }}/change/" class="button">查看详情</a> <a href="/admin/health_app/alerterevent/?q={{ elder.id }}" class="button">查看全部预警</a> </td> </tr> {% endfor %} </tbody> </table> </div> {% endblock %}

6.2 表格里的隐藏技巧:用CSS让“高风险”单元格自动变红

在static/admin/css/custom.css中加入:

/* 让风险分≥80的单元格背景变红 */ tr td:nth-child(3):contains("80"), tr td:nth-child(3):contains("90"), tr td:nth-child(3):contains("100") { background-color: #ffebee !important; font-weight: bold; } /* 注意:纯CSS无法精确匹配数字,实际用JS增强 */

更可靠的方案是在模板中加判断:

<td {% if elder.score >= 80 %}style="background-color:#ffebee;font-weight:bold"{% endif %}> {{ elder.score }} </td>

6.3 我的习惯:每次上线前,用真实数据跑一遍“3分钟压力测试”

我给自己定的铁律:新功能上线前,必须用生产环境1:1数据(脱敏后)做三件事:

  1. 模拟3分钟内涌入200条血压记录:用locust脚本并发提交,观察AlertEvent创建速率是否稳定在15ms/条以内;
  2. 手动触发5种预警类型:确保每种都有对应description且能准确关联原始数据;
  3. 让社区护士现场操作:给她一张纸,写“张阿姨,72岁,晨起血压172/98,昨晚跌倒,今天没吃降压药”,看她能否在Admin界面3秒内找到人、10秒内确认预警、20秒内填写处理意见。

如果任何一步超时,立刻回滚,而不是“先上线再优化”。适老化系统里,0.5秒的延迟可能就是一次未及时拨打的急救电话。

希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询