feat(doctor): 新增医生聊天与订单确认功能

- 在医生聊天页面增加患者与医师列表渲染逻辑
- 新增订单确认页面,支持处方信息预览与发送
- 更新开方页面跳转逻辑,将数据通过本地存储传递至确认页-优化聊天页面初始化逻辑,区分医生与普通用户角色-修复 CMS 文章详情内容判断逻辑,避免空内容解析异常
- 新增关于我们页面,支持富文本内容展示- 调整医生相关模型字段,如添加头像、替换手机号字段为 phone
- 补充页面配置文件,完善导航栏标题与样式设置
- 新增医生订单确认页样式文件及详细交互布局
- 提供订单确认页使用说明文档,涵盖功能概述与技术实现细节
This commit is contained in:
2025-10-28 17:24:23 +08:00
parent 18940c8fd6
commit 25eb1366c5
17 changed files with 1540 additions and 242 deletions

View File

@@ -12,8 +12,10 @@ export interface ClinicDoctorUser {
userId?: number;
// 姓名
realName?: string;
// 头像
avatar?: string;
// 手机号
mobile?: string;
phone?: string;
// 支付密码
payPassword?: string;
// 当前可提现佣金

View File

@@ -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",

View File

@@ -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处理)

View File

@@ -0,0 +1,3 @@
export default definePageConfig({
navigationBarTitleText: '聊天'
})

158
src/chat/doctor/index.tsx Normal file
View File

@@ -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<ClinicDoctorUser>()
const [list, setList] = useState<ShopChatMessage[]>([])
const [isDoctor, setIsDoctor] = useState<boolean>(false)
const [doctors, setDoctors] = useState<ClinicDoctorUser[]>([])
const [patientUsers, setPatientUsers] = useState<ClinicPatientUser[]>([])
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 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) => (
<View key={item.userId} className="bg-white rounded-lg p-4 mb-3 shadow-sm">
<View className="flex items-center">
<View className="flex-1 flex justify-between items-center">
<View className="flex justify-between">
<Avatar src={item.avatar} size={'large'}/>
<View className={'flex flex-col mx-3'}>
<Text className="font-semibold text-gray-800 mr-2">
{item.realName}
</Text>
<View>
<Tag background="#f3f3f3" color="#999999"></Tag>
</View>
<View className={'my-1'}>
<Text className={'text-gray-400 text-xs'}> 1 </Text>
<Divider direction="vertical"/>
<Text className={'text-gray-400 text-xs'}> 3 </Text>
</View>
</View>
</View>
<Button type="warning"></Button>
</View>
</View>
</View>
);
// 渲染患者项
const renderPatientUserItem = (item: ClinicPatientUser) => (
<View key={item.userId} className="bg-white rounded-lg p-4 mb-3 shadow-sm">
<View className="flex items-center">
<View className="flex-1 flex justify-between items-center">
<View className="flex justify-between">
<Avatar src={item.avatar} size={'large'}/>
<View className={'flex flex-col mx-3'}>
<Text className="font-semibold text-gray-800 mr-2">
{item.realName}
</Text>
<View>
{
<Text
className={'text-gray-400 text-xs'}>{item.sex}</Text>
}
{
item.age && (
<>
<Divider direction="vertical"/>
<Text className={'text-gray-400 text-xs'}>{item.age}</Text>
</>
)
}
{
item.weight && (
<>
<Divider direction="vertical"/>
<Text className={'text-gray-400 text-xs'}>{item.weight}</Text>
</>
)
}
</View>
<View>
<Text className={'text-gray-400 text-xs'}>{item.allergyHistory}</Text>
</View>
</View>
</View>
<Button type="warning" onClick={() => navTo(`/doctor/orders/add?id=${item.userId}`)}></Button>
</View>
</View>
</View>
);
return (
<View className="min-h-screen bg-gray-50 w-full">
<View className={'p-4'}>
{list?.map(renderPatientUserItem)}
</View>
<View className={'fixed bottom-0 w-full bg-orange-50 pt-2 pb-8'}>
<View className={'flex flex-1 items-center justify-between'}>
<Voice className={'mx-2'} />
<Input className={'w-full'} style={{
borderRadius: '6px',
paddingLeft: '12px',
paddingRight: '12px'
}} />
<FaceMild size={26} className={'ml-2'} />
<AddCircle size={26} className={'mx-2'} />
</View>
</View>
</View>
);
};
export default CustomerIndex;

