feat(clinic): 添加患者管理和处方开立功能

- 新增患者管理页面,支持搜索、查看、编辑备注和删除患者
- 实现选择患者功能,用于处方开立时关联患者信息- 添加处方开立页面,支持诊断结果、治疗方案输入及图片上传- 新增用药订单页面,展示患者的历史处方订单并支持支付
- 在处方模型中增加医生姓名字段,优化处方数据显示- 扩展处方查询参数,支持按医生和用户ID筛选
This commit is contained in:
2025-11-03 06:03:15 +08:00
parent a5efb6250f
commit 32811faf54
26 changed files with 2834 additions and 162 deletions

View File

@@ -22,6 +22,8 @@ export interface ClinicPrescription {
weight?: string;
// 医生
doctorId?: number;
// 姓名
doctorName?: string;
// 订单编号
orderNo?: string;
// 关联就诊表
@@ -68,5 +70,7 @@ export interface ClinicPrescription {
*/
export interface ClinicPrescriptionParam extends PageParam {
id?: number;
doctorId?: number;
userId?: number;
keywords?: string;
}

View File

@@ -104,10 +104,17 @@ export default {
"root": "clinic",
"pages": [
"index",
"clinicPatientUser/index",
"clinicPatientUser/add",
"clinicPatientUser/selectPatient",
"clinicPatientUser/prescription",
"clinicDoctorUser/index",
"clinicDoctorUser/add",
"clinicPrescription/index",
"clinicPrescription/add",
"clinicPrescription/selectPrescription",
"clinicPrescription/confirm",
"clinicPrescription/detail"
]
},
{

View File

@@ -0,0 +1,3 @@
export default definePageConfig({
navigationBarTitleText: '患者管理'
})

View File

@@ -0,0 +1,363 @@
import {useState, useEffect, useCallback} from 'react'
import {View, Text} from '@tarojs/components'
import Taro, {useDidShow} from '@tarojs/taro'
import {Loading, InfiniteLoading, Empty, Space, Button, SearchBar} from '@nutui/nutui-react-taro'
import {Phone} from '@nutui/icons-react-taro'
import type {ClinicPatientUser as PatientUserType} from "@/api/clinic/clinicPatientUser/model";
import FixedButton from "@/components/FixedButton";
import navTo from "@/utils/common";
import {
pageClinicPatientUser,
removeClinicPatientUser,
updateClinicPatientUser
} from "@/api/clinic/clinicPatientUser";
// 扩展患者类型
interface PatientUser extends PatientUserType {
}
const PatientIndex = () => {
const [list, setList] = useState<PatientUser[]>([])
const [loading, setLoading] = useState<boolean>(false)
const [searchValue, setSearchValue] = useState<string>('')
const [displaySearchValue, setDisplaySearchValue] = useState<string>('')
const [page, setPage] = useState(1)
const [hasMore, setHasMore] = useState(true)
// 复制手机号
const copyPhone = (phone: string) => {
Taro.setClipboardData({
data: phone,
success: () => {
Taro.showToast({
title: '手机号已复制',
icon: 'success',
duration: 1500
});
}
});
};
// 一键拨打
const makePhoneCall = (phone: string) => {
Taro.makePhoneCall({
phoneNumber: phone,
fail: () => {
Taro.showToast({
title: '拨打取消',
icon: 'error'
});
}
});
};
// 编辑备注
const editComments = (patient: PatientUser) => {
Taro.showModal({
title: '编辑备注',
// @ts-ignore
editable: true,
placeholderText: '请输入备注信息',
content: patient.comments || '',
success: async (res) => {
// @ts-ignore
if (res.confirm && res.content !== undefined) {
try {
// 更新备注
await updateClinicPatientUser({
...patient,
// @ts-ignore
comments: res.content.trim()
});
Taro.showToast({
title: '更新成功',
icon: 'success'
});
// 刷新列表
setList([]);
setPage(1);
setHasMore(true);
fetchPatientData(true);
} catch (error) {
console.error('更新备注失败:', error);
Taro.showToast({
title: '更新失败,请重试',
icon: 'error'
});
}
}
}
});
};
// 获取患者数据
const fetchPatientData = useCallback(async (resetPage = false, targetPage?: number) => {
setLoading(true);
try {
const currentPage = resetPage ? 1 : (targetPage || page);
// 构建API参数
const params: any = {
page: currentPage
};
// 添加搜索关键词
if (displaySearchValue.trim()) {
params.keywords = displaySearchValue.trim();
}
const res = await pageClinicPatientUser(params);
if (res?.list && res.list.length > 0) {
// 如果是重置页面或第一页,直接设置新数据;否则追加数据
if (resetPage || currentPage === 1) {
setList(res.list);
} else {
setList(prevList => prevList.concat(res.list));
}
// 正确判断是否还有更多数据
const hasMoreData = res.list.length >= 10; // 假设每页10条数据
setHasMore(hasMoreData);
} else {
if (resetPage || currentPage === 1) {
setList([]);
}
setHasMore(false);
}
setPage(currentPage);
} catch (error) {
console.error('获取患者数据失败:', error);
Taro.showToast({
title: '加载失败,请重试',
icon: 'none'
});
} finally {
setLoading(false);
}
}, [page, displaySearchValue]);
const reloadMore = async () => {
if (loading || !hasMore) return; // 防止重复加载
const nextPage = page + 1;
await fetchPatientData(false, nextPage);
}
// 防抖搜索功能
useEffect(() => {
const timer = setTimeout(() => {
setDisplaySearchValue(searchValue);
}, 300); // 300ms 防抖
return () => clearTimeout(timer);
}, [searchValue]);
// 删除患者
const handleDelete = (patient: PatientUser) => {
removeClinicPatientUser(patient.id).then(() => {
Taro.showToast({
title: '删除成功',
icon: 'success'
});
// 刷新数据
setList([]);
setPage(1);
setHasMore(true);
fetchPatientData(true).then();
})
}
// 初始化数据
useEffect(() => {
fetchPatientData(true).then();
}, [displaySearchValue]);
// 监听页面显示,当从其他页面返回时刷新数据
useDidShow(() => {
// 刷新数据
setList([]);
setPage(1);
setHasMore(true);
fetchPatientData(true);
});
// 渲染患者项
const renderPatientItem = (patient: PatientUser) => (
<View key={patient.id} className="bg-white rounded-lg p-4 mb-3 shadow-sm">
<View className="flex items-center mb-3">
<View className="flex-1">
<View className="flex items-center mb-1">
<Space>
<Text className="font-semibold text-gray-800 mr-2">
{patient.realName || '未命名'}
</Text>
<Text className={'text-gray-400 font-normal'}>{patient.age}</Text>
<Text className={'text-gray-400 font-normal'}>{patient.sex == '1' ? '男' : ''}{patient.sex == '2' ? '女' : ''}</Text>
</Space>
</View>
<View className="flex items-center mb-1">
<Space direction="vertical">
<View className="flex items-center">
<Text className="text-xs text-gray-500" onClick={(e) => {
e.stopPropagation();
makePhoneCall(patient.phone || '');
}}>{patient.phone || '未提供'}</Text>
<View className="flex items-center ml-2">
<Phone
size={12}
className="text-green-500 mr-2"
onClick={(e) => {
e.stopPropagation();
makePhoneCall(patient.phone || '');
}}
/>
<Text
className="text-xs text-blue-500 cursor-pointer"
onClick={(e) => {
e.stopPropagation();
copyPhone(patient.phone || '');
}}
>
</Text>
</View>
</View>
<Text className="text-xs text-gray-500">
{patient.createTime || '未知'}
</Text>
</Space>
</View>
{/* 显示 comments 字段 */}
<Space className="flex items-center">
<Text className="text-xs text-gray-500">{patient.comments || '暂无'}</Text>
<Text
className="text-xs text-blue-500 cursor-pointer"
onClick={(e) => {
e.stopPropagation();
editComments(patient);
}}
>
</Text>
</Space>
</View>
</View>
{/* 操作按钮 */}
<Space className="flex justify-end">
<Button
size="small"
onClick={() => navTo(`/doctor/orders/add?id=${patient.userId}`, true)}
style={{marginRight: '8px', backgroundColor: '#ff4d4f', color: 'white'}}
>
</Button>
<Button
size="small"
onClick={() => navTo(`/doctor/customer/add?id=${patient.id}`, true)}
style={{marginRight: '8px', backgroundColor: '#1890ff', color: 'white'}}
>
</Button>
<Button
size="small"
onClick={() => handleDelete(patient)}
style={{backgroundColor: '#ff4d4f', color: 'white'}}
>
</Button>
</Space>
</View>
);
// 渲染患者列表
const renderPatientList = () => {
const isSearching = displaySearchValue.trim().length > 0;
return (
<View className="flex-1">
{/* 搜索结果统计 */}
{isSearching && (
<View className="bg-white px-4 py-2 border-b border-gray-100">
<Text className="text-sm text-gray-600">
"{displaySearchValue}" {list.length}
</Text>
</View>
)}
<View className="p-4" style={{
height: isSearching ? 'calc(90vh - 40px)' : '90vh',
overflowY: 'auto',
overflowX: 'hidden'
}}>
<InfiniteLoading
target="scroll"
hasMore={hasMore}
onLoadMore={reloadMore}
onScroll={() => {
// 滚动事件处理
}}
onScrollToUpper={() => {
// 滚动到顶部事件处理
}}
loadingText={
<>
...
</>
}
loadMoreText={
list.length === 0 ? (
<Empty
style={{backgroundColor: 'transparent'}}
description={loading ? "加载中..." : "暂无患者数据"}
/>
) : (
<View className={'h-3 flex items-center justify-center'}>
<Text className="text-gray-500 text-sm"></Text>
</View>
)
}
>
{loading && list.length === 0 ? (
<View className="flex items-center justify-center py-8">
<Loading/>
<Text className="text-gray-500 mt-2 ml-2">...</Text>
</View>
) : (
list.map(renderPatientItem)
)}
</InfiniteLoading>
</View>
</View>
);
};
return (
<View className="min-h-screen bg-gray-50">
{/* 搜索栏 */}
<View className="bg-white py-2 border-b border-gray-100">
<SearchBar
value={searchValue}
placeholder="搜索患者姓名、手机号"
onChange={(value) => setSearchValue(value)}
onClear={() => {
setSearchValue('');
setDisplaySearchValue('');
}}
clearable
/>
</View>
{/* 患者列表 */}
{renderPatientList()}
<FixedButton text={'添加患者'} onClick={() => Taro.navigateTo({url: '/doctor/customer/add'})}/>
</View>
);
};
export default PatientIndex;

View File

@@ -0,0 +1,4 @@
export default definePageConfig({
navigationBarTitleText: '用药订单',
navigationBarTextStyle: 'black'
})

View File

@@ -0,0 +1,142 @@
import {useState} from "react";
import Taro, {useDidShow} from '@tarojs/taro'
import {Button, Cell, CellGroup, Space, Empty, ConfigProvider, Tag} from '@nutui/nutui-react-taro'
import {View, Text} from '@tarojs/components'
import {ClinicPrescription} from "@/api/clinic/clinicPrescription/model";
import {
pageClinicPrescription,
removeClinicPrescription
} from "@/api/clinic/clinicPrescription";
import {copyText} from "@/utils/common";
import {ShopGoods} from "@/api/shop/shopGoods/model";
import {buildSingleGoodsOrder, PaymentHandler} from "@/utils/payment";
const ClinicPrescriptionList = () => {
const [list, setList] = useState<ClinicPrescription[]>([])
const [loading, setLoading] = useState<boolean>(false)
const reload = () => {
setLoading(true)
pageClinicPrescription({
// 添加查询条件
userId: Taro.getStorageSync('UserId'),
})
.then(data => {
setList(data?.list || [])
})
.catch(() => {
Taro.showToast({
title: '获取数据失败',
icon: 'error'
});
})
.finally(() => {
setLoading(false)
})
}
/**
* 统一支付入口
*/
const onPay = async (goods: ShopGoods) => {
try {
// 构建订单数据
const orderData = buildSingleGoodsOrder(
);
// 执行支付
await PaymentHandler.pay(orderData, 1);
} catch (error: any) {
}
};
useDidShow(() => {
reload()
});
if (list.length === 0 && !loading) {
return (
<ConfigProvider>
<View className={'h-full flex flex-col justify-center items-center'} style={{
height: 'calc(100vh - 300px)',
}}>
<Empty
style={{
backgroundColor: 'transparent'
}}
description="暂无用药订单"
/>
</View>
</ConfigProvider>
)
}
return (
<>
<View className="p-3">
{list.map((item) => (
<CellGroup key={item.id} className="mb-3">
<Cell
title={item.orderNo}
extra={
<Tag type={'warning'} className="font-medium"></Tag>
}
onClick={() => copyText(`${item.orderNo}`)}
/>
{item.diagnosis && (
<Cell
title="开方信息"
extra={
<Text className="text-gray-600 text-sm">
{item.diagnosis.length > 20
? `${item.diagnosis.substring(0, 20)}...`
: item.diagnosis}
</Text>
}
/>
)}
<Cell
title={'开方医生'}
extra={
<Space>
<Text className="font-medium">{item.doctorName}</Text>
</Space>
}
/>
<Cell
title="订单金额"
extra={
<Text className="text-red-500 font-medium">
¥{item.orderPrice || '0.00'}
</Text>
}
/>
<Cell
title="开方时间"
extra={
<Text className="text-gray-500 text-xs">
{item.createTime}
</Text>
}
/>
<Cell>
<Space className="w-full justify-end">
<Button
type={'warning'}
onClick={() => onPay(item)}
>
</Button>
</Space>
</Cell>
</CellGroup>
))}
</View>
</>
)
}
export default ClinicPrescriptionList;

View File

@@ -0,0 +1,4 @@
export default definePageConfig({
navigationBarTitleText: '选择患者',
navigationBarTextStyle: 'black'
})

View File

@@ -0,0 +1,285 @@
import {useState, useEffect, useCallback} from 'react'
import {View, Text} from '@tarojs/components'
import Taro, {useDidShow} from '@tarojs/taro'
import {Loading, InfiniteLoading, Empty, Space, SearchBar, Button} from '@nutui/nutui-react-taro'
import {Phone} from '@nutui/icons-react-taro'
import type {ClinicPatientUser as PatientUserType} from "@/api/clinic/clinicPatientUser/model";
import {
pageClinicPatientUser
} from "@/api/clinic/clinicPatientUser";
// 患者类型
interface PatientUser extends PatientUserType {
}
const SelectPatient = () => {
const [list, setList] = useState<PatientUser[]>([])
const [loading, setLoading] = useState<boolean>(false)
const [searchValue, setSearchValue] = useState<string>('')
const [displaySearchValue, setDisplaySearchValue] = useState<string>('')
const [page, setPage] = useState(1)
const [hasMore, setHasMore] = useState(true)
// 复制手机号
const copyPhone = (phone: string) => {
Taro.setClipboardData({
data: phone,
success: () => {
Taro.showToast({
title: '手机号已复制',
icon: 'success',
duration: 1500
});
}
});
};
// 一键拨打
const makePhoneCall = (phone: string) => {
Taro.makePhoneCall({
phoneNumber: phone,
fail: () => {
Taro.showToast({
title: '拨打取消',
icon: 'error'
});
}
});
};
// 获取患者数据
const fetchPatientData = useCallback(async (resetPage = false, targetPage?: number) => {
setLoading(true);
try {
const currentPage = resetPage ? 1 : (targetPage || page);
// 构建API参数
const params: any = {
page: currentPage
};
// 添加搜索关键词
if (displaySearchValue.trim()) {
params.keywords = displaySearchValue.trim();
}
const res = await pageClinicPatientUser(params);
if (res?.list && res.list.length > 0) {
// 如果是重置页面或第一页,直接设置新数据;否则追加数据
if (resetPage || currentPage === 1) {
setList(res.list);
} else {
setList(prevList => [...prevList, ...res.list]);
}
// 正确判断是否还有更多数据
const hasMoreData = res.list.length >= 10; // 假设每页10条数据
setHasMore(hasMoreData);
} else {
if (resetPage || currentPage === 1) {
setList([]);
}
setHasMore(false);
}
setPage(currentPage);
} catch (error) {
console.error('获取患者数据失败:', error);
Taro.showToast({
title: '加载失败,请重试',
icon: 'none'
});
} finally {
setLoading(false);
}
}, [page, displaySearchValue]);
const reloadMore = async () => {
if (loading || !hasMore) return; // 防止重复加载
const nextPage = page + 1;
await fetchPatientData(false, nextPage);
}
// 防抖搜索功能
useEffect(() => {
const timer = setTimeout(() => {
setDisplaySearchValue(searchValue);
}, 300); // 300ms 防抖
return () => clearTimeout(timer);
}, [searchValue]);
// 初始化数据
useEffect(() => {
fetchPatientData(true).then();
}, [displaySearchValue]);
// 监听页面显示,当从其他页面返回时刷新数据
useDidShow(() => {
// 刷新数据
setList([]);
setPage(1);
setHasMore(true);
fetchPatientData(true);
});
// 选择患者
const selectPatient = (patient: PatientUser) => {
// 将选中的患者信息传递回上一个页面
const pages = Taro.getCurrentPages();
if (pages.length > 1) {
const prevPage = pages[pages.length - 2];
// @ts-ignore
if (prevPage && typeof prevPage.setSelectedPatient === 'function') {
// @ts-ignore
prevPage.setSelectedPatient(patient);
}
}
// 同时存储到本地存储,作为备选方案
try {
Taro.setStorageSync('selectedPatient', JSON.stringify(patient));
} catch (e) {
console.error('存储患者信息失败:', e);
}
Taro.navigateBack();
};
// 渲染患者项
const renderPatientItem = (patient: PatientUser) => (
<View key={patient.id} className="bg-white rounded-lg p-4 mb-3 shadow-sm">
<View className="flex items-center mb-3">
<View className="flex-1">
<View className="flex items-center justify-between mb-1">
<Text className="font-semibold text-gray-800 mr-2">
{patient.realName || '未命名'}
</Text>
</View>
<View className="flex items-center mb-1">
<Space direction="vertical">
<View className="flex items-center">
<Text className="text-xs text-gray-500" onClick={(e) => {
e.stopPropagation();
makePhoneCall(patient.phone || '');
}}>{patient.phone || '未提供'}</Text>
<View className="flex items-center ml-2">
<Phone
size={12}
className="text-green-500 mr-2"
onClick={(e) => {
e.stopPropagation();
makePhoneCall(patient.phone || '');
}}
/>
<Text
className="text-xs text-blue-500 cursor-pointer"
onClick={(e) => {
e.stopPropagation();
copyPhone(patient.phone || '');
}}
>
</Text>
</View>
</View>
<Text className="text-xs text-gray-500">
{patient.createTime || '未知'}
</Text>
</Space>
</View>
{/* 显示 comments 字段 */}
<View className="flex items-center">
<Text className="text-xs text-gray-500">{patient.comments || '暂无'}</Text>
</View>
</View>
</View>
{/* 选择按钮 */}
<Button
size="small"
onClick={() => selectPatient(patient)}
style={{backgroundColor: '#1890ff', color: 'white'}}
>
</Button>
</View>
);
// 渲染患者列表
const renderPatientList = () => {
const isSearching = displaySearchValue.trim().length > 0;
return (
<View className="flex-1">
{/* 搜索结果统计 */}
{isSearching && (
<View className="bg-white px-4 py-2 border-b border-gray-100">
<Text className="text-sm text-gray-600">
"{displaySearchValue}" {list.length}
</Text>
</View>
)}
<View className="p-4" style={{
height: isSearching ? 'calc(90vh - 40px)' : '90vh',
overflowY: 'auto',
overflowX: 'hidden'
}}>
<InfiniteLoading
target="scroll"
hasMore={hasMore}
onLoadMore={reloadMore}
loadingText="加载中..."
loadMoreText={
list.length === 0 ? (
<Empty
style={{backgroundColor: 'transparent'}}
description={loading ? "加载中..." : "暂无患者数据"}
/>
) : (
<View className={'h-3 flex items-center justify-center'}>
<Text className="text-gray-500 text-sm"></Text>
</View>
)
}
>
{loading && list.length === 0 ? (
<View className="flex items-center justify-center py-8">
<Loading/>
<Text className="text-gray-500 mt-2 ml-2">...</Text>
</View>
) : (
list.map(renderPatientItem)
)}
</InfiniteLoading>
</View>
</View>
);
};
return (
<View className="min-h-screen bg-gray-50">
{/* 搜索栏 */}
<View className="bg-white py-2 border-b border-gray-100">
<SearchBar
value={searchValue}
placeholder="搜索患者姓名、手机号"
onChange={(value) => setSearchValue(value)}
onClear={() => {
setSearchValue('');
setDisplaySearchValue('');
}}
clearable
/>
</View>
{/* 患者列表 */}
{renderPatientList()}
</View>
);
};
export default SelectPatient;

View File

@@ -1,4 +1,4 @@
export default definePageConfig({
navigationBarTitleText: '新增处方主表',
navigationBarTitleText: '开处方',
navigationBarTextStyle: 'black'
})

View File

@@ -1,51 +1,272 @@
import {useEffect, useState, useRef} from "react";
import {useRouter} from '@tarojs/taro'
import {Button, Loading, CellGroup, Input, TextArea, Form} from '@nutui/nutui-react-taro'
import {
Loading,
Button,
Form,
Cell,
Avatar,
Input,
Space,
TextArea
} from '@nutui/nutui-react-taro'
import {ArrowRight} from '@nutui/icons-react-taro'
import {View, Text} from '@tarojs/components'
import Taro from '@tarojs/taro'
import {View} from '@tarojs/components'
import FixedButton from "@/components/FixedButton";
import navTo from "@/utils/common";
import {ClinicPatientUser} from "@/api/clinic/clinicPatientUser/model";
import {ClinicPrescription} from "@/api/clinic/clinicPrescription/model";
import {getClinicPrescription, listClinicPrescription, updateClinicPrescription, addClinicPrescription} from "@/api/clinic/clinicPrescription";
import {TenantId} from "@/config/app";
import {getClinicPatientUser} from "@/api/clinic/clinicPatientUser";
const AddClinicPrescription = () => {
// 图片数据接口
interface UploadedImageData {
url?: string;
src?: string;
name?: string;
uid?: string;
message?: string;
type?: string;
}
const AddClinicOrder = () => {
const {params} = useRouter();
const [loading, setLoading] = useState<boolean>(true)
const [FormData, setFormData] = useState<ClinicPrescription>({})
const formRef = useRef<any>(null)
const [toUser, setToUser] = useState<ClinicPatientUser>()
const [loading, setLoading] = useState<boolean>(false)
const formRef = useRef<any>()
const [fileList, setFileList] = useState<UploadedImageData[]>([]) // 图片文件列表
// 患者和处方状态
const [selectedPatient, setSelectedPatient] = useState<ClinicPatientUser>()
const [selectedPrescription, setSelectedPrescription] = useState<ClinicPrescription>()
// 表单数据
const [formData, setFormData] = useState<ClinicPrescription>({
userId: undefined,
doctorId: undefined,
diagnosis: '',
treatmentPlan: '',
orderPrice: '',
decoctionInstructions: '',
items: [],
image: '' // 添加image字段
})
// 判断是编辑还是新增模式
const isEditMode = !!params.id
const toUserId = params.id ? Number(params.id) : undefined
const reload = async () => {
if (params.id) {
const data = await getClinicPrescription(Number(params.id))
setFormData(data)
} else {
setFormData({})
if (toUserId) {
getClinicPatientUser(Number(toUserId)).then(data => {
setToUser(data)
})
}
}
// 提交表单
const submitSucceed = async (values: any) => {
try {
if (params.id) {
// 编辑模式
await updateClinicPrescription({
...values,
id: Number(params.id)
})
} else {
// 新增模式
await addClinicPrescription(values)
// 设置选中的患者(供其他页面调用)
// @ts-ignore
const setSelectedPatientFunc = (patient: ClinicPatientUser) => {
console.log('设置选中的患者:', patient)
setToUser(patient)
setSelectedPatient(patient)
}
// 设置选中的处方(供其他页面调用)
// @ts-ignore
const setSelectedPrescriptionFunc = (prescription: ClinicPrescription) => {
setSelectedPrescription(prescription)
}
// 处理表单字段变化
const handleFormChange = (field: string, value: string) => {
setFormData(prev => ({
...prev,
[field]: value
}))
}
// 选择并上传图片
const handleChooseImage = () => {
if (fileList.length >= 5) { // 修正最大图片数量为5
Taro.showToast({
title: '最多只能上传5张图片',
icon: 'none'
})
return
}
Taro.chooseImage({
count: 5 - fileList.length, // 剩余可选择的数量
sizeType: ['compressed'],
sourceType: ['album', 'camera'],
success: (res) => {
console.log('选择图片成功:', res)
// 逐个上传选中的图片
res.tempFilePaths.forEach((filePath, index) => {
uploadSingleImage(filePath, index)
})
},
fail: (err) => {
console.log('选择图片失败:', err)
Taro.showToast({
title: '选择图片失败',
icon: 'error'
})
}
})
}
// 处理文件删除
const handleFileRemove = (file: any) => {
console.log('删除文件:', file)
const newFileList = fileList.filter(f => f.uid !== file.uid)
setFileList(newFileList)
// 更新表单数据 - 使用JSON格式存储
if (newFileList.length === 0) {
setFormData(prev => ({
...prev,
image: ''
}))
} else {
const imageData: UploadedImageData[] = newFileList.map(f => ({
url: f.url,
src: f.url,
name: f.name,
uid: f.uid
}))
setFormData(prev => ({
...prev,
image: JSON.stringify(imageData)
}))
}
}
// 上传单张图片
const uploadSingleImage = (filePath: any, index: number) => {
Taro.uploadFile({
url: 'https://server.websoft.top/api/oss/upload',
filePath: filePath,
name: 'file',
header: {
'content-type': 'multipart/form-data',
TenantId
},
success: (res) => {
try {
const data = JSON.parse(res.data);
console.log('上传成功', data)
if (data.code === 0 && data.data && data.data.url) {
// 更新文件列表
const newFile = {
name: `图片${Date.now()}_${index}`,
url: data.data.url,
status: 'success',
message: '上传成功',
type: 'image',
uid: `${Date.now()}_${index}`,
}
setFileList(prev => {
const newList = [...prev, newFile]
// 同时更新表单数据 - 使用JSON格式存储
const imageData: UploadedImageData[] = newList.map(f => ({
url: f.url,
name: f.name,
uid: f.uid
}))
setFormData(prevForm => ({
...prevForm,
image: JSON.stringify(imageData)
}))
return newList
})
Taro.showToast({
title: `操作成功`,
title: '上传成功',
icon: 'success'
})
setTimeout(() => {
return Taro.navigateBack()
}, 1000)
} catch (error) {
} else {
Taro.showToast({
title: `操作失败`,
title: data.message || '上传失败',
icon: 'error'
})
}
} catch (error) {
console.error('解析响应失败:', error)
Taro.showToast({
title: '上传失败: 数据格式错误',
icon: 'error'
})
}
},
fail: (err) => {
console.log('上传请求失败', err);
Taro.showToast({
title: '上传失败: 网络错误',
icon: 'error'
})
}
})
}
// 提交表单 - 修改为跳转到确认页
const submitSucceed = async (values: any) => {
try {
console.log('提交数据:', values)
// 参数校验
if (!toUser && !selectedPatient) {
Taro.showToast({
title: `请选择发送对象或患者`,
icon: 'error'
});
return false;
}
if (!values.diagnosis) {
Taro.showToast({
title: `请输入诊断结果`,
icon: 'error'
});
return false;
}
if (!values.treatmentPlan) {
Taro.showToast({
title: `请输入治疗方案`,
icon: 'error'
});
return false;
}
// 构建订单数据
const orderData = {
patient: toUser || selectedPatient,
prescription: selectedPrescription,
diagnosis: values.diagnosis,
treatmentPlan: values.treatmentPlan,
decoctionInstructions: values.decoctionInstructions || formData.decoctionInstructions,
images: fileList,
orderPrice: selectedPrescription?.orderPrice || '0.00'
}
// 保存到本地存储
Taro.setStorageSync('tempOrderData', JSON.stringify(orderData))
console.log('跳转到订单确认页,订单数据:', orderData)
// 跳转到确认页
Taro.navigateTo({
url: '/clinic/clinicPrescription/confirm'
})
} catch (error: any) {
console.error('数据处理失败:', error);
Taro.showToast({
title: `数据处理失败: ${error.message || error || '未知错误'}`,
icon: 'error'
});
}
@@ -59,7 +280,45 @@ const AddClinicPrescription = () => {
reload().then(() => {
setLoading(false)
})
}, []);
// 设置页面实例的方法,供其他页面调用
try {
// @ts-ignore
if (Taro.getCurrentInstance() && Taro.getCurrentInstance().page) {
// @ts-ignore
Taro.getCurrentInstance().page.setSelectedPatient = setSelectedPatientFunc;
// @ts-ignore
Taro.getCurrentInstance().page.setSelectedPrescription = setSelectedPrescriptionFunc;
}
} catch (error) {
console.error('设置页面实例方法失败:', error);
}
// 从本地存储获取之前选择的患者和处方
try {
const storedPatient = Taro.getStorageSync('selectedPatient');
if (storedPatient) {
const parsedPatient = JSON.parse(storedPatient);
setSelectedPatient(parsedPatient);
Taro.removeStorageSync('selectedPatient');
}
} catch (error) {
console.error('解析存储的患者数据失败:', error);
Taro.removeStorageSync('selectedPatient');
}
try {
const storedPrescription = Taro.getStorageSync('selectedPrescription');
if (storedPrescription) {
const parsedPrescription = JSON.parse(storedPrescription);
setSelectedPrescription(parsedPrescription);
Taro.removeStorageSync('selectedPrescription');
}
} catch (error) {
console.error('解析存储的处方数据失败:', error);
Taro.removeStorageSync('selectedPrescription');
}
}, [isEditMode]);
if (loading) {
return <Loading className={'px-2'}></Loading>
@@ -67,32 +326,188 @@ const AddClinicPrescription = () => {
return (
<>
{/* 显示已选择的用户(如果有的话) */}
<Cell title={(
<View className={'flex items-center'}>
<Avatar src={toUser?.avatar}/>
<View className={'ml-2 flex flex-col'}>
<Text>{toUser?.realName || '请选择'}</Text>
<Text className={'text-gray-300'}>{toUser?.phone}</Text>
</View>
</View>
)} extra={(
<ArrowRight color="#cccccc" className={'mt-2'} size={20}/>
)} onClick={() => navTo(`/clinic/clinicPatientUser/selectPatient`, true)}
/>
{toUser && (
<Form
ref={formRef}
divider
initialValues={FormData}
labelPosition="left"
initialValues={formData}
labelPosition={'top'}
onFinish={(values) => submitSucceed(values)}
onFinishFailed={(errors) => submitFailed(errors)}
footer={
<div
>
{/* 基本信息 */}
<Form.Item
name="diagnosis"
label={'诊断结果'}
required
rules={[{required: true, message: '请填写诊断结果'}]}
initialValue={formData.diagnosis}
>
<Input placeholder="请输入诊断结果" style={{
backgroundColor: '#f9f9f9',
padding: '4px',
}}/>
</Form.Item>
<Form.Item
name="treatmentPlan"
label={'治疗方案'}
required
rules={[{required: true, message: '请填写治疗方案'}]}
initialValue={formData.treatmentPlan}
>
<TextArea
value={formData.treatmentPlan}
onChange={(value) => handleFormChange('treatmentPlan', value)}
placeholder="请填写治疗方案"
style={{
backgroundColor: '#f9f9f9',
padding: '4px',
}}
/>
</Form.Item>
<Form.Item
label={'拍照上传'}
name="image"
rules={[{message: '请上传照片'}]}
>
<View style={{
display: 'flex',
flexDirection: 'row',
flexWrap: 'wrap',
gap: '8px',
padding: '0'
}}>
{/* 显示已上传的图片 */}
{fileList.map((file) => (
<View key={file.uid} style={{
position: 'relative',
width: '80px',
height: '80px',
borderRadius: '8px',
overflow: 'hidden',
border: '1px solid #d9d9d9'
}}>
<img
src={file.url}
alt={file.name}
style={{
width: '100%',
height: '100%',
objectFit: 'cover'
}}
/>
<Button
size="small"
type="default"
style={{
position: 'absolute',
top: '-8px',
right: '-8px',
width: '20px',
height: '20px',
borderRadius: '10px',
fontSize: '12px',
minWidth: '20px',
padding: 0,
lineHeight: '20px'
}}
onClick={() => handleFileRemove(file)}
>
×
</Button>
</View>
))}
{/* 添加图片按钮 */}
{fileList.length < 5 && (
<View
onClick={handleChooseImage}
style={{
width: '80px',
height: '80px',
borderRadius: '8px',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
width: '100%'
border: '2px dashed #d9d9d9',
backgroundColor: '#fafafa',
cursor: 'pointer'
}}
>
<Button
nativeType="submit"
type="success"
size="large"
className={'w-full'}
block
>
{params.id ? '更新' : '保存'}
</Button>
</div>
}
>
<CellGroup style={{padding: '4px 0'}}>
<Form.Item name="userId" label="患者" initialValue={FormData.userId} required>
<span style={{fontSize: '20px', color: '#d9d9d9'}}>+</span>
<span style={{fontSize: '10px', marginTop: '2px', color: '#666'}}>
</span>
</View>
)}
</View>
<View className="text-xs text-gray-500">
//{fileList.length}
</View>
</Form.Item>
</Form>
)}
{/* 选择处方 */}
<Cell
title="选择处方"
extra={selectedPrescription ? (
<View className={'flex items-center'}>
<Text className={'mr-2'}>{selectedPrescription.treatmentPlan || '未知处方'}</Text>
<ArrowRight color="#cccccc" size={18}/>
</View>
) : (
<Text className={'text-gray-400'}></Text>
)}
onClick={() => navTo(`/clinic/clinicPrescription/selectPrescription`, true)}
/>
{/* 药方信息 */}
{selectedPrescription && (
<>
<Cell extra={'药方信息'}>
<View className={'flex flex-col'}>
<View className={'py-3'}>RP: {selectedPrescription.prescriptionType === 0 ? '中药' : '西药'} {selectedPrescription.items?.length}{selectedPrescription.orderPrice}</View>
<Space className={'flex flex-wrap'}>
{selectedPrescription.items?.map(item => (
<Button>{item.medicineName} 105</Button>
))}
</Space>
</View>
</Cell>
{/* 煎药说明 */}
<TextArea
value={formData.decoctionInstructions}
onChange={(value) => handleFormChange('decoctionInstructions', value)}
placeholder="请填写用药说明"
rows={2}
maxLength={200}
/>
</>
)}
{/* 底部浮动按钮 */}
<FixedButton text={'下一步:确认订单信息'} onClick={() => formRef.current?.submit()}/>
</>
);
};
export default AddClinicOrder;

View File

@@ -0,0 +1,6 @@
export default {
navigationBarTitleText: '确认处方订单',
navigationBarBackgroundColor: '#fff',
navigationBarTextStyle: 'black',
backgroundColor: '#f5f5f5'
}

View File

@@ -0,0 +1,286 @@
.doctor-order-confirm {
min-height: 100vh;
background-color: #f5f5f5;
padding-bottom: 80px;
// 页面提示
.confirm-tip {
background: linear-gradient(135deg, #f0f9ff 0%, #e0f2fe 100%);
padding: 12px 16px;
display: flex;
align-items: center;
gap: 8px;
border-bottom: 1px solid #e5e7eb;
.tip-text {
font-size: 14px;
color: #059669;
font-weight: 500;
}
}
// 加载状态
.order-confirm-loading {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
font-size: 14px;
color: #999;
}
// 分组样式
.section-group {
margin-bottom: 10px;
background: #fff;
.nut-cell-group__title {
padding: 12px 16px;
font-size: 15px;
font-weight: 600;
color: #1f2937;
}
}
// 患者信息
.patient-info {
display: flex;
gap: 16px;
padding: 4px 0;
.patient-detail {
flex: 1;
display: flex;
flex-direction: column;
gap: 6px;
.patient-name {
display: flex;
align-items: center;
gap: 8px;
.name {
font-size: 16px;
font-weight: 600;
color: #111827;
}
}
.patient-phone {
font-size: 14px;
color: #6b7280;
}
.patient-extra {
display: flex;
gap: 16px;
font-size: 13px;
color: #9ca3af;
.age, .idcard {
font-size: 13px;
}
}
}
}
// 诊断信息
.diagnosis-content,
.treatment-content,
.decoction-content {
padding: 8px 0;
.content-text {
font-size: 14px;
color: #374151;
line-height: 1.6;
white-space: pre-wrap;
word-break: break-all;
}
}
// 处方信息
.prescription-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 4px 0;
.prescription-type {
font-size: 15px;
font-weight: 600;
color: #059669;
}
}
// 药品列表
.medicine-list {
padding: 8px 0;
.medicine-item {
padding: 12px 16px;
border-bottom: 1px solid #f3f4f6;
&:last-child {
border-bottom: none;
}
.medicine-main {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
.medicine-name {
font-size: 15px;
font-weight: 500;
color: #111827;
}
.medicine-price {
font-size: 15px;
font-weight: 600;
color: #dc2626;
}
}
.medicine-sub {
display: flex;
justify-content: space-between;
align-items: center;
.medicine-spec {
font-size: 13px;
color: #6b7280;
}
.medicine-subtotal {
font-size: 13px;
color: #9ca3af;
}
}
}
}
// 图片画廊
.image-gallery {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 8px;
padding: 8px 0;
.image-item {
position: relative;
width: 100%;
padding-bottom: 100%; // 1:1 aspect ratio
border-radius: 8px;
overflow: hidden;
border: 1px solid #e5e7eb;
img, image {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: cover;
}
}
}
// 费用明细
.price-text {
font-size: 15px;
font-weight: 500;
color: #111827;
}
.total-price-row {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 0;
.total-label {
font-size: 15px;
font-weight: 600;
color: #111827;
}
.total-amount {
font-size: 20px;
font-weight: 700;
color: #dc2626;
}
}
// 温馨提示
.warm-tips {
margin: 12px 16px;
padding: 16px;
background: #fffbeb;
border-radius: 8px;
border: 1px solid #fef3c7;
.tips-title {
display: block;
font-size: 14px;
font-weight: 600;
color: #d97706;
margin-bottom: 8px;
}
.tips-item {
display: block;
font-size: 13px;
color: #92400e;
line-height: 1.8;
margin-bottom: 4px;
&:last-child {
margin-bottom: 0;
}
}
}
// 底部操作栏
.fixed-bottom-bar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background: #fff;
border-top: 1px solid #e5e7eb;
padding: 12px 16px 24px;
z-index: 999;
box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.05);
.bottom-price {
display: flex;
align-items: center;
justify-content: flex-end;
margin-bottom: 12px;
.price-label {
font-size: 14px;
color: #6b7280;
}
.price-value {
font-size: 22px;
font-weight: 700;
color: #dc2626;
margin-left: 4px;
}
}
.bottom-actions {
display: flex;
gap: 12px;
.nut-button {
flex: 1;
}
}
}
}

View File

@@ -0,0 +1,408 @@
import {useEffect, useState} from "react";
import {
Button,
Cell,
CellGroup,
Avatar,
Tag,
Image,
Space
} from '@nutui/nutui-react-taro'
import {Edit} from '@nutui/icons-react-taro'
import {View, Text} from '@tarojs/components'
import Taro from '@tarojs/taro'
import FixedButton from "@/components/FixedButton";
import {ClinicPatientUser} from "@/api/clinic/clinicPatientUser/model";
import {ClinicPrescription} from "@/api/clinic/clinicPrescription/model";
import {addClinicPrescription} from "@/api/clinic/clinicPrescription";
import {addClinicPrescriptionItem} from "@/api/clinic/clinicPrescriptionItem";
import './confirm.scss'
// 订单数据接口
interface OrderData {
patient: ClinicPatientUser;
prescription?: ClinicPrescription;
diagnosis: string;
treatmentPlan: string;
decoctionInstructions?: string;
images?: Array<{
url: string;
name?: string;
uid?: string;
}>;
orderPrice?: string;
}
const DoctorOrderConfirm = () => {
const [orderData, setOrderData] = useState<OrderData | null>(null)
const [loading, setLoading] = useState<boolean>(false)
const [submitLoading, setSubmitLoading] = useState<boolean>(false)
// 计算药品总价
const getMedicinePrice = () => {
if (!orderData?.prescription?.items) return '0.00'
const total = orderData.prescription.items.reduce((sum, item) => {
const price = parseFloat(item.unitPrice || '0')
const quantity = item.quantity || 1
return sum + (price * quantity)
}, 0)
return total.toFixed(2)
}
// 计算服务费(可根据实际业务调整)
const getServiceFee = () => {
return '10.00' // 固定服务费10元
}
// 计算订单总价
const getTotalPrice = () => {
const medicinePrice = parseFloat(getMedicinePrice())
const serviceFee = parseFloat(getServiceFee())
return (medicinePrice + serviceFee).toFixed(2)
}
// 获取处方类型文本
const getPrescriptionType = () => {
if (!orderData?.prescription) return ''
return orderData.prescription.prescriptionType === 0 ? '中药' : '西药'
}
// 返回编辑
const handleBack = () => {
Taro.navigateBack()
}
// 确认并发送订单
const handleConfirmOrder = async () => {
if (!orderData) {
Taro.showToast({
title: '订单数据缺失',
icon: 'error'
})
return
}
try {
setSubmitLoading(true)
const doctorId = Taro.getStorageSync('UserId') // 当前医生ID
// 第一步:创建处方主表记录
console.log('开始创建处方记录...')
const prescriptionData: ClinicPrescription = {
userId: orderData.patient.userId,
doctorId: doctorId,
prescriptionType: orderData.prescription?.prescriptionType || 0, // 处方类型
diagnosis: orderData.diagnosis, // 诊断结果
treatmentPlan: orderData.treatmentPlan, // 治疗方案
decoctionInstructions: orderData.decoctionInstructions, // 煎药说明
image: orderData.images ? JSON.stringify(orderData.images) : '', // 病例图片
orderPrice: getTotalPrice(), // 订单总金额
price: getMedicinePrice(), // 药品单价
payPrice: getTotalPrice(), // 实付金额
status: 0, // 状态0正常
isInvalid: 0, // 未失效
isSettled: 0, // 未结算
comments: `患者:${orderData.patient.realName},年龄:${orderData.patient.age}`
}
const createdPrescription = await addClinicPrescription(prescriptionData)
console.log('处方创建成功:', createdPrescription)
if (!createdPrescription || !createdPrescription.id) {
throw new Error('处方创建失败未返回处方ID')
}
const prescriptionId = createdPrescription.id
// 第二步:创建处方明细记录(药品列表)
if (orderData.prescription?.items && orderData.prescription.items.length > 0) {
console.log('开始创建处方明细...')
for (const item of orderData.prescription.items) {
const prescriptionItemData = {
prescriptionId: prescriptionId, // 关联处方ID
prescriptionNo: createdPrescription.orderNo, // 处方编号
medicineId: item.medicineId,
medicineName: item.medicineName,
specification: item.specification,
dosage: item.dosage,
usageFrequency: item.usageFrequency,
days: item.days,
amount: item.amount,
unitPrice: item.unitPrice,
quantity: item.quantity,
userId: orderData.patient.userId,
comments: item.comments
}
await addClinicPrescriptionItem(prescriptionItemData)
}
console.log('处方明细创建成功')
}
console.log('处方创建完成处方ID:', prescriptionId)
// 清除临时数据
Taro.removeStorageSync('tempOrderData')
Taro.showToast({
title: '处方已发送给患者',
icon: 'success',
duration: 2000
})
setTimeout(() => {
// 跳转到订单列表
Taro.redirectTo({
url: '/clinic/clinicPrescription/index'
})
}, 2000)
} catch (error: any) {
console.error('创建处方/订单失败:', error)
Taro.showToast({
title: error.message || '发送失败,请重试',
icon: 'error'
})
} finally {
setSubmitLoading(false)
}
}
useEffect(() => {
try {
setLoading(true)
// 从本地存储获取订单数据
const tempData = Taro.getStorageSync('tempOrderData')
if (!tempData) {
Taro.showToast({
title: '订单数据缺失,请重新填写',
icon: 'error'
})
setTimeout(() => {
Taro.navigateBack()
}, 1500)
return
}
const parsedData = JSON.parse(tempData)
console.log('订单确认页获取数据:', parsedData)
setOrderData(parsedData)
} catch (error) {
console.error('解析订单数据失败:', error)
Taro.showToast({
title: '数据解析失败',
icon: 'error'
})
} finally {
setLoading(false)
}
}, [])
if (loading || !orderData) {
return (
<View className="order-confirm-loading">
<Text>...</Text>
</View>
)
}
return (
<>
{/* 页面标题提示 */}
{/* 患者信息 */}
<CellGroup>
<View className={'p-3'}></View>
<Cell>
<Space>
<Avatar
src={orderData.patient.avatar}
size="large"
/>
<View className="flex flex-col gap-1">
<Text className="font-medium">{orderData.patient.realName}</Text>
<Text className="text-gray-500">{orderData.patient.phone}</Text>
<Space className="patient-extra">
<Text className="text-gray-500">{orderData.patient.age}</Text>
</Space>
</View>
</Space>
</Cell>
</CellGroup>
{/* 诊断信息 */}
<CellGroup>
<View className={'p-3'}></View>
<Cell
extra={
<Button
size="small"
icon={<Edit size="12"/>}
onClick={handleBack}
>
</Button>
}
>
<View className="text-gray-500">
<Text>{orderData.diagnosis}</Text>
</View>
</Cell>
<View className={'p-3'}></View>
<Cell>
<View className={'text-gray-500'}>
<Text>{orderData.treatmentPlan}</Text>
</View>
</Cell>
</CellGroup>
{/* 处方信息 */}
{orderData.prescription && (
<CellGroup>
<View className={'p-3'}></View>
<Cell>
<Space>
<Text className="text-gray-500">
RP: {getPrescriptionType()}
</Text>
<Tag type="success">
{orderData.prescription.items?.length || 0}
</Tag>
</Space>
</Cell>
{/* 药品列表 */}
{orderData.prescription.items?.map((item, index) => (
<Cell key={index}>
<View className={'flex justify-between w-full text-gray-500'}>
<Space>
<Text className="medicine-name">{item.medicineName}</Text>
<Text className="medicine-price">¥{item.unitPrice}</Text>
<Text className="medicine-spec">
{item.specification || '规格未知'} × {item.quantity || 1}
</Text>
</Space>
<View className="medicine-sub">
<Text className="medicine-subtotal">
¥{(parseFloat(item.unitPrice || '0') * (item.quantity || 1)).toFixed(2)}
</Text>
</View>
</View>
</Cell>
))}
{/* 煎药说明 */}
{orderData.decoctionInstructions && (
<>
<View className={'p-3'}></View>
<Cell>
<Text className={'text-gray-500'}>{orderData.decoctionInstructions}</Text>
</Cell>
</>
)}
</CellGroup>
)}
{/* 上传的图片 */}
{orderData.images && orderData.images.length > 0 && (
<CellGroup title="病例图片" className="section-group">
<Cell>
<View className="image-gallery">
{orderData.images.map((image, index) => (
<View
key={image.uid || index}
className="image-item"
onClick={() => {
// 预览图片
Taro.previewImage({
urls: orderData.images!.map(img => img.url),
current: image.url
})
}}
>
<Image
src={image.url}
mode="aspectFill"
width="100%"
height="100%"
/>
</View>
))}
</View>
</Cell>
</CellGroup>
)}
{/* 费用明细 */}
<CellGroup>
<View className={'p-3'}></View>
<Cell
title={<Text className="text-gray-500"></Text>}
extra={<Text className="price-text">¥{getMedicinePrice()}</Text>}
/>
<Cell
title={<Text className="text-gray-500"></Text>}
extra={<Text className="price-text">¥{getServiceFee()}</Text>}
/>
<Cell extra={
(
<Space className="total-price-row">
<Text className="total-label"></Text>
<Text className="total-amount">¥{getTotalPrice()}</Text>
</Space>
)
}>
</Cell>
</CellGroup>
{/* 温馨提示 */}
<CellGroup>
<View className={'p-3'}>📌 </View>
<View className={'flex flex-col px-3 pb-5 text-gray-500 text-sm'}>
<Text> </Text>
<Text> </Text>
<Text> </Text>
<Text> "修改"</Text>
</View>
</CellGroup>
{/* 底部操作按钮 */}
<FixedButton
text={submitLoading ? '发送中...' : '确认并发送给患者'}
icon={<Edit />}
onClick={handleConfirmOrder}
/>
<View className="fixed-bottom-bar">
<View className="bottom-price">
<Text className="price-label"></Text>
<Text className="price-value">¥{getTotalPrice()}</Text>
</View>
<View className="bottom-actions">
<Button
size="large"
fill="outline"
onClick={handleBack}
disabled={submitLoading}
>
</Button>
<Button
type="primary"
size="large"
onClick={handleConfirmOrder}
loading={submitLoading}
disabled={submitLoading}
style={{flex: 1}}
>
{submitLoading ? '发送中...' : '确认并发送给患者'}
</Button>
</View>
</View>
</>
)
}
export default DoctorOrderConfirm

View File

@@ -0,0 +1,6 @@
export default {
navigationBarTitleText: '开方详情',
navigationBarBackgroundColor: '#fff',
navigationBarTextStyle: 'black',
backgroundColor: '#f5f5f5'
}

View File

@@ -0,0 +1,190 @@
import {useState} from "react";
import Taro, {useDidShow} from '@tarojs/taro'
import {Button, Cell, CellGroup, Space, Empty, ConfigProvider, Tag} from '@nutui/nutui-react-taro'
import {Del, Edit} from '@nutui/icons-react-taro'
import {View, Text} from '@tarojs/components'
import {ClinicPrescription} from "@/api/clinic/clinicPrescription/model";
import {
pageClinicPrescription,
removeClinicPrescription
} from "@/api/clinic/clinicPrescription";
import FixedButton from "@/components/FixedButton";
import {copyText} from "@/utils/common";
const ClinicPrescriptionList = () => {
const [list, setList] = useState<ClinicPrescription[]>([])
const [loading, setLoading] = useState<boolean>(false)
const reload = () => {
setLoading(true)
pageClinicPrescription({
// 添加查询条件
doctorId: Taro.getStorageSync('UserId'),
})
.then(data => {
setList(data?.list || [])
})
.catch(() => {
Taro.showToast({
title: '获取数据失败',
icon: 'error'
});
})
.finally(() => {
setLoading(false)
})
}
const onDel = async (item: ClinicPrescription) => {
const res = await Taro.showModal({
title: '确认删除',
content: `确定要删除处方编号「${item.orderNo}」吗?`,
})
if (res.confirm) {
try {
await removeClinicPrescription(item.id)
Taro.showToast({
title: '删除成功',
icon: 'success'
});
reload();
} catch (error) {
Taro.showToast({
title: '删除失败',
icon: 'error'
});
}
}
}
const onEdit = (item: ClinicPrescription) => {
Taro.navigateTo({
url: `/clinic/clinicPrescription/add?id=${item.id}`
})
}
const getSexName = (sex?: number) => {
return sex === 0 ? '男' : sex === 1 ? '女' : ''
}
useDidShow(() => {
reload()
});
if (list.length === 0 && !loading) {
return (
<ConfigProvider>
<View className={'h-full flex flex-col justify-center items-center'} style={{
height: 'calc(100vh - 300px)',
}}>
<Empty
style={{
backgroundColor: 'transparent'
}}
description="暂无处方数据"
/>
<Space style={{marginTop: '20px'}}>
<Button
type="primary"
onClick={() => Taro.navigateTo({url: '/clinic/clinicPrescription/add'})}
>
</Button>
</Space>
</View>
</ConfigProvider>
)
}
return (
<>
<View className="p-3">
{list.map((item) => (
<CellGroup key={item.id} className="mb-3">
<Cell
title={item.orderNo}
extra={
<Tag type={'warning'} className="font-medium"></Tag>
}
onClick={() => copyText(`${item.orderNo}`)}
/>
<Cell
title={'患者名称'}
extra={
<Space>
<Text className="font-medium">{item.realName}</Text>
<Text className="font-medium">{item.age}</Text>
<Text className="font-medium">{getSexName(item.sex)}</Text>
</Space>
}
/>
{/*<Cell*/}
{/* title="处方类型"*/}
{/* extra={*/}
{/* <Tag type="info">*/}
{/* {getPrescriptionTypeText(item.prescriptionType)}*/}
{/* </Tag>*/}
{/* }*/}
{/*/>*/}
{item.diagnosis && (
<Cell
title="诊断结果"
extra={
<Text className="text-gray-600 text-sm">
{item.diagnosis.length > 20
? `${item.diagnosis.substring(0, 20)}...`
: item.diagnosis}
</Text>
}
/>
)}
<Cell
title="订单金额"
extra={
<Text className="text-red-500 font-medium">
¥{item.orderPrice || '0.00'}
</Text>
}
/>
<Cell
title="创建时间"
extra={
<Text className="text-gray-500 text-xs">
{item.createTime}
</Text>
}
/>
<Cell>
<Space className="w-full justify-end">
<Button
size="small"
type="primary"
icon={<Edit/>}
onClick={() => onEdit(item)}
>
</Button>
<Button
size="small"
type="danger"
icon={<Del/>}
onClick={() => onDel(item)}
>
</Button>
</Space>
</Cell>
</CellGroup>
))}
</View>
<FixedButton
text="开处方"
onClick={() => Taro.navigateTo({url: '/clinic/clinicPrescription/add'})}
/>
</>
)
}
export default ClinicPrescriptionList;

View File

@@ -1,6 +1,6 @@
import {useState} from "react";
import Taro, {useDidShow} from '@tarojs/taro'
import {Button, Cell, CellGroup, Space, Empty, ConfigProvider, Tag, Divider} from '@nutui/nutui-react-taro'
import {Button, Cell, CellGroup, Space, Empty, ConfigProvider, Tag} from '@nutui/nutui-react-taro'
import {Del, Edit} from '@nutui/icons-react-taro'
import {View, Text} from '@tarojs/components'
import {ClinicPrescription} from "@/api/clinic/clinicPrescription/model";
@@ -9,6 +9,7 @@ import {
removeClinicPrescription
} from "@/api/clinic/clinicPrescription";
import FixedButton from "@/components/FixedButton";
import {copyText} from "@/utils/common";
const ClinicPrescriptionList = () => {
const [list, setList] = useState<ClinicPrescription[]>([])
@@ -18,6 +19,7 @@ const ClinicPrescriptionList = () => {
setLoading(true)
pageClinicPrescription({
// 添加查询条件
doctorId: Taro.getStorageSync('UserId'),
})
.then(data => {
setList(data?.list || [])
@@ -62,41 +64,10 @@ const ClinicPrescriptionList = () => {
})
}
// 获取处方类型文本
const getPrescriptionTypeText = (type?: number) => {
return type === 0 ? '中药' : type === 1 ? '西药' : '未知'
}
// 获取状态文本
const getStatusText = (status?: number) => {
switch (status) {
case 0:
return '正常'
case 1:
return '已完成'
case 2:
return '已支付'
case 3:
return '已取消'
default:
return '未知'
}
}
// 获取状态颜色
const getStatusType = (status?: number): 'primary' | 'success' | 'danger' | 'warning' | 'default' => {
switch (status) {
case 0:
return 'primary'
case 1:
return 'success'
case 2:
return 'success'
case 3:
return 'danger'
default:
return 'default'
}
const onDetail = (item: ClinicPrescription) => {
Taro.navigateTo({
url: `/clinic/clinicPrescription/detail?id=${item.id}`
})
}
const getSexName = (sex?: number) => {
@@ -138,28 +109,30 @@ const ClinicPrescriptionList = () => {
{list.map((item) => (
<CellGroup key={item.id} className="mb-3">
<Cell
title={
<View className="flex items-center justify-between">
title={item.orderNo}
extra={
<Tag type={'warning'} className="font-medium"></Tag>
}
onClick={() => copyText(`${item.orderNo}`)}
/>
<Cell
title={'患者名称'}
extra={
<Space>
<Text className="font-medium">{item.realName}</Text>
<Text className="font-medium">{item.age}</Text>
<Text className="font-medium">{getSexName(item.sex)}</Text>
</Space>
<Tag type={getStatusType(item.status)}>
{getStatusText(item.status)}
</Tag>
</View>
}
/>
<Cell
title="处方类型"
extra={
<Tag type="info">
{getPrescriptionTypeText(item.prescriptionType)}
</Tag>
}
/>
{/*<Cell*/}
{/* title="处方类型"*/}
{/* extra={*/}
{/* <Tag type="info">*/}
{/* {getPrescriptionTypeText(item.prescriptionType)}*/}
{/* </Tag>*/}
{/* }*/}
{/*/>*/}
{item.diagnosis && (
<Cell
title="诊断结果"
@@ -172,18 +145,6 @@ const ClinicPrescriptionList = () => {
}
/>
)}
{item.treatmentPlan && (
<Cell
title="治疗方案"
extra={
<Text className="text-gray-600 text-sm">
{item.treatmentPlan.length > 20
? `${item.treatmentPlan.substring(0, 20)}...`
: item.treatmentPlan}
</Text>
}
/>
)}
<Cell
title="订单金额"
extra={
@@ -192,16 +153,6 @@ const ClinicPrescriptionList = () => {
</Text>
}
/>
{item.items && item.items.length > 0 && (
<Cell
title="药品数量"
extra={
<Tag type="warning">
{item.items.length}
</Tag>
}
/>
)}
<Cell
title="创建时间"
extra={
@@ -210,24 +161,17 @@ const ClinicPrescriptionList = () => {
</Text>
}
/>
{item.comments && (
<Cell
title="备注"
extra={
<Text className="text-gray-600 text-sm">
{item.comments.length > 30
? `${item.comments.substring(0, 30)}...`
: item.comments}
</Text>
}
/>
)}
<Divider />
<Cell>
<Space className="w-full justify-end">
<Button
size="small"
type="primary"
icon={<Edit/>}
onClick={() => onDetail(item)}
>
</Button>
<Button
size="small"
icon={<Edit/>}
onClick={() => onEdit(item)}
>
@@ -235,7 +179,6 @@ const ClinicPrescriptionList = () => {
</Button>
<Button
size="small"
type="danger"
icon={<Del/>}
onClick={() => onDel(item)}
>

View File

@@ -0,0 +1,4 @@
export default definePageConfig({
navigationBarTitleText: '选择患者',
navigationBarTextStyle: 'black'
})

View File

@@ -0,0 +1,285 @@
import {useState, useEffect, useCallback} from 'react'
import {View, Text} from '@tarojs/components'
import Taro, {useDidShow} from '@tarojs/taro'
import {Loading, InfiniteLoading, Empty, Space, SearchBar, Button} from '@nutui/nutui-react-taro'
import {Phone} from '@nutui/icons-react-taro'
import type {ClinicPatientUser as PatientUserType} from "@/api/clinic/clinicPatientUser/model";
import {
pageClinicPatientUser
} from "@/api/clinic/clinicPatientUser";
// 患者类型
interface PatientUser extends PatientUserType {
}
const SelectPatient = () => {
const [list, setList] = useState<PatientUser[]>([])
const [loading, setLoading] = useState<boolean>(false)
const [searchValue, setSearchValue] = useState<string>('')
const [displaySearchValue, setDisplaySearchValue] = useState<string>('')
const [page, setPage] = useState(1)
const [hasMore, setHasMore] = useState(true)
// 复制手机号
const copyPhone = (phone: string) => {
Taro.setClipboardData({
data: phone,
success: () => {
Taro.showToast({
title: '手机号已复制',
icon: 'success',
duration: 1500
});
}
});
};
// 一键拨打
const makePhoneCall = (phone: string) => {
Taro.makePhoneCall({
phoneNumber: phone,
fail: () => {
Taro.showToast({
title: '拨打取消',
icon: 'error'
});
}
});
};
// 获取患者数据
const fetchPatientData = useCallback(async (resetPage = false, targetPage?: number) => {
setLoading(true);
try {
const currentPage = resetPage ? 1 : (targetPage || page);
// 构建API参数
const params: any = {
page: currentPage
};
// 添加搜索关键词
if (displaySearchValue.trim()) {
params.keywords = displaySearchValue.trim();
}
const res = await pageClinicPatientUser(params);
if (res?.list && res.list.length > 0) {
// 如果是重置页面或第一页,直接设置新数据;否则追加数据
if (resetPage || currentPage === 1) {
setList(res.list);
} else {
setList(prevList => [...prevList, ...res.list]);
}
// 正确判断是否还有更多数据
const hasMoreData = res.list.length >= 10; // 假设每页10条数据
setHasMore(hasMoreData);
} else {
if (resetPage || currentPage === 1) {
setList([]);
}
setHasMore(false);
}
setPage(currentPage);
} catch (error) {
console.error('获取患者数据失败:', error);
Taro.showToast({
title: '加载失败,请重试',
icon: 'none'
});
} finally {
setLoading(false);
}
}, [page, displaySearchValue]);
const reloadMore = async () => {
if (loading || !hasMore) return; // 防止重复加载
const nextPage = page + 1;
await fetchPatientData(false, nextPage);
}
// 防抖搜索功能
useEffect(() => {
const timer = setTimeout(() => {
setDisplaySearchValue(searchValue);
}, 300); // 300ms 防抖
return () => clearTimeout(timer);
}, [searchValue]);
// 初始化数据
useEffect(() => {
fetchPatientData(true).then();
}, [displaySearchValue]);
// 监听页面显示,当从其他页面返回时刷新数据
useDidShow(() => {
// 刷新数据
setList([]);
setPage(1);
setHasMore(true);
fetchPatientData(true);
});
// 选择患者
const selectPatient = (patient: PatientUser) => {
// 将选中的患者信息传递回上一个页面
const pages = Taro.getCurrentPages();
if (pages.length > 1) {
const prevPage = pages[pages.length - 2];
// @ts-ignore
if (prevPage && typeof prevPage.setSelectedPatient === 'function') {
// @ts-ignore
prevPage.setSelectedPatient(patient);
}
}
// 同时存储到本地存储,作为备选方案
try {
Taro.setStorageSync('selectedPatient', JSON.stringify(patient));
} catch (e) {
console.error('存储患者信息失败:', e);
}
Taro.navigateBack();
};
// 渲染患者项
const renderPatientItem = (patient: PatientUser) => (
<View key={patient.id} className="bg-white rounded-lg p-4 mb-3 shadow-sm">
<View className="flex items-center mb-3">
<View className="flex-1">
<View className="flex items-center justify-between mb-1">
<Text className="font-semibold text-gray-800 mr-2">
{patient.realName || '未命名'}
</Text>
</View>
<View className="flex items-center mb-1">
<Space direction="vertical">
<View className="flex items-center">
<Text className="text-xs text-gray-500" onClick={(e) => {
e.stopPropagation();
makePhoneCall(patient.phone || '');
}}>{patient.phone || '未提供'}</Text>
<View className="flex items-center ml-2">
<Phone
size={12}
className="text-green-500 mr-2"
onClick={(e) => {
e.stopPropagation();
makePhoneCall(patient.phone || '');
}}
/>
<Text
className="text-xs text-blue-500 cursor-pointer"
onClick={(e) => {
e.stopPropagation();
copyPhone(patient.phone || '');
}}
>
</Text>
</View>
</View>
<Text className="text-xs text-gray-500">
{patient.createTime || '未知'}
</Text>
</Space>
</View>
{/* 显示 comments 字段 */}
<View className="flex items-center">
<Text className="text-xs text-gray-500">{patient.comments || '暂无'}</Text>
</View>
</View>
</View>
{/* 选择按钮 */}
<Button
size="small"
onClick={() => selectPatient(patient)}
style={{backgroundColor: '#1890ff', color: 'white'}}
>
</Button>
</View>
);
// 渲染患者列表
const renderPatientList = () => {
const isSearching = displaySearchValue.trim().length > 0;
return (
<View className="flex-1">
{/* 搜索结果统计 */}
{isSearching && (
<View className="bg-white px-4 py-2 border-b border-gray-100">
<Text className="text-sm text-gray-600">
"{displaySearchValue}" {list.length}
</Text>
</View>
)}
<View className="p-4" style={{
height: isSearching ? 'calc(90vh - 40px)' : '90vh',
overflowY: 'auto',
overflowX: 'hidden'
}}>
<InfiniteLoading
target="scroll"
hasMore={hasMore}
onLoadMore={reloadMore}
loadingText="加载中..."
loadMoreText={
list.length === 0 ? (
<Empty
style={{backgroundColor: 'transparent'}}
description={loading ? "加载中..." : "暂无患者数据"}
/>
) : (
<View className={'h-3 flex items-center justify-center'}>
<Text className="text-gray-500 text-sm"></Text>
</View>
)
}
>
{loading && list.length === 0 ? (
<View className="flex items-center justify-center py-8">
<Loading/>
<Text className="text-gray-500 mt-2 ml-2">...</Text>
</View>
) : (
list.map(renderPatientItem)
)}
</InfiniteLoading>
</View>
</View>
);
};
return (
<View className="min-h-screen bg-gray-50">
{/* 搜索栏 */}
<View className="bg-white py-2 border-b border-gray-100">
<SearchBar
value={searchValue}
placeholder="搜索患者姓名、手机号"
onChange={(value) => setSearchValue(value)}
onClear={() => {
setSearchValue('');
setDisplaySearchValue('');
}}
clearable
/>
</View>
{/* 患者列表 */}
{renderPatientList()}
</View>
);
};
export default SelectPatient;

View File

@@ -0,0 +1,4 @@
export default definePageConfig({
navigationBarTitleText: '选择处方',
navigationBarTextStyle: 'black'
})

View File

@@ -0,0 +1,234 @@
import {useState, useEffect, useCallback} from 'react'
import {View, Text} from '@tarojs/components'
import Taro, {useDidShow} from '@tarojs/taro'
import {Loading, InfiniteLoading, Empty, Space, SearchBar, Button} from '@nutui/nutui-react-taro'
import {
pageClinicPrescription
} from "@/api/clinic/clinicPrescription";
import {ClinicPrescription} from "@/api/clinic/clinicPrescription/model";
const SelectPrescription = () => {
const [list, setList] = useState<ClinicPrescription[]>([])
const [loading, setLoading] = useState<boolean>(false)
const [searchValue, setSearchValue] = useState<string>('')
const [displaySearchValue, setDisplaySearchValue] = useState<string>('')
const [page, setPage] = useState(1)
const [hasMore, setHasMore] = useState(true)
// 获取处方数据
const fetchPrescriptionData = useCallback(async (resetPage = false, targetPage?: number) => {
setLoading(true);
try {
const currentPage = resetPage ? 1 : (targetPage || page);
// 构建API参数
const params: any = {
page: currentPage
};
// 添加搜索关键词
if (displaySearchValue.trim()) {
params.keywords = displaySearchValue.trim();
}
const res = await pageClinicPrescription(params);
if (res?.list && res.list.length > 0) {
// 如果是重置页面或第一页,直接设置新数据;否则追加数据
if (resetPage || currentPage === 1) {
setList(res.list);
} else {
setList(prevList => [...prevList, ...res.list]);
}
// 正确判断是否还有更多数据
const hasMoreData = res.list.length >= 10; // 假设每页10条数据
setHasMore(hasMoreData);
} else {
if (resetPage || currentPage === 1) {
setList([]);
}
setHasMore(false);
}
setPage(currentPage);
} catch (error) {
console.error('获取处方数据失败:', error);
Taro.showToast({
title: '加载失败,请重试',
icon: 'none'
});
} finally {
setLoading(false);
}
}, [page, displaySearchValue]);
const reloadMore = async () => {
if (loading || !hasMore) return; // 防止重复加载
const nextPage = page + 1;
await fetchPrescriptionData(false, nextPage);
}
// 防抖搜索功能
useEffect(() => {
const timer = setTimeout(() => {
setDisplaySearchValue(searchValue);
}, 300); // 300ms 防抖
return () => clearTimeout(timer);
}, [searchValue]);
// 初始化数据
useEffect(() => {
fetchPrescriptionData(true).then();
}, [displaySearchValue]);
// 监听页面显示,当从其他页面返回时刷新数据
useDidShow(() => {
// 刷新数据
setList([]);
setPage(1);
setHasMore(true);
fetchPrescriptionData(true);
});
// 选择处方
const selectPrescription = (prescription: ClinicPrescription) => {
// 将选中的处方信息传递回上一个页面
const pages = Taro.getCurrentPages();
if (pages.length > 1) {
const prevPage = pages[pages.length - 2];
// @ts-ignore
if (prevPage && typeof prevPage.setSelectedPrescription === 'function') {
// @ts-ignore
prevPage.setSelectedPrescription(prescription);
}
}
// 同时存储到处方存储,作为备选方案
try {
Taro.setStorageSync('selectedPrescription', JSON.stringify(prescription));
} catch (e) {
console.error('存储处方信息失败:', e);
}
Taro.navigateBack();
};
// 渲染处方项
const renderPrescriptionItem = (prescription: ClinicPrescription) => (
<View key={prescription.id} className="bg-white rounded-lg p-4 mb-3 shadow-sm">
<View className="flex items-center mb-3">
<View className="flex-1">
<View className="flex items-center justify-between mb-1">
<Text className="font-semibold text-gray-800 mr-2">
: {prescription.orderNo || '无编号'}
</Text>
</View>
<View className="flex items-center mb-1">
<Space direction="vertical">
<Text className="text-xs text-gray-500">
: {prescription.prescriptionType === 0 ? '中药' : prescription.prescriptionType === 1 ? '西药' : '未知'}
</Text>
<Text className="text-xs text-gray-500">
: {prescription.diagnosis || '无'}
</Text>
<Text className="text-xs text-gray-500">
: {prescription.createTime || '未知'}
</Text>
</Space>
</View>
{/* 显示备注字段 */}
<View className="flex items-center">
<Text className="text-xs text-gray-500">: {prescription.comments || '暂无'}</Text>
</View>
</View>
</View>
{/* 选择按钮 */}
<Button
size="small"
onClick={() => selectPrescription(prescription)}
style={{backgroundColor: '#1890ff', color: 'white'}}
>
</Button>
</View>
);
// 渲染处方列表
const renderPrescriptionList = () => {
const isSearching = displaySearchValue.trim().length > 0;
return (
<View className="flex-1">
{/* 搜索结果统计 */}
{isSearching && (
<View className="bg-white px-4 py-2 border-b border-gray-100">
<Text className="text-sm text-gray-600">
"{displaySearchValue}" {list.length}
</Text>
</View>
)}
<View className="p-4" style={{
height: isSearching ? 'calc(90vh - 40px)' : '90vh',
overflowY: 'auto',
overflowX: 'hidden'
}}>
<InfiniteLoading
target="scroll"
hasMore={hasMore}
onLoadMore={reloadMore}
loadingText="加载中..."
loadMoreText={
list.length === 0 ? (
<Empty
style={{backgroundColor: 'transparent'}}
description={loading ? "加载中..." : "暂无处方数据"}
/>
) : (
<View className={'h-3 flex items-center justify-center'}>
<Text className="text-gray-500 text-sm"></Text>
</View>
)
}
>
{loading && list.length === 0 ? (
<View className="flex items-center justify-center py-8">
<Loading/>
<Text className="text-gray-500 mt-2 ml-2">...</Text>
</View>
) : (
list.map(renderPrescriptionItem)
)}
</InfiniteLoading>
</View>
</View>
);
};
return (
<View className="min-h-screen bg-gray-50">
{/* 搜索栏 */}
<View className="bg-white py-2 border-b border-gray-100">
<SearchBar
value={searchValue}
placeholder="搜索处方编号、诊断结果"
onChange={(value) => setSearchValue(value)}
onClear={() => {
setSearchValue('');
setDisplaySearchValue('');
}}
clearable
/>
</View>
{/* 处方列表 */}
{renderPrescriptionList()}
</View>
);
};
export default SelectPrescription;

View File

@@ -147,7 +147,7 @@ const DealerIndex: React.FC = () => {
</View>
</Grid.Item>
<Grid.Item text={'在线开方'} onClick={() => navigateToPage('/doctor/orders/add')}>
<Grid.Item text={'在线开方'} onClick={() => navigateToPage('/clinic/clinicPrescription/add')}>
<View className="text-center">
<View className="w-12 h-12 bg-green-50 rounded-xl flex items-center justify-center mx-auto mb-2">
<Edit color="#10b981" size="20"/>

View File

@@ -1,4 +1,4 @@
export default definePageConfig({
navigationBarTitleText: '在线开方',
navigationBarTitleText: '开方',
navigationBarTextStyle: 'black'
})

17
src/pages/index/Role.scss Normal file
View File

@@ -0,0 +1,17 @@
.doctor-user{
background: url("https://oss.wsdns.cn/20251102/f893dba3f4da479f93e87c6def563d60.png");
background-repeat: no-repeat;
background-size: 100%;
width: 100%;
height: 140px;
border-radius: 25px;
}
.patient-user{
background: url("https://oss.wsdns.cn/20251102/d42d50e672d844bcaf7265aee03f3606.png");
background-repeat: no-repeat;
background-size: 100%;
width: 100%;
height: 140px;
border-radius: 25px;
}

60
src/pages/index/Role.tsx Normal file
View File

@@ -0,0 +1,60 @@
import {Space, Image} from '@nutui/nutui-react-taro'
import {Loading} from '@nutui/nutui-react-taro'
import Taro from '@tarojs/taro'
import {View, Text} from '@tarojs/components'
import {goTo} from "@/utils/navigation"
import {useShopInfo} from "@/hooks/useShopInfo"
import './Role.scss'
import navTo from "@/utils/common";
import {useEffect, useState} from "react";
const Page = () => {
const [isDoctor, setIsDoctor] = useState<boolean>(false)
// 使用 useShopInfo hooks 获取导航数据
const {
loading: shopLoading,
error,
getNavigation
} = useShopInfo()
// 获取顶部导航菜单
const navigation = getNavigation()
const home = navigation.topNavs.find(item => item.model == 'index')
const navItems = navigation.topNavs.filter(item => item.parentId == home?.navigationId) || []
const onNav = (item: any) => {
if (item.path) {
return goTo(`${item.path}`)
}
}
useEffect(() => {
setIsDoctor(Taro.getStorageSync('Doctor') || Taro.getStorageSync('Doctor') == 'true')
}, []);
// 处理错误状态
if (error) {
return (
<View className={'p-2 text-center text-red-500'}>
</View>
)
}
return (
<View className={'p-2 z-50 mt-1 gap-2 flex justify-between'}>
{isDoctor && (
<View className={'doctor-user rounded-lg w-full block'} onClick={() => navTo(`/clinic/index`)}></View>
)}
{!isDoctor && (
<View className={'doctor-user rounded-lg w-full block'} onClick={() => Taro.switchTab({
url: `/pages/chat/chat`
})}></View>
)}
<View className={'patient-user rounded-lg bg-white w-full'}
onClick={() => navTo(`/clinic/clinicPatientUser/prescription`, true)}></View>
</View>
)
}
export default Page

View File

@@ -8,6 +8,7 @@ import Banner from "./Banner";
import {checkAndHandleInviteRelation, hasPendingInvite} from "@/utils/invite";
import './index.scss'
import GoodsList from './GoodsList';
import Role from "@/pages/index/Role";
function Home() {
// 吸顶状态
@@ -158,7 +159,8 @@ function Home() {
<Header />
<div className={'flex flex-col mt-12'}>
<Menu/>
{/*<Menu/>*/}
<Role />
<Banner/>
<GoodsList onStickyChange={handleTabsStickyChange}/>
</div>