后端(新增 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 返回数组、 咨询时段跨年错误
304 行
10 KiB
PHP
304 行
10 KiB
PHP
<?php
|
||
|
||
namespace app\api\controller;
|
||
|
||
use app\common\controller\Api;
|
||
use app\common\service\AssessmentService;
|
||
use app\common\service\ContentService;
|
||
use fast\Random;
|
||
use think\Db;
|
||
|
||
/**
|
||
* 两性关系接口(M7)
|
||
*
|
||
* 关系测评 / 沟通引导卡 / 冲突工具 / 关系课程 / 双人任务 / 纪念回顾
|
||
*/
|
||
class Relation extends Api
|
||
{
|
||
protected $noNeedLogin = [];
|
||
protected $noNeedRight = '*';
|
||
|
||
/**
|
||
* 关系模块总览
|
||
*/
|
||
public function index()
|
||
{
|
||
$userId = $this->auth->id;
|
||
|
||
$this->success('', [
|
||
'assessments' => AssessmentService::lists('relation', $userId),
|
||
'cards' => ContentService::lists($userId, ['type' => 'card', 'limit' => 3])['list'],
|
||
'conflicts' => ContentService::lists($userId, ['type' => 'conflict', 'limit' => 3])['list'],
|
||
'courses' => ['list' => $this->coursesData()],
|
||
'tasks' => $this->tasks(),
|
||
'memories' => $this->memoryList(5),
|
||
'stats' => $this->stats($userId),
|
||
]);
|
||
}
|
||
|
||
/**
|
||
* 关系测评列表 / 详情 / 提交
|
||
*/
|
||
public function assessments()
|
||
{
|
||
$this->success('', ['list' => AssessmentService::lists('relation', $this->auth->id)]);
|
||
}
|
||
|
||
public function assessmentDetail()
|
||
{
|
||
$data = AssessmentService::detail($this->request->param('id', 0));
|
||
if (!$data) {
|
||
$this->error('量表不存在');
|
||
}
|
||
$this->success('', $data);
|
||
}
|
||
|
||
public function assessmentSubmit()
|
||
{
|
||
$answers = $this->request->post('answers', '');
|
||
$answers = is_array($answers) ? $answers : json_decode(htmlspecialchars_decode((string)$answers, ENT_QUOTES), true);
|
||
if (!is_array($answers)) {
|
||
$this->error('请完成全部题目');
|
||
}
|
||
$result = AssessmentService::submit($this->auth->id, (int)$this->request->post('id', 0), $answers);
|
||
if (isset($result['error'])) {
|
||
$this->error($result['error']);
|
||
}
|
||
$this->success('测评完成', $result);
|
||
}
|
||
|
||
/**
|
||
* 沟通引导卡 / 冲突工具
|
||
*/
|
||
public function cards()
|
||
{
|
||
$this->success('', ContentService::lists($this->auth->id, ['type' => 'card', 'limit' => 30]));
|
||
}
|
||
|
||
public function conflicts()
|
||
{
|
||
$this->success('', ContentService::lists($this->auth->id, ['type' => 'conflict', 'limit' => 30]));
|
||
}
|
||
|
||
/**
|
||
* 关系课程列表 + 购买
|
||
*/
|
||
public function courses()
|
||
{
|
||
$this->success('', [
|
||
'list' => $this->coursesData(),
|
||
'total' => count($this->coursesData()),
|
||
]);
|
||
}
|
||
|
||
protected function coursesData()
|
||
{
|
||
$data = ContentService::lists($this->auth->id, ['type' => 'course', 'part' => 'relation', 'limit' => 30]);
|
||
return $data['list'];
|
||
}
|
||
|
||
public function courseBuy()
|
||
{
|
||
$userId = $this->auth->id;
|
||
$contentId = (int)$this->request->post('id', 0);
|
||
$course = Db::name('content')->where('id', $contentId)->where('type', 'course')->find();
|
||
if (!$course) {
|
||
$this->error('课程不存在');
|
||
}
|
||
if (ContentService::hasPurchased($userId, $contentId)) {
|
||
$this->success('已购买', ['purchased' => true]);
|
||
}
|
||
|
||
$orderNo = 'HC' . date('YmdHis') . Random::numeric(4);
|
||
$id = Db::name('course_order')->insertGetId([
|
||
'order_no' => $orderNo,
|
||
'user_id' => $userId,
|
||
'content_id' => $contentId,
|
||
'amount' => 0,
|
||
'pay_status' => 'unpaid',
|
||
'createtime' => time(),
|
||
]);
|
||
|
||
$this->success('订单已创建', [
|
||
'order_id' => (int)$id,
|
||
'order_no' => $orderNo,
|
||
'amount' => (float)0,
|
||
'pay_hint' => '课程支付通道待接入,可联系客服开通',
|
||
'purchased' => false,
|
||
]);
|
||
}
|
||
|
||
/**
|
||
* 双人任务
|
||
*/
|
||
public function taskList()
|
||
{
|
||
$this->success('', ['list' => $this->tasks()]);
|
||
}
|
||
|
||
public function taskSave()
|
||
{
|
||
$userId = $this->auth->id;
|
||
$id = (int)$this->request->post('id', 0);
|
||
$title = mb_substr(trim((string)$this->request->post('title', '')), 0, 80);
|
||
if (!$title) {
|
||
$this->error('请填写任务内容');
|
||
}
|
||
$data = [
|
||
'user_id' => $userId,
|
||
'scope' => 'relation',
|
||
'title' => $title,
|
||
'category' => $this->request->post('category', 'daily'),
|
||
'share_partner' => 1,
|
||
'updatetime' => time(),
|
||
];
|
||
if ($id) {
|
||
$row = Db::name('task')->where('id', $id)->where('user_id', $userId)->find();
|
||
if (!$row) {
|
||
$this->error('任务不存在');
|
||
}
|
||
Db::name('task')->where('id', $id)->update($data);
|
||
} else {
|
||
$data['createtime'] = time();
|
||
$id = Db::name('task')->insertGetId($data);
|
||
}
|
||
$this->success('已保存', ['id' => (int)$id, 'list' => $this->tasks()]);
|
||
}
|
||
|
||
public function taskDone()
|
||
{
|
||
$userId = $this->auth->id;
|
||
$id = (int)$this->request->post('id', 0);
|
||
$row = Db::name('task')->where('id', $id)->where('user_id', $userId)->where('scope', 'relation')->find();
|
||
if (!$row) {
|
||
$this->error('任务不存在');
|
||
}
|
||
$done = $row['done'] ? 0 : 1;
|
||
Db::name('task')->where('id', $id)->update([
|
||
'done' => $done, 'done_time' => $done ? time() : null, 'updatetime' => time(),
|
||
]);
|
||
// 完成双人任务,双人共同记录进「爱的记忆」
|
||
if ($done) {
|
||
Db::name('memory')->insert([
|
||
'user_id' => $userId,
|
||
'bind_id' => 0,
|
||
'title' => '一起完成:' . $row['title'],
|
||
'memory_date' => date('Y-m-d'),
|
||
'type' => 'daily',
|
||
'content' => '',
|
||
'remind' => 0,
|
||
'createtime' => time(),
|
||
]);
|
||
}
|
||
$this->success($done ? '已完成' : '已取消', ['done' => $done, 'list' => $this->tasks()]);
|
||
}
|
||
|
||
/**
|
||
* 纪念与回顾
|
||
*/
|
||
public function memories()
|
||
{
|
||
$this->success('', ['list' => $this->memoryList(50)]);
|
||
}
|
||
|
||
public function memorySave()
|
||
{
|
||
$userId = $this->auth->id;
|
||
$title = mb_substr(trim((string)$this->request->post('title', '')), 0, 60);
|
||
$date = (string)$this->request->post('memory_date', date('Y-m-d'));
|
||
if (!$title) {
|
||
$this->error('请填写纪念日名称');
|
||
}
|
||
if (!strtotime($date)) {
|
||
$this->error('日期格式不正确');
|
||
}
|
||
$id = Db::name('memory')->insertGetId([
|
||
'user_id' => $userId,
|
||
'title' => $title,
|
||
'memory_date' => $date,
|
||
'type' => $this->request->post('type', 'anniversary'),
|
||
'content' => mb_substr((string)$this->request->post('content', ''), 0, 500),
|
||
'remind' => (int)$this->request->post('remind', 1),
|
||
'createtime' => time(),
|
||
]);
|
||
$this->success('已记录', ['id' => (int)$id, 'list' => $this->memoryList(50)]);
|
||
}
|
||
|
||
public function memoryRemove()
|
||
{
|
||
$id = (int)$this->request->post('id', 0);
|
||
$row = Db::name('memory')->where('id', $id)->where('user_id', $this->auth->id)->find();
|
||
if (!$row) {
|
||
$this->error('记录不存在');
|
||
}
|
||
Db::name('memory')->where('id', $id)->update(['deletetime' => time()]);
|
||
$this->success('已删除', ['list' => $this->memoryList(50)]);
|
||
}
|
||
|
||
// ---------------- 内部方法 ----------------
|
||
|
||
protected function tasks()
|
||
{
|
||
$rows = Db::name('task')
|
||
->where('user_id', $this->auth->id)
|
||
->where('scope', 'relation')
|
||
->whereNull('deletetime')
|
||
->order('done asc, id desc')
|
||
->select();
|
||
return array_map(function ($row) {
|
||
return [
|
||
'id' => (int)$row['id'],
|
||
'title' => $row['title'],
|
||
'category' => $row['category'],
|
||
'done' => (int)$row['done'],
|
||
];
|
||
}, $rows);
|
||
}
|
||
|
||
protected function memoryList($limit)
|
||
{
|
||
$rows = Db::name('memory')
|
||
->where('user_id', $this->auth->id)
|
||
->whereNull('deletetime')
|
||
->order('memory_date', 'desc')
|
||
->limit($limit)
|
||
->select();
|
||
|
||
return array_map(function ($row) {
|
||
$days = (int)floor((strtotime(date('Y-m-d')) - strtotime($row['memory_date'])) / 86400);
|
||
$nextDays = null;
|
||
if ($row['type'] === 'anniversary') {
|
||
$nextTs = strtotime(date('Y') . '-' . date('m-d', strtotime($row['memory_date'])));
|
||
if ($nextTs < strtotime(date('Y-m-d'))) {
|
||
$nextTs = strtotime('+1 year', $nextTs);
|
||
}
|
||
$nextDays = (int)ceil(($nextTs - strtotime(date('Y-m-d'))) / 86400);
|
||
}
|
||
return [
|
||
'id' => (int)$row['id'],
|
||
'title' => $row['title'],
|
||
'memory_date' => $row['memory_date'],
|
||
'type' => $row['type'],
|
||
'content' => $row['content'],
|
||
'days_since' => $days,
|
||
'days_to_next' => $nextDays,
|
||
];
|
||
}, $rows);
|
||
}
|
||
|
||
protected function stats($userId)
|
||
{
|
||
return [
|
||
'task_total' => Db::name('task')->where('user_id', $userId)->where('scope', 'relation')->whereNull('deletetime')->count(),
|
||
'task_done' => Db::name('task')->where('user_id', $userId)->where('scope', 'relation')->where('done', 1)->whereNull('deletetime')->count(),
|
||
'memory_total' => Db::name('memory')->where('user_id', $userId)->whereNull('deletetime')->count(),
|
||
'test_count' => Db::name('assessment_record')
|
||
->where('user_id', $userId)
|
||
->whereIn('assessment_id', function ($query) {
|
||
$query->name('assessment')->where('scope', 'relation')->field('id');
|
||
})
|
||
->count(),
|
||
];
|
||
}
|
||
}
|