View File

@@ -0,0 +1,4 @@
export default definePageConfig({
navigationBarTitleText: '关于我们',
navigationBarTextStyle: 'black'
})

48
src/cms/about/index.tsx Normal file
View File

@@ -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<boolean>(true)
// 文章详情
const [item, setItem] = useState<CmsArticle>()
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 (
<Loading className={'px-2'}></Loading>
)
}
return (
<div className={'bg-white'}>
<View className={'content p-4'}>
<RichText nodes={item?.content}/>
</View>
<Line height={44}/>
</div>
)
}
export default Index

View File

@@ -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({

322
src/doctor/orders/README.md Normal file
View File

@@ -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<OrderData | null>(null)
const [loading, setLoading] = useState<boolean>(false)
const [submitLoading, setSubmitLoading] = useState<boolean>(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

View File

@@ -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 = () => {
)}
{/* 底部浮动按钮 */}
<FixedButton text={'生成处方并发送给患者'} onClick={() => formRef.current?.submit()}/>
<FixedButton text={'下一步:确认订单信息'} onClick={() => formRef.current?.submit()}/>
</>
);

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,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<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 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 (
<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

@@ -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<any[]>([])
const [isDoctor, setIsDoctor] = useState<boolean>(false)
const [doctors, setDoctors] = useState<ClinicDoctorUser[]>([])
const [patientUsers, setPatientUsers] = useState<ClinicPatientUser[]>([])
const [loading, setLoading] = useState<boolean>(false)
const [activeTab, setActiveTab] = useState<any>('all')
const [searchValue, setSearchValue] = useState<string>('')
const [displaySearchValue, setDisplaySearchValue] = useState<string>('')
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) => (
<View key={item.userId} className="bg-white rounded-lg p-4 mb-3 shadow-sm">
<View className="flex items-center">
<View className="flex-1 flex justify-between items-center">
@@ -156,14 +172,14 @@ const CustomerIndex = () => {
</View>
</View>
</View>
<Button type="warning"></Button>
<Button type="warning" onClick={() => navTo(`/chat/doctor/index?id=${item.userId}`)}></Button>
</View>
</View>
</View>
);
// 渲染患者项
const renderPatientUserItem = (item: ShopUser) => (
const renderPatientUserItem = (item: ClinicPatientUser) => (
<View key={item.userId} className="bg-white rounded-lg p-4 mb-3 shadow-sm">
<View className="flex items-center">
<View className="flex-1 flex justify-between items-center">
@@ -171,16 +187,36 @@ const CustomerIndex = () => {
<Avatar src={item.avatar} size={'large'}/>
<View className={'flex flex-col mx-3'}>
<Text className="font-semibold text-gray-800 mr-2">
{item.realName} {item.sex} {item.age}
{item.realName}
</Text>
<View className={'my-1'}>
<Text className={'text-gray-400 text-xs'}></Text>
<Divider direction="vertical"/>
<Text className={'text-gray-400 text-xs'}></Text>
<View>
{
<Text
className={'text-gray-400 text-xs'}>{getSexName(item.sex)}</Text>
}
{
item.age && (
<>
<Divider direction="vertical"/>
<Text className={'text-gray-400 text-xs'}>{item.age}</Text>
</>
)
}
{
item.weight && (
<>
<Divider direction="vertical"/>
<Text className={'text-gray-400 text-xs'}>{item.weight}</Text>
</>
)
}
</View>
<View>
<Text className={'text-gray-400 text-xs'}>{item.allergyHistory}</Text>
</View>
</View>
</View>
<Button type="warning"></Button>
<Button type="warning" onClick={() => navTo(`/doctor/orders/add?id=${item.userId}`)}></Button>
</View>
</View>
</View>
@@ -188,16 +224,14 @@ const CustomerIndex = () => {
// 渲染患者列表
const renderPatientUserList = () => {
const filteredList = getFilteredList();
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}
"{displaySearchValue}" {patientUsers.length}
</Text>
</View>
)}
@@ -223,10 +257,10 @@ const CustomerIndex = () => {
</>
}
loadMoreText={
filteredList.length === 0 ? (
patientUsers.length === 0 ? (
<Empty
style={{backgroundColor: 'transparent'}}
description={loading ? "加载中..." : "暂无客户数据"}
description={loading ? "加载中..." : "暂无患者数据"}
/>
) : (
<View className={'h-3 flex items-center justify-center'}>
@@ -235,13 +269,13 @@ const CustomerIndex = () => {
)
}
>
{loading && filteredList.length === 0 ? (
{loading && patientUsers.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(renderPatientUserItem)
patientUsers.map(renderPatientUserItem)
)}
</InfiniteLoading>
</View>
@@ -251,16 +285,14 @@ const CustomerIndex = () => {
// 渲染医生列表
const renderDoctorList = () => {
const filteredList = getFilteredList();
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}" {filteredList.length}
"{displaySearchValue}" {doctors.length}
</Text>
</View>
)}
@@ -286,7 +318,7 @@ const CustomerIndex = () => {
</>
}
loadMoreText={
filteredList.length === 0 ? (
doctors.length === 0 ? (
<Empty
style={{backgroundColor: 'transparent'}}
description={loading ? "加载中..." : "暂无医生数据"}
@@ -298,13 +330,13 @@ const CustomerIndex = () => {
)
}
>
{loading && filteredList.length === 0 ? (
{loading && doctors.length === 0 ? (
<View className="flex items-center justify-center py-8">
<Loading/>
<Text className="text-gray-500 mt-2 ml-2">...</Text>
</View>
) : (
filteredList.map(renderDoctorItem)
doctors.map(renderDoctorItem)
)}
</InfiniteLoading>
</View>
@@ -318,7 +350,7 @@ const CustomerIndex = () => {
<View className="bg-white pt-2 border-b border-gray-100">
<SearchBar
value={searchValue}
placeholder="搜索客户名称、手机号"
placeholder="搜索患者名称、手机号"
onChange={(value) => setSearchValue(value)}
onClear={() => {
setSearchValue('');
@@ -329,10 +361,9 @@ const CustomerIndex = () => {
</View>
{/* 患者列表 */}
{!user?.certification && (renderPatientUserList())}
{isDoctor && (renderPatientUserList())}
{/* 医生列表 */}
{user?.certification && (renderDoctorList())}
{!isDoctor && (renderDoctorList())}
</View>
);

View File

@@ -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<boolean>(true)
// 文章详情
const [item, setItem] = useState<CmsArticle>()
const reload = async () => {
const item = await getCmsArticleByCode('xieyi')
const [content, setContent] = useState<any>('')
const reload = () => {
Taro.hideTabBar()
setContent('<p>' +
'<span style="font-size: 14px;">欢迎使用</span>' +
'<span style="font-size: 14px;">&nbsp;</span>' +
'<span style="font-size: 14px;"><strong><span style="color: rgb(255, 0, 0);">【WebSoft】</span></strong></span>' +
'<span style="font-size: 14px;">服务协议&nbsp;</span>' +
'</p>')
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 (
<Loading className={'px-2'}></Loading>
)
}
return (
<>
<View className={'content text-gray-700 text-sm p-4'}>
<RichText nodes={content}/>
<div className={'bg-white'}>
<View className={'content p-4'}>
<RichText nodes={item?.content}/>
</View>
</>
<Line height={44}/>
</div>
)
}
export default Agreement

View File

@@ -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 (
<>
<div className={'flex flex-col justify-center px-5'}>
<div className={'text-3xl text-center py-5 font-normal my-10'}></div>
<View className={'flex flex-col justify-center px-5'}>
<View className={'text-3xl text-center py-5 font-normal my-10'}></View>
<>
<div className={'flex flex-col justify-between items-center my-2'}>
<Input type="text" placeholder="手机号" maxLength={11}
style={{backgroundColor: '#ffffff', borderRadius: '8px'}}/>
</div>
<div className={'flex flex-col justify-between items-center my-2'}>
<Input type="password" placeholder="密码" style={{backgroundColor: '#ffffff', borderRadius: '8px'}}/>
</div>
<div className={'flex justify-between my-2 text-left px-1'}>
<a href={'#'} className={'text-blue-600 text-sm'}
onClick={() => Taro.navigateTo({url: '/passport/forget'})}></a>
<a href={'#'} className={'text-blue-600 text-sm'}
onClick={() => Taro.navigateTo({url: '/passport/register'})}></a>
</div>
<div className={'flex justify-center my-5'}>
<Button type="info" size={'large'} className={'w-full rounded-lg p-2'} disabled={!isAgree}></Button>
</div>
<div className={'my-2 flex fixed justify-center bottom-20 left-0 text-sm items-center text-center w-full'}>
<Button onClick={() => Taro.navigateTo({url: '/passport/setting'})}></Button>
</div>
{/*<div className={'w-full fixed bottom-20 my-2 flex justify-center text-sm items-center text-center'}>*/}
{/* 没有账号?<a href={''} onClick={() => Taro.navigateTo({url: '/passport/register'})}*/}
{/* className={'text-blue-600'}>立即注册</a>*/}
{/*</div>*/}
{isAgree && (
<View className={'flex justify-center my-5 py-3 rounded-lg '} style={{
backgroundColor: '#22c55e',
}}>
<Button open-type="getPhoneNumber"
style={{color: '#ffffff', height: 'auto'}}
onGetPhoneNumber={handleGetPhoneNumber} size={'large'} className={'w-full rounded-lg p-2'}
disabled={!isAgree}></Button>
</View>
)}
{!isAgree && (
<View className={'flex justify-center my-5 py-1 rounded-lg '} style={{
backgroundColor: '#93a3af',
}}>
<View style={{color: '#ffffff', height: 'auto'}} className={'w-full rounded-lg text-sm text-center p-2'}></View>
</View>
)}
</>
<div className={'my-2 flex text-sm items-center px-1'}>
<View className={'my-2 flex text-sm items-center px-1'}>
<Radio style={{color: '#333333'}} checked={isAgree} onClick={() => setIsAgree(!isAgree)}></Radio>
<span className={'text-gray-400'} onClick={() => setIsAgree(!isAgree)}></span><a
onClick={() => Taro.navigateTo({url: '/passport/agreement'})}
className={'text-blue-600'}></a>
</div>
</div>
</View>
</View>
</>
)
}

View File

@@ -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<CmsNavigation>()
const [design, setDesign] = useState<CmsDesign>()
const [category, setCategory] = useState<CmsNavigation[]>([])
const [config, setConfig] = useState<Config>()
const [about, setAbout] = useState<CmsArticle>()
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 (
<div className={'px-3'}>
<View className={'px-3'}>
<Cell>
{nav && (
<View className={'flex flex-col justify-center items-center w-full'}>
<Avatar
src={design?.photo}
size={'100'}
/>
<View className={'font-bold text-sm'}>
{design?.comments}
</View>
<View className={'text-left py-3 text-gray-600'}>
<RichText
nodes={design?.content || '关于我们的简单描述'}/>
</View>
<View className={'flex flex-col justify-center items-center w-full'}>
<Avatar
src={shopInfo?.logo}
size={'100'}
/>
<View className={'font-bold text-sm'}>
{shopInfo?.appName}
</View>
)}
<View className={'text-left py-3 text-gray-600'}>
<RichText
nodes={shopInfo?.description || '关于我们的简单描述'}/>
</View>
</View>
</Cell>
<Cell title={about?.title} description={(
<>
<Divider/>
<View className={'text-left text-gray-500'}>
<RichText
nodes={about?.content}/>
</View>
</>
)}>
</Cell>
{category.map((item, index) => (
<Cell
title={(
<div className={'font-bold'} id={`${index}`}>
{item.categoryName}
</div>
)}
description={(
<>
<Divider/>
{item.articles?.map((child, _) => (
<View className={'item flex justify-between items-center my-2'}>
<View
onClick={() => Taro.navigateTo({url: `/cms/detail/index?id=${child.articleId}`})}>{child.title}</View>
<ArrowRight size={16} className={'text-gray-400'}/>
</View>
))}
</>
)}
>
</Cell>
))}
<Cell className={'flex flex-col'}>
<span>线{config?.tel}</span>
<span>{config?.workDay}</span>
<span>{config?.deliveryText}</span>
</Cell>
</div>
<Line height={44}/>
</View>
);
};