后端(新增 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 返回数组、 咨询时段跨年错误
339 行
11 KiB
PHP
339 行
11 KiB
PHP
<?php
|
||
|
||
namespace app\api\controller;
|
||
|
||
use app\common\controller\Api;
|
||
use app\common\service\ContentService;
|
||
use app\common\service\CycleService;
|
||
use app\common\service\MemberService;
|
||
use fast\Random;
|
||
use think\Db;
|
||
|
||
/**
|
||
* 家人共护接口(M4)
|
||
*
|
||
* 绑定(≤5 人)/ 关怀提醒 / 话术库 / 爱心积分 / 私密沟通空间 / 家庭健康视图
|
||
*/
|
||
class Family extends Api
|
||
{
|
||
protected $noNeedLogin = [];
|
||
protected $noNeedRight = '*';
|
||
|
||
/** 最多绑定人数 */
|
||
const MAX_BIND = 5;
|
||
|
||
/**
|
||
* 家庭总览
|
||
*/
|
||
public function index()
|
||
{
|
||
$userId = $this->auth->id;
|
||
$binds = Db::name('family_bind')
|
||
->where('user_id', $userId)
|
||
->where('status', 'normal')
|
||
->order('id', 'desc')
|
||
->select();
|
||
|
||
$this->success('', [
|
||
'binds' => array_map([$this, 'formatBind'], $binds),
|
||
'max' => self::MAX_BIND,
|
||
'can_invite' => count($binds) < self::MAX_BIND,
|
||
'points' => $this->pointsData($userId),
|
||
'scripts' => ContentService::lists($userId, ['type' => 'care', 'limit' => 3]),
|
||
'care_today' => $this->careToday($userId),
|
||
'member' => MemberService::status($userId),
|
||
'script_locked' => !MemberService::can($userId, 'family.script.full'),
|
||
]);
|
||
}
|
||
|
||
/**
|
||
* 生成邀请(二维码/链接)
|
||
* POST relation=partner|parent|friend, member_name
|
||
*/
|
||
public function invite()
|
||
{
|
||
$userId = $this->auth->id;
|
||
$count = Db::name('family_bind')
|
||
->where('user_id', $userId)
|
||
->whereIn('status', ['pending', 'normal'])
|
||
->count();
|
||
if ($count >= self::MAX_BIND) {
|
||
$this->error('最多绑定 ' . self::MAX_BIND . ' 位家人');
|
||
}
|
||
|
||
$relation = $this->request->post('relation', 'partner');
|
||
if (!in_array($relation, ['partner', 'parent', 'friend'], true)) {
|
||
$relation = 'partner';
|
||
}
|
||
$code = strtoupper(Random::alnum(8));
|
||
|
||
$id = Db::name('family_bind')->insertGetId([
|
||
'user_id' => $userId,
|
||
'member_name' => mb_substr((string)$this->request->post('member_name', ''), 0, 30),
|
||
'relation' => $relation,
|
||
'scope' => 'remind,care',
|
||
'invite_code' => $code,
|
||
'status' => 'pending',
|
||
'createtime' => time(),
|
||
'updatetime' => time(),
|
||
]);
|
||
|
||
$this->success('邀请已生成', [
|
||
'id' => (int)$id,
|
||
'invite_code' => $code,
|
||
'invite_text' => '邀请你成为我花期里的守护者,绑定码:' . $code,
|
||
'expire_days' => 7,
|
||
]);
|
||
}
|
||
|
||
/**
|
||
* 确认绑定(家人端输入邀请码)
|
||
* POST invite_code
|
||
*/
|
||
public function confirm()
|
||
{
|
||
$userId = $this->auth->id;
|
||
$code = strtoupper(trim((string)$this->request->post('invite_code', '')));
|
||
if (!$code) {
|
||
$this->error('请输入绑定码');
|
||
}
|
||
$bind = Db::name('family_bind')->where('invite_code', $code)->find();
|
||
if (!$bind) {
|
||
$this->error('绑定码不存在或已失效');
|
||
}
|
||
if ((int)$bind['user_id'] === (int)$userId) {
|
||
$this->error('不能与自己绑定');
|
||
}
|
||
if ($bind['status'] === 'normal') {
|
||
$this->error('该邀请已被使用');
|
||
}
|
||
if ($bind['createtime'] < time() - 7 * 86400) {
|
||
$this->error('绑定码已过期,请让对方重新生成');
|
||
}
|
||
|
||
Db::name('family_bind')->where('id', $bind['id'])->update([
|
||
'member_user_id' => $userId,
|
||
'status' => 'normal',
|
||
'confirm_time' => time(),
|
||
'updatetime' => time(),
|
||
]);
|
||
|
||
$this->success('绑定成功', ['bind_id' => (int)$bind['id']]);
|
||
}
|
||
|
||
/**
|
||
* 解绑
|
||
*/
|
||
public function remove()
|
||
{
|
||
$id = (int)$this->request->post('id', 0);
|
||
$row = Db::name('family_bind')
|
||
->where('id', $id)
|
||
->where(function ($query) {
|
||
$query->where('user_id', $this->auth->id)->whereOr('member_user_id', $this->auth->id);
|
||
})
|
||
->find();
|
||
if (!$row) {
|
||
$this->error('绑定不存在');
|
||
}
|
||
Db::name('family_bind')->where('id', $id)->update(['status' => 'removed', 'updatetime' => time()]);
|
||
$this->success('已解绑,对方将无法再看到你的提醒');
|
||
}
|
||
|
||
/**
|
||
* 话术库(暖心话术,完整库为会员权益)
|
||
*/
|
||
public function scripts()
|
||
{
|
||
$isMember = MemberService::can($this->auth->id, 'family.script.full');
|
||
$data = ContentService::lists($this->auth->id, ['type' => 'care', 'limit' => $isMember ? 30 : 4]);
|
||
$data['locked'] = !$isMember;
|
||
$data['tip'] = $isMember ? '' : '开通会员可查看完整话术库';
|
||
$this->success('', $data);
|
||
}
|
||
|
||
/**
|
||
* 沟通空间:留言列表
|
||
* GET bind_id
|
||
*/
|
||
public function messages()
|
||
{
|
||
$bindId = (int)$this->request->param('bind_id', 0);
|
||
$bind = $this->checkBind($bindId);
|
||
$list = Db::name('care_message')
|
||
->where('bind_id', $bind['id'])
|
||
->order('id', 'desc')
|
||
->limit(50)
|
||
->select();
|
||
|
||
$list = array_map(function ($row) {
|
||
return [
|
||
'id' => (int)$row['id'],
|
||
'type' => $row['type'],
|
||
'content' => $row['content'],
|
||
'mine' => (int)$row['user_id'] === (int)$this->auth->id,
|
||
'time_text' => date('m-d H:i', (int)$row['createtime']),
|
||
];
|
||
}, $list);
|
||
|
||
$this->success('', ['bind' => $this->formatBind($bind), 'list' => array_reverse($list)]);
|
||
}
|
||
|
||
/**
|
||
* 沟通空间:发送留言
|
||
*/
|
||
public function messageSend()
|
||
{
|
||
$bindId = (int)$this->request->post('bind_id', 0);
|
||
$bind = $this->checkBind($bindId);
|
||
$content = mb_substr(trim((string)$this->request->post('content', '')), 0, 500);
|
||
if (!$content) {
|
||
$this->error('留言不能为空');
|
||
}
|
||
$id = Db::name('care_message')->insertGetId([
|
||
'bind_id' => $bind['id'],
|
||
'user_id' => $this->auth->id,
|
||
'type' => 'text',
|
||
'content' => $content,
|
||
'createtime' => time(),
|
||
]);
|
||
$this->success('已发送', ['id' => (int)$id]);
|
||
}
|
||
|
||
/**
|
||
* 记录一次关怀行动(每日一次,+2 积分)
|
||
*/
|
||
public function careAction()
|
||
{
|
||
$userId = $this->auth->id;
|
||
if ($this->careToday($userId)) {
|
||
$this->success('今天已经记录过关怀啦', ['points' => $this->pointsData($userId)]);
|
||
}
|
||
Db::name('point_log')->insert([
|
||
'user_id' => $userId,
|
||
'action' => 'care_daily',
|
||
'point' => 2,
|
||
'ref_id' => 0,
|
||
'remark' => '今日关怀行动',
|
||
'createtime' => time(),
|
||
]);
|
||
$this->success('+2 爱心积分', ['points' => $this->pointsData($userId)]);
|
||
}
|
||
|
||
/**
|
||
* 积分明细
|
||
*/
|
||
public function points()
|
||
{
|
||
$userId = $this->auth->id;
|
||
$log = Db::name('point_log')
|
||
->where('user_id', $userId)
|
||
->order('id', 'desc')
|
||
->limit(30)
|
||
->select();
|
||
$list = array_map(function ($row) {
|
||
return [
|
||
'id' => (int)$row['id'],
|
||
'action' => $row['action'],
|
||
'point' => (int)$row['point'],
|
||
'remark' => $row['remark'],
|
||
'date' => date('Y-m-d H:i', (int)$row['createtime']),
|
||
];
|
||
}, $log);
|
||
|
||
$this->success('', ['points' => $this->pointsData($userId), 'list' => $list]);
|
||
}
|
||
|
||
/**
|
||
* 家庭健康视图:谁在看护、当前阶段、共享待办
|
||
*/
|
||
public function healthView()
|
||
{
|
||
$userId = $this->auth->id;
|
||
$cycle = CycleService::current($userId);
|
||
$binds = Db::name('family_bind')
|
||
->where(function ($query) use ($userId) {
|
||
$query->where('user_id', $userId)->whereOr('member_user_id', $userId);
|
||
})
|
||
->where('status', 'normal')
|
||
->select();
|
||
|
||
$tasks = Db::name('task')
|
||
->where('user_id', $userId)
|
||
->where('scope', 'family')
|
||
->whereNull('deletetime')
|
||
->where('done', 0)
|
||
->select();
|
||
|
||
$this->success('', [
|
||
'cycle' => [
|
||
'has_data' => $cycle['has_data'],
|
||
'phase_text' => $cycle['phase_text'],
|
||
'day' => $cycle['day'],
|
||
'next_start' => $cycle['next_start'],
|
||
'days_to_next' => $cycle['days_to_next'],
|
||
],
|
||
'caregivers' => array_map([$this, 'formatBind'], $binds),
|
||
'tasks' => array_map(function ($row) {
|
||
return ['id' => (int)$row['id'], 'title' => $row['title'], 'due_date' => $row['due_date']];
|
||
}, $tasks),
|
||
'privacy_note' => '家人仅可见阶段与提醒,看不到你的原始症状与情绪日记',
|
||
]);
|
||
}
|
||
|
||
// ---------------- 内部方法 ----------------
|
||
|
||
protected function checkBind($bindId)
|
||
{
|
||
$userId = $this->auth->id;
|
||
$bind = Db::name('family_bind')
|
||
->where('id', $bindId)
|
||
->where('status', 'normal')
|
||
->where(function ($query) use ($userId) {
|
||
$query->where('user_id', $userId)->whereOr('member_user_id', $userId);
|
||
})
|
||
->find();
|
||
if (!$bind) {
|
||
$this->error('绑定关系不存在或已解除');
|
||
}
|
||
return $bind;
|
||
}
|
||
|
||
protected function formatBind($row)
|
||
{
|
||
$relationText = ['partner' => '伴侣', 'parent' => '父母', 'friend' => '朋友'];
|
||
return [
|
||
'id' => (int)$row['id'],
|
||
'name' => $row['member_name'] ?: '待确认',
|
||
'relation' => $row['relation'],
|
||
'relation_text' => isset($relationText[$row['relation']]) ? $relationText[$row['relation']] : '家人',
|
||
'scope' => $row['scope'],
|
||
'status' => $row['status'],
|
||
'invite_code' => $row['status'] === 'pending' ? $row['invite_code'] : '',
|
||
'confirmed' => $row['status'] === 'normal',
|
||
];
|
||
}
|
||
|
||
protected function pointsData($userId)
|
||
{
|
||
$total = (int)Db::name('point_log')->where('user_id', $userId)->sum('point');
|
||
$todayStart = strtotime(date('Y-m-d'));
|
||
$today = (int)Db::name('point_log')->where('user_id', $userId)->where('createtime', '>=', $todayStart)->sum('point');
|
||
return [
|
||
'total' => $total,
|
||
'today' => $today,
|
||
'can_earn' => $this->careToday($userId) ? 0 : 2,
|
||
'rules' => ['每日关怀行动 +2', '完成备孕任务 +2', '连续陪伴 7 天 +10'],
|
||
];
|
||
}
|
||
|
||
protected function careToday($userId)
|
||
{
|
||
return Db::name('point_log')
|
||
->where('user_id', $userId)
|
||
->where('action', 'care_daily')
|
||
->where('createtime', '>=', strtotime(date('Y-m-d')))
|
||
->count() > 0;
|
||
}
|
||
}
|