This commit is contained in:
Your Name
2026-06-03 11:39:36 +08:00
parent a3bae1555a
commit ec4e0b9f29
22 changed files with 1353 additions and 646 deletions
@@ -457,6 +457,10 @@ class TcmController extends BaseApiController
return ['ok' => false, 'error' => '诊单不存在'];
}
if ((int) ($diagnosis['show_card'] ?? 1) !== 1) {
return ['ok' => false, 'error' => '该就诊卡已在统计端隐藏'];
}
// 通过 diagnosis_view_records 校验当前小程序用户与该诊单的归属关系
// 该表是「就诊卡列表」的来源(参考 DiagnosisLogic::getCardList
$owned = \think\facade\Db::name('diagnosis_view_records')
+37 -4
View File
@@ -499,8 +499,10 @@ class DailyDietAiLogic
3. 严禁:白馒头、油条、粘豆包、糯米饭、西瓜、含糖饮料、白稀饭(杂面窝头、玉米碴粥除外)。
4. 低 GI 优先;农村家常;禁止轻食/沙拉/藜麦等词。
5. lunch 字段写法:蛋白菜放前面,淀粉(如有)放最后且注明「小半碗/一个」。
6. 只输出 JSON
{"breakfast":"...","lunch":"...","dinner":"...","tips":"...","avoid":["...","..."]}
6. 早餐必须包含「驼奶粉」(温水冲服,适量),可与粥、蛋、菜并列写。
7. drinks 单独写全天喝什么:温开水为主;可写驼奶粉冲饮时间;严禁含糖饮料、果汁、酒。
8. 只输出 JSON
{"breakfast":"...","drinks":"...","lunch":"...","dinner":"...","tips":"...","avoid":["...","..."]}
SYS;
$user = sprintf(
@@ -544,9 +546,11 @@ SYS;
2. 淀粉(粥/饭/面/饼/窝头/薯/南瓜)每餐最多 1 种,早中晚互不相同。
3. 严禁白馒头、油条、粘豆包、糯米饭、西瓜、含糖饮料。
4. 农村家常饭,禁止轻食/沙拉/藜麦。
5. 早餐必须含驼奶粉(温水冲服);喝的单独一行。
严格按下面 5 行格式输出,不要 JSON、不要 markdown、不要多余解释:
早餐:(具体食物)
严格按下面 6 行格式输出,不要 JSON、不要 markdown、不要多余解释:
早餐:(具体食物,须含驼奶粉
喝的:(温开水、驼奶粉冲饮安排等,忌甜饮)
午餐:(具体食物,蛋白放前)
晚餐:(具体食物)
提示:(一句土话提醒)
@@ -593,6 +597,7 @@ SYS;
$map = [
'breakfast' => null,
'drinks' => null,
'lunch' => null,
'dinner' => null,
'tips' => null,
@@ -602,6 +607,9 @@ SYS;
if (preg_match('/早餐[:]\s*(.+)$/mu', $text, $m)) {
$map['breakfast'] = trim(preg_split('/\R/u', $m[1])[0] ?? $m[1]);
}
if (preg_match('/喝的[:]\s*(.+)$/mu', $text, $m)) {
$map['drinks'] = trim(preg_split('/\R/u', $m[1])[0] ?? $m[1]);
}
if (preg_match('/午餐[:]\s*(.+)$/mu', $text, $m)) {
$map['lunch'] = trim(preg_split('/\R/u', $m[1])[0] ?? $m[1]);
}
@@ -623,6 +631,7 @@ SYS;
$out = [
'breakfast' => (string) $map['breakfast'],
'drinks' => (string) ($map['drinks'] ?? ''),
'lunch' => (string) ($map['lunch'] ?? ''),
'dinner' => (string) ($map['dinner'] ?? ''),
'tips' => (string) ($map['tips'] ?? ''),
@@ -632,6 +641,29 @@ SYS;
return $out;
}
/**
* 产品要求:早餐含驼奶粉,并单独给出「喝的」
*
* @param array<string, mixed> $plan
* @return array<string, mixed>
*/
private static function applyDietProductDefaults(array $plan): array
{
$breakfast = trim((string) ($plan['breakfast'] ?? ''));
if ($breakfast !== '' && mb_strpos($breakfast, '驼奶粉') === false && mb_strpos($breakfast, '驼奶') === false) {
$plan['breakfast'] = '驼奶粉(温水冲服)、' . $breakfast;
}
$drinks = trim((string) ($plan['drinks'] ?? ''));
if ($drinks === '') {
$plan['drinks'] = '白天多喝温开水;早餐按量冲驼奶粉,别喝甜饮料、果汁。';
} elseif (mb_strpos($drinks, '驼奶') === false) {
$plan['drinks'] = $drinks . ';早餐已安排驼奶粉';
}
return $plan;
}
/**
* 规范化 AI 三餐方案;校验未过也保留 AI 文案(避免流式展示后被规则模板覆盖)
*
@@ -640,6 +672,7 @@ SYS;
*/
private static function finalizeAiRecommendPlan(array $parsed): array
{
$parsed = self::applyDietProductDefaults($parsed);
$checked = DietMealValidator::validateAndNormalize($parsed);
$data = $checked['plan'];
$data['source'] = 'ai';
@@ -4,6 +4,7 @@ namespace app\api\logic\tcm;
use app\common\model\tcm\DailyFamilyLike;
use app\common\model\tcm\DailyShareInvite;
use app\common\model\tcm\Diagnosis;
/**
* 家人点赞(邀请观看页 → 患者日常页展示)
@@ -204,6 +205,11 @@ class DailyFamilyLikeLogic
return self::setError('无权查看该诊单') ? false : false;
}
$diagnosis = Diagnosis::where('id', $diagnosisId)->where('delete_time', null)->field('show_card')->find();
if (!$diagnosis || (int) ($diagnosis['show_card'] ?? 1) !== 1) {
return self::setError('该就诊卡已在统计端隐藏') ? false : false;
}
$likeDate = date('Y-m-d');
$count = self::countToday($diagnosisId, $likeDate);
@@ -49,6 +49,13 @@ class DailyGamifyLogic
if (!$owned) {
return self::setError('无权操作该诊单') ? false : false;
}
$diagnosis = \app\common\model\tcm\Diagnosis::where('id', $diagnosisId)
->where('delete_time', null)
->field('show_card')
->find();
if (!$diagnosis || (int) ($diagnosis['show_card'] ?? 1) !== 1) {
return self::setError('该就诊卡已在统计端隐藏') ? false : false;
}
return true;
}
+85 -15
View File
@@ -3,6 +3,8 @@
namespace app\api\logic\tcm;
use app\adminapi\logic\tcm\DiagnosisLogic;
use app\common\model\DiagnosisViewRecord;
use app\common\model\tcm\Diagnosis;
use app\common\model\user\User;
/**
@@ -74,19 +76,38 @@ class DailyPhoneLogic
if (!empty($cardList)) {
$card = $cardList[0];
return self::buildContextOk($card, $mobile, false, false);
}
// 该手机号已在诊单库建档:关联查看记录,禁止重复创建轻量档案
$existing = Diagnosis::where('phone', $mobile)
->where('delete_time', null)
->order('id', 'desc')
->find();
if ($existing && (int) ($existing['show_card'] ?? 1) !== 1) {
return [
'ok' => true,
'ok' => false,
'need_mobile' => false,
'created' => false,
'diagnosis_id' => (int) $card['id'],
'patient_id' => (int) $card['patient_id'],
'patient_name' => (string) ($card['patient_name'] ?? ''),
'gender' => (int) ($card['gender'] ?? 0),
'age' => (int) ($card['age'] ?? 0),
'mobile' => $mobile,
'create_source' => (string) ($card['create_source'] ?? ''),
'error' => '该手机号对应就诊卡因隐私设置已隐藏,请联系医生',
];
}
if ($existing) {
if (!self::ensureUserViewRecord($userId, (int) $existing['id'], (int) $existing['patient_id'])) {
return [
'ok' => false,
'need_mobile' => false,
'error' => '该手机号已建档,暂无法关联到当前账号',
];
}
return self::buildContextOk([
'id' => (int) $existing['id'],
'patient_id' => (int) $existing['patient_id'],
'patient_name' => (string) ($existing['patient_name'] ?? ''),
'gender' => (int) ($existing['gender'] ?? 0),
'age' => (int) ($existing['age'] ?? 0),
'create_source' => (string) ($existing['create_source'] ?? ''),
], $mobile, false, true);
}
$displayName = trim((string) (($user['real_name'] ?? '') ?: ($user['nickname'] ?? '')));
if ($displayName === '') {
@@ -114,17 +135,66 @@ class DailyPhoneLogic
];
}
return [
'ok' => true,
'need_mobile' => false,
'created' => true,
'diagnosis_id' => (int) $result['id'],
return self::buildContextOk([
'id' => (int) $result['id'],
'patient_id' => (int) $result['patient_id'],
'patient_name' => $displayName,
'gender' => $gender,
'age' => max(0, (int) ($user['age'] ?? 0)),
'mobile' => $mobile,
'create_source' => self::CREATE_SOURCE_MNP_DAILY,
], $mobile, true, false);
}
/**
* @param array{id:int,patient_id:int,patient_name?:string,gender?:int,age?:int,create_source?:string} $card
*/
private static function buildContextOk(array $card, string $mobile, bool $created, bool $linkedExisting): array
{
return [
'ok' => true,
'need_mobile' => false,
'created' => $created,
'linked_existing' => $linkedExisting,
'diagnosis_id' => (int) $card['id'],
'patient_id' => (int) $card['patient_id'],
'patient_name' => (string) ($card['patient_name'] ?? ''),
'gender' => (int) ($card['gender'] ?? 0),
'age' => (int) ($card['age'] ?? 0),
'mobile' => $mobile,
'create_source' => (string) ($card['create_source'] ?? ''),
];
}
/** 将已有诊单挂到当前小程序用户(diagnosis_view_records */
private static function ensureUserViewRecord(int $userId, int $diagnosisId, int $patientId): bool
{
if ($userId <= 0 || $diagnosisId <= 0) {
return false;
}
try {
$exists = DiagnosisViewRecord::where('user_id', $userId)
->where('diagnosis_id', $diagnosisId)
->where('delete_time', null)
->find();
if ($exists) {
return true;
}
$now = time();
DiagnosisViewRecord::create([
'user_id' => $userId,
'diagnosis_id' => $diagnosisId,
'patient_id' => $patientId,
'share_user_id' => 0,
'view_count' => 1,
'first_view_time' => $now,
'last_view_time' => $now,
'is_confirmed' => 0,
'create_time' => $now,
'update_time' => $now,
]);
return true;
} catch (\Throwable $e) {
return false;
}
}
}
@@ -50,6 +50,9 @@ class DailyShareLogic
if (!$diagnosis) {
return self::setError('诊单不存在') ? false : false;
}
if ((int) ($diagnosis['show_card'] ?? 1) !== 1) {
return self::setError('该就诊卡已在统计端隐藏') ? false : false;
}
$inviteDate = date('Y-m-d');
$code = self::generateUniqueCode();
@@ -114,6 +114,7 @@ class DietMealValidator
$normalized = [
'breakfast' => $breakfast,
'drinks' => trim((string) ($plan['drinks'] ?? '')),
'lunch' => $lunch,
'dinner' => $dinner,
'tips' => $tips,
+19 -9
View File
@@ -18,7 +18,7 @@ class GiKnowledge
'蔬菜类' => ['魔芋', '粟米', '大白菜', '黄瓜', '芹菜', '茄子', '青椒', '海带', '金针菇', '香菇', '菠菜', '番茄', '豆芽', '芦笋', '花椰菜', '洋葱', '生菜'],
'豆类' => ['黄豆', '眉豆', '鸡心豆', '豆腐', '豆角', '绿豆', '扁豆', '四季豆'],
'水果类' => ['苹果', '水梨', '橙子', '桃', '提子', '沙田柚', '雪梨', '车厘子', '柚子', '草莓', '樱桃', '金桔', '葡萄'],
'奶类' => ['牛奶', '低脂奶', '脱脂奶', '低脂乳酪', '红茶', '优格', '无糖豆浆'],
'奶类' => ['牛奶', '低脂奶', '脱脂奶', '驼奶粉', '驼奶', '低脂乳酪', '红茶', '优格', '无糖豆浆'],
],
self::LEVEL_MEDIUM => [
'主食类' => ['红米饭', '糙米饭', '西米', '乌冬面', '面包', '麦片', '番薯', '芋头'],
@@ -36,40 +36,46 @@ class GiKnowledge
],
];
/** @var list<array{breakfast:string,lunch:string,dinner:string,tips:string}> */
/** @var list<array{breakfast:string,drinks:string,lunch:string,dinner:string,tips:string}> */
private static array $mealTemplates = [
[
'breakfast' => '玉米碴子粥、煮鸡蛋、拌黄瓜',
'breakfast' => '驼奶粉(温水冲服)、玉米碴子粥、煮鸡蛋、拌黄瓜',
'drinks' => '白天多喝温开水;早餐冲驼奶粉,别喝甜饮料、果汁。',
'lunch' => '清炖鲤鱼、豆腐炖白菜、清炒豆角、米饭小半碗',
'dinner' => '玉米面饼一个、瘦肉丝、凉拌茄子',
'tips' => '中午鱼豆肉吃足;早中晚淀粉别重复,一顿一种主食就够。',
],
[
'breakfast' => '小米粥、煮鸡蛋、小碟咸菜(少盐)',
'breakfast' => '驼奶粉(温水冲服)、小米粥、煮鸡蛋、小碟咸菜(少盐)',
'drinks' => '温开水为主;驼奶粉跟早餐一起吃,下午也记得喝水。',
'lunch' => '去皮炖鸡肉、芹菜炒豆干、木耳炒鸡蛋',
'dinner' => '蒸红薯(小半块)、清炒菠菜、豆腐汤',
'tips' => '午餐最讲究蛋白;晚上才吃薯,别跟中午再配大米饭。',
],
[
'breakfast' => '豆腐脑(少卤)、鸡蛋、一个苹果',
'breakfast' => '驼奶粉(温水冲服)、豆腐脑(少卤)、鸡蛋',
'drinks' => '早餐饮驼奶粉;白天喝白开水,别碰含糖饮料。',
'lunch' => '清炖鸡肉、炖芸豆、番茄炒蛋、贴饼子一个',
'dinner' => '挂面一小碗、清蒸小鱼、拌海带丝',
'tips' => '中午饼配肉豆,蛋白管够;面条放晚上,别三顿都是粥饭。',
],
[
'breakfast' => '高粱米粥、茶叶蛋、拌萝卜丝',
'breakfast' => '驼奶粉(温水冲服)、高粱米粥、茶叶蛋、拌萝卜丝',
'drinks' => '温开水;早餐驼奶粉按说明冲,别加糖。',
'lunch' => '炖牛肉(瘦)、豆腐炒韭菜、清炒油菜、杂豆饭小半碗',
'dinner' => '蒸南瓜(小半碗)、鸡蛋羹、拍黄瓜',
'tips' => '中午肉蛋豆要多吃;南瓜算晚饭主食,别再加馒头稀饭。',
],
[
'breakfast' => '棒子面粥、水煮蛋、大拌菜(少油)',
'breakfast' => '驼奶粉(温水冲服)、棒子面粥、水煮蛋、大拌菜(少油)',
'drinks' => '多喝温开水;驼奶粉放早餐,忌可乐汽水果汁。',
'lunch' => '清炖鲫鱼、番茄炒蛋、炖扁豆、糙米饭小半碗',
'dinner' => '玉米面饼一个、清炒豆芽、豆腐脑',
'tips' => '午饭鱼蛋豆齐上;玉米面放晚上,跟中午米饭分开。',
],
[
'breakfast' => '小米粥、煮鸡蛋、拌生菜',
'breakfast' => '驼奶粉(温水冲服)、小米粥、煮鸡蛋、拌生菜',
'drinks' => '白天小口多次喝温水;早餐冲好驼奶粉再吃饭。',
'lunch' => '白切鸡(去皮)、炖豆腐、清炒豆角、二米饭小半碗',
'dinner' => '杂面馒头一个、瘦肉炒芹菜、凉拌黄瓜',
'tips' => '中午蛋白打主力;三顿别都喝粥吃面,一顿一种淀粉。',
@@ -92,7 +98,8 @@ class GiKnowledge
private static array $aliases = [
'棒子面' => '荞麦面', '玉米碴' => '粟米', '地瓜' => '番薯', '红薯' => '番薯',
'洋芋' => '芋头', '土豆' => '芋头', '窝头' => '通心粉', '贴饼子' => '通心粉',
'二米饭' => '红米饭', '稀饭' => '白饭', '白稀饭' => '白饭', '糖糕' => '麦芽糖',
'二米饭' => '红米饭', '稀饭' => '白饭', '白稀饭' => '白饭', '糖糕' => '麦芽糖',
'驼奶' => '驼奶粉',
];
/**
@@ -132,6 +139,8 @@ class GiKnowledge
public static function ruralMealHints(): string
{
return implode("\n", [
'【早餐】须安排驼奶粉温水冲服(低 GI 奶类,稳血糖),可与粥、蛋、菜同餐。',
'【喝的】单独说明:温开水为主;早餐驼奶粉;严禁含糖饮料、果汁、酒。',
'【午餐】蛋白质要最高:至少 2 样高蛋白(鱼/鸡鸭/瘦肉/蛋/豆腐/豆干),肉蛋豆是午饭主角。',
'【淀粉不重复】粥/饭/面/饼/窝头/红薯/土豆/玉米/南瓜等算淀粉;早中晚各最多 1 种,且三顿不能同一种、不要顿顿大主食。',
'推荐蛋白:鸡蛋、豆腐、豆干、自家鸡鸭蛋、瘦肉、鲫鱼鲤鱼;',
@@ -177,6 +186,7 @@ class GiKnowledge
$plan = [
'breakfast' => $meal['breakfast'],
'drinks' => $meal['drinks'] ?? '白天多喝温开水;早餐冲驼奶粉,别喝甜饮。',
'lunch' => $meal['lunch'],
'dinner' => $meal['dinner'],
'tips' => $meal['tips'],