feat(ai): 新增 AI 客服聊天功能 + 消息页重构为小华助手
- 新增 POST /ai/chat 端点,由 LLM(Ollama qwen3)担任 24h 健康客服"小华" - 新增 ai.chat.send 权限,绑定管理员/患者/医生/护士/健康管理师角色 - 消息页从咨询列表重构为单窗口 AI 对话(欢迎态 + 聊天态 + 快捷问诊) - 通知功能迁移到"我的"页面菜单项(带未读角标),独立通知列表页 - 修复气泡文字截断:改用百分比 max-width + block Text + pre-wrap 换行 - 修复权限绑定:迁移 SQL 角色名从英文改为中文(admin→管理员,patient→患者)
This commit is contained in:
@@ -6,6 +6,7 @@ import { usePointsStore } from '../../stores/points';
|
||||
import { useUIStore } from '../../stores/ui';
|
||||
import { navigateToLogin } from '../../utils/navigate';
|
||||
import { usePageData } from '@/hooks/usePageData';
|
||||
import { notificationService } from '@/services/notification';
|
||||
import Loading from '../../components/Loading';
|
||||
import PageShell from '@/components/ui/PageShell';
|
||||
import ContentCard from '@/components/ui/ContentCard';
|
||||
@@ -91,12 +92,18 @@ export default function Profile() {
|
||||
const isGuest = !user;
|
||||
const groups = isGuest ? GUEST_GROUPS : LOGGED_IN_GROUPS;
|
||||
const [pointsLoading, setPointsLoading] = useState(false);
|
||||
const [unreadCount, setUnreadCount] = useState(0);
|
||||
|
||||
const fetchPoints = useCallback(async () => {
|
||||
if (!isGuest) {
|
||||
setPointsLoading(true);
|
||||
await refreshPoints();
|
||||
setPointsLoading(false);
|
||||
try {
|
||||
const res = await notificationService.list<{ total?: number; data?: { read?: boolean }[] }>({ page: 1, page_size: 50 });
|
||||
const items = (res as { data?: { read?: boolean }[] })?.data || [];
|
||||
setUnreadCount(items.filter((n) => !n.read).length);
|
||||
} catch { /* ignore */ }
|
||||
}
|
||||
}, [isGuest, refreshPoints]);
|
||||
|
||||
@@ -171,6 +178,29 @@ export default function Profile() {
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* 消息通知入口 */}
|
||||
{!isGuest && (
|
||||
<View className='menu-group'>
|
||||
<ContentCard
|
||||
padding="none"
|
||||
onPress={() => Taro.navigateTo({ url: '/pages/pkg-profile/notifications/index' })}
|
||||
>
|
||||
<View className='menu-item'>
|
||||
<View className='menu-icon menu-icon--pri-l'>
|
||||
<Text className='menu-icon-text menu-icon-text--pri'>讯</Text>
|
||||
</View>
|
||||
<Text className='menu-label'>消息通知</Text>
|
||||
{unreadCount > 0 && (
|
||||
<View className='menu-badge'>
|
||||
<Text className='menu-badge-text'>{unreadCount > 99 ? '99+' : unreadCount}</Text>
|
||||
</View>
|
||||
)}
|
||||
<Text className='menu-arrow'>›</Text>
|
||||
</View>
|
||||
</ContentCard>
|
||||
</View>
|
||||
)}
|
||||
|
||||
{/* 分组菜单 */}
|
||||
{groups.map((group) => (
|
||||
<View className='menu-group' key={group.title}>
|
||||
|
||||
Reference in New Issue
Block a user