【Flutter for OpenHarmony】Flutter三方库PHQ-9抑郁量表的鸿蒙化适配与实战指南

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net


一、什么是PHQ-9量表?

我是 IntMainJhy,上海某高校大一计算机专业的学生。说起 PHQ-9 量表,我真的花了不少时间研究。

PHQ-9 的全称是 Patient Health Questionnaire-9,中文名叫"患者健康问卷-9项"。它是临床上最常用的抑郁症筛查工具之一,由美国哥伦比亚大学的 Robert L. Spitzer 等人于1999年开发。

为什么我要做这个功能?

说实话,一开始我是被室友安利的。他说他在国外留学的时候,学校心理咨询中心就推荐他用 PHQ-9 做自我筛查。我一听,感觉这个东西挺有意思的,就想能不能做成一个 Flutter App。

后来我查了更多资料才发现,心理健康筛查在国内其实是刚需。根据统计数据,我国抑郁症患者已经超过9500万,但就诊率只有不到20%。很大一个原因是大家不知道自己的状态算不算"有问题"。

所以我就想,能不能做一个简单的自评工具,让大家能够了解自己的心理健康状态?


二、PHQ-9量表详解

2.1 量表内容

PHQ-9 一共有9道题,用来评估一个人过去两周的情绪状态:

题号 问题描述
1 做事时提不起劲或没有兴趣
2 感到心情低落、沮丧或绝望
3 入睡困难、睡不安稳或睡眠过多
4 感觉疲倦或没有活力
5 食欲不振或吃太多
6 觉得自己很糟,或觉得自己很失败让家人失望
7 对事物专注有困难,例如看报纸或看电视时
8 动作或说话速度缓慢,或与此相反(坐立不安、来回走动)
9 有不如死掉或用某种方式伤害自己的念头

2.2 评分标准

每道题都有4个选项,代表过去两周内出现该症状的频率:

选项 含义 分数
完全没有 过去两周没有或几乎没有 0分
好几天 1-6天 1分
一半以上天数 7-11天 2分
几乎每天 12-14天 3分

总分范围:0-27分

2.3 结果解读

分数 抑郁程度 建议
0-4 无或极轻微 继续保持良好的生活习惯
5-9 轻度抑郁 建议尝试放松技巧,保持社交
10-14 中度抑郁 建议咨询心理咨询师
15-19 中重度抑郁 强烈建议寻求专业帮助
20-27 重度抑郁 需要立即寻求专业治疗

三、在Flutter中实现PHQ-9

3.1 数据模型

// lib/mental_health/models/quiz_model.dart

/// 测试类型
enum QuizCategory {
  phq9('PHQ-9 抑郁量表', '评估过去两周的抑郁症状', Color(0xFF6C63FF), 9, 27),
  gad7('GAD-7 焦虑量表', '评估过去一个月的焦虑水平', Color(0xFFFF6B6B), 7, 21);

  final String name;
  final String description;
  final Color color;
  final int questionCount;
  final int maxScore;

  const QuizCategory(this.name, this.description, this.color,
                     this.questionCount, this.maxScore);
}

/// 测试题目
class QuizQuestion {
  /// 题目文本
  final String questionText;
  
  /// 选项列表
  final List<String> options;
  
  /// 每个选项对应的分数
  final List<int> scores;

  const QuizQuestion({
    required this.questionText,
    required this.options,
    required this.scores,
  });

  /// 获取选项数量
  int get optionCount => options.length;
  
  /// 获取最高分
  int get maxScore => scores.isNotEmpty ? scores.reduce((a, b) => a > b ? a : b) : 0;
}

/// PHQ-9 量表题目
class PHQ9Questions {
  static const List<QuizQuestion> questions = [
    QuizQuestion(
      questionText: '做事时提不起劲或没有兴趣',
      options: ['完全没有', '好几天', '一半以上天数', '几乎每天'],
      scores: [0, 1, 2, 3],
    ),
    QuizQuestion(
      questionText: '感到心情低落、沮丧或绝望',
      options: ['完全没有', '好几天', '一半以上天数', '几乎每天'],
      scores: [0, 1, 2, 3],
    ),
    QuizQuestion(
      questionText: '入睡困难、睡不安稳或睡眠过多',
      options: ['完全没有', '好几天', '一半以上天数', '几乎每天'],
      scores: [0, 1, 2, 3],
    ),
    QuizQuestion(
      questionText: '感觉疲倦或没有活力',
      options: ['完全没有', '好几天', '一半以上天数', '几乎每天'],
      scores: [0, 1, 2, 3],
    ),
    QuizQuestion(
      questionText: '食欲不振或吃太多',
      options: ['完全没有', '好几天', '一半以上天数', '几乎每天'],
      scores: [0, 1, 2, 3],
    ),
    QuizQuestion(
      questionText: '觉得自己很糟,或觉得自己很失败让家人失望',
      options: ['完全没有', '好几天', '一半以上天数', '几乎每天'],
      scores: [0, 1, 2, 3],
    ),
    QuizQuestion(
      questionText: '对事物专注有困难,例如看报纸或看电视时',
      options: ['完全没有', '好几天', '一半以上天数', '几乎每天'],
      scores: [0, 1, 2, 3],
    ),
    QuizQuestion(
      questionText: '动作或说话速度缓慢,或与此相反(坐立不安、来回走动)',
      options: ['完全没有', '好几天', '一半以上天数', '几乎每天'],
      scores: [0, 1, 2, 3],
    ),
    QuizQuestion(
      questionText: '有不如死掉或用某种方式伤害自己的念头',
      options: ['完全没有', '好几天', '一半以上天数', '几乎每天'],
      scores: [0, 1, 2, 3],
    ),
  ];
}

/// PHQ-9 结果解读
class PHQ9Interpretation {
  /// 分数区间定义
  static const List<InterpretationRange> ranges = [
    InterpretationRange(
      minScore: 0,
      maxScore: 4,
      level: '无或极轻微抑郁',
      color: Color(0xFF27AE60),
      description: '你的抑郁症状处于正常范围内。继续保持良好的生活习惯和积极的心态。',
      suggestion: '建议保持规律作息,适度运动,与朋友家人保持联系。',
      severity: 'none',
    ),
    InterpretationRange(
      minScore: 5,
      maxScore: 9,
      level: '轻度抑郁',
      color: Color(0xFFF39C12),
      description: '你可能有轻度抑郁症状。建议多关注自己的情绪变化,适当放松。',
      suggestion: '建议尝试深呼吸、正念冥想,保持社交活动。如症状持续,考虑寻求专业帮助。',
      severity: 'mild',
    ),
    InterpretationRange(
      minScore: 10,
      maxScore: 14,
      level: '中度抑郁',
      color: Color(0xFFE67E22),
      description: '你可能有中度抑郁症状。建议咨询心理健康专业人士。',
      suggestion: '建议寻求心理咨询师或精神科医生的帮助。',
      severity: 'moderate',
    ),
    InterpretationRange(
      minScore: 15,
      maxScore: 19,
      level: '中重度抑郁',
      color: Color(0xFFE74C3C),
      description: '你可能有中重度抑郁症状。建议尽快寻求专业帮助。',
      suggestion: '强烈建议预约心理咨询师或精神科医生进行评估。',
      severity: 'moderately_severe',
    ),
    InterpretationRange(
      minScore: 20,
      maxScore: 27,
      level: '重度抑郁',
      color: Color(0xFFC0392B),
      description: '你可能有重度抑郁症状。需要立即寻求专业帮助。',
      suggestion: '请立即联系心理健康专业人士或前往医院就诊。',
      severity: 'severe',
    ),
  ];

  /// 根据分数获取解读
  static InterpretationRange getInterpretation(int score) {
    for (final range in ranges) {
      if (score >= range.minScore && score <= range.maxScore) {
        return range;
      }
    }
    return ranges.first;
  }
}

/// 解读区间
class InterpretationRange {
  final int minScore;
  final int maxScore;
  final String level;
  final Color color;
  final String description;
  final String suggestion;
  final String severity;

  const InterpretationRange({
    required this.minScore,
    required this.maxScore,
    required this.level,
    required this.color,
    required this.description,
    required this.suggestion,
    required this.severity,
  });
}

四、测试页面实现

// lib/mental_health/screens/quiz_screen.dart

class QuizScreen extends StatefulWidget {
  const QuizScreen({super.key});

  
  State<QuizScreen> createState() => _QuizScreenState();
}

class _QuizScreenState extends State<QuizScreen> {
  // 当前测试类型
  QuizCategory? _currentCategory;
  
  // 题目列表
  List<QuizQuestion> _questions = [];
  
  // 当前题目索引
  int _currentIndex = 0;
  
  // 用户答案 {questionIndex: selectedOptionIndex}
  Map<int, int> _answers = {};
  
  // 是否完成
  bool _isCompleted = false;
  
  // 结果
  QuizResult? _result;

  
  Widget build(BuildContext context) {
    // 根据状态显示不同内容
    if (_currentCategory == null) {
      return _buildSelectionView();
    } else if (_isCompleted) {
      return _buildResultView();
    } else {
      return _buildQuestionView();
    }
  }

  /// 测试选择页面
  Widget _buildSelectionView() {
    return Scaffold(
      appBar: AppBar(title: const Text('心理测试')),
      body: ListView(
        padding: const EdgeInsets.all(16),
        children: [
          // PHQ-9 卡片
          _buildQuizCard(
            category: QuizCategory.phq9,
            icon: Icons.mood,
          ),
          const SizedBox(height: 16),
          // GAD-7 卡片
          _buildQuizCard(
            category: QuizCategory.gad7,
            icon: Icons.psychology,
          ),
        ],
      ),
    );
  }

  /// 测试题目页面
  Widget _buildQuestionView() {
    final question = _questions[_currentIndex];
    
    return Scaffold(
      appBar: AppBar(
        title: Text(_currentCategory!.name),
        leading: IconButton(
          icon: const Icon(Icons.close),
          onPressed: () {
            setState(() {
              _currentCategory = null;
              _answers = {};
              _currentIndex = 0;
            });
          },
        ),
      ),
      body: Column(
        children: [
          // 进度条
          LinearProgressIndicator(
            value: (_currentIndex + 1) / _questions.length,
          ),
          
          // 题目内容
          Expanded(
            child: Padding(
              padding: const EdgeInsets.all(20),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text(
                    '问题 ${_currentIndex + 1}/${_questions.length}',
                    style: const TextStyle(
                      fontSize: 14,
                      color: Color(0xFF636E72),
                    ),
                  ),
                  const SizedBox(height: 16),
                  Text(
                    question.questionText,
                    style: const TextStyle(
                      fontSize: 20,
                      fontWeight: FontWeight.bold,
                    ),
                  ),
                  const SizedBox(height: 24),
                  
                  // 选项列表
                  ...List.generate(question.options.length, (index) {
                    final isSelected = _answers[_currentIndex] == index;
                    return _buildOptionItem(
                      index: index,
                      text: question.options[index],
                      isSelected: isSelected,
                      onTap: () {
                        setState(() {
                          _answers[_currentIndex] = index;
                        });
                      },
                    );
                  }),
                ],
              ),
            ),
          ),
          
          // 底部导航
          Padding(
            padding: const EdgeInsets.all(16),
            child: Row(
              children: [
                if (_currentIndex > 0)
                  TextButton(
                    onPressed: () {
                      setState(() => _currentIndex--);
                    },
                    child: const Text('上一题'),
                  ),
                const Spacer(),
                ElevatedButton(
                  onPressed: _answers.containsKey(_currentIndex)
                      ? () {
                          if (_currentIndex < _questions.length - 1) {
                            setState(() => _currentIndex++);
                          } else {
                            _calculateResult();
                          }
                        }
                      : null,
                  child: Text(
                    _currentIndex < _questions.length - 1 ? '下一题' : '查看结果',
                  ),
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }

  /// 结果页面
  Widget _buildResultView() {
    return Scaffold(
      body: Center(
        child: Padding(
          padding: const EdgeInsets.all(24),
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              // 环形进度
              QuizResultWidget(result: _result!),
              const SizedBox(height: 24),
              
              // 建议
              Container(
                padding: const EdgeInsets.all(16),
                decoration: BoxDecoration(
                  color: const Color(0xFFFFF3E0),
                  borderRadius: BorderRadius.circular(12),
                ),
                child: Row(
                  children: [
                    const Icon(Icons.lightbulb, color: Color(0xFFFF9800)),
                    const SizedBox(width: 12),
                    Expanded(
                      child: Text(
                        PHQ9Interpretation.getInterpretation(_result!.totalScore).suggestion,
                        style: const TextStyle(fontSize: 14),
                      ),
                    ),
                  ],
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }

  Widget _buildQuizCard({
    required QuizCategory category,
    required IconData icon,
  }) {
    return Card(
      child: InkWell(
        onTap: () {
          setState(() {
            _currentCategory = category;
            _questions = category == QuizCategory.phq9
                ? PHQ9Questions.questions
                : GAD7Questions.questions;
            _answers = {};
            _currentIndex = 0;
            _isCompleted = false;
          });
        },
        child: Padding(
          padding: const EdgeInsets.all(20),
          child: Row(
            children: [
              Icon(icon, size: 40, color: category.color),
              const SizedBox(width: 16),
              Expanded(
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text(
                      category.name,
                      style: const TextStyle(
                        fontSize: 18,
                        fontWeight: FontWeight.bold,
                      ),
                    ),
                    Text(
                      category.description,
                      style: const TextStyle(color: Color(0xFF636E72)),
                    ),
                  ],
                ),
              ),
              const Icon(Icons.arrow_forward_ios),
            ],
          ),
        ),
      ),
    );
  }

  Widget _buildOptionItem({
    required int index,
    required String text,
    required bool isSelected,
    required VoidCallback onTap,
  }) {
    return Padding(
      padding: const EdgeInsets.only(bottom: 12),
      child: InkWell(
        onTap: onTap,
        borderRadius: BorderRadius.circular(12),
        child: Container(
          padding: const EdgeInsets.all(16),
          decoration: BoxDecoration(
            color: isSelected
                ? _currentCategory!.color.withOpacity(0.1)
                : Colors.white,
            borderRadius: BorderRadius.circular(12),
            border: Border.all(
              color: isSelected
                  ? _currentCategory!.color
                  : const Color(0xFFE0E0E0),
            ),
          ),
          child: Row(
            children: [
              Icon(
                isSelected ? Icons.check_circle : Icons.circle_outlined,
                color: isSelected
                    ? _currentCategory!.color
                    : const Color(0xFFB2BEC3),
              ),
              const SizedBox(width: 12),
              Expanded(child: Text(text)),
            ],
          ),
        ),
      ),
    );
  }

  void _calculateResult() {
    int totalScore = 0;
    for (var entry in _answers.entries) {
      totalScore += _questions[entry.key].scores[entry.value];
    }

    setState(() {
      _result = QuizResult(
        totalScore: totalScore,
        maxScore: _currentCategory!.maxScore,
        category: _currentCategory!,
      );
      _isCompleted = true;
    });
  }
}

五、重要提醒

⚠️ 免责声明

本测试结果仅供参考,不能替代专业医生的诊断。

PHQ-9 量表只是一个筛查工具,不是诊断工具。它可以帮助你了解自己的情绪状态,但最终的诊断和治疗方案需要由专业医生来确定。

如果你有以下情况,请立即就医:

  • 有自我伤害的念头
  • 测试得分持续很高
  • 症状严重影响日常生活

六、我的踩坑记录

坑1:分数计算错误

问题:第9题的分数计算和预期不符。

原因:我没注意到第9题的特殊性,它的选项虽然一样,但含义完全不同。

解决:严格按照量表原文设计题目,不要凭理解修改。


七、大一学生真实学习总结

做这个 PHQ-9 功能让我学到了很多:

  1. 技术要和专业知识结合:不懂心理学,根本做不出这个功能。
  2. 科学态度很重要:这个功能涉及心理健康,不能乱来。
  3. 免责声明不可少:保护用户,也保护自己。

作者:IntMainJhy
创作时间:2026年5月
在这里插入图片描述
在这里插入图片描述

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