feat(mp): 小程序功能完善 — 服务层扩展 + 页面优化

- 新增 actionInbox 服务层(待办事项列表/线程查询)
- consultation 服务扩展(会话详情/发送消息)
- 多页面代码优化(profile/messages/health/article)
- 新增 navigate 工具函数
This commit is contained in:
iven
2026-05-13 23:26:38 +08:00
parent 93c77c5857
commit 616e0a1539
10 changed files with 141 additions and 23 deletions

View File

@@ -1,9 +1,10 @@
import React, { useState, useEffect } from 'react';
import { View, Text, RichText } from '@tarojs/components';
import Taro, { useRouter, useShareAppMessage } from '@tarojs/taro';
import { getArticleDetail, Article } from '../../../services/article';
import { getArticleDetail, getPublicArticleDetail, Article } from '../../../services/article';
import { trackEvent } from '@/services/analytics';
import { useElderClass } from '../../../hooks/useElderClass';
import { useAuthStore } from '../../../stores/auth';
import './index.scss';
export default function ArticleDetail() {
@@ -25,7 +26,9 @@ export default function ArticleDetail() {
useEffect(() => {
if (!id) return;
setLoading(true);
getArticleDetail(id)
const user = useAuthStore.getState().user;
const fetcher = user ? getArticleDetail(id) : getPublicArticleDetail(id);
fetcher
.then((data) => setArticle(data))
.catch(() => Taro.showToast({ title: '加载失败', icon: 'none' }))
.finally(() => setLoading(false));

View File

@@ -107,7 +107,7 @@ export default function ConsultationDetail() {
success: async (res) => {
if (res.confirm) {
try {
await doctorApi.closeSession(sessionId);
await doctorApi.closeSession(sessionId, session?.version ?? 0);
Taro.showToast({ title: '已关闭', icon: 'success' });
loadData();
} catch {

View File

@@ -69,13 +69,12 @@ function GuestHome({ modeClass }: { modeClass: string }) {
]);
if (bannerData.status === 'fulfilled' && bannerData.value?.length > 0) {
const baseUrl = process.env.TARO_APP_API_URL || 'http://localhost:3000/api/v1';
const fileBase = baseUrl.replace(/\/api\/v1$/, '');
const apiBase = process.env.TARO_APP_API_URL || 'http://localhost:3000/api/v1';
const withLocal = await Promise.all(
bannerData.value.map(async (b) => {
if (!b.image_url) return b;
try {
const fullUrl = b.image_url.startsWith('http') ? b.image_url : `${fileBase}${b.image_url}`;
const fullUrl = b.image_url.startsWith('http') ? b.image_url : `${apiBase}${b.image_url}`;
const res = await Taro.downloadFile({ url: fullUrl });
if (res.tempFilePath) {
return { ...b, local_path: res.tempFilePath };
@@ -94,6 +93,7 @@ function GuestHome({ modeClass }: { modeClass: string }) {
}
} catch {
setBanners(FALLBACK_SLIDES);
Taro.showToast({ title: '内容加载失败', icon: 'none' });
}
};
@@ -135,7 +135,11 @@ function GuestHome({ modeClass }: { modeClass: string }) {
{articles.length > 0 ? (
<View className='guest-articles'>
{articles.map((article) => (
<View className='guest-article-card' key={article.id}>
<View
className='guest-article-card'
key={article.id}
onClick={() => Taro.navigateTo({ url: `/pages/article/detail/index?id=${article.id}` })}
>
{article.cover_image && (
<Image className='guest-article-cover' src={article.cover_image} mode='aspectFill' />
)}
@@ -261,7 +265,7 @@ function HomeDashboard({ modeClass }: { modeClass: string }) {
const hour = new Date().getHours();
const greeting = hour < 12 ? '上午好' : hour < 18 ? '下午好' : '晚上好';
const displayName = user?.display_name || currentPatient?.name || '访客';
const displayName = user?.display_name || currentPatient?.name || user?.username || (user?.phone ? `${user.phone.slice(-4)}` : '') || '用户';
const summary = todaySummary || {};
const indicators = [!!summary.blood_pressure, !!summary.heart_rate, !!summary.blood_sugar, !!summary.weight];

View File

@@ -20,12 +20,12 @@ interface NotificationItem {
read?: boolean;
}
const NOTIFY_ICONS: Record<string, { icon: string; bg: string; color: string }> = {
appointment: { icon: '约', bg: '#F0DDD4', color: '#C4623A' },
alert: { icon: '警', bg: '#FFF3E0', color: '#C4873A' },
followup: { icon: '随', bg: '#E8F0E8', color: '#5B7A5E' },
points: { icon: '分', bg: '#F0DDD4', color: '#C4623A' },
report: { icon: '报', bg: '#E8F0E8', color: '#5B7A5E' },
const NOTIFY_ICONS: Record<string, { icon: string; cls: string }> = {
appointment: { icon: '约', cls: 'notify-type-appointment' },
alert: { icon: '警', cls: 'notify-type-alert' },
followup: { icon: '随', cls: 'notify-type-followup' },
points: { icon: '分', cls: 'notify-type-points' },
report: { icon: '报', cls: 'notify-type-report' },
};
export default function Messages() {
@@ -68,6 +68,7 @@ export default function Messages() {
if (isRefresh) {
if (tab === 'consultation') setSessions([]);
else setNotifications([]);
Taro.showToast({ title: '加载失败,下拉重试', icon: 'none' });
}
} finally {
setLoading(false);
@@ -202,8 +203,8 @@ export default function Messages() {
const isUnread = !n.read;
return (
<View key={n.id} className={`notify-card ${isUnread ? '' : 'notify-card-muted'}`}>
<View className='notify-icon' style={`background:${cfg.bg};`}>
<Text className='notify-icon-char' style={`color:${cfg.color};`}>{cfg.icon}</Text>
<View className={`notify-icon ${cfg.cls}`}>
<Text className={`notify-icon-char ${cfg.cls}`}>{cfg.icon}</Text>
</View>
<View className='notify-body'>
<View className='notify-row'>

View File

@@ -81,6 +81,7 @@ export default function Profile() {
const mode = useUIStore((s) => s.mode);
const modeClass = mode === 'elder' ? 'elder-mode' : '';
const isGuest = !user;
const groups = isGuest ? GUEST_GROUPS : LOGGED_IN_GROUPS;
useDidShow(() => {
if (!isGuest) refreshPoints();
@@ -105,7 +106,8 @@ export default function Profile() {
});
};
const groups = isGuest ? GUEST_GROUPS : LOGGED_IN_GROUPS;
const displayName = user?.display_name || user?.username || (user?.phone ? `${user.phone.slice(-4)}` : '') || '用户';
const displayInitial = (user?.display_name || user?.username || '用').charAt(0);
return (
<View className={`profile-page ${modeClass}`}>
@@ -125,10 +127,10 @@ export default function Profile() {
<>
<View className='profile-user-card'>
<View className='profile-avatar'>
<Text className='profile-avatar-char'>{(user?.display_name || '访').charAt(0)}</Text>
<Text className='profile-avatar-char'>{displayInitial}</Text>
</View>
<View className='profile-user-info'>
<Text className='profile-name'>{user?.display_name || '访客'}</Text>
<Text className='profile-name'>{displayName}</Text>
<Text className='profile-phone'>
{user?.phone ? `${user.phone.slice(0, 3)}****${user.phone.slice(-4)}` : ''}
</Text>