diff --git a/config/env.ts b/config/env.ts index 9bf999e..c93d070 100644 --- a/config/env.ts +++ b/config/env.ts @@ -2,19 +2,23 @@ export const ENV_CONFIG = { // 开发环境 development: { - API_BASE_URL: 'http://127.0.0.1:9200/api', + API_BASE_URL: 'https://clinic-api.websoft.top/api', + // API_BASE_URL: 'http://127.0.0.1:9200/api', + WS_URL: 'ws://127.0.0.1:9200/api/chat', APP_NAME: '开发环境', DEBUG: 'true', }, // 生产环境 production: { API_BASE_URL: 'https://clinic-api.websoft.top/api', + WS_URL: 'wss://clinic-api.websoft.top/api/chat', APP_NAME: '通源堂健康生态平台', DEBUG: 'false', }, // 测试环境 test: { API_BASE_URL: 'https://clinic-api.websoft.top/api', + WS_URL: 'wss://clinic-api.websoft.top/api/chat', APP_NAME: '测试环境', DEBUG: 'true', } @@ -37,6 +41,7 @@ export function getEnvConfig() { // 导出环境变量 export const { API_BASE_URL, + WS_URL, APP_NAME, DEBUG } = getEnvConfig() diff --git a/src/api/clinic/clinicDoctorUser/index.ts b/src/api/clinic/clinicDoctorUser/index.ts index 8ee3d68..06c7a33 100644 --- a/src/api/clinic/clinicDoctorUser/index.ts +++ b/src/api/clinic/clinicDoctorUser/index.ts @@ -99,3 +99,13 @@ export async function getClinicDoctorUser(id: number) { } return Promise.reject(new Error(res.message)); } + +export async function getClinicDoctorUserByUserId(id: number) { + const res = await request.get>( + '/clinic/clinic-doctor-user/getByUserId/' + id + ); + if (res.code === 0 && res.data) { + return res.data; + } + return Promise.reject(new Error(res.message)); +} diff --git a/src/api/clinic/clinicPatientUser/index.ts b/src/api/clinic/clinicPatientUser/index.ts index 358f61e..db5581f 100644 --- a/src/api/clinic/clinicPatientUser/index.ts +++ b/src/api/clinic/clinicPatientUser/index.ts @@ -16,6 +16,20 @@ export async function pageClinicPatientUser(params: ClinicPatientUserParam) { return Promise.reject(new Error(res.message)); } +/** + * 分页查询患者 + */ +export async function userPageClinicPatientUser(params: ClinicPatientUserParam) { + const res = await request.get>>( + '/clinic/clinic-patient-user/userPage', + params + ); + if (res.code === 0) { + return res.data; + } + return Promise.reject(new Error(res.message)); +} + /** * 查询患者列表 */ @@ -99,3 +113,13 @@ export async function getClinicPatientUser(id: number) { } return Promise.reject(new Error(res.message)); } + +export async function clinicPatientUserByPatientUserId(id: number) { + const res = await request.get>( + '/clinic/clinic-patient-user/getByPatientUserId/' + id + ); + if (res.code === 0 && res.data) { + return res.data; + } + return Promise.reject(new Error(res.message)); +} diff --git a/src/api/clinic/clinicPatientUser/model/index.ts b/src/api/clinic/clinicPatientUser/model/index.ts index 0799c7b..8764a0a 100644 --- a/src/api/clinic/clinicPatientUser/model/index.ts +++ b/src/api/clinic/clinicPatientUser/model/index.ts @@ -10,6 +10,7 @@ export interface ClinicPatientUser { type?: number; // 自增ID userId?: number; + patientUserId?: number; // 姓名 realName?: string; // 头像 diff --git a/src/api/clinic/clinicPrescription/model/index.ts b/src/api/clinic/clinicPrescription/model/index.ts index 05d0fc7..f1afb86 100644 --- a/src/api/clinic/clinicPrescription/model/index.ts +++ b/src/api/clinic/clinicPrescription/model/index.ts @@ -1,5 +1,6 @@ import type { PageParam } from '@/api/index'; import {ClinicPrescriptionItem} from "@/api/clinic/clinicPrescriptionItem/model"; +import {ShopOrder} from "@/api/shop/shopOrder/model"; /** * 处方主表 @@ -66,6 +67,7 @@ export interface ClinicPrescription { payStatus?: boolean; // 订单状态 orderStatus?: number; + shopOrder?: ShopOrder } /** @@ -77,4 +79,5 @@ export interface ClinicPrescriptionParam extends PageParam { doctorId?: number; userId?: number; keywords?: string; + withDoctor?: boolean; } diff --git a/src/api/clinic/clinicPrescriptionItem/index.ts b/src/api/clinic/clinicPrescriptionItem/index.ts index 06230a9..a8e7885 100644 --- a/src/api/clinic/clinicPrescriptionItem/index.ts +++ b/src/api/clinic/clinicPrescriptionItem/index.ts @@ -46,6 +46,16 @@ export async function addClinicPrescriptionItem(data: ClinicPrescriptionItem) { } return Promise.reject(new Error(res.message)); } +export async function batchAddClinicPrescriptionItem(data: ClinicPrescriptionItem[]) { + const res = await request.post>( + '/clinic/clinic-prescription-item/batch', + data + ); + if (res.code === 0) { + return res.message; + } + return Promise.reject(new Error(res.message)); +} /** * 修改处方明细表 diff --git a/src/api/shop/shopChatConversation/index.ts b/src/api/shop/shopChatConversation/index.ts index 6a5ba5b..c3bc204 100644 --- a/src/api/shop/shopChatConversation/index.ts +++ b/src/api/shop/shopChatConversation/index.ts @@ -34,12 +34,12 @@ export async function listShopChatConversation(params?: ShopChatConversationPara * 添加聊天会话表 */ export async function addShopChatConversation(data: ShopChatConversation) { - const res = await request.post>( + const res = await request.post>( '/shop/shop-chat-conversation', data ); if (res.code === 0) { - return res.message; + return res.data; } return Promise.reject(new Error(res.message)); } @@ -99,3 +99,13 @@ export async function getShopChatConversation(id: number) { } return Promise.reject(new Error(res.message)); } + +export async function chatConversationByBothUserId(userId: string) { + const res = await request.get>( + '/shop/shop-chat-conversation/getByBothUserId/' + userId + ); + if (res.code === 0) { + return res.data; + } + return Promise.reject(new Error(res.message)); +} diff --git a/src/api/shop/shopChatMessage/model/index.ts b/src/api/shop/shopChatMessage/model/index.ts index 411d45c..16785f8 100644 --- a/src/api/shop/shopChatMessage/model/index.ts +++ b/src/api/shop/shopChatMessage/model/index.ts @@ -30,6 +30,8 @@ export interface ShopChatMessage { type?: string; // 消息内容 content?: string; + // 会话ID + conversationId?: number; // 屏蔽接收方 sideTo?: number; // 屏蔽发送方 @@ -52,6 +54,8 @@ export interface ShopChatMessage { createTime?: string; // 修改时间 updateTime?: string; + // 是否本人消息 + isMine?: boolean; } /** @@ -59,5 +63,6 @@ export interface ShopChatMessage { */ export interface ShopChatMessageParam extends PageParam { id?: number; + conversationId?: number; keywords?: string; } diff --git a/src/api/shop/shopOrder/index.ts b/src/api/shop/shopOrder/index.ts index c2637c4..2ccdb78 100644 --- a/src/api/shop/shopOrder/index.ts +++ b/src/api/shop/shopOrder/index.ts @@ -53,7 +53,7 @@ export async function updateShopOrder(data: ShopOrder) { data ); if (res.code === 0) { - return res.message; + return res; } return Promise.reject(new Error(res.message)); } diff --git a/src/api/shop/shopOrder/model/index.ts b/src/api/shop/shopOrder/model/index.ts index 45cd3ce..852f5b5 100644 --- a/src/api/shop/shopOrder/model/index.ts +++ b/src/api/shop/shopOrder/model/index.ts @@ -147,6 +147,7 @@ export interface ShopOrder { hasTakeGift?: string; // 订单商品项 orderGoods?: OrderGoods[]; + makePay?: boolean } /** diff --git a/src/api/shop/shopOrderGoods/index.ts b/src/api/shop/shopOrderGoods/index.ts index 7636446..67e002f 100644 --- a/src/api/shop/shopOrderGoods/index.ts +++ b/src/api/shop/shopOrderGoods/index.ts @@ -44,6 +44,17 @@ export async function addShopOrderGoods(data: ShopOrderGoods) { return Promise.reject(new Error(res.message)); } +export async function batchAddShopOrderGoods(data: ShopOrderGoods[]) { + const res = await request.post>( + '/shop/shop-order-goods/batch', + data + ); + if (res.code === 0) { + return res.message; + } + return Promise.reject(new Error(res.message)); +} + /** * 修改商品信息 */ diff --git a/src/api/system/order/model/index.ts b/src/api/system/order/model/index.ts index d403932..d3f5ff8 100644 --- a/src/api/system/order/model/index.ts +++ b/src/api/system/order/model/index.ts @@ -82,6 +82,7 @@ export interface Order { updateTime?: string; // 创建时间 createTime?: string; + makePay?: boolean; } /** diff --git a/src/app.config.ts b/src/app.config.ts index 9bfa313..295e869 100644 --- a/src/app.config.ts +++ b/src/app.config.ts @@ -108,6 +108,7 @@ export default { "clinicPatientUser/add", "clinicPatientUser/selectPatient", "clinicPatientUser/prescription", + "clinicPatientUser/detail", "clinicDoctorUser/index", "clinicDoctorUser/add", "clinicPrescription/index", diff --git a/src/chat/doctor/index.tsx b/src/chat/doctor/index.tsx index 9bf0ff7..7d6e942 100644 --- a/src/chat/doctor/index.tsx +++ b/src/chat/doctor/index.tsx @@ -1,158 +1,284 @@ -import {useState, useEffect} from 'react' +import {useState, useEffect, useRef} from 'react' import {View, Text} from '@tarojs/components' -import Taro, {useDidShow} from '@tarojs/taro' -import {useRouter} from '@tarojs/taro' -import { - Loading, - InfiniteLoading, - Empty, - Space, - Input, - Avatar, - Tag, - Divider, - Button -} from '@nutui/nutui-react-taro' -import { Voice, FaceMild, AddCircle } from '@nutui/icons-react-taro' -import {getClinicDoctorUser} from "@/api/clinic/clinicDoctorUser"; -import {ClinicDoctorUser} from "@/api/clinic/clinicDoctorUser/model"; -import {ClinicPatientUser} from "@/api/clinic/clinicPatientUser/model"; -import navTo from "@/utils/common"; -import {pageShopChatMessage} from "@/api/shop/shopChatMessage"; +import Taro, {useDidShow, useRouter, useLoad} from '@tarojs/taro' +import {Input, Button} from '@nutui/nutui-react-taro' +import {Voice, FaceMild} from '@nutui/icons-react-taro' +import {getClinicDoctorUserByUserId} from "@/api/clinic/clinicDoctorUser"; +import {addShopChatMessage, listShopChatMessage} from "@/api/shop/shopChatMessage"; import {ShopChatMessage} from "@/api/shop/shopChatMessage/model"; -import Line from "@/components/Gap"; +import {addShopChatConversation, chatConversationByBothUserId} from "@/api/shop/shopChatConversation"; +// @ts-ignore +import {WS_URL} from "@/config/env"; +import {clinicPatientUserByPatientUserId} from "@/api/clinic/clinicPatientUser"; const CustomerIndex = () => { - const {params} = useRouter(); - const [doctor, setDoctor] = useState() - const [list, setList] = useState([]) - + const {params} = useRouter() + const [messages, setMessages] = useState([]) + const [conversationId, setConversationId] = useState(null) + const [friendUserId, setFriendUserId] = useState('') + const [messageInput, setMessageInput] = useState('') + const [sending, setSending] = useState(false) const [isDoctor, setIsDoctor] = useState(false) - const [doctors, setDoctors] = useState([]) - const [patientUsers, setPatientUsers] = useState([]) - const [loading, setLoading] = useState(false) - const [searchValue, setSearchValue] = useState('') - const [displaySearchValue, setDisplaySearchValue] = useState('') - const [page, setPage] = useState(1) - const [hasMore, setHasMore] = useState(true) + const socketRef = useRef(null) - // 获取列表数据 - const fetchData = async () => { - setLoading(true); - if (Taro.getStorageSync('Doctor')) { - setIsDoctor(true) - } - const doctorUser = await getClinicDoctorUser(Number(params.id)) - if (doctorUser) { - setDoctor(doctorUser) - Taro.setNavigationBarTitle({title: `${doctorUser.realName}`}); - } - const messages = await pageShopChatMessage({}) + const quickActions = [ + {label: '开方', type: 'prescription'}, + {label: '快捷回复', type: 'quickReply'}, + {label: '发问诊单', type: 'sendInquiry'} + ] + const handleQuickAction = (actionType: string) => { + switch (actionType) { + case 'prescription': + if (friendUserId) { + Taro.navigateTo({url: `/doctor/orders/add?id=${friendUserId}`}) + } else { + Taro.showToast({title: '请选择患者', icon: 'none'}) + } + break + case 'quickReply': + Taro.showToast({title: '快捷回复敬请期待', icon: 'none'}) + break + case 'sendInquiry': + Taro.showToast({title: '问诊单功能敬请期待', icon: 'none'}) + break + } } - // 初始化数据 + const fetchData = async (userId?: string) => { + if (!userId) return + try { + console.log("Taro.getStorageSync('Doctor')", Taro.getStorageSync('Doctor')) + if (Taro.getStorageSync('Doctor')) { + setIsDoctor(true) + } + const isDoctorData = Taro.getStorageSync('Doctor') + if (!isDoctorData) { + const doctorUser = await getClinicDoctorUserByUserId(Number(params.userId)) + if (doctorUser?.realName) { + await Taro.setNavigationBarTitle({title: `${doctorUser.realName}`}) + } + } else { + const patient = await clinicPatientUserByPatientUserId(Number(params.userId)) + if (patient?.realName) { + await Taro.setNavigationBarTitle({title: `${patient.realName}`}) + } + } + + let conversation = await chatConversationByBothUserId(userId) + if (!conversation) { + conversation = await addShopChatConversation({ + friendId: parseInt(userId, 10), + content: '' + }) + } + + if (conversation?.id) { + setConversationId(conversation.id) + const messageList = await listShopChatMessage({conversationId: conversation.id}) + setMessages(messageList || []) + } else { + setMessages([]) + } + } catch (error) { + console.error('加载聊天数据失败:', error) + Taro.showToast({title: '聊天加载失败', icon: 'none'}) + } + } + + const connectWebSocket = async (id: number) => { + const base = (WS_URL || '').replace(/\/$/, '') + if (!base) { + console.warn('WS_URL 未配置') + return + } + + if (socketRef.current) { + socketRef.current.close({}) + } + + const userId = Taro.getStorageSync('UserId') + const result = Taro.connectSocket({ + url: `${base}/${id}_${userId}` + }) + + const socketTask = typeof (result as Promise)?.then === 'function' + ? await (result as Promise) + : (result as Taro.SocketTask) + + if (!socketTask) { + return + } + + socketRef.current = socketTask + + socketTask.onOpen(() => { + console.log('WebSocket连接成功') + }) + + socketTask.onMessage((event) => { + console.log('收到消息:', event,) + try { + if (event.data === '连接成功' || event.data === 'pong') return + const payload = typeof event.data === 'string' ? JSON.parse(event.data) : event.data + if (!payload) return + if (Array.isArray(payload)) { + setMessages(prev => [...prev, ...payload]) + } else { + payload.isMine = parseInt(payload.fromUserId) !== parseInt(params?.userId) + setMessages(prev => [...prev, payload]) + } + } catch (err) { + console.error('解析消息失败:', err) + } + }) + + socketTask.onError((err) => { + console.error('WebSocket异常:', err) + }) + + socketTask.onClose(() => { + socketRef.current = null + }) + } + + const handleSendMessage = async () => { + const content = messageInput.trim() + if (!content) { + Taro.showToast({title: '请输入内容', icon: 'none'}) + return + } + if (!conversationId || !friendUserId) { + Taro.showToast({title: '会话未初始化', icon: 'none'}) + return + } + if (sending) { + return + } + + try { + setSending(true) + await addShopChatMessage({ + content, + conversationId, + toUserId: parseInt(friendUserId, 10), + type: 'text' + }) + + // const localMessage: ShopChatMessage = { + // id: Date.now(), + // content, + // conversationId, + // toUserId: parseInt(friendUserId, 10), + // type: 'text', + // isMine: true + // } + // + // setMessages(prev => [...prev, localMessage]) + setMessageInput('') + } catch (error) { + console.error('发送消息失败:', error) + Taro.showToast({title: '发送失败,请重试', icon: 'none'}) + } finally { + setSending(false) + } + } + + useLoad((options) => { + if (options?.userId) { + const userId = String(options.userId) + setFriendUserId(userId) + fetchData(userId) + } + console.log('Taro.getStorageSync(\'UserId\')', Taro.getStorageSync('UserId')) + }) + useEffect(() => { - fetchData().then() - }, []); - - // 监听页面显示,当从其他页面返回时刷新数据 - useDidShow(() => { - // 刷新当前tab的数据和统计信息 - fetchData().then(); - }); - - // 渲染医师项 - const renderDoctorItem = (item: ClinicDoctorUser) => ( - - - - - - - - {item.realName} 医师 - - - 中医内科 - - - 问诊 1 次 - - 开方 3 次 - - - - - - - - ); - - // 渲染患者项 - const renderPatientUserItem = (item: ClinicPatientUser) => ( - - - - - - - - {item.realName} - - - { - {item.sex} - } - { - item.age && ( - <> - - {item.age}岁 - - ) - } - { - item.weight && ( - <> - - {item.weight} - - ) - } - - - {item.allergyHistory} - - - - - - - - ); - + if (conversationId) { + connectWebSocket(conversationId).catch(err => { + console.error('WebSocket连接失败:', err) + }) + } + return () => { + socketRef.current?.close() + socketRef.current = null + } + }, [conversationId]) return ( - - - {list?.map(renderPatientUserItem)} + + + {messages.length === 0 ? ( + + 暂时还没有聊天记录 + + ) : ( + messages.map((item, index) => ( + + + {item.content} + + + )) + )} - - - - - - + + + + {quickActions.map(action => ( + handleQuickAction(action.type)} + style={{ + padding: '6px 12px', + borderRadius: '20px', + backgroundColor: '#fff', + fontSize: '14px', + color: '#8b5a2b', + boxShadow: '0 4px 10px rgba(0,0,0,0.05)', + display: 'flex', + alignItems: 'center', + flexShrink: 0 + }} + > + {action.label} + + ))} + + + + setMessageInput(value)} + confirmType="send" + onConfirm={handleSendMessage} + /> + + - ); -}; + ) +} export default CustomerIndex; diff --git a/src/clinic/clinicPatientUser/detail.config.ts b/src/clinic/clinicPatientUser/detail.config.ts new file mode 100644 index 0000000..eda2ef4 --- /dev/null +++ b/src/clinic/clinicPatientUser/detail.config.ts @@ -0,0 +1,4 @@ +export default definePageConfig({ + navigationBarTitleText: '开方详情', + navigationBarTextStyle: 'black' +}) diff --git a/src/clinic/clinicPatientUser/detail.scss b/src/clinic/clinicPatientUser/detail.scss new file mode 100644 index 0000000..6b60f0c --- /dev/null +++ b/src/clinic/clinicPatientUser/detail.scss @@ -0,0 +1,72 @@ +.prescription-detail-page { + min-height: 100vh; + background: #f7f8fa; + padding: 12px; + box-sizing: border-box; + font-size: 25rpx; + line-height: 1.6; +} + +.detail-header-card { + background: #ffffff; + border-radius: 16px; + padding: 16px; + margin-bottom: 12px; + display: flex; + justify-content: space-between; + align-items: center; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.04); +} + +.detail-header-card__time { + font-weight: 600; + color: #1f2c3d; + font-size: 25rpx; +} + +.detail-card { + background: #ffffff; + border-radius: 16px; + padding: 16px; + margin-bottom: 12px; + font-size: 25rpx; +} + +.detail-action-row { + margin-top: 12px; + display: flex; + gap: 8px; +} + +.detail-row { + display: flex; + justify-content: space-between; + margin-bottom: 6px; + color: #4a5568; + font-size: 25rpx; +} + +.detail-row strong { + color: #1f2c3d; +} + +.detail-medicine-list { + display: flex; + flex-wrap: wrap; + gap: 8px; +} + +.detail-medicine-chip { + padding: 6px 10px; + background: #f5f7fa; + border-radius: 12px; + color: #1f2c3d; + font-size: 25rpx; +} + +.detail-section-title { + font-weight: 600; + margin-bottom: 8px; + color: #1f2c3d; + font-size: 25rpx; +} diff --git a/src/clinic/clinicPatientUser/detail.tsx b/src/clinic/clinicPatientUser/detail.tsx new file mode 100644 index 0000000..b276746 --- /dev/null +++ b/src/clinic/clinicPatientUser/detail.tsx @@ -0,0 +1,159 @@ +import {useEffect, useState} from "react"; +import {View, Text} from '@tarojs/components' +import {Button, Cell, CellGroup, Loading, Space, Tag} from '@nutui/nutui-react-taro' +import Taro, {useRouter} from '@tarojs/taro' +import {ClinicPrescription} from "@/api/clinic/clinicPrescription/model"; +import {getClinicPrescription} from "@/api/clinic/clinicPrescription"; +import {copyText} from "@/utils/common"; +import './detail.scss' + +const statusMap: Record = { + 0: '待开方', + 1: '已完成', + 2: '已支付', + 3: '已取消' +} + +const PrescriptionDetail = () => { + const {params} = useRouter() + const [detail, setDetail] = useState() + const [loading, setLoading] = useState(false) + + const loadDetail = async () => { + if (!params.id) return + try { + setLoading(true) + const data = await getClinicPrescription(Number(params.id)) + setDetail(data) + } catch (error) { + Taro.showToast({ + title: '加载详情失败', + icon: 'error' + }) + } finally { + setLoading(false) + } + } + + useEffect(() => { + loadDetail() + }, [params.id]) + + const getPatientDesc = () => { + if (!detail) return '' + const sexText = detail.sex === 0 ? '男' : detail.sex === 1 ? '女' : '' + const ageText = detail.age ? `${detail.age}岁` : '' + return [detail.realName, sexText, ageText].filter(Boolean).join(' ') + } + + if (loading || !detail) { + return ( + + 加载中... + + ) + } + + const medicines = detail.items || [] + // const shopOrder: any = (detail as any).shopOrder + + return ( + + + + {detail.createTime || ''} + 开方 + + {detail.doctorName || ''} + + + {/**/} + {/* 处方状态*/} + {/* {statusMap[detail.status || 0] || '待处理'}*/} + {/* {shopOrder?.address && (*/} + {/* */} + {/* {shopOrder.address}*/} + {/* */} + {/* )}*/} + {/* */} + {/* */} + {/* */} + {/* */} + {/**/} + + + + 药方编号 + + {detail.orderNo || '-'} + {detail.orderNo && ( + copyText(detail.orderNo || '')}>复制 + )} + + + + + + 患者信息 + + {getPatientDesc()} + Taro.showToast({title: '患者档案敬请期待', icon: 'none'})}> + 患者档案 + + + {detail.diagnosis && ( + + 诊断:{detail.diagnosis} + Taro.showToast({title: '诊断详情敬请期待', icon: 'none'})}> + 查看详情 + + + )} + {detail.treatmentPlan && ( + 治疗方案:{detail.treatmentPlan} + )} + {detail.decoctionInstructions && ( + 用药说明:{detail.decoctionInstructions} + )} + + + + + 药方信息 + Taro.showToast({title: '已设为常用', icon: 'success'})}>存为常用方 + + + {medicines.length === 0 && ( + 暂无药材信息 + )} + {medicines.map((item, index) => ( + + {item.medicineName} {item.quantity || item.dosage || ''} + + ))} + + + + + + + + + ¥{detail.orderPrice || '0.00'} + Taro.showToast({title: '暂无明细', icon: 'none'})}>价格明细 + + )} + /> + + + ) +} + +export default PrescriptionDetail; diff --git a/src/clinic/clinicPatientUser/index.tsx b/src/clinic/clinicPatientUser/index.tsx index 33cec2e..0d0fbe3 100644 --- a/src/clinic/clinicPatientUser/index.tsx +++ b/src/clinic/clinicPatientUser/index.tsx @@ -9,12 +9,12 @@ import { getStatusText, getStatusTagType, getStatusOptions, - mapApplyStatusToCustomerStatus, mapCustomerStatusToApplyStatus } from '@/utils/customerStatus'; import FixedButton from "@/components/FixedButton"; import navTo from "@/utils/common"; import {pageShopDealerApply, removeShopDealerApply, updateShopDealerApply} from "@/api/shop/shopDealerApply"; +import {userPageClinicPatientUser} from "@/api/clinic/clinicPatientUser"; // 扩展User类型,添加客户状态和保护天数 interface CustomerUser extends UserType { @@ -102,69 +102,6 @@ const CustomerIndex = () => { }); }; - // 计算剩余保护天数(基于过期时间) - const calculateProtectDays = (expirationTime?: string, applyTime?: string): number => { - try { - // 优先使用过期时间字段 - if (expirationTime) { - const expDate = new Date(expirationTime.replace(' ', 'T')); - const now = new Date(); - - // 计算剩余毫秒数 - const remainingMs = expDate.getTime() - now.getTime(); - - // 转换为天数,向上取整 - const remainingDays = Math.ceil(remainingMs / (1000 * 60 * 60 * 24)); - - console.log('=== 基于过期时间计算 ==='); - console.log('过期时间:', expirationTime); - console.log('当前时间:', now.toLocaleString()); - console.log('剩余天数:', remainingDays); - console.log('======================'); - - return Math.max(0, remainingDays); - } - - // 如果没有过期时间,回退到基于申请时间计算 - if (!applyTime) return 0; - - const protectionPeriod = 7; // 保护期7天 - - // 解析申请时间 - let applyDate: Date; - if (applyTime.includes('T')) { - applyDate = new Date(applyTime); - } else { - applyDate = new Date(applyTime.replace(' ', 'T')); - } - - // 获取当前时间 - const now = new Date(); - - // 只比较日期部分,忽略时间 - const applyDateOnly = new Date(applyDate.getFullYear(), applyDate.getMonth(), applyDate.getDate()); - const currentDateOnly = new Date(now.getFullYear(), now.getMonth(), now.getDate()); - - // 计算已经过去的天数 - const timeDiff = currentDateOnly.getTime() - applyDateOnly.getTime(); - const daysPassed = Math.floor(timeDiff / (1000 * 60 * 60 * 24)); - - // 计算剩余保护天数 - const remainingDays = protectionPeriod - daysPassed; - - console.log('=== 基于申请时间计算 ==='); - console.log('申请时间:', applyTime); - console.log('已过去天数:', daysPassed); - console.log('剩余保护天数:', remainingDays); - console.log('======================'); - - return Math.max(0, remainingDays); - } catch (error) { - console.error('日期计算错误:', error); - return 0; - } - }; - // 获取客户数据 const fetchCustomerData = useCallback(async (statusFilter?: CustomerStatus, resetPage = false, targetPage?: number) => { @@ -174,22 +111,22 @@ const CustomerIndex = () => { // 构建API参数,根据状态筛选 const params: any = { - type: 4, - page: currentPage + // type: 4, + page: currentPage, }; const applyStatus = mapCustomerStatusToApplyStatus(statusFilter || activeTab); if (applyStatus !== undefined) { params.applyStatus = applyStatus; } - const res = await pageShopDealerApply(params); + const res = await userPageClinicPatientUser(params); if (res?.list && res.list.length > 0) { // 正确映射状态并计算保护天数 const mappedList = res.list.map(customer => ({ ...customer, - customerStatus: mapApplyStatusToCustomerStatus(customer.applyStatus || 10), - protectDays: calculateProtectDays(customer.expirationTime, customer.applyTime || customer.createTime || '') + // customerStatus: mapApplyStatusToCustomerStatus(customer.applyStatus || 10), + // protectDays: calculateProtectDays(customer.expirationTime, customer.applyTime || customer.createTime || '') })); // 如果是重置页面或第一页,直接设置新数据;否则追加数据 diff --git a/src/clinic/clinicPatientUser/prescription.tsx b/src/clinic/clinicPatientUser/prescription.tsx index f6d5f1f..cc570dc 100644 --- a/src/clinic/clinicPatientUser/prescription.tsx +++ b/src/clinic/clinicPatientUser/prescription.tsx @@ -1,24 +1,32 @@ import {useState} from "react"; import Taro, {useDidShow} from '@tarojs/taro' -import {Button, Cell, CellGroup, Space, Empty, ConfigProvider, Tag} from '@nutui/nutui-react-taro' +import {Button, Cell, CellGroup, Space, Empty, ConfigProvider, Tag, Popup} from '@nutui/nutui-react-taro' import {View, Text} from '@tarojs/components' import {ClinicPrescription} from "@/api/clinic/clinicPrescription/model"; import { - pageClinicPrescription, updateClinicPrescription, + pageClinicPrescription, WxPayResult } from "@/api/clinic/clinicPrescription"; import {copyText} from "@/utils/common"; -import {createOrder} from "@/api/shop/shopOrder"; +import {updateShopOrder} from "@/api/shop/shopOrder"; +import {listShopUserAddress} from "@/api/shop/shopUserAddress"; +import {ShopUserAddress} from "@/api/shop/shopUserAddress/model"; const ClinicPrescriptionList = () => { const [list, setList] = useState([]) const [loading, setLoading] = useState(false) + const [addressList, setAddressList] = useState([]) + const [addressPopupVisible, setAddressPopupVisible] = useState(false) + const [addressLoading, setAddressLoading] = useState(false) + const [addressSaving, setAddressSaving] = useState(false) + const [selectedAddressId, setSelectedAddressId] = useState(null) + const [pendingOrder, setPendingOrder] = useState(null) const reload = () => { setLoading(true) pageClinicPrescription({ - // 添加查询条件 userId: Taro.getStorageSync('UserId'), + withDoctor: true, }) .then(data => { setList(data?.list || []) @@ -35,6 +43,112 @@ const ClinicPrescriptionList = () => { } + const fetchAddressList = async () => { + try { + setAddressLoading(true) + const data = await listShopUserAddress({ + userId: Taro.getStorageSync('UserId') + }) + const addressData = data || [] + setAddressList(addressData) + if (addressData.length > 0) { + setSelectedAddressId(addressData[0].id || null) + } else { + setSelectedAddressId(null) + } + return addressData + } catch (error) { + console.error('加载收货地址失败:', error) + Taro.showToast({ + title: '加载收货地址失败', + icon: 'error' + }) + setAddressList([]) + setSelectedAddressId(null) + return [] + } finally { + setAddressLoading(false) + } + } + + const closeAddressPopup = () => { + setAddressPopupVisible(false) + setPendingOrder(null) + } + + const formatFullAddress = (address?: ShopUserAddress) => { + if (!address) return '' + return address.fullAddress || `${address.province || ''}${address.city || ''}${address.region || ''}${address.address || ''}` + } + + const ensureAddressBeforePay = async (item: ClinicPrescription) => { + setPendingOrder(item) + const addresses = await fetchAddressList() + if (addresses.length === 0) { + Taro.showModal({ + title: '暂无收货地址', + content: '请先添加收货地址后再支付', + confirmText: '去添加', + success: (res) => { + if (res.confirm) { + Taro.navigateTo({url: '/user/address/index'}) + } + } + }) + return + } + setAddressPopupVisible(true) + } + + const handleAddressConfirm = async () => { + if (!selectedAddressId) { + Taro.showToast({ + title: '请选择收货地址', + icon: 'error' + }) + return + } + if (!pendingOrder || !(pendingOrder as any).shopOrder || !(pendingOrder as any).shopOrder.orderId) { + Taro.showToast({ + title: '订单信息缺失', + icon: 'error' + }) + return + } + const targetAddress = addressList.find(addr => addr.id === selectedAddressId) + if (!targetAddress) { + Taro.showToast({ + title: '地址不存在', + icon: 'error' + }) + return + } + try { + setAddressSaving(true) + await updateShopOrder({ + orderId: (pendingOrder as any).shopOrder.orderId, + addressId: targetAddress.id, + realName: targetAddress.name, + address: formatFullAddress(targetAddress) + } as any) + Taro.showToast({ + title: '地址已更新', + icon: 'success' + }) + const orderToPay = pendingOrder + closeAddressPopup() + await onPay(orderToPay, true) + } catch (error: any) { + console.error('更新收货地址失败:', error) + Taro.showToast({ + title: error?.message || '更新地址失败', + icon: 'error' + }) + } finally { + setAddressSaving(false) + } + } + /** * 处理微信支付 */ @@ -45,11 +159,6 @@ const ClinicPrescriptionList = () => { throw new Error('微信支付参数错误'); } - // 验证微信支付必要参数 - if (!result.timeStamp || !result.nonceStr || !result.package || !result.paySign) { - throw new Error('微信支付参数不完整'); - } - try { await Taro.requestPayment({ timeStamp: result.timeStamp, @@ -79,7 +188,14 @@ const ClinicPrescriptionList = () => { /** * 统一支付入口 */ - const onPay = async (item: ClinicPrescription) => { + const onPay = async (item: ClinicPrescription | null, skipAddressCheck: boolean = false) => { + if (!item) { + Taro.showToast({ + title: '处方信息缺失', + icon: 'error' + }); + return; + } if (!item.id) { Taro.showToast({ title: '处方信息缺失', @@ -88,48 +204,39 @@ const ClinicPrescriptionList = () => { return; } - Taro.showLoading({title: '支付中...'}); + const shopOrder = (item as any).shopOrder + if (!skipAddressCheck) { + if (shopOrder && (!shopOrder.addressId || shopOrder.addressId === 0)) { + await ensureAddressBeforePay(item) + return + } + } + + await Taro.showLoading({title: '支付中...'}); try { // 调用统一支付接口 - const result = await createOrder( + // @ts-ignore + const {data} = await updateShopOrder( { - type: 1, - addressId: 10951, - comments: '开方', - deliveryType: 0, - payType: 1, - goodsItems: [ - { - goodsId: 10056, - quantity: 1 - } - ] + orderId: shopOrder.orderId, + makePay: true } ); - console.log(result, 'resultresultresultresultresultresult') - + const result = data as WxPayResult; console.log('订单创建结果:', result); - if (!result) { - throw new Error('创建订单失败'); - } - - if (!result.orderNo) { - throw new Error('订单号获取失败'); - } - // 调用微信支付 await handleWechatPay(result); // 支付成功 - console.log('支付成功,订单号:', result.orderNo); + // console.log('支付成功,订单号:', result.orderNo); - await updateClinicPrescription({ - id: item.id, - orderNo: result.orderNo, - status: 2 - }) + // await updateClinicPrescription({ + // id: item.id, + // orderNo: result.orderNo, + // status: 2 + // }) Taro.showToast({ title: '支付成功', @@ -185,7 +292,7 @@ const ClinicPrescriptionList = () => { {item.status == 2 ? '已支付' : '待支付'} + {item?.shopOrder.payStatus == 1 ? '已支付' : '待支付'} } onClick={() => copyText(`${item.orderNo}`)} /> @@ -226,21 +333,81 @@ const ClinicPrescriptionList = () => { } /> - {item.status == 0 && ( - - + + + + {item?.shopOrder?.payStatus == 0 && ( - - - )} + )} + + ))} + + + + 请选择收货地址 + {addressLoading ? ( + 加载地址中... + ) : addressList.length === 0 ? ( + 暂无收货地址,请先添加 + ) : ( + + {addressList.map(address => ( + setSelectedAddressId(address.id || null)} + > + + {address.name} + {address.phone} + + {formatFullAddress(address)} + {address.isDefault && ( + 默认地址 + )} + + ))} + + )} + + + + + + + ) } diff --git a/src/clinic/clinicPatientUser/selectPatient.tsx b/src/clinic/clinicPatientUser/selectPatient.tsx index 5fd0faa..06f9840 100644 --- a/src/clinic/clinicPatientUser/selectPatient.tsx +++ b/src/clinic/clinicPatientUser/selectPatient.tsx @@ -5,7 +5,7 @@ import {Loading, InfiniteLoading, Empty, Space, SearchBar, Button} from '@nutui/ import {Phone} from '@nutui/icons-react-taro' import type {ClinicPatientUser as PatientUserType} from "@/api/clinic/clinicPatientUser/model"; import { - pageClinicPatientUser + userPageClinicPatientUser } from "@/api/clinic/clinicPatientUser"; // 患者类型 @@ -63,7 +63,7 @@ const SelectPatient = () => { params.keywords = displaySearchValue.trim(); } - const res = await pageClinicPatientUser(params); + const res = await userPageClinicPatientUser(params); if (res?.list && res.list.length > 0) { // 如果是重置页面或第一页,直接设置新数据;否则追加数据 @@ -136,14 +136,14 @@ const SelectPatient = () => { prevPage.setSelectedPatient(patient); } } - + // 同时存储到本地存储,作为备选方案 try { Taro.setStorageSync('selectedPatient', JSON.stringify(patient)); } catch (e) { console.error('存储患者信息失败:', e); } - + Taro.navigateBack(); }; @@ -282,4 +282,4 @@ const SelectPatient = () => { ); }; -export default SelectPatient; \ No newline at end of file +export default SelectPatient; diff --git a/src/clinic/clinicPrescription/add.tsx b/src/clinic/clinicPrescription/add.tsx index fdfd8e0..858d4d9 100644 --- a/src/clinic/clinicPrescription/add.tsx +++ b/src/clinic/clinicPrescription/add.tsx @@ -76,6 +76,7 @@ const AddClinicOrder = () => { // 设置选中的处方(供其他页面调用) // @ts-ignore const setSelectedPrescriptionFunc = (prescription: ClinicPrescription) => { + console.log('设置选中的处方:', prescription) setSelectedPrescription(prescription) } diff --git a/src/clinic/clinicPrescription/components/OrderList.tsx b/src/clinic/clinicPrescription/components/OrderList.tsx index 93ebaba..6f3e340 100644 --- a/src/clinic/clinicPrescription/components/OrderList.tsx +++ b/src/clinic/clinicPrescription/components/OrderList.tsx @@ -12,6 +12,7 @@ import PaymentCountdown from "@/components/PaymentCountdown"; import {PaymentType} from "@/utils/payment"; import {goTo} from "@/utils/navigation"; import {pageClinicPrescription} from "@/api/clinic/clinicPrescription"; +import Prescription from "@/clinic/clinicPatientUser/prescription"; // 判断订单是否支付已过期 const isPaymentExpired = (createTime: string, timeoutHours: number = 24): boolean => { @@ -389,9 +390,10 @@ function OrderList(props: OrderListProps) { }; // 立即支付 - const payOrder = async (order: ShopOrder) => { + // @ts-ignore + const payOrder = async (order: Prescription) => { try { - if (!order.orderId || !order.orderNo) { + if (!order.id || !order.orderNo) { Taro.showToast({ title: '订单信息错误', icon: 'error' @@ -428,19 +430,20 @@ function OrderList(props: OrderListProps) { Taro.showLoading({title: '发起支付...'}); // 构建商品数据 - const goodsItems = order.orderGoods?.map(goods => ({ - goodsId: goods.goodsId, + const goodsItems = order.items?.map(goods => ({ + goodsId: goods.medicineId, quantity: goods.totalNum || 1 })) || []; // 对于已存在的订单,我们需要重新发起支付 // 构建支付请求数据,包含完整的商品信息 const paymentData = { - orderId: order.orderId, + orderId: order.id, orderNo: order.orderNo, goodsItems: goodsItems, - addressId: order.addressId, - payType: PaymentType.WECHAT + // addressId: order.addressId, + payType: PaymentType.WECHAT, + type: 3 }; console.log('重新支付数据:', paymentData); @@ -701,10 +704,12 @@ function OrderList(props: OrderListProps) { e.stopPropagation(); void cancelOrder(item); }}>取消订单 - + {item.showPayButton && ( + + )} )} diff --git a/src/clinic/clinicPrescription/confirm.tsx b/src/clinic/clinicPrescription/confirm.tsx index a06ffce..913661d 100644 --- a/src/clinic/clinicPrescription/confirm.tsx +++ b/src/clinic/clinicPrescription/confirm.tsx @@ -281,9 +281,11 @@ const DoctorOrderConfirm = () => { {item.medicineName} ¥{item.unitPrice} - - {item.specification || '规格未知'} × {item.quantity || 1} - + {!!item.specification && ( + + {item.specification || '规格未知'} × {item.quantity || 1} + + )} @@ -372,7 +374,7 @@ const DoctorOrderConfirm = () => { {/* 底部操作按钮 */} } + icon={} onClick={handleConfirmOrder} /> diff --git a/src/clinic/clinicPrescription/index.tsx b/src/clinic/clinicPrescription/index.tsx index 76a2022..c95b1bb 100644 --- a/src/clinic/clinicPrescription/index.tsx +++ b/src/clinic/clinicPrescription/index.tsx @@ -11,7 +11,7 @@ function ClinicPrescriptionList() { const [searchParams, setSearchParams] = useState({ statusFilter: params.statusFilter != undefined && params.statusFilter != '' ? parseInt(params.statusFilter) : -1 }) - const [showSearch, setShowSearch] = useState(false) + const [showSearch] = useState(false) const [searchKeyword, setSearchKeyword] = useState('') const searchTimeoutRef = useRef() diff --git a/src/clinic/clinicPrescription/selectPrescription.tsx b/src/clinic/clinicPrescription/selectPrescription.tsx index b039787..fc23150 100644 --- a/src/clinic/clinicPrescription/selectPrescription.tsx +++ b/src/clinic/clinicPrescription/selectPrescription.tsx @@ -104,14 +104,14 @@ const SelectPrescription = () => { prevPage.setSelectedPrescription(prescription); } } - + // 同时存储到处方存储,作为备选方案 try { Taro.setStorageSync('selectedPrescription', JSON.stringify(prescription)); } catch (e) { console.error('存储处方信息失败:', e); } - + Taro.navigateBack(); }; @@ -128,11 +128,14 @@ const SelectPrescription = () => { - 处方类型: {prescription.prescriptionType === 0 ? '中药' : prescription.prescriptionType === 1 ? '西药' : '未知'} + 处方名称: {prescription.treatmentPlan} - 诊断结果: {prescription.diagnosis || '无'} + 处方类型: {prescription.prescriptionType === 0 ? '中药' : prescription.prescriptionType === 1 ? '西药' : '未知'} + {/**/} + {/* 诊断结果: {prescription.diagnosis || '无'}*/} + {/**/} 创建时间: {prescription.createTime || '未知'} @@ -231,4 +234,4 @@ const SelectPrescription = () => { ); }; -export default SelectPrescription; \ No newline at end of file +export default SelectPrescription; diff --git a/src/doctor/customer/add.tsx b/src/doctor/customer/add.tsx index 650fe93..e191f07 100644 --- a/src/doctor/customer/add.tsx +++ b/src/doctor/customer/add.tsx @@ -56,7 +56,7 @@ const AddPatient = () => { } // 验证手机号格式 - const phoneRegex = /^1[3-9]\d{9}$/; + const phoneRegex = /^1[2-9]\d{9}$/; if (!phoneRegex.test(values.phone)) { Taro.showToast({ title: '请填写正确的手机号', diff --git a/src/doctor/orders/add.scss b/src/doctor/orders/add.scss new file mode 100644 index 0000000..6efaeab --- /dev/null +++ b/src/doctor/orders/add.scss @@ -0,0 +1,134 @@ +.usage-card { + margin: 12px 16px; + margin-top: 4px; + padding: 16px; + border-radius: 16px; + background: #ffffff; + box-shadow: 0 12px 32px rgba(54, 87, 142, 0.08); +} + +.usage-card__header { + display: flex; + align-items: center; + gap: 8px; + margin-bottom: 12px; +} + +.usage-card__icon { + width: 32px; + height: 32px; + border-radius: 10px; + background: linear-gradient(135deg, #fceecd, #ffd886); + display: flex; + align-items: center; + justify-content: center; +} + +.usage-card__icon-text { + font-size: 16px; + color: #8c5a00; + font-weight: 600; +} + +.usage-card__title { + font-size: 16px; + font-weight: 600; + color: #1c1c1e; +} + +.usage-card__dose-row { + display: flex; + align-items: center; + justify-content: space-between; + padding-bottom: 12px; + margin-bottom: 12px; + border-bottom: 1px solid #f0f0f0; +} + +.usage-card__label { + font-size: 14px; + color: #7c7c7c; +} + +.usage-card__dose-value { + display: flex; + align-items: baseline; + gap: 4px; +} + +.usage-card__dose-value .nut-input { + width: 72px; + background: transparent; +} + +.usage-card__dose-value .nut-input input { + text-align: center; + font-size: 18px; + color: #1c1c1e; +} + +.usage-card__grid-item--picker { + cursor: pointer; +} + +.usage-card__dose-number { + font-size: 20px; + font-weight: 600; + color: #1c1c1e; +} + +.usage-card__dose-unit { + font-size: 12px; + color: #a1a1a1; +} + +.usage-card__grid { + display: flex; + gap: 10px; + margin-bottom: 12px; +} + +.usage-card__grid-item { + flex: 1; + background: #f8f9fb; + border-radius: 12px; + padding: 10px 12px; +} + +.usage-card__grid-label { + font-size: 12px; + color: #9aa1b2; +} + +.usage-card__grid-value { + margin-top: 6px; + display: flex; + align-items: center; + justify-content: space-between; +} + +.usage-card__grid-text { + font-size: 14px; + color: #1d1d1f; + font-weight: 500; +} + +.usage-card__desc { + font-size: 12px; + color: #8c8c8c; + margin-bottom: 10px; +} + +.usage-card__hint { + font-size: 12px; + color: #4b4b4d; + background: #f5f5f5; + border-radius: 12px; + padding: 12px; + margin-bottom: 8px; +} + +.usage-card__hint--muted { + color: #9ba0ab; + background: #f0f2f5; +} diff --git a/src/doctor/orders/add.tsx b/src/doctor/orders/add.tsx index 6a09cc4..62f7751 100644 --- a/src/doctor/orders/add.tsx +++ b/src/doctor/orders/add.tsx @@ -8,9 +8,10 @@ import { Avatar, Input, Space, - TextArea + TextArea, + Picker as NutPicker } from '@nutui/nutui-react-taro' -import {ArrowRight} from '@nutui/icons-react-taro' +import {ArrowRight, ArrowDown} from '@nutui/icons-react-taro' import {View, Text} from '@tarojs/components' import Taro from '@tarojs/taro' import FixedButton from "@/components/FixedButton"; @@ -18,7 +19,8 @@ import navTo from "@/utils/common"; import {ClinicPatientUser} from "@/api/clinic/clinicPatientUser/model"; import {ClinicPrescription} from "@/api/clinic/clinicPrescription/model"; import {TenantId} from "@/config/app"; -import {getClinicPatientUser} from "@/api/clinic/clinicPatientUser"; +import {clinicPatientUserByPatientUserId} from "@/api/clinic/clinicPatientUser"; +import './add.scss' // 图片数据接口 interface UploadedImageData { @@ -30,6 +32,215 @@ interface UploadedImageData { type?: string; } +const frequencyOptions = ['一次', '两次', '三次', '四次', '五次'] +const perDoseOptions = Array.from({length: 20}, (_, index) => `${(index + 1) * 5}g`) + +const getFrequencyIndexFromText = (text?: string) => { + if (!text) { + return 2 + } + const numberMatch = text.match(/\d+/) + if (numberMatch) { + const num = Math.min(Math.max(parseInt(numberMatch[0], 10), 1), 5) + return num - 1 + } + const chineseDigits = ['一', '二', '三', '四', '五'] + const foundIndex = chineseDigits.findIndex(char => text.includes(char)) + if (foundIndex !== -1) { + return foundIndex + } + return 2 +} + +const getPerDoseIndexFromText = (text?: string) => { + if (!text) { + return 0 + } + const numberMatch = text.match(/\d+/) + if (!numberMatch) { + return 0 + } + const num = Math.min(Math.max(parseInt(numberMatch[0], 10), 5), 100) + const normalized = Math.round(num / 5) + const index = Math.max(0, Math.min(perDoseOptions.length - 1, normalized - 1)) + return index +} + +interface UsageSummaryCardProps { + prescription: ClinicPrescription; + doseCount: string; + onDoseChange?: (value: string) => void; + frequencyIndex: number; + onFrequencyChange?: (value: number) => void; + perDoseIndex: number; + onPerDoseChange?: (value: number) => void; +} + +const UsageSummaryCard = ({ + prescription, + doseCount, + onDoseChange, + frequencyIndex, + onFrequencyChange, + perDoseIndex, + onPerDoseChange +}: UsageSummaryCardProps) => { + const firstItem = prescription.items?.[0] + const [showFrequencyPicker, setShowFrequencyPicker] = useState(false) + const [showPerDosePicker, setShowPerDosePicker] = useState(false) + + const frequencyPickerOptions = frequencyOptions.map((label, index) => ({ + text: label, + value: String(index) + })) + + const perDosePickerOptions = perDoseOptions.map((label, index) => ({ + text: label, + value: String(index) + })) + + // const formatFrequency = (frequency?: string) => { + // if (!frequency) { + // return {label: '每日', value: '3次'} + // } + // if (frequency.includes('每日')) { + // return {label: '每日', value: frequency.replace('每日', '') || '1次'} + // } + // if (frequency.includes('每天')) { + // return {label: '每天', value: frequency.replace('每天', '') || '1次'} + // } + // return {label: '频率', value: frequency} + // } + + const formatDosage = () => { + const dosageText = firstItem?.dosage || firstItem?.specification + if (!dosageText) { + return '5g' + } + return dosageText + } + + const formatDuration = () => { + const totalDoses = Number(doseCount) || 0 + const perDay = frequencyIndex + 1 + if (totalDoses <= 0) { + return '0天' + } + const days = Math.max(1, Math.ceil(totalDoses / perDay)) + return `${days}天` + } + + // const summaryDesc = firstItem?.comments || prescription.comments || '请根据患者情况调整剂量,严格按照医嘱服用。' + // const decoctionHint = prescription.decoctionInstructions || '设置用药时间、用药禁忌、医嘱等' + + const handleDoseInputChange = (value: string) => { + const sanitized = value.replace(/[^0-9]/g, '') + onDoseChange?.(sanitized) + } + + return ( + + + + + + 用法 + + + + 剂数 + + handleDoseInputChange(value)} + /> + + + + + + setShowFrequencyPicker(true)} + > + 每日 + + {frequencyOptions[frequencyIndex] || '一次'} + + + + setShowPerDosePicker(true)} + > + 每次 + + {perDoseOptions[perDoseIndex] || formatDosage()} + + + + + 可服 + + {formatDuration()} + + + + + {/**/} + {/* {summaryDesc}*/} + {/**/} + + {/**/} + {/* {decoctionHint}*/} + {/**/} + + {/**/} + {/* 请输入制作要求,该内容患者不可见*/} + {/**/} + + { + const selected = Array.isArray(value) && value.length > 0 ? Number(value[0]) : 0 + onFrequencyChange?.(selected) + setShowFrequencyPicker(false) + }} + onClose={() => setShowFrequencyPicker(false)} + onCancel={() => setShowFrequencyPicker(false)} + /> + + { + const selected = Array.isArray(value) && value.length > 0 ? Number(value[0]) : 0 + onPerDoseChange?.(selected) + setShowPerDosePicker(false) + }} + onClose={() => setShowPerDosePicker(false)} + onCancel={() => setShowPerDosePicker(false)} + /> + + ) +} + const AddClinicOrder = () => { const {params} = useRouter(); const [toUser, setToUser] = useState() @@ -53,13 +264,17 @@ const AddClinicOrder = () => { image: '' // 添加image字段 }) + const [doseCount, setDoseCount] = useState('1') + const [frequencyIndex, setFrequencyIndex] = useState(2) + const [perDoseIndex, setPerDoseIndex] = useState(0) + // 判断是编辑还是新增模式 const isEditMode = !!params.id const toUserId = params.id ? Number(params.id) : undefined const reload = async () => { if (toUserId) { - getClinicPatientUser(Number(toUserId)).then(data => { + clinicPatientUserByPatientUserId(Number(toUserId)).then(data => { setToUser(data) }) } @@ -320,6 +535,24 @@ const AddClinicOrder = () => { } }, [isEditMode]); + useEffect(() => { + if (selectedPrescription) { + const firstItem = selectedPrescription.items?.[0] + const inferredDose = firstItem?.quantity || firstItem?.amount || firstItem?.days || selectedPrescription.items?.length + if (inferredDose) { + setDoseCount(String(inferredDose)) + } else { + setDoseCount('1') + } + setFrequencyIndex(getFrequencyIndexFromText(firstItem?.usageFrequency)) + setPerDoseIndex(getPerDoseIndexFromText(firstItem?.dosage || firstItem?.specification)) + } else { + setDoseCount('1') + setFrequencyIndex(2) + setPerDoseIndex(0) + } + }, [selectedPrescription]) + if (loading) { return 加载中 } @@ -479,7 +712,7 @@ const AddClinicOrder = () => { onClick={() => navTo(`/doctor/orders/selectPrescription`, true)} /> {/* 药方信息 */} - {selectedPrescription && ( + {selectedPrescription ? ( <> @@ -492,6 +725,16 @@ const AddClinicOrder = () => { + + {/* 煎药说明 */}