文件
huaqiapp/server/application/api/controller/Home.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

238 行
8.0 KiB
PHP

此文件含有不可见的 Unicode 字符

此文件含有人类无法区分的不可见的 Unicode 字符,但可以由计算机进行不同的处理。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。

<?php
namespace app\api\controller;
use app\common\controller\Api;
use app\common\service\CycleService;
use app\common\service\MemberService;
use think\Db;
/**
* 首页聚合接口
*
* 一次请求返回首页全部数据,减少端上串行请求,保证首屏流畅。
*/
class Home extends Api
{
protected $noNeedLogin = [];
protected $noNeedRight = '*';
public function index()
{
$userId = $this->auth->id;
$today = date('Y-m-d');
$hour = (int)date('H');
if ($hour < 6) {
$greeting = '夜深了';
} elseif ($hour < 11) {
$greeting = '早上好';
} elseif ($hour < 14) {
$greeting = '中午好';
} elseif ($hour < 18) {
$greeting = '下午好';
} else {
$greeting = '晚上好';
}
$cycle = CycleService::current($userId);
$loggedToday = Db::name('cycle_log')
->where('user_id', $userId)
->where('log_date', $today)
->whereNull('deletetime')
->count() > 0;
$this->success('', [
'greeting' => $greeting,
'nickname' => $this->auth->nickname ?: '亲爱的',
'today' => $today,
'date_text' => date('n月j日') . ' ' . $this->weekText($today),
'cycle' => $cycle,
'logged_today' => $loggedToday,
'phase_tip' => $this->phaseTip($cycle),
'jijing' => $this->pickJijing($cycle['phase'], $today),
'reminders' => $this->reminders($cycle, $loggedToday),
'mood' => $this->moodSummary($userId),
'member' => MemberService::status($userId),
]);
}
/**
* 按阶段/日期稳定挑选一条心语(同一天结果不变)
*/
protected function pickJijing($phase, $today)
{
$pool = Db::name('content')
->where('type', 'jijing')
->where('status', 'normal')
->where('verify', 'passed')
->order('weigh', 'desc')
->select();
if (!$pool) {
return null;
}
$matched = [];
foreach ($pool as $item) {
$phases = array_filter(explode(',', $item['phase']));
if (!$phase || in_array('all', $phases) || in_array($phase, $phases)) {
$matched[] = $item;
}
}
if (!$matched) {
$matched = $pool;
}
$index = crc32($today . ($phase ?: 'all')) % count($matched);
$item = $matched[$index];
return [
'id' => (int)$item['id'],
'title' => $item['title'],
'summary' => $item['summary'],
'body' => $item['body'],
'emoji' => $item['cover_emoji'],
'access' => $item['access'],
];
}
/**
* 阶段建议文案
*/
protected function phaseTip($cycle)
{
if (!$cycle['has_data']) {
return [
'title' => '先记录一次经期',
'desc' => '记录后即可获得阶段预测与个性化建议',
'action' => '去记录',
];
}
$map = [
'menstrual' => ['keyword' => '这几天适合放慢节奏', 'desc' => '注意保暖、补铁,减少高强度运动'],
'follicular' => ['keyword' => '能量回升期', 'desc' => '精力与情绪较好,适合安排计划与运动'],
'ovulation' => ['keyword' => '易孕期', 'desc' => '宫颈黏液变清亮,注意私处清洁与补水'],
'luteal' => ['keyword' => '黄体期', 'desc' => '情绪易波动,优先睡眠与温热饮食'],
];
$tip = $map[$cycle['phase']] ?: $map['follicular'];
return [
'title' => $tip['keyword'],
'desc' => $tip['desc'],
'action' => '',
];
}
/**
* 今日提醒(由周期与记录状态推导,非冗余推送)
*/
protected function reminders($cycle, $loggedToday)
{
$list = [];
if (!$loggedToday) {
$list[] = [
'type' => 'record',
'icon' => '✍️',
'title' => '今天还没有记录',
'desc' => '花 10 秒记录身体感受,预测会更准',
];
}
if ($cycle['has_data']) {
if ($cycle['is_period']) {
$list[] = [
'type' => 'care',
'icon' => '🌡',
'title' => '经期第 ' . $cycle['day'] . ' 天',
'desc' => '小腹保暖 + 温热饮食,避免冰饮',
];
} elseif ($cycle['is_fertile']) {
$list[] = [
'type' => 'fertile',
'icon' => '🌱',
'title' => '易孕期',
'desc' => '排卵期前后,注意休息与营养',
];
} elseif ($cycle['days_to_next'] !== null && $cycle['days_to_next'] <= 3 && $cycle['days_to_next'] >= 1) {
$list[] = [
'type' => 'period',
'icon' => '📅',
'title' => '经期还有 ' . $cycle['days_to_next'] . ' 天',
'desc' => '可以提前准备卫生用品与暖贴',
];
} elseif ($cycle['phase'] === 'luteal') {
$list[] = [
'type' => 'mood',
'icon' => '🍂',
'title' => '黄体期留意情绪',
'desc' => '敏感易累是生理性的,允许自己慢一点',
];
}
}
// 自定义提醒(吃药 / 记录 / 复诊等)
$custom = Db::name('reminder')
->where('user_id', $this->auth->id)
->where('enabled', 1)
->whereNull('deletetime')
->order('remind_time', 'asc')
->select();
$today = date('Y-m-d');
foreach ($custom as $item) {
if ($item['repeat_rule'] === 'once' && $item['remind_date'] !== $today) {
continue;
}
$list[] = [
'type' => $item['type'] ?: 'custom',
'icon' => '⏰',
'title' => $item['title'],
'desc' => substr($item['remind_time'], 0, 5) . ' 提醒',
];
}
return $list;
}
/**
* 近 7 天情绪概览
*/
protected function moodSummary($userId)
{
$start = date('Y-m-d', strtotime('-6 day'));
$logs = Db::name('mood_log')
->where('user_id', $userId)
->whereNull('deletetime')
->where('log_date', '>=', $start)
->order('log_date', 'asc')
->select();
$scoreMap = ['happy' => 5, 'calm' => 4, 'low' => 2, 'anxious' => 2, 'angry' => 1];
$days = [];
for ($i = 6; $i >= 0; $i--) {
$d = date('Y-m-d', strtotime('-' . $i . ' day'));
$days[$d] = ['date' => $d, 'score' => null, 'mood' => ''];
}
foreach ($logs as $log) {
if (isset($days[$log['log_date']])) {
$days[$log['log_date']]['score'] = isset($scoreMap[$log['mood']]) ? $scoreMap[$log['mood']] : 3;
$days[$log['log_date']]['mood'] = $log['mood'];
}
}
$recorded = array_values(array_filter($days, function ($d) {
return $d['score'] !== null;
}));
$avg = $recorded ? round(array_sum(array_column($recorded, 'score')) / count($recorded), 1) : 0;
return [
'days' => array_values($days),
'recorded' => count($recorded),
'avg_score' => $avg,
'today_mood' => isset($days[date('Y-m-d')]) ? $days[date('Y-m-d')]['mood'] : '',
];
}
protected function weekText($date)
{
$weeks = ['日', '一', '二', '三', '四', '五', '六'];
return '星期' . $weeks[(int)date('w', strtotime($date))];
}
}