文件
huaqiapp/server/application/api/controller/Mood.php
linda d243ece39c feat: 补齐 M1–M9 全部 47 个功能点并打通前后端
后端(新增 7 控制器 + 2 服务 + 17 张表)
- Prepare:排卵预测、BBT(双向体温识别)、试纸识别(规则引擎+额度)、备孕任务、同房指导
- Family:邀请码绑定(≤5)、话术库、爱心积分、私密留言、家庭健康视图
- Health:按周期阶段输出饮食/运动/睡眠/痛经四类建议
- Relation:关系测评、引导卡、冲突工具、课程下单、双人任务(联动记忆)、纪念日
- Parenting:宝宝档案、成长记录、月龄活动匹配、共育任务、育儿情绪、疫苗儿保排期
- Consult:咨询师(资质/定价/时段)、预约下单/取消、量表测评(4 套)、危机干预转介
- Setting:自定义提醒 CRUD、数据备份快照与恢复(保留 5 份)
- Mood 增补急救包;Home 提醒合并自定义提醒
- 服务层:ContentService(统一内容/锁态/购买态)、AssessmentService(计分分级+危机识别)

前端(新增 8 页面)
- 备孕、家人共护、健康建议、两性关系、亲子教育、心理咨询、测评执行页、情绪急救包
- 首页快捷入口扩至 8 个模块;我的页新增提醒中心与数据备份
- utils/api.js 扩至 84 个接口

数据(可重复执行脚本)
- 03_modules.sql:fa_reminder/bbt/lh_test/task/family_bind/care_message/point_log/memory/
  assessment(+record)/course_order/baby/growth_record/vaccine/counselor/consult_order/user_backup
- 04_module_seed.sql:话术6/引导卡6/冲突3/急救4/同房3/课程6/活动6/育儿情绪4/知识7、
  4 套量表、17 项疫苗儿保、3 位咨询师

验证(全部通过)
- 接口全链路 82 项断言(M1–M9,含高风险测评危机干预、备份恢复)
- 契约校验 84 接口 0 偏差;Vue SFC 19/19、SCSS 18/18 编译通过
- 修复 3 个真实缺陷:action 内调用 action 导致响应被劫持、coTasks 返回数组、
  咨询时段跨年错误
2026-11-05 23:27:21 +08:00

222 行
7.6 KiB
PHP

此文件含有模棱两可的 Unicode 字符

此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。

