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 返回数组、 咨询时段跨年错误
这个提交包含在:
@@ -126,4 +126,212 @@ class Setting extends Api
|
||||
$arr = is_array($json) ? $json : json_decode(htmlspecialchars_decode((string)$json, ENT_QUOTES), true);
|
||||
return is_array($arr) ? $arr : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 自定义提醒列表(M1/M6 智能提醒)
|
||||
*/
|
||||
public function reminderList()
|
||||
{
|
||||
$rows = \think\Db::name('reminder')
|
||||
->where('user_id', $this->auth->id)
|
||||
->whereNull('deletetime')
|
||||
->order('enabled desc, remind_time asc')
|
||||
->select();
|
||||
$this->success('', ['list' => array_map([$this, 'formatReminder'], $rows)]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增 / 修改提醒
|
||||
* POST id, title, type, remind_time, repeat_rule, remind_date, enabled
|
||||
*/
|
||||
public function reminderSave()
|
||||
{
|
||||
$userId = $this->auth->id;
|
||||
$id = (int)$this->request->post('id', 0);
|
||||
$title = mb_substr(trim((string)$this->request->post('title', '')), 0, 60);
|
||||
if (!$title) {
|
||||
$this->error('请填写提醒内容');
|
||||
}
|
||||
$time = (string)$this->request->post('remind_time', '21:00');
|
||||
if (!preg_match('/^\d{2}:\d{2}$/', $time)) {
|
||||
$this->error('提醒时间格式不正确');
|
||||
}
|
||||
$repeat = $this->request->post('repeat_rule', 'daily');
|
||||
if (!in_array($repeat, ['daily', 'once', 'weekly'], true)) {
|
||||
$repeat = 'daily';
|
||||
}
|
||||
$date = (string)$this->request->post('remind_date', '');
|
||||
$data = [
|
||||
'user_id' => $userId,
|
||||
'title' => $title,
|
||||
'type' => $this->request->post('type', 'custom'),
|
||||
'remind_time' => $time,
|
||||
'repeat_rule' => $repeat,
|
||||
'remind_date' => $date && strtotime($date) ? $date : null,
|
||||
'enabled' => (int)$this->request->post('enabled', 1),
|
||||
'updatetime' => time(),
|
||||
];
|
||||
if ($id) {
|
||||
$row = \think\Db::name('reminder')->where('id', $id)->where('user_id', $userId)->find();
|
||||
if (!$row) {
|
||||
$this->error('提醒不存在');
|
||||
}
|
||||
\think\Db::name('reminder')->where('id', $id)->update($data);
|
||||
} else {
|
||||
$data['createtime'] = time();
|
||||
$id = \think\Db::name('reminder')->insertGetId($data);
|
||||
}
|
||||
$this->success('已保存', ['id' => (int)$id]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除提醒
|
||||
*/
|
||||
public function reminderRemove()
|
||||
{
|
||||
$id = (int)$this->request->post('id', 0);
|
||||
$row = \think\Db::name('reminder')->where('id', $id)->where('user_id', $this->auth->id)->find();
|
||||
if (!$row) {
|
||||
$this->error('提醒不存在');
|
||||
}
|
||||
\think\Db::name('reminder')->where('id', $id)->update(['deletetime' => time()]);
|
||||
$this->success('已删除');
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据备份:创建快照(本地/云端同步的前提能力)
|
||||
*/
|
||||
public function backupCreate()
|
||||
{
|
||||
$userId = $this->auth->id;
|
||||
$payload = $this->collectData($userId);
|
||||
$json = json_encode($payload, JSON_UNESCAPED_UNICODE);
|
||||
$id = \think\Db::name('user_backup')->insertGetId([
|
||||
'user_id' => $userId,
|
||||
'name' => '备份 ' . date('Y-m-d H:i'),
|
||||
'size' => strlen($json),
|
||||
'payload' => $json,
|
||||
'createtime' => time(),
|
||||
]);
|
||||
|
||||
// 仅保留最近 5 份,避免存储膨胀
|
||||
$ids = \think\Db::name('user_backup')
|
||||
->where('user_id', $userId)
|
||||
->order('id', 'desc')
|
||||
->limit(100)
|
||||
->column('id');
|
||||
if (count($ids) > 5) {
|
||||
\think\Db::name('user_backup')->whereIn('id', array_slice($ids, 5))->delete();
|
||||
}
|
||||
|
||||
$this->success('备份完成', [
|
||||
'id' => (int)$id,
|
||||
'size' => strlen($json),
|
||||
'counts' => $payload['counts'],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 备份列表
|
||||
*/
|
||||
public function backupList()
|
||||
{
|
||||
$rows = \think\Db::name('user_backup')
|
||||
->where('user_id', $this->auth->id)
|
||||
->order('id', 'desc')
|
||||
->field('id,name,size,createtime')
|
||||
->select();
|
||||
$list = array_map(function ($row) {
|
||||
return [
|
||||
'id' => (int)$row['id'],
|
||||
'name' => $row['name'],
|
||||
'size' => (int)$row['size'],
|
||||
'size_text' => round($row['size'] / 1024, 1) . ' KB',
|
||||
'date' => date('Y-m-d H:i', (int)$row['createtime']),
|
||||
];
|
||||
}, $rows);
|
||||
$this->success('', ['list' => $list]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从备份恢复(仅恢复本人数据,先清理同范围记录)
|
||||
*/
|
||||
public function backupRestore()
|
||||
{
|
||||
$userId = $this->auth->id;
|
||||
$id = (int)$this->request->post('id', 0);
|
||||
$row = \think\Db::name('user_backup')->where('id', $id)->where('user_id', $userId)->find();
|
||||
if (!$row) {
|
||||
$this->error('备份不存在');
|
||||
}
|
||||
$payload = json_decode($row['payload'], true);
|
||||
if (!is_array($payload)) {
|
||||
$this->error('备份内容损坏');
|
||||
}
|
||||
|
||||
$restored = [];
|
||||
$map = [
|
||||
'cyle' => 'cycle',
|
||||
];
|
||||
$this->restoreTable('cycle', $payload['cycle'] ?? [], $userId, $restored);
|
||||
$this->restoreTable('cycle_log', $payload['cycle_log'] ?? [], $userId, $restored);
|
||||
$this->restoreTable('mood_log', $payload['mood_log'] ?? [], $userId, $restored);
|
||||
$this->restoreTable('bbt_record', $payload['bbt_record'] ?? [], $userId, $restored);
|
||||
$this->restoreTable('task', $payload['task'] ?? [], $userId, $restored);
|
||||
$this->restoreTable('memory', $payload['memory'] ?? [], $userId, $restored);
|
||||
|
||||
$this->success('恢复完成', ['restored' => $restored]);
|
||||
}
|
||||
|
||||
protected function collectData($userId)
|
||||
{
|
||||
$data = [
|
||||
'cycle' => \think\Db::name('cycle')->where('user_id', $userId)->whereNull('deletetime')->select(),
|
||||
'cycle_log' => \think\Db::name('cycle_log')->where('user_id', $userId)->whereNull('deletetime')->select(),
|
||||
'mood_log' => \think\Db::name('mood_log')->where('user_id', $userId)->whereNull('deletetime')->select(),
|
||||
'bbt_record' => \think\Db::name('bbt_record')->where('user_id', $userId)->select(),
|
||||
'task' => \think\Db::name('task')->where('user_id', $userId)->whereNull('deletetime')->select(),
|
||||
'memory' => \think\Db::name('memory')->where('user_id', $userId)->whereNull('deletetime')->select(),
|
||||
'reminder' => \think\Db::name('reminder')->where('user_id', $userId)->whereNull('deletetime')->select(),
|
||||
'setting' => \think\Db::name('user_setting')->where('user_id', $userId)->find(),
|
||||
];
|
||||
$data['counts'] = [
|
||||
'cycle' => count($data['cycle']),
|
||||
'cycle_log' => count($data['cycle_log']),
|
||||
'mood_log' => count($data['mood_log']),
|
||||
'bbt' => count($data['bbt_record']),
|
||||
'task' => count($data['task']),
|
||||
'memory' => count($data['memory']),
|
||||
];
|
||||
$data['version'] = 'v1';
|
||||
$data['created'] = date('Y-m-d H:i:s');
|
||||
return $data;
|
||||
}
|
||||
|
||||
protected function restoreTable($table, $rows, $userId, &$restored)
|
||||
{
|
||||
if (!is_array($rows)) {
|
||||
return;
|
||||
}
|
||||
\think\Db::name($table)->where('user_id', $userId)->delete();
|
||||
foreach ($rows as $row) {
|
||||
unset($row['id']);
|
||||
$row['user_id'] = $userId;
|
||||
\think\Db::name($table)->insert($row);
|
||||
}
|
||||
$restored[$table] = count($rows);
|
||||
}
|
||||
|
||||
protected function formatReminder($row)
|
||||
{
|
||||
return [
|
||||
'id' => (int)$row['id'],
|
||||
'title' => $row['title'],
|
||||
'type' => $row['type'],
|
||||
'remind_time' => $row['remind_time'],
|
||||
'repeat_rule' => $row['repeat_rule'],
|
||||
'remind_date' => $row['remind_date'],
|
||||
'enabled' => (int)$row['enabled'],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
在新工单中引用
屏蔽一个用户