diff --git a/src/api/clinic/clinicDoctorUser/model/index.ts b/src/api/clinic/clinicDoctorUser/model/index.ts index b7062ec..02786f6 100644 --- a/src/api/clinic/clinicDoctorUser/model/index.ts +++ b/src/api/clinic/clinicDoctorUser/model/index.ts @@ -12,8 +12,10 @@ export interface ClinicDoctorUser { userId?: number; // 姓名 realName?: string; + // 头像 + avatar?: string; // 手机号 - mobile?: string; + phone?: string; // 支付密码 payPassword?: string; // 当前可提现佣金 diff --git a/src/app.config.ts b/src/app.config.ts index caaca2b..a8305de 100644 --- a/src/app.config.ts +++ b/src/app.config.ts @@ -25,7 +25,14 @@ export default { "root": "cms", "pages": [ 'category/index', - "detail/index" + "detail/index", + "about/index" + ] + }, + { + "root": "chat", + "pages": [ + "doctor/index" ] }, { @@ -81,6 +88,7 @@ export default { "withdraw/index", "orders/index", "orders/add", + "orders/confirm", "orders/selectPatient", "orders/selectPrescription", "team/index", @@ -167,4 +175,4 @@ export default { "desc": "你的位置信息将用于小程序位置接口的效果展示" } } -} \ No newline at end of file +} diff --git a/src/app.ts b/src/app.ts index b3c8e85..f78698e 100644 --- a/src/app.ts +++ b/src/app.ts @@ -8,12 +8,14 @@ import {TenantId} from "@/config/app"; import {saveStorageByLoginUser} from "@/utils/server"; import {parseInviteParams, saveInviteParams, trackInviteSource, handleInviteRelation} from "@/utils/invite"; import {useConfig} from "@/hooks/useConfig"; -import {addShopUser, getShopUser} from "@/api/shop/shopUser"; // 引入新的自定义Hook +import { User } from '@/api/system/user/model'; +import {addShopUser, getShopUser} from "@/api/shop/shopUser"; function App(props: { children: any; }) { const {refetch: handleTheme} = useConfig(); // 使用新的Hook const reload = () => { + Taro.login({ success: (res) => { // 无感登录 @@ -24,6 +26,14 @@ function App(props: { children: any; }) { if (data) { saveStorageByLoginUser(data.access_token, data.user) + // 判断是否是医生 + if (hasRole('doctor', data.user)) { + console.log('是否医生?', true) + Taro.setStorageSync('Doctor',true) + }else { + Taro.setStorageSync('Doctor',false) + } + // 处理邀请关系 if (data.user?.userId) { try { @@ -56,6 +66,15 @@ function App(props: { children: any; }) { } }) }; + + // 检查是否有特定角色 + const hasRole = (roleCode: string, item: User) => { + if (!item || !item.roles) { + return false; + } + return item.roles.some(role => role.roleCode === roleCode); + } + // 可以使用所有的 React Hooks useEffect(() => { // 设置主题 (现在由useConfig Hook处理) diff --git a/src/chat/doctor/index.config.ts b/src/chat/doctor/index.config.ts new file mode 100644 index 0000000..79f694d --- /dev/null +++ b/src/chat/doctor/index.config.ts @@ -0,0 +1,3 @@ +export default definePageConfig({ + navigationBarTitleText: '聊天' +}) diff --git a/src/chat/doctor/index.tsx b/src/chat/doctor/index.tsx new file mode 100644 index 0000000..9bf0ff7 --- /dev/null +++ b/src/chat/doctor/index.tsx @@ -0,0 +1,158 @@ +import {useState, useEffect} 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 {ShopChatMessage} from "@/api/shop/shopChatMessage/model"; +import Line from "@/components/Gap"; + +const CustomerIndex = () => { + const {params} = useRouter(); + const [doctor, setDoctor] = useState() + const [list, setList] = useState([]) + + 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 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({}) + + } + + // 初始化数据 + 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} + + + + + + + + ); + + + return ( + + + {list?.map(renderPatientUserItem)} + + + + + + + + + + + ); +}; + +export default CustomerIndex; diff --git a/src/cms/about/index.config.ts b/src/cms/about/index.config.ts new file mode 100644 index 0000000..f96248b --- /dev/null +++ b/src/cms/about/index.config.ts @@ -0,0 +1,4 @@ +export default definePageConfig({ + navigationBarTitleText: '关于我们', + navigationBarTextStyle: 'black' +}) diff --git a/src/cms/about/index.tsx b/src/cms/about/index.tsx new file mode 100644 index 0000000..9cb2b74 --- /dev/null +++ b/src/cms/about/index.tsx @@ -0,0 +1,48 @@ +import Taro from '@tarojs/taro' +import {useEffect, useState} from 'react' +import {Loading} from '@nutui/nutui-react-taro' +import {View, RichText} from '@tarojs/components' +import {wxParse} from "@/utils/common"; +import {getCmsArticleByCode} from "@/api/cms/cmsArticle"; +import {CmsArticle} from "@/api/cms/cmsArticle/model" +import Line from "@/components/Gap"; + +function Index() { + const [loading, setLoading] = useState(true) + // 文章详情 + const [item, setItem] = useState() + const reload = async () => { + const item = await getCmsArticleByCode('xieyi') + + if (item && item.content) { + item.content = wxParse(item.content) + setItem(item) + Taro.setNavigationBarTitle({ + title: `${item?.categoryName}` + }) + } + } + + useEffect(() => { + reload().then(() => { + setLoading(false) + }); + }, []); + + if (loading) { + return ( + 加载中 + ) + } + + return ( +
+ + + + +
+ ) +} + +export default Index diff --git a/src/cms/detail/index.tsx b/src/cms/detail/index.tsx index f92c890..4dfef55 100644 --- a/src/cms/detail/index.tsx +++ b/src/cms/detail/index.tsx @@ -17,7 +17,7 @@ function Detail() { const reload = async () => { const item = await getCmsArticle(Number(params.id)) - if (item) { + if (item && item.content) { item.content = wxParse(item.content) setItem(item) Taro.setNavigationBarTitle({ diff --git a/src/doctor/orders/README.md b/src/doctor/orders/README.md new file mode 100644 index 0000000..90da30a --- /dev/null +++ b/src/doctor/orders/README.md @@ -0,0 +1,322 @@ +# 医生开方订单确认页面使用说明 + +## 📋 功能概述 + +医生开方订单确认页面是一个独立的业务页面,用于医生在填写完处方信息后,预览并确认订单详情,然后发送给患者。 + +## 🎯 页面路径 + +``` +/doctor/orders/confirm +``` + +## 📊 页面结构 + +### 1. 页面信息展示 + +#### 患者信息区 +- ✅ 患者头像 +- ✅ 患者姓名 +- ✅ 性别标签 +- ✅ 联系电话 +- ✅ 年龄信息 + +#### 诊断信息区 +- ✅ 诊断结果 +- ✅ 治疗方案 +- ✅ 修改按钮(返回编辑) + +#### 处方信息区 +- ✅ 处方类型(中药/西药) +- ✅ 药品数量统计 +- ✅ 药品明细列表 + - 药品名称 + - 单价 + - 规格 + - 数量 + - 小计 +- ✅ 煎药说明(可选) + +#### 病例图片区(可选) +- ✅ 图片网格展示 +- ✅ 点击预览大图 + +#### 费用明细区 +- ✅ 药品费用 +- ✅ 服务费 +- ✅ 订单总计 + +#### 温馨提示区 +- ✅ 处方发送后的流程说明 +- ✅ 患者支付提醒 +- ✅ 修改提示 + +### 2. 底部操作栏 +- ✅ 订单总价显示 +- ✅ 返回修改按钮 +- ✅ 确认并发送按钮(带loading状态) + +## 🔄 业务流程 + +### 完整流程 + +``` +开方页面 (/doctor/orders/add.tsx) + ↓ + [填写诊断信息] + ↓ + [选择患者] + ↓ + [选择处方] + ↓ + [上传病例图片] + ↓ + [填写煎药说明] + ↓ + [点击"下一步:确认订单信息"] + ↓ +订单确认页 (/doctor/orders/confirm.tsx) + ↓ + [预览所有信息] + ↓ + [检查费用明细] + ↓ + [点击"确认并发送给患者"] + ↓ +调用API创建订单 + ↓ +发送成功提示 + ↓ +跳转订单列表 (/doctor/orders/index) +``` + +### 数据传递方式 + +使用 Taro 本地存储传递数据: + +```typescript +// add.tsx 传递数据 +const orderData = { + patient: toUser || selectedPatient, + prescription: selectedPrescription, + diagnosis: values.diagnosis, + treatmentPlan: values.treatmentPlan, + decoctionInstructions: values.decoctionInstructions, + images: fileList, + orderPrice: selectedPrescription?.orderPrice || '0.00' +} +Taro.setStorageSync('tempOrderData', JSON.stringify(orderData)) + +// confirm.tsx 接收数据 +const tempData = Taro.getStorageSync('tempOrderData') +const parsedData = JSON.parse(tempData) +``` + +## 💻 技术实现 + +### 主要组件 + +```typescript +import { + Button, + Cell, + CellGroup, + Avatar, + Tag, + Divider, + Image +} from '@nutui/nutui-react-taro' +``` + +### 状态管理 + +```typescript +const [orderData, setOrderData] = useState(null) +const [loading, setLoading] = useState(false) +const [submitLoading, setSubmitLoading] = useState(false) +``` + +### 关键方法 + +#### 1. 计算药品总价 +```typescript +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) +} +``` + +#### 2. 计算订单总价 +```typescript +const getTotalPrice = () => { + const medicinePrice = parseFloat(getMedicinePrice()) + const serviceFee = parseFloat(getServiceFee()) + return (medicinePrice + serviceFee).toFixed(2) +} +``` + +#### 3. 确认订单 +```typescript +const handleConfirmOrder = async () => { + const clinicOrderData = { + userId: orderData.patient.userId, + doctorId: Taro.getStorageSync('UserId'), + type: 0, + title: `${orderData.patient.realName}的处方订单`, + totalPrice: getTotalPrice(), + payPrice: getTotalPrice(), + // ... 其他字段 + } + + await addClinicOrder(clinicOrderData) + Taro.removeStorageSync('tempOrderData') + + Taro.redirectTo({ + url: '/doctor/orders/index' + }) +} +``` + +## 🎨 样式特点 + +### 配色方案 +- 主色调: 绿色系 (#059669) +- 价格强调: 红色 (#dc2626) +- 提示信息: 黄色系 (#d97706) +- 背景色: 浅灰 (#f5f5f5) + +### 响应式设计 +- 采用 Flexbox 布局 +- 图片采用 Grid 布局(3列) +- 底部操作栏固定定位 + +### 交互细节 +- 图片点击预览大图 +- 修改按钮返回编辑 +- 提交按钮带 loading 状态 +- 成功提示后自动跳转 + +## 📱 页面配置 + +```typescript +// confirm.config.ts +export default { + navigationBarTitleText: '确认处方订单', + navigationBarBackgroundColor: '#fff', + navigationBarTextStyle: 'black', + backgroundColor: '#f5f5f5' +} +``` + +## 🔐 数据类型定义 + +```typescript +interface OrderData { + patient: ClinicPatientUser; + prescription?: ClinicPrescription; + diagnosis: string; + treatmentPlan: string; + decoctionInstructions?: string; + images?: Array<{ + url: string; + name?: string; + uid?: string; + }>; + orderPrice?: string; +} +``` + +## ✅ 与商城订单确认页的区别 + +| 维度 | 商城订单 | 医生开方订单 | +|------|---------|------------| +| **目标用户** | 普通消费者 | 医生专业人员 | +| **业务场景** | 购买商品 | 诊疗开方 | +| **关键信息** | 商品、价格、地址、优惠券 | 患者、诊断、处方、药品 | +| **支付流程** | 立即支付 | 患者后续支付 | +| **地址信息** | 收货地址 | 患者信息 | +| **优惠系统** | 支持优惠券 | 无优惠券 | +| **配送方式** | 快递/自提 | 无配送选择 | +| **特殊功能** | 购物车合并 | 图片上传、煎药说明 | + +## 🚀 使用示例 + +### 从开方页面跳转 + +```typescript +// add.tsx +const submitSucceed = async (values: any) => { + const orderData = { + patient: toUser, + prescription: selectedPrescription, + diagnosis: values.diagnosis, + treatmentPlan: values.treatmentPlan, + decoctionInstructions: values.decoctionInstructions, + images: fileList, + orderPrice: selectedPrescription?.orderPrice + } + + Taro.setStorageSync('tempOrderData', JSON.stringify(orderData)) + + Taro.navigateTo({ + url: '/doctor/orders/confirm' + }) +} +``` + +## 🐛 常见问题 + +### 1. 数据加载失败 +**原因**: 本地存储数据缺失或格式错误 +**解决**: 检查 `tempOrderData` 是否正确存储,确保 JSON 格式正确 + +### 2. 图片无法预览 +**原因**: 图片 URL 格式不正确 +**解决**: 确保图片 URL 是完整的网络地址 + +### 3. 价格计算错误 +**原因**: 药品单价或数量字段缺失 +**解决**: 确保处方明细中的 `unitPrice` 和 `quantity` 字段存在 + +### 4. 提交后跳转失败 +**原因**: 路由路径配置错误 +**解决**: 检查 `app.config.ts` 中的路由配置是否正确 + +## 📝 注意事项 + +1. ⚠️ 确保患者信息完整(特别是 userId) +2. ⚠️ 处方信息必须包含药品明细 +3. ⚠️ 服务费金额可根据业务需求调整 +4. ⚠️ 提交成功后会清除临时数据,无法返回 +5. ⚠️ 图片上传数量限制为5张 + +## 🔧 扩展建议 + +### 可优化点 +1. 添加价格优惠功能 +2. 支持处方模板 +3. 添加患者病历查看 +4. 支持打印处方 +5. 增加订单草稿保存 + +### 待完善功能 +1. 处方审核流程 +2. 电子签名 +3. 处方分享 +4. 统计报表 +5. 患者评价 + +## 📞 技术支持 + +如有问题,请联系开发团队或查阅项目文档。 + +--- + +**版本**: v1.0.0 +**创建日期**: 2025-01-28 +**最后更新**: 2025-01-28 diff --git a/src/doctor/orders/add.tsx b/src/doctor/orders/add.tsx index 752c75f..d19db9e 100644 --- a/src/doctor/orders/add.tsx +++ b/src/doctor/orders/add.tsx @@ -214,7 +214,7 @@ const AddClinicOrder = () => { }) } - // 提交表单 + // 提交表单 - 修改为跳转到确认页 const submitSucceed = async (values: any) => { try { console.log('提交数据:', values) @@ -243,46 +243,31 @@ const AddClinicOrder = () => { return false; } - // 如果选择了患者,在消息内容中添加患者信息 - let content = values.content; - if (selectedPatient) { - content = `[患者: ${selectedPatient.realName || '未知'}] ${content}`; + // 构建订单数据 + 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' } - // 如果选择了处方,在消息内容中添加处方信息 - if (selectedPrescription) { - content = `[处方: ${selectedPrescription.orderNo || '未知'}] ${content}`; - } + // 保存到本地存储 + Taro.setStorageSync('tempOrderData', JSON.stringify(orderData)) - // 添加诊断结果和治疗方案到消息内容 - if (values.diagnosis) { - content = `诊断结果: ${values.diagnosis}\n` + content; - } + console.log('跳转到订单确认页,订单数据:', orderData) - if (values.treatmentPlan) { - content = `治疗方案: ${values.treatmentPlan}\n` + content; - } - - if (values.decoctionInstructions) { - content = `煎药说明: ${values.decoctionInstructions}\n` + content; - } - - // 执行新增或更新操作 - await addClinicOrder({}); - - Taro.showToast({ - title: `发送成功`, - icon: 'success' - }); - - setTimeout(() => { - Taro.navigateBack(); - }, 1000); + // 跳转到确认页 + Taro.navigateTo({ + url: '/doctor/orders/confirm' + }) } catch (error: any) { - console.error('发送失败:', error); + console.error('数据处理失败:', error); Taro.showToast({ - title: `发送失败: ${error.message || error || '未知错误'}`, + title: `数据处理失败: ${error.message || error || '未知错误'}`, icon: 'error' }); } @@ -520,7 +505,7 @@ const AddClinicOrder = () => { )} {/* 底部浮动按钮 */} - formRef.current?.submit()}/> + formRef.current?.submit()}/> ); diff --git a/src/doctor/orders/confirm.config.ts b/src/doctor/orders/confirm.config.ts new file mode 100644 index 0000000..a227d35 --- /dev/null +++ b/src/doctor/orders/confirm.config.ts @@ -0,0 +1,6 @@ +export default { + navigationBarTitleText: '确认处方订单', + navigationBarBackgroundColor: '#fff', + navigationBarTextStyle: 'black', + backgroundColor: '#f5f5f5' +} diff --git a/src/doctor/orders/confirm.scss b/src/doctor/orders/confirm.scss new file mode 100644 index 0000000..e2ba6e6 --- /dev/null +++ b/src/doctor/orders/confirm.scss @@ -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; + } + } + } +} diff --git a/src/doctor/orders/confirm.tsx b/src/doctor/orders/confirm.tsx new file mode 100644 index 0000000..4cd8d57 --- /dev/null +++ b/src/doctor/orders/confirm.tsx @@ -0,0 +1,378 @@ +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 {addClinicOrder} from "@/api/clinic/clinicOrder"; +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(null) + const [loading, setLoading] = useState(false) + const [submitLoading, setSubmitLoading] = useState(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 clinicOrderData = { + userId: orderData.patient.userId, + doctorId: Taro.getStorageSync('UserId'), // 当前医生ID + type: 0, // 订单类型:诊所订单 + title: `${orderData.patient.realName}的处方订单`, + totalPrice: getTotalPrice(), + payPrice: getTotalPrice(), + buyerRemarks: orderData.diagnosis, + merchantRemarks: orderData.treatmentPlan, + comments: JSON.stringify({ + diagnosis: orderData.diagnosis, + treatmentPlan: orderData.treatmentPlan, + decoctionInstructions: orderData.decoctionInstructions, + prescriptionId: orderData.prescription?.id, + images: orderData.images + }), + payStatus: '0', // 未付款 + orderStatus: 0, // 待支付 + deliveryStatus: 10, // 未发货 + } + + console.log('提交订单数据:', clinicOrderData) + + // 调用API创建订单 + await addClinicOrder(clinicOrderData) + + // 清除临时数据 + Taro.removeStorageSync('tempOrderData') + + Taro.showToast({ + title: '处方已发送给患者', + icon: 'success', + duration: 2000 + }) + + setTimeout(() => { + // 跳转到订单列表 + Taro.redirectTo({ + url: '/doctor/orders/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 ( + + 加载中... + + ) + } + + return ( + <> + {/* 页面标题提示 */} + + {/* 患者信息 */} + + 患者信息 + + + + + {orderData.patient.realName} + {orderData.patient.phone} + + {orderData.patient.age}岁 + + + + + + + {/* 诊断信息 */} + + 诊断信息 + } + onClick={handleBack} + > + 修改 + + } + > + + {orderData.diagnosis} + + + 治疗方案 + + + {orderData.treatmentPlan} + + + + + {/* 处方信息 */} + {orderData.prescription && ( + + 处方信息 + + + + RP: {getPrescriptionType()} + + + 共 {orderData.prescription.items?.length || 0} 味药 + + + + + {/* 药品列表 */} + {orderData.prescription.items?.map((item, index) => ( + + + + {item.medicineName} + ¥{item.unitPrice} + + {item.specification || '规格未知'} × {item.quantity || 1} + + + + + 小计:¥{(parseFloat(item.unitPrice || '0') * (item.quantity || 1)).toFixed(2)} + + + + + ))} + + {/* 煎药说明 */} + {orderData.decoctionInstructions && ( + <> + 煎药说明 + + {orderData.decoctionInstructions} + + + )} + + )} + + {/* 上传的图片 */} + {orderData.images && orderData.images.length > 0 && ( + + + + {orderData.images.map((image, index) => ( + { + // 预览图片 + Taro.previewImage({ + urls: orderData.images!.map(img => img.url), + current: image.url + }) + }} + > + + + ))} + + + + )} + + {/* 费用明细 */} + + 费用明细 + 药品费用} + extra={¥{getMedicinePrice()}} + /> + 服务费} + extra={¥{getServiceFee()}} + /> + + 订单总计 + ¥{getTotalPrice()} + + ) + }> + + + + {/* 温馨提示 */} + + 📌 温馨提示 + + • 请仔细核对订单信息 + • 处方发送后,患者将收到支付通知 + • 患者支付成功后,订单将自动流转至配药环节 + • 如需修改处方信息,请点击对应模块的"修改"按钮 + + + + {/* 底部操作按钮 */} + } + onClick={handleConfirmOrder} + /> + + + 订单总计: + ¥{getTotalPrice()} + + + + + + + + ) +} + +export default DoctorOrderConfirm diff --git a/src/pages/chat/chat.tsx b/src/pages/chat/chat.tsx index 0a1fd4a..35214ee 100644 --- a/src/pages/chat/chat.tsx +++ b/src/pages/chat/chat.tsx @@ -11,28 +11,28 @@ import { Button, SearchBar } from '@nutui/nutui-react-taro' -import {ShopUser} from "@/api/shop/shopUser/model"; -import {useUser} from "@/hooks/useUser"; -import {useDoctorUser} from "@/hooks/useDoctorUser"; +import {pageClinicDoctorUser} from "@/api/clinic/clinicDoctorUser"; import {ClinicDoctorUser} from "@/api/clinic/clinicDoctorUser/model"; import {ClinicPatientUser} from "@/api/clinic/clinicPatientUser/model"; -import {pageClinicDoctorUser} from "@/api/clinic/clinicDoctorUser"; +import {pageClinicPatientUser} from "@/api/clinic/clinicPatientUser"; +import navTo from "@/utils/common"; const CustomerIndex = () => { - const [list, setList] = useState([]) + const [isDoctor, setIsDoctor] = useState(false) + const [doctors, setDoctors] = useState([]) + const [patientUsers, setPatientUsers] = useState([]) const [loading, setLoading] = useState(false) - const [activeTab, setActiveTab] = useState('all') const [searchValue, setSearchValue] = useState('') const [displaySearchValue, setDisplaySearchValue] = useState('') const [page, setPage] = useState(1) const [hasMore, setHasMore] = useState(true) - - const {user} = useUser(); - - // 获取客户数据 - const fetchDoctorUsers = useCallback(async (resetPage = false, targetPage?: number) => { + // 获取列表数据 + const fetchData = useCallback(async (resetPage = false, targetPage?: number) => { setLoading(true); + if (Taro.getStorageSync('Doctor')) { + setIsDoctor(true) + } try { const currentPage = resetPage ? 1 : (targetPage || page); @@ -41,33 +41,60 @@ const CustomerIndex = () => { page: currentPage }; - const res = await pageClinicDoctorUser(params); + console.log(isDoctor, 'isDoctor>>>>') + // 获取患者数据 + if (isDoctor || Taro.getStorageSync('Doctor')) { + console.log('获取患者数据') + const res = await pageClinicPatientUser(params); + if (res?.list && res.list.length > 0) { + const mappedList = res.list.map(item => ({ + ...item + })); - if (res?.list && res.list.length > 0) { - const mappedList = res.list.map(item => ({ - ...item - })); + // 如果是重置页面或第一页,直接设置新数据;否则追加数据 + if (resetPage || currentPage === 1) { + setPatientUsers(mappedList); + } else { + setPatientUsers(prevList => prevList.concat(mappedList)); + } - // 如果是重置页面或第一页,直接设置新数据;否则追加数据 - if (resetPage || currentPage === 1) { - setList(mappedList); - } else { - setList(prevList => prevList.concat(mappedList)); + // 正确判断是否还有更多数据 } - - // 正确判断是否还有更多数据 - const hasMoreData = res.list.length >= 10; // 假设每页10条数据 - setHasMore(hasMoreData); } else { - if (resetPage || currentPage === 1) { - setList([]); + // 获取医师数据 + console.log('获取医师数据') + const res = await pageClinicDoctorUser(params); + if (res?.list && res.list.length > 0) { + const mappedList = res.list.map(item => ({ + ...item + })); + + console.log(mappedList, 'mappedList') + // 如果是重置页面或第一页,直接设置新数据;否则追加数据 + if (resetPage || currentPage === 1) { + console.log('设置新数据') + setDoctors(mappedList); + } else { + console.log('追加数据') + setDoctors(prevList => prevList.concat(mappedList)); + } + console.log(doctors, 'doctors1') + // 正确判断是否还有更多数据 + const hasMoreData = res.list.length >= 10; // 假设每页10条数据 + setHasMore(hasMoreData); + console.log(doctors, 'doctors2') + } else { + if (resetPage || currentPage === 1) { + console.log('设置新数据222') + setDoctors([]); + } + setHasMore(false); } - setHasMore(false); } setPage(currentPage); } catch (error) { - console.error('获取客户数据失败:', error); + console.error('获取患者数据失败:', error); Taro.showToast({ title: '加载失败,请重试', icon: 'none' @@ -75,12 +102,22 @@ const CustomerIndex = () => { } finally { setLoading(false); } - }, [activeTab, page]); + }, [page]); const reloadMore = async () => { if (loading || !hasMore) return; // 防止重复加载 const nextPage = page + 1; - await fetchDoctorUsers( false, nextPage); + await fetchData(false, nextPage); + } + + const getSexName = (sex: any) => { + if (sex === '1') { + return '男' + } + if (sex === '2') { + return '女' + } + return '未知' } @@ -93,50 +130,29 @@ const CustomerIndex = () => { return () => clearTimeout(timer); }, [searchValue]); - // 根据搜索条件筛选数据(状态筛选已在API层面处理) - const getFilteredList = () => { - let filteredList = list; - - // 按搜索关键词筛选 - if (displaySearchValue.trim()) { - const keyword = displaySearchValue.trim().toLowerCase(); - filteredList = filteredList.filter(item => - (item.realName && item.realName.toLowerCase().includes(keyword)) || - (item.dealerName && item.dealerName.toLowerCase().includes(keyword)) || - (item.dealerCode && item.dealerCode.toLowerCase().includes(keyword)) || - (item.mobile && item.mobile.includes(keyword)) || - (item.userId && item.userId.toString().includes(keyword)) - ); - } - - return filteredList; - }; - // 初始化数据 useEffect(() => { - fetchDoctorUsers( true).then(); - + fetchData(true).then(); }, []); - // 当activeTab变化时重新获取数据 - useEffect(() => { - setList([]); // 清空列表 - setPage(1); // 重置页码 - setHasMore(true); // 重置加载状态 - fetchDoctorUsers(true); - }, [activeTab]); + // useEffect(() => { + // setPage(1); // 重置页码 + // setHasMore(true); // 重置加载状态 + // fetchData(true); + // }, []); // 监听页面显示,当从其他页面返回时刷新数据 useDidShow(() => { // 刷新当前tab的数据和统计信息 - setList([]); + setPatientUsers([]); + setDoctors([]) setPage(1); setHasMore(true); - fetchDoctorUsers(true); + fetchData(true).then(); }); // 渲染医师项 - const renderDoctorItem = (item: ShopUser) => ( + const renderDoctorItem = (item: ClinicDoctorUser) => ( @@ -156,14 +172,14 @@ const CustomerIndex = () => { - + ); // 渲染患者项 - const renderPatientUserItem = (item: ShopUser) => ( + const renderPatientUserItem = (item: ClinicPatientUser) => ( @@ -171,16 +187,36 @@ const CustomerIndex = () => { - {item.realName} {item.sex} {item.age}岁 + {item.realName} - - 头晕 - - 呕吐 + + { + {getSexName(item.sex)} + } + { + item.age && ( + <> + + {item.age}岁 + + ) + } + { + item.weight && ( + <> + + {item.weight} + + ) + } + + + {item.allergyHistory} - + @@ -188,16 +224,14 @@ const CustomerIndex = () => { // 渲染患者列表 const renderPatientUserList = () => { - const filteredList = getFilteredList(); const isSearching = displaySearchValue.trim().length > 0; - return ( {/* 搜索结果统计 */} {isSearching && ( - 搜索 "{displaySearchValue}" 的结果,共找到 {list.length} 条记录 + 搜索 "{displaySearchValue}" 的结果,共找到 {patientUsers.length} 条记录 )} @@ -223,10 +257,10 @@ const CustomerIndex = () => { } loadMoreText={ - filteredList.length === 0 ? ( + patientUsers.length === 0 ? ( ) : ( @@ -235,13 +269,13 @@ const CustomerIndex = () => { ) } > - {loading && filteredList.length === 0 ? ( + {loading && patientUsers.length === 0 ? ( 加载中... ) : ( - list.map(renderPatientUserItem) + patientUsers.map(renderPatientUserItem) )} @@ -251,16 +285,14 @@ const CustomerIndex = () => { // 渲染医生列表 const renderDoctorList = () => { - const filteredList = getFilteredList(); const isSearching = displaySearchValue.trim().length > 0; - return ( {/* 搜索结果统计 */} {isSearching && ( - 搜索 "{displaySearchValue}" 的结果,共找到 {filteredList.length} 条记录 + 搜索 "{displaySearchValue}" 的结果,共找到 {doctors.length} 条记录 )} @@ -286,7 +318,7 @@ const CustomerIndex = () => { } loadMoreText={ - filteredList.length === 0 ? ( + doctors.length === 0 ? ( { ) } > - {loading && filteredList.length === 0 ? ( + {loading && doctors.length === 0 ? ( 加载中... ) : ( - filteredList.map(renderDoctorItem) + doctors.map(renderDoctorItem) )} @@ -318,7 +350,7 @@ const CustomerIndex = () => { setSearchValue(value)} onClear={() => { setSearchValue(''); @@ -329,10 +361,9 @@ const CustomerIndex = () => { {/* 患者列表 */} - {!user?.certification && (renderPatientUserList())} + {isDoctor && (renderPatientUserList())} {/* 医生列表 */} - {user?.certification && (renderDoctorList())} - + {!isDoctor && (renderDoctorList())} ); diff --git a/src/passport/agreement.tsx b/src/passport/agreement.tsx index 5ce26d1..8db6534 100644 --- a/src/passport/agreement.tsx +++ b/src/passport/agreement.tsx @@ -1,30 +1,48 @@ -import {useEffect, useState} from "react"; import Taro from '@tarojs/taro' +import {useEffect, useState} from 'react' +import {Loading} from '@nutui/nutui-react-taro' import {View, RichText} from '@tarojs/components' +import {wxParse} from "@/utils/common"; +import {getCmsArticleByCode} from "@/api/cms/cmsArticle"; +import {CmsArticle} from "@/api/cms/cmsArticle/model" +import Line from "@/components/Gap"; -const Agreement = () => { +function Agreement() { + const [loading, setLoading] = useState(true) + // 文章详情 + const [item, setItem] = useState() + const reload = async () => { + const item = await getCmsArticleByCode('xieyi') - const [content, setContent] = useState('') - const reload = () => { - Taro.hideTabBar() - setContent('

' + - '欢迎使用' + - ' ' + - '【WebSoft】' + - '服务协议 ' + - '

') + if (item && item.content) { + item.content = wxParse(item.content) + setItem(item) + Taro.setNavigationBarTitle({ + title: `${item?.categoryName}` + }) + } } useEffect(() => { - reload() - }, []) + reload().then(() => { + setLoading(false) + }); + }, []); + + if (loading) { + return ( + 加载中 + ) + } return ( - <> - - +
+ + - + +
) } + export default Agreement diff --git a/src/passport/login.tsx b/src/passport/login.tsx index bec45c8..bd0a7ea 100644 --- a/src/passport/login.tsx +++ b/src/passport/login.tsx @@ -1,55 +1,118 @@ import {useEffect, useState} from "react"; import Taro from '@tarojs/taro' -import {Input, Radio, Button} from '@nutui/nutui-react-taro' +import {View} from '@tarojs/components' +import {Radio, Button} from '@nutui/nutui-react-taro' +import {getStoredInviteParams} from "@/utils/invite"; +import {TenantId} from "@/config/app"; +import {useUser} from "@/hooks/useUser"; const Login = () => { const [isAgree, setIsAgree] = useState(false) + + const {loginUser} = useUser(); + const reload = () => { Taro.hideTabBar() } + /* 获取用户手机号 */ + const handleGetPhoneNumber = ({detail}: { detail: { code?: string, encryptedData?: string, iv?: string } }) => { + const {code, encryptedData, iv} = detail + + // 获取存储的邀请参数 + const inviteParams = getStoredInviteParams() + const refereeId = inviteParams?.inviter ? parseInt(inviteParams.inviter) : 0 + + Taro.login({ + success: function (loginRes) { + if (code) { + Taro.request({ + url: 'https://server.websoft.top/api/wx-login/loginByMpWxPhone', + method: 'POST', + data: { + authCode: loginRes.code, + code, + encryptedData, + iv, + notVerifyPhone: true, + refereeId: refereeId, // 使用解析出的推荐人ID + sceneType: 'save_referee', + tenantId: TenantId + }, + header: { + 'content-type': 'application/json', + TenantId + }, + success: function (res) { + if (res.data.code == 1) { + Taro.showToast({ + title: res.data.message, + icon: 'error', + duration: 2000 + }) + return false; + } + // 登录成功 + const token = res.data.data.access_token; + const userData = res.data.data.user; + + // 使用useUser Hook的loginUser方法更新状态 + loginUser(token, userData); + + // 显示登录成功提示 + Taro.showToast({ + title: '登录成功', + icon: 'success', + duration: 1500 + }) + + // 返回上一页 + Taro.navigateBack(); + } + }) + } else { + console.log('登录失败!') + } + } + }) + } + useEffect(() => { reload() }, []) return ( <> -
-
账号登录
+ + 授权登录 <> -
- -
-
- -
- -
- -
-
- -
- {/**/} + {isAgree && ( + + + + )} + {!isAgree && ( + + 授权手机号并登录 + + )} -
+ setIsAgree(!isAgree)}> setIsAgree(!isAgree)}>勾选表示您已阅读并同意 Taro.navigateTo({url: '/passport/agreement'})} className={'text-blue-600'}>《服务协议及隐私政策》 -
-
+
+ ) } diff --git a/src/user/about/index.tsx b/src/user/about/index.tsx index fec73d9..0c88242 100644 --- a/src/user/about/index.tsx +++ b/src/user/about/index.tsx @@ -1,42 +1,22 @@ import {useEffect, useState} from "react"; -import Taro from '@tarojs/taro'; -import {listCmsArticle} from "@/api/cms/cmsArticle"; +import {getCmsArticleByCode} from "@/api/cms/cmsArticle"; import {Avatar, Cell, Divider} from '@nutui/nutui-react-taro' -import {ArrowRight} from '@nutui/icons-react-taro' -import {CmsNavigation} from "@/api/cms/cmsNavigation/model"; -import {listCmsNavigation} from "@/api/cms/cmsNavigation"; // 显示html富文本 import {View, RichText} from '@tarojs/components' -import {listCmsDesign} from "@/api/cms/cmsDesign"; -import {CmsDesign} from "@/api/cms/cmsDesign/model"; -import {type Config} from "@/api/cms/cmsWebsiteField/model"; -import {configWebsiteField} from "@/api/cms/cmsWebsiteField"; +import {useConfig} from "@/hooks/useConfig"; +import {useShopInfo} from "@/hooks/useShopInfo"; +import Line from "@/components/Gap"; +import {CmsArticle} from "@/api/cms/cmsArticle/model"; const Helper = () => { - const [nav, setNav] = useState() - const [design, setDesign] = useState() - const [category, setCategory] = useState([]) - const [config, setConfig] = useState() + const [about, setAbout] = useState() + const {config} = useConfig() + const {shopInfo} = useShopInfo() const reload = async () => { - const navs = await listCmsNavigation({model: 'page', parentId: 0}); - if (navs.length > 0) { - const nav = navs[0]; - setNav(nav); - // 查询页面信息 - const design = await listCmsDesign({categoryId: nav.navigationId}) - setDesign(design[0]) - // 查询子栏目 - const category = await listCmsNavigation({parentId: nav.navigationId}) - category.map(async (item, index) => { - category[index].articles = await listCmsArticle({categoryId: item.navigationId}); - }) - setCategory(category) - // 查询字段 - const configInfo = await configWebsiteField({}) - setConfig(configInfo) - } + const aboutInfo = await getCmsArticleByCode('about') + setAbout(aboutInfo) } useEffect(() => { @@ -44,51 +24,38 @@ const Helper = () => { }, []); return ( -
+ - {nav && ( - - - - {design?.comments} - - - - + + + + {shopInfo?.appName} - )} + + + + + + + + + + + + )}> - {category.map((item, index) => ( - - {item.categoryName} -
- )} - description={( - <> - - {item.articles?.map((child, _) => ( - - Taro.navigateTo({url: `/cms/detail/index?id=${child.articleId}`})}>{child.title} - - - ))} - - )} - > - - ))} 服务热线:{config?.tel} - 工作日:{config?.workDay} + 工作日:{config?.deliveryText} - + + ); };