<?php
namespace app\api\controller;
use app\common\controller\Api;
use think\Db;
use think\Validate;
/**
* 情绪疗愈接口(M2)
*/
class Mood extends Api
{
protected $noNeedLogin = [];
protected $noNeedRight = '*';
/** 情绪枚举 */
public static $moods = [
'happy' => ['text' => '开心', 'emoji' => '☀️', 'score' => 5, 'color' => '#F5A65B'],
'calm' => ['text' => '平静', 'emoji' => '🌤', 'score' => 4, 'color' => '#9BC1A6'],
'anxious' => ['text' => '焦虑', 'emoji' => '🌪', 'score' => 2, 'color' => '#E8A87C'],
'low' => ['text' => '低落', 'emoji' => '🌧', 'score' => 2, 'color' => '#8FA6C4'],
'angry' => ['text' => '烦躁', 'emoji' => '⚡', 'score' => 1, 'color' => '#E8604C'],
];
/**
* 记录情绪(一天可多条)
* POST log_date, mood, intensity, triggers, content
*/
public function save()
{
$userId = $this->auth->id;
$mood = (string)$this->request->post('mood', '');
if (!isset(self::$moods[$mood])) {
$this->error('请选择情绪');
}
$logDate = $this->request->post('log_date', date('Y-m-d'));
if (!strtotime($logDate)) {
$this->error('日期格式不正确');
}
if (strtotime($logDate) > strtotime('+1 day')) {
$this->error('不能记录未来日期');
}
$content = mb_substr((string)$this->request->post('content', ''), 0, 1000);
$id = Db::name('mood_log')->insertGetId([
'user_id' => $userId,
'log_date' => $logDate,
'mood' => $mood,
'intensity' => min(5, max(1, (int)$this->request->post('intensity', 3))),
'triggers' => mb_substr((string)$this->request->post('triggers', ''), 0, 255),
'content' => $content,
'createtime' => time(),
'updatetime' => time(),
]);
$this->success('已记录', [
'id' => (int)$id,
'mood' => self::$moods[$mood],
]);
}
/**
* 情绪急救包(M2)
* 返回 4 个即时可用的安抚工具,按当前情绪排序
*/
public function firstaid()
{
$data = \app\common\service\ContentService::lists($this->auth->id, [
'type' => 'firstaid',
'limit' => 10,
]);
// 按当前情绪优先排序(若今日有记录)
$today = Db::name('mood_log')
->where('user_id', $this->auth->id)
->where('log_date', date('Y-m-d'))
->order('id', 'desc')
->find();
$mood = $today ? $today['mood'] : '';
if ($mood) {
usort($data['list'], function ($a, $b) use ($mood) {
$aHit = strpos((string)$a['summary'] . (string)$a['title'], $mood) !== false ? 1 : 0;
$bHit = strpos((string)$b['summary'] . (string)$b['title'], $mood) !== false ? 1 : 0;
return $bHit - $aHit;
});
}
$this->success('', [
'list' => $data['list'],
'today_mood' => $mood,
'tip' => '情绪上头时,先照顾身体,再处理问题',
'hotline' => ['name' => '心理援助热线', 'phone' => '12356'],
]);
}
/**
* 情绪列表
* GET limit=20 month=2026-11
*/
public function index()
{
$userId = $this->auth->id;
$limit = min(100, max(1, (int)$this->request->param('limit', 20)));
$month = $this->request->param('month', '');
$query = Db::name('mood_log')
->where('user_id', $userId)
->whereNull('deletetime');
if ($month && Validate::regex($month, '/^\d{4}-\d{2}$/')) {
$query->whereBetween('log_date', [$month . '-01', date('Y-m-t', strtotime($month . '-01'))]);
}
$list = $query->order('log_date', 'desc')->order('id', 'desc')->limit($limit)->select();
$list = array_map([$this, 'format'], $list);
$this->success('', ['list' => $list, 'moods' => $this->moodOptions()]);
}
/**
* 情绪趋势
* GET days=7|30
*/
public function trend()
{
$userId = $this->auth->id;
$days = (int)$this->request->param('days', 7);
$days = in_array($days, [7, 14, 30]) ? $days : 7;
$start = date('Y-m-d', strtotime('-' . ($days - 1) . ' day'));
$logs = Db::name('mood_log')
->where('user_id', $userId)
->whereNull('deletetime')
->where('log_date', '>=', $start)
->order('log_date', 'asc')
->select();
$bucket = [];
for ($i = $days - 1; $i >= 0; $i--) {
$d = date('Y-m-d', strtotime('-' . $i . ' day'));
$bucket[$d] = ['date' => $d, 'label' => date('n/j', strtotime($d)), 'score' => null, 'count' => 0, 'mood' => ''];
}
foreach ($logs as $log) {
if (!isset($bucket[$log['log_date']])) {
continue;
}
$score = self::$moods[$log['mood']]['score'] ?? 3;
$item = &$bucket[$log['log_date']];
$item['count']++;
$item['score'] = $item['score'] === null ? $score : round(($item['score'] * ($item['count'] - 1) + $score) / $item['count'], 1);
$item['mood'] = $log['mood'];
unset($item);
}
$recorded = array_values(array_filter($bucket, function ($v) {
return $v['score'] !== null;
}));
$avg = $recorded ? round(array_sum(array_column($recorded, 'score')) / count($recorded), 1) : 0;
// 主情绪统计
$count = [];
foreach ($logs as $log) {
$count[$log['mood']] = isset($count[$log['mood']]) ? $count[$log['mood']] + 1 : 1;
}
arsort($count);
$topMood = key($count) ?: '';
$this->success('', [
'days' => $days,
'list' => array_values($bucket),
'recorded' => count($recorded),
'avg_score' => $avg,
'top_mood' => $topMood ? array_merge(['key' => $topMood], self::$moods[$topMood]) : null,
'moods' => $this->moodOptions(),
]);
}
/**
* 删除一条情绪记录
*/
public function remove()
{
$id = (int)$this->request->post('id', 0);
$row = Db::name('mood_log')->where('id', $id)->where('user_id', $this->auth->id)->find();
if (!$row) {
$this->error('记录不存在');
}
Db::name('mood_log')->where('id', $id)->update(['deletetime' => time()]);
$this->success('已删除');
}
protected function moodOptions()
{
$list = [];
foreach (self::$moods as $key => $item) {
$list[] = array_merge(['key' => $key], $item);
}
return $list;
}
protected function format($log)
{
$mood = self::$moods[$log['mood']] ?? ['text' => '', 'emoji' => '', 'score' => 0, 'color' => ''];
return [
'id' => (int)$log['id'],
'log_date' => $log['log_date'],
'date_text' => date('n月j日', strtotime($log['log_date'])),
'week_text' => '周' . ['日', '一', '二', '三', '四', '五', '六'][(int)date('w', strtotime($log['log_date']))],
'mood' => $log['mood'],
'mood_text' => $mood['text'],
'emoji' => $mood['emoji'],
'color' => $mood['color'],
'intensity' => (int)$log['intensity'],
'triggers' => $log['triggers'],
'content' => $log['content'],
'time_text' => date('H:i', (int)$log['createtime']),
];
}
}