diff --git a/config/env.ts b/config/env.ts index 48b2cc4..bcb4783 100644 --- a/config/env.ts +++ b/config/env.ts @@ -8,13 +8,13 @@ export const ENV_CONFIG = { }, // 生产环境 production: { - API_BASE_URL: 'https://cms-api.websoft.top/api', + API_BASE_URL: 'https://mp-api.websoft.top/api', APP_NAME: '通源堂健康生态平台', DEBUG: 'false', }, // 测试环境 test: { - API_BASE_URL: 'https://cms-api.s209.websoft.top/api', + API_BASE_URL: 'https://mp-api.websoft.top/api', APP_NAME: '测试环境', DEBUG: 'true', } diff --git a/src/api/clinic/clinicPatientUser/model/index.ts b/src/api/clinic/clinicPatientUser/model/index.ts index 76b2d4f..3de55a2 100644 --- a/src/api/clinic/clinicPatientUser/model/index.ts +++ b/src/api/clinic/clinicPatientUser/model/index.ts @@ -13,7 +13,7 @@ export interface ClinicPatientUser { // 姓名 realName?: string; // 手机号 - mobile?: string; + phone?: string; // 支付密码 payPassword?: string; // 当前可提现佣金 diff --git a/src/api/clinic/clinicPrescription/model/index.ts b/src/api/clinic/clinicPrescription/model/index.ts index 44d263f..78561b0 100644 --- a/src/api/clinic/clinicPrescription/model/index.ts +++ b/src/api/clinic/clinicPrescription/model/index.ts @@ -1,4 +1,5 @@ import type { PageParam } from '@/api/index'; +import {ClinicPrescriptionItem} from "@/api/clinic/clinicPrescriptionItem/model"; /** * 处方主表 @@ -45,6 +46,8 @@ export interface ClinicPrescription { createTime?: string; // 修改时间 updateTime?: string; + // 药方信息 + items?: ClinicPrescriptionItem[]; } /** diff --git a/src/api/clinic/clinicPrescriptionItem/model/index.ts b/src/api/clinic/clinicPrescriptionItem/model/index.ts index 61a4813..bb34b76 100644 --- a/src/api/clinic/clinicPrescriptionItem/model/index.ts +++ b/src/api/clinic/clinicPrescriptionItem/model/index.ts @@ -13,6 +13,10 @@ export interface ClinicPrescriptionItem { prescriptionNo?: string; // 关联药品 medicineId?: number; + // 药品名称 + medicineName?: string; + // 规格(如“10g”) + specification?: string; // 剂量(如“10g”) dosage?: string; // 用法频率(如“每日三次”) diff --git a/src/app.config.ts b/src/app.config.ts index 563d238..caaca2b 100644 --- a/src/app.config.ts +++ b/src/app.config.ts @@ -81,6 +81,8 @@ export default { "withdraw/index", "orders/index", "orders/add", + "orders/selectPatient", + "orders/selectPrescription", "team/index", "qrcode/index", "invite-stats/index", @@ -93,6 +95,7 @@ export default { { "root": "clinic", "pages": [ + "index", "clinicPatientUser/add", "clinicDoctorUser/add" ] @@ -164,4 +167,4 @@ export default { "desc": "你的位置信息将用于小程序位置接口的效果展示" } } -} +} \ No newline at end of file diff --git a/src/clinic/clinicPatientUser/index.config.ts b/src/clinic/clinicPatientUser/index.config.ts new file mode 100644 index 0000000..539f8b2 --- /dev/null +++ b/src/clinic/clinicPatientUser/index.config.ts @@ -0,0 +1,3 @@ +export default definePageConfig({ + navigationBarTitleText: '患者管理' +}) diff --git a/src/clinic/clinicPatientUser/index.tsx b/src/clinic/clinicPatientUser/index.tsx new file mode 100644 index 0000000..33cec2e --- /dev/null +++ b/src/clinic/clinicPatientUser/index.tsx @@ -0,0 +1,583 @@ +import {useState, useEffect, useCallback} from 'react' +import {View, Text} from '@tarojs/components' +import Taro, {useDidShow} from '@tarojs/taro' +import {Loading, InfiniteLoading, Empty, Space, Tabs, TabPane, Tag, Button, SearchBar} from '@nutui/nutui-react-taro' +import {Phone, AngleDoubleLeft} from '@nutui/icons-react-taro' +import type {ShopDealerApply, ShopDealerApply as UserType} from "@/api/shop/shopDealerApply/model"; +import { + CustomerStatus, + getStatusText, + getStatusTagType, + getStatusOptions, + mapApplyStatusToCustomerStatus, + mapCustomerStatusToApplyStatus +} from '@/utils/customerStatus'; +import FixedButton from "@/components/FixedButton"; +import navTo from "@/utils/common"; +import {pageShopDealerApply, removeShopDealerApply, updateShopDealerApply} from "@/api/shop/shopDealerApply"; + +// 扩展User类型,添加客户状态和保护天数 +interface CustomerUser extends UserType { + customerStatus?: CustomerStatus; + protectDays?: number; // 剩余保护天数 +} + +const CustomerIndex = () => { + const [list, setList] = 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) + + // Tab配置 + const tabList = getStatusOptions(); + + // 复制手机号 + 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 = (customer: CustomerUser) => { + Taro.showModal({ + title: '编辑跟进情况', + // @ts-ignore + editable: true, + placeholderText: '请输入跟进情况', + content: customer.comments || '', + success: async (res) => { + // @ts-ignore + if (res.confirm && res.content !== undefined) { + try { + // 更新跟进情况 + await updateShopDealerApply({ + ...customer, + // @ts-ignore + comments: res.content.trim() + }); + + Taro.showToast({ + title: '更新成功', + icon: 'success' + }); + + // 刷新列表 + setList([]); + setPage(1); + setHasMore(true); + fetchCustomerData(activeTab, true); + } catch (error) { + console.error('更新跟进情况失败:', error); + Taro.showToast({ + title: '更新失败,请重试', + icon: 'error' + }); + } + } + } + }); + }; + + // 计算剩余保护天数(基于过期时间) + const calculateProtectDays = (expirationTime?: string, applyTime?: string): number => { + try { + // 优先使用过期时间字段 + if (expirationTime) { + const expDate = new Date(expirationTime.replace(' ', 'T')); + const now = new Date(); + + // 计算剩余毫秒数 + const remainingMs = expDate.getTime() - now.getTime(); + + // 转换为天数,向上取整 + const remainingDays = Math.ceil(remainingMs / (1000 * 60 * 60 * 24)); + + console.log('=== 基于过期时间计算 ==='); + console.log('过期时间:', expirationTime); + console.log('当前时间:', now.toLocaleString()); + console.log('剩余天数:', remainingDays); + console.log('======================'); + + return Math.max(0, remainingDays); + } + + // 如果没有过期时间,回退到基于申请时间计算 + if (!applyTime) return 0; + + const protectionPeriod = 7; // 保护期7天 + + // 解析申请时间 + let applyDate: Date; + if (applyTime.includes('T')) { + applyDate = new Date(applyTime); + } else { + applyDate = new Date(applyTime.replace(' ', 'T')); + } + + // 获取当前时间 + const now = new Date(); + + // 只比较日期部分,忽略时间 + const applyDateOnly = new Date(applyDate.getFullYear(), applyDate.getMonth(), applyDate.getDate()); + const currentDateOnly = new Date(now.getFullYear(), now.getMonth(), now.getDate()); + + // 计算已经过去的天数 + const timeDiff = currentDateOnly.getTime() - applyDateOnly.getTime(); + const daysPassed = Math.floor(timeDiff / (1000 * 60 * 60 * 24)); + + // 计算剩余保护天数 + const remainingDays = protectionPeriod - daysPassed; + + console.log('=== 基于申请时间计算 ==='); + console.log('申请时间:', applyTime); + console.log('已过去天数:', daysPassed); + console.log('剩余保护天数:', remainingDays); + console.log('======================'); + + return Math.max(0, remainingDays); + } catch (error) { + console.error('日期计算错误:', error); + return 0; + } + }; + + + // 获取客户数据 + const fetchCustomerData = useCallback(async (statusFilter?: CustomerStatus, resetPage = false, targetPage?: number) => { + setLoading(true); + try { + const currentPage = resetPage ? 1 : (targetPage || page); + + // 构建API参数,根据状态筛选 + const params: any = { + type: 4, + page: currentPage + }; + const applyStatus = mapCustomerStatusToApplyStatus(statusFilter || activeTab); + if (applyStatus !== undefined) { + params.applyStatus = applyStatus; + } + + const res = await pageShopDealerApply(params); + + if (res?.list && res.list.length > 0) { + // 正确映射状态并计算保护天数 + const mappedList = res.list.map(customer => ({ + ...customer, + customerStatus: mapApplyStatusToCustomerStatus(customer.applyStatus || 10), + protectDays: calculateProtectDays(customer.expirationTime, customer.applyTime || customer.createTime || '') + })); + + // 如果是重置页面或第一页,直接设置新数据;否则追加数据 + 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([]); + } + setHasMore(false); + } + + setPage(currentPage); + } catch (error) { + console.error('获取客户数据失败:', error); + Taro.showToast({ + title: '加载失败,请重试', + icon: 'none' + }); + } finally { + setLoading(false); + } + }, [activeTab, page]); + + const reloadMore = async () => { + if (loading || !hasMore) return; // 防止重复加载 + const nextPage = page + 1; + await fetchCustomerData(activeTab, false, nextPage); + } + + + // 防抖搜索功能 + useEffect(() => { + const timer = setTimeout(() => { + setDisplaySearchValue(searchValue); + }, 300); // 300ms 防抖 + + return () => clearTimeout(timer); + }, [searchValue]); + + // 根据搜索条件筛选数据(状态筛选已在API层面处理) + const getFilteredList = () => { + let filteredList = list; + + // 按搜索关键词筛选 + if (displaySearchValue.trim()) { + const keyword = displaySearchValue.trim().toLowerCase(); + filteredList = filteredList.filter(customer => + (customer.realName && customer.realName.toLowerCase().includes(keyword)) || + (customer.dealerName && customer.dealerName.toLowerCase().includes(keyword)) || + (customer.dealerCode && customer.dealerCode.toLowerCase().includes(keyword)) || + (customer.mobile && customer.mobile.includes(keyword)) || + (customer.userId && customer.userId.toString().includes(keyword)) + ); + } + + return filteredList; + }; + + // 获取各状态的统计数量 + const [statusCounts, setStatusCounts] = useState({ + all: 0, + pending: 0, + signed: 0, + cancelled: 0 + }); + + // 获取所有状态的统计数量 + const fetchStatusCounts = useCallback(async () => { + try { + // 并行获取各状态的数量 + const [allRes, pendingRes, signedRes, cancelledRes] = await Promise.all([ + pageShopDealerApply({type: 4}), // 全部 + pageShopDealerApply({applyStatus: 10, type: 4}), // 跟进中 + pageShopDealerApply({applyStatus: 20, type: 4}), // 已签约 + pageShopDealerApply({applyStatus: 30, type: 4}) // 已取消 + ]); + + setStatusCounts({ + all: allRes?.count || 0, + pending: pendingRes?.count || 0, + signed: signedRes?.count || 0, + cancelled: cancelledRes?.count || 0 + }); + } catch (error) { + console.error('获取状态统计失败:', error); + } + }, []); + + const getStatusCounts = () => statusCounts; + + // 取消操作 + const handleCancel = (customer: ShopDealerApply) => { + updateShopDealerApply({ + ...customer, + applyStatus: 30 + }).then(() => { + Taro.showToast({ + title: '取消成功', + icon: 'success' + }); + // 重新加载当前tab的数据 + setList([]); + setPage(1); + setHasMore(true); + fetchCustomerData(activeTab, true).then(); + fetchStatusCounts().then(); + }) + }; + + // 删除 + const handleDelete = (customer: ShopDealerApply) => { + removeShopDealerApply(customer.applyId).then(() => { + Taro.showToast({ + title: '删除成功', + icon: 'success' + }); + // 刷新当前tab的数据 + setList([]); + setPage(1); + setHasMore(true); + fetchCustomerData(activeTab, true).then(); + fetchStatusCounts().then(); + }) + } + + // 初始化数据 + useEffect(() => { + fetchCustomerData(activeTab, true).then(); + fetchStatusCounts().then(); + }, []); + + // 当activeTab变化时重新获取数据 + useEffect(() => { + setList([]); // 清空列表 + setPage(1); // 重置页码 + setHasMore(true); // 重置加载状态 + fetchCustomerData(activeTab, true); + }, [activeTab]); + + // 监听页面显示,当从其他页面返回时刷新数据 + useDidShow(() => { + // 刷新当前tab的数据和统计信息 + setList([]); + setPage(1); + setHasMore(true); + fetchCustomerData(activeTab, true); + fetchStatusCounts(); + }); + + // 渲染客户项 + const renderCustomerItem = (customer: CustomerUser) => ( + + + + + + {customer.dealerName} + + {customer.customerStatus && ( + + {getStatusText(customer.customerStatus)} + + )} + + + + 联系人:{customer.realName} + + { + e.stopPropagation(); + makePhoneCall(customer.mobile || ''); + }}>联系电话:{customer.mobile} + + { + e.stopPropagation(); + makePhoneCall(customer.mobile || ''); + }} + /> + { + e.stopPropagation(); + copyPhone(customer.mobile || ''); + }} + > + 复制 + + + + + 添加时间:{customer.createTime} + + + + + {/* 保护天数显示 */} + {customer.applyStatus === 10 && ( + + 保护期: + {customer.protectDays && customer.protectDays > 0 ? ( + + 剩余{customer.protectDays}天 + + ) : ( + + 已过期 + + )} + + )} + + + 报备人:{customer?.nickName} + + {customer?.refereeName} + + + {/* 显示 comments 字段 */} + + 跟进情况:{customer.comments || '暂无'} + { + e.stopPropagation(); + editComments(customer); + }} + > + 编辑 + + + + + + {/* 跟进中状态显示操作按钮 */} + {(customer.applyStatus === 10 && customer.userId == Taro.getStorageSync('UserId')) && ( + + + + + )} + {(customer.applyStatus === 30 && customer.userId == Taro.getStorageSync('UserId')) && ( + + + + )} + + ); + + // 渲染客户列表 + const renderCustomerList = () => { + const filteredList = getFilteredList(); + const isSearching = displaySearchValue.trim().length > 0; + + return ( + + {/* 搜索结果统计 */} + {isSearching && ( + + + 搜索 "{displaySearchValue}" 的结果,共找到 {filteredList.length} 条记录 + + + )} + + + { + // 滚动事件处理 + }} + onScrollToUpper={() => { + // 滚动到顶部事件处理 + }} + loadingText={ + <> + 加载中... + + } + loadMoreText={ + filteredList.length === 0 ? ( + + ) : ( + + 没有更多了 + + ) + } + > + {loading && filteredList.length === 0 ? ( + + + 加载中... + + ) : ( + filteredList.map(renderCustomerItem) + )} + + + + ); + }; + + return ( + + {/* 搜索栏 */} + + setSearchValue(value)} + onClear={() => { + setSearchValue(''); + setDisplaySearchValue(''); + }} + clearable + /> + + + {/* 顶部Tabs */} + + setActiveTab(value as CustomerStatus)} + > + {tabList.map(tab => { + const counts = getStatusCounts(); + const count = counts[tab.value as keyof typeof counts] || 0; + return ( + 0 ? `(${count})` : ''}`} + value={tab.value} + /> + ); + })} + + + + {/* 客户列表 */} + {renderCustomerList()} + + Taro.navigateTo({url: '/doctor/customer/add'})}/> + + ); +}; + +export default CustomerIndex; diff --git a/src/clinic/index.config.ts b/src/clinic/index.config.ts new file mode 100644 index 0000000..f05d7ba --- /dev/null +++ b/src/clinic/index.config.ts @@ -0,0 +1,3 @@ +export default definePageConfig({ + navigationBarTitleText: '医生端' +}) diff --git a/src/clinic/index.scss b/src/clinic/index.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/clinic/index.tsx b/src/clinic/index.tsx new file mode 100644 index 0000000..b093750 --- /dev/null +++ b/src/clinic/index.tsx @@ -0,0 +1,258 @@ +import React from 'react' +import {View, Text} from '@tarojs/components' +import {ConfigProvider, Grid, Avatar} from '@nutui/nutui-react-taro' +import { + User, + UserAdd, + Edit, + Comment, + QrCode, + Notice, + Orderlist, + Health, + PickedUp +} from '@nutui/icons-react-taro' +import {useDealerUser} from '@/hooks/useDealerUser' +import { useThemeStyles } from '@/hooks/useTheme' +import {gradientUtils} from '@/styles/gradients' +import Taro from '@tarojs/taro' + +const DealerIndex: React.FC = () => { + const { + dealerUser + } = useDealerUser() + + // 使用主题样式 + const themeStyles = useThemeStyles() + + // 导航到各个功能页面 + const navigateToPage = (url: string) => { + Taro.navigateTo({url}) + } + + // 格式化金额 + // const formatMoney = (money?: string) => { + // if (!money) return '0.00' + // return parseFloat(money).toFixed(2) + // } + + // 格式化时间 + const formatTime = (time?: string) => { + if (!time) return '-' + return new Date(time).toLocaleDateString() + } + + // 获取用户主题 + const userTheme = gradientUtils.getThemeByUserId(dealerUser?.userId) + + // 获取渐变背景 + const getGradientBackground = (themeColor?: string) => { + if (themeColor) { + const darkerColor = gradientUtils.adjustColorBrightness(themeColor, -30) + return gradientUtils.createGradient(themeColor, darkerColor) + } + return userTheme.background + } + + console.log(getGradientBackground(),'getGradientBackground()') + + // if (error) { + // return ( + // + // + // {error} + // + // + // + // ) + // } + + return ( + + + {/*头部信息*/} + {dealerUser && ( + + {/* 装饰性背景元素 - 小程序兼容版本 */} + + + + + } + className="mr-4" + style={{ + border: '2px solid rgba(255, 255, 255, 0.3)' + }} + /> + + + {dealerUser?.realName || '医生名称'} + + + 医生编号: {dealerUser.userId} + + + + 加入时间 + + {formatTime(dealerUser.createTime)} + + + + + )} + + {/* 功能导航 */} + + 管理工具 + + + navigateToPage('/clinic/clinicPatientUser/index')}> + + + + + + + + navigateToPage('/doctor/orders/add')}> + + + + + + + + navigateToPage('/doctor/team/index')}> + + + + + + + + navigateToPage('/doctor/orders/index')}> + + + + + + + + + + + + + + + + navigateToPage('/doctor/team/index')}> + + + + + + + + navigateToPage('/doctor/qrcode/index')}> + + + + + + + + navigateToPage('/doctor/apply/add')}> + + + + + + + + + + {/* 第二行功能 */} + {/**/} + {/* navigateToPage('/doctor/invite-stats/index')}>*/} + {/* */} + {/* */} + {/* */} + {/* */} + {/* */} + {/* */} + + {/* /!* 预留其他功能位置 *!/*/} + {/* */} + {/* */} + {/* */} + {/* */} + {/* */} + {/* */} + + {/* */} + {/* */} + {/* */} + {/* */} + {/* */} + {/* */} + + {/* */} + {/* */} + {/* */} + {/* */} + {/* */} + {/* */} + {/**/} + + + + + {/* 底部安全区域 */} + + + ) +} + +export default DealerIndex diff --git a/src/dealer/team/index.tsx b/src/dealer/team/index.tsx index af7d8e7..1a98ba9 100644 --- a/src/dealer/team/index.tsx +++ b/src/dealer/team/index.tsx @@ -405,7 +405,7 @@ const DealerTeam: React.FC = () => { if (!dealerUser) { return ( - navTo(`/dealer/apply/add`, true)}]} /> diff --git a/src/doctor/customer/add.config.ts b/src/doctor/customer/add.config.ts index 596fab1..e4d92d3 100644 --- a/src/doctor/customer/add.config.ts +++ b/src/doctor/customer/add.config.ts @@ -1,4 +1,4 @@ export default definePageConfig({ - navigationBarTitleText: '患者报备', + navigationBarTitleText: '添加患者', navigationBarTextStyle: 'black' -}) +}) \ No newline at end of file diff --git a/src/doctor/customer/add.tsx b/src/doctor/customer/add.tsx index d91c3b0..4015549 100644 --- a/src/doctor/customer/add.tsx +++ b/src/doctor/customer/add.tsx @@ -1,78 +1,47 @@ import {useEffect, useState, useRef} from "react"; -import {Loading, CellGroup, Cell, Input, Form, Calendar} from '@nutui/nutui-react-taro' +import {Loading, CellGroup, Input, Form, Calendar} from '@nutui/nutui-react-taro' import {Edit, Calendar as CalendarIcon} from '@nutui/icons-react-taro' import Taro from '@tarojs/taro' import {useRouter} from '@tarojs/taro' import {View, Text} from '@tarojs/components' import FixedButton from "@/components/FixedButton"; import {useUser} from "@/hooks/useUser"; -import {ShopDealerApply} from "@/api/shop/shopDealerApply/model"; +import {ClinicPatientUser} from "@/api/clinic/clinicPatientUser/model"; import { - addShopDealerApply, getShopDealerApply, pageShopDealerApply, - updateShopDealerApply -} from "@/api/shop/shopDealerApply"; + addClinicPatientUser, + getClinicPatientUser, + updateClinicPatientUser +} from "@/api/clinic/clinicPatientUser"; import { formatDateForDatabase, extractDateForCalendar, formatDateForDisplay } from "@/utils/dateUtils"; -const AddShopDealerApply = () => { +const AddPatient = () => { const {user} = useUser() const {params} = useRouter(); const [loading, setLoading] = useState(true) - const [FormData, setFormData] = useState() + const [formData, setFormData] = useState() const formRef = useRef(null) const [isEditMode, setIsEditMode] = useState(false) - const [existingApply, setExistingApply] = useState(null) // 日期选择器状态 - const [showApplyTimePicker, setShowApplyTimePicker] = useState(false) - const [showContractTimePicker, setShowContractTimePicker] = useState(false) - const [applyTime, setApplyTime] = useState('') - const [contractTime, setContractTime] = useState('') + const [showCreateTimePicker, setShowCreateTimePicker] = useState(false) + const [createTime, setCreateTime] = useState('') - // 获取审核状态文字 - const getApplyStatusText = (status?: number) => { - switch (status) { - case 10: - return '待审核' - case 20: - return '已签约' - case 30: - return '已取消' - default: - return '未知状态' - } - } + console.log('AddPatient Component Rendered'); - console.log(getApplyStatusText) - - // 处理签约时间选择 - const handleApplyTimeConfirm = (param: string) => { + // 处理创建时间选择 + const handleCreateTimeConfirm = (param: string) => { const selectedDate = param[3] // 选中的日期字符串 (YYYY-M-D) const formattedDate = formatDateForDatabase(selectedDate) // 转换为数据库格式 - setApplyTime(selectedDate) // 保存原始格式用于显示 - setShowApplyTimePicker(false) + setCreateTime(selectedDate) // 保存原始格式用于显示 + setShowCreateTimePicker(false) // 更新表单数据(使用数据库格式) if (formRef.current) { formRef.current.setFieldsValue({ - applyTime: formattedDate - }) - } - } - - // 处理合同日期选择 - const handleContractTimeConfirm = (param: string) => { - const selectedDate = param[3] // 选中的日期字符串 (YYYY-M-D) - const formattedDate = formatDateForDatabase(selectedDate) // 转换为数据库格式 - setContractTime(selectedDate) // 保存原始格式用于显示 - setShowContractTimePicker(false) - - // 更新表单数据(使用数据库格式) - if (formRef.current) { - formRef.current.setFieldsValue({ - contractTime: formattedDate + createTime: formattedDate }) } } @@ -81,54 +50,32 @@ const AddShopDealerApply = () => { if (!params.id) { return false; } - // 查询当前用户ID是否已有申请记录 + // 查询患者信息 try { - const dealerApply = await getShopDealerApply(Number(params.id)); - if (dealerApply) { - setFormData(dealerApply) + const patient = await getClinicPatientUser(Number(params.id)); + if (patient) { + setFormData(patient) setIsEditMode(true); - setExistingApply(dealerApply) // 初始化日期数据(从数据库格式转换为Calendar组件格式) - if (dealerApply.applyTime) { - setApplyTime(extractDateForCalendar(dealerApply.applyTime)) - } - if (dealerApply.contractTime) { - setContractTime(extractDateForCalendar(dealerApply.contractTime)) + if (patient.createTime) { + setCreateTime(extractDateForCalendar(patient.createTime)) } - Taro.setNavigationBarTitle({title: '签约'}) + Taro.setNavigationBarTitle({title: '编辑患者'}) } } catch (error) { setLoading(true) - console.error('查询申请记录失败:', error); + console.error('查询患者信息失败:', error); setIsEditMode(false); - setExistingApply(null); } } // 提交表单 - // 计算保护期过期时间(7天后) - const calculateExpirationTime = (): string => { - const now = new Date(); - const expirationDate = new Date(now); - expirationDate.setDate(now.getDate() + 7); // 7天后 - - // 格式化为数据库需要的格式:YYYY-MM-DD HH:mm:ss - const year = expirationDate.getFullYear(); - const month = String(expirationDate.getMonth() + 1).padStart(2, '0'); - const day = String(expirationDate.getDate()).padStart(2, '0'); - const hours = String(expirationDate.getHours()).padStart(2, '0'); - const minutes = String(expirationDate.getMinutes()).padStart(2, '0'); - const seconds = String(expirationDate.getSeconds()).padStart(2, '0'); - - return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; - }; - const submitSucceed = async (values: any) => { try { // 验证必填字段 - if (!values.mobile || values.mobile.trim() === '') { + if (!values.phone || values.phone.trim() === '') { Taro.showToast({ title: '请填写联系方式', icon: 'error' @@ -138,7 +85,7 @@ const AddShopDealerApply = () => { // 验证手机号格式 const phoneRegex = /^1[3-9]\d{9}$/; - if (!phoneRegex.test(values.mobile)) { + if (!phoneRegex.test(values.phone)) { Taro.showToast({ title: '请填写正确的手机号', icon: 'error' @@ -146,96 +93,35 @@ const AddShopDealerApply = () => { return; } - // 检查客户是否已存在 - const res = await pageShopDealerApply({dealerName: values.dealerName, type: 4, applyStatus: 10}); - - if (res && res.count > 0) { - const existingCustomer = res.list[0]; - - // 检查是否在7天保护期内 - if (!isEditMode && existingCustomer.applyTime) { - // 将申请时间字符串转换为时间戳进行比较 - const applyTimeStamp = new Date(existingCustomer.applyTime).getTime(); - const currentTimeStamp = new Date().getTime(); - const sevenDaysInMs = 7 * 24 * 60 * 60 * 1000; // 7天的毫秒数 - - // 如果在7天保护期内,不允许重复添加 - if (currentTimeStamp - applyTimeStamp < sevenDaysInMs) { - const remainingDays = Math.ceil((sevenDaysInMs - (currentTimeStamp - applyTimeStamp)) / (24 * 60 * 60 * 1000)); - - Taro.showToast({ - title: `该客户还在保护期,还需等待${remainingDays}天后才能重新添加`, - icon: 'none', - duration: 3000 - }); - return false; - } else { - // 超过7天保护期,可以重新添加,显示确认对话框 - const modalResult = await new Promise((resolve) => { - Taro.showModal({ - title: '提示', - content: '该客户已超过7天保护期,是否重新添加跟进?', - showCancel: true, - cancelText: '取消', - confirmText: '确定', - success: (modalRes) => { - resolve(modalRes.confirm); - }, - fail: () => { - resolve(false); - } - }); - }); - - if (!modalResult) { - return false; // 用户取消,不继续执行 - } - // 用户确认后继续执行添加逻辑 - } - } - } - - - // 计算过期时间 - const expirationTime = isEditMode ? existingApply?.expirationTime : calculateExpirationTime(); - // 准备提交的数据 - const submitData = { + const submitData: ClinicPatientUser = { ...values, - type: 4, realName: values.realName || user?.nickname, - mobile: values.mobile, - refereeId: 33534, - applyStatus: isEditMode ? 20 : 10, - auditTime: undefined, - // 设置保护期过期时间(7天后) - expirationTime: expirationTime, + phone: values.phone, // 确保日期数据正确提交(使用数据库格式) - applyTime: values.applyTime || (applyTime ? formatDateForDatabase(applyTime) : ''), - contractTime: values.contractTime || (contractTime ? formatDateForDatabase(contractTime) : '') + createTime: values.createTime || (createTime ? formatDateForDatabase(createTime) : new Date().toISOString().slice(0, 19).replace('T', ' ')) }; // 调试信息 console.log('=== 提交数据调试 ==='); console.log('是否编辑模式:', isEditMode); - console.log('计算的过期时间:', expirationTime); console.log('提交的数据:', submitData); console.log('=================='); - // 如果是编辑模式,添加现有申请的id - if (isEditMode && existingApply?.applyId) { - submitData.applyId = existingApply.applyId; + // 如果是编辑模式,添加现有患者的id + if (isEditMode && formData?.id) { + submitData.id = formData.id; } // 执行新增或更新操作 if (isEditMode) { - await updateShopDealerApply(submitData); + await updateClinicPatientUser(submitData); } else { - await addShopDealerApply(submitData); + await addClinicPatientUser(submitData); } Taro.showToast({ - title: `${isEditMode ? '更新' : '提交'}成功`, + title: `${isEditMode ? '更新' : '添加'}成功`, icon: 'success' }); @@ -277,124 +163,55 @@ const AddShopDealerApply = () => {
submitSucceed(values)} onFinishFailed={(errors) => submitFailed(errors)} > - - + + - - + + - - + + - - - - - - - - - - {isEditMode && ( - <> - - - - - setShowApplyTimePicker(true)} - > - - - - {applyTime ? formatDateForDisplay(applyTime) : '请选择签约时间'} - - - - - - setShowContractTimePicker(true)} - > - - - - {contractTime ? formatDateForDisplay(contractTime) : '请选择合同生效起止时间'} - - - - - {/**/} - {/* */} - {/**/} - - )} - -选择 + + setShowCreateTimePicker(true)} + > + + + + {createTime ? formatDateForDisplay(createTime) : '请选择创建时间'} + + +
- {/* 签约时间选择器 */} + {/* 创建时间选择器 */} setShowApplyTimePicker(false)} - onConfirm={handleApplyTimeConfirm} + visible={showCreateTimePicker} + defaultValue={createTime} + onClose={() => setShowCreateTimePicker(false)} + onConfirm={handleCreateTimeConfirm} /> - {/* 合同日期选择器 */} - setShowContractTimePicker(false)} - onConfirm={handleContractTimeConfirm} - /> - - {/* 审核状态显示(仅在编辑模式下显示) */} - {isEditMode && ( - - {/**/} - {/* {getApplyStatusText(FormData?.applyStatus)}*/} - {/* */} - {/* }*/} - {/*/>*/} - {FormData?.applyStatus === 20 && ( - - )} - {FormData?.applyStatus === 30 && ( - - )} - - )} - - {/* 底部浮动按钮 */} - {(!isEditMode || FormData?.applyStatus === 10) && ( - } - text={'立即提交'} - onClick={handleFixedButtonClick} - /> - )} + } + text={isEditMode ? '更新患者' : '添加患者'} + onClick={handleFixedButtonClick} + /> ); }; -export default AddShopDealerApply; +export default AddPatient; diff --git a/src/doctor/customer/addPatient.config.ts b/src/doctor/customer/addPatient.config.ts new file mode 100644 index 0000000..e4d92d3 --- /dev/null +++ b/src/doctor/customer/addPatient.config.ts @@ -0,0 +1,4 @@ +export default definePageConfig({ + navigationBarTitleText: '添加患者', + navigationBarTextStyle: 'black' +}) \ No newline at end of file diff --git a/src/doctor/customer/addPatient.tsx b/src/doctor/customer/addPatient.tsx new file mode 100644 index 0000000..ce6157d --- /dev/null +++ b/src/doctor/customer/addPatient.tsx @@ -0,0 +1,217 @@ +import {useEffect, useState, useRef} from "react"; +import {Loading, CellGroup, Cell, Input, Form, Calendar} from '@nutui/nutui-react-taro' +import {Edit, Calendar as CalendarIcon} from '@nutui/icons-react-taro' +import Taro from '@tarojs/taro' +import {useRouter} from '@tarojs/taro' +import {View, Text} from '@tarojs/components' +import FixedButton from "@/components/FixedButton"; +import {useUser} from "@/hooks/useUser"; +import {ClinicPatientUser} from "@/api/clinic/clinicPatientUser/model"; +import { + addClinicPatientUser, + getClinicPatientUser, + updateClinicPatientUser +} from "@/api/clinic/clinicPatientUser"; +import { + formatDateForDatabase, + extractDateForCalendar, formatDateForDisplay +} from "@/utils/dateUtils"; + +const AddPatient = () => { + const {user} = useUser() + const {params} = useRouter(); + const [loading, setLoading] = useState(true) + const [formData, setFormData] = useState() + const formRef = useRef(null) + const [isEditMode, setIsEditMode] = useState(false) + + // 日期选择器状态 + const [showCreateTimePicker, setShowCreateTimePicker] = useState(false) + const [createTime, setCreateTime] = useState('') + + console.log('AddPatient Component Rendered'); + + // 处理创建时间选择 + const handleCreateTimeConfirm = (param: string) => { + const selectedDate = param[3] // 选中的日期字符串 (YYYY-M-D) + const formattedDate = formatDateForDatabase(selectedDate) // 转换为数据库格式 + setCreateTime(selectedDate) // 保存原始格式用于显示 + setShowCreateTimePicker(false) + + // 更新表单数据(使用数据库格式) + if (formRef.current) { + formRef.current.setFieldsValue({ + createTime: formattedDate + }) + } + } + + const reload = async () => { + if (!params.id) { + return false; + } + // 查询患者信息 + try { + const patient = await getClinicPatientUser(Number(params.id)); + if (patient) { + setFormData(patient) + setIsEditMode(true); + + // 初始化日期数据(从数据库格式转换为Calendar组件格式) + if (patient.createTime) { + setCreateTime(extractDateForCalendar(patient.createTime)) + } + + Taro.setNavigationBarTitle({title: '编辑患者'}) + } + } catch (error) { + setLoading(true) + console.error('查询患者信息失败:', error); + setIsEditMode(false); + } + } + + // 提交表单 + const submitSucceed = async (values: any) => { + try { + // 验证必填字段 + if (!values.mobile || values.mobile.trim() === '') { + Taro.showToast({ + title: '请填写联系方式', + icon: 'error' + }); + return; + } + + // 验证手机号格式 + const phoneRegex = /^1[3-9]\d{9}$/; + if (!phoneRegex.test(values.mobile)) { + Taro.showToast({ + title: '请填写正确的手机号', + icon: 'error' + }); + return; + } + + // 准备提交的数据 + const submitData: ClinicPatientUser = { + ...values, + realName: values.realName || user?.nickname, + mobile: values.mobile, + // 确保日期数据正确提交(使用数据库格式) + createTime: values.createTime || (createTime ? formatDateForDatabase(createTime) : new Date().toISOString().slice(0, 19).replace('T', ' ')) + }; + + // 调试信息 + console.log('=== 提交数据调试 ==='); + console.log('是否编辑模式:', isEditMode); + console.log('提交的数据:', submitData); + console.log('=================='); + + // 如果是编辑模式,添加现有患者的id + if (isEditMode && formData?.id) { + submitData.id = formData.id; + } + + // 执行新增或更新操作 + if (isEditMode) { + await updateClinicPatientUser(submitData); + } else { + await addClinicPatientUser(submitData); + } + + Taro.showToast({ + title: `${isEditMode ? '更新' : '添加'}成功`, + icon: 'success' + }); + + setTimeout(() => { + Taro.navigateBack(); + }, 1000); + + } catch (error) { + console.error('提交失败:', error); + Taro.showToast({ + title: '提交失败,请重试', + icon: 'error' + }); + } + } + + // 处理固定按钮点击事件 + const handleFixedButtonClick = () => { + // 触发表单提交 + formRef.current?.submit(); + }; + + const submitFailed = (error: any) => { + console.log(error, 'err...') + } + + useEffect(() => { + reload().then(() => { + setLoading(false) + }) + }, []); // 依赖用户ID,当用户变化时重新加载 + + if (loading) { + return 加载中 + } + + return ( + <> +
submitSucceed(values)} + onFinishFailed={(errors) => submitFailed(errors)} + > + + + + + + + + + + + + + setShowCreateTimePicker(true)} + > + + + + {createTime ? formatDateForDisplay(createTime) : '请选择创建时间'} + + + + + +
+ + {/* 创建时间选择器 */} + setShowCreateTimePicker(false)} + onConfirm={handleCreateTimeConfirm} + /> + + {/* 底部浮动按钮 */} + } + text={isEditMode ? '更新患者' : '添加患者'} + onClick={handleFixedButtonClick} + /> + + + ); +}; + +export default AddPatient; \ No newline at end of file diff --git a/src/doctor/customer/index.tsx b/src/doctor/customer/index.tsx index 33cec2e..1a0b77b 100644 --- a/src/doctor/customer/index.tsx +++ b/src/doctor/customer/index.tsx @@ -1,39 +1,29 @@ import {useState, useEffect, useCallback} from 'react' import {View, Text} from '@tarojs/components' import Taro, {useDidShow} from '@tarojs/taro' -import {Loading, InfiniteLoading, Empty, Space, Tabs, TabPane, Tag, Button, SearchBar} from '@nutui/nutui-react-taro' -import {Phone, AngleDoubleLeft} from '@nutui/icons-react-taro' -import type {ShopDealerApply, ShopDealerApply as UserType} from "@/api/shop/shopDealerApply/model"; -import { - CustomerStatus, - getStatusText, - getStatusTagType, - getStatusOptions, - mapApplyStatusToCustomerStatus, - mapCustomerStatusToApplyStatus -} from '@/utils/customerStatus'; +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 {pageShopDealerApply, removeShopDealerApply, updateShopDealerApply} from "@/api/shop/shopDealerApply"; +import { + pageClinicPatientUser, + removeClinicPatientUser, + updateClinicPatientUser +} from "@/api/clinic/clinicPatientUser"; -// 扩展User类型,添加客户状态和保护天数 -interface CustomerUser extends UserType { - customerStatus?: CustomerStatus; - protectDays?: number; // 剩余保护天数 +// 扩展患者类型 +interface PatientUser extends PatientUserType { } -const CustomerIndex = () => { - const [list, setList] = useState([]) +const PatientIndex = () => { + const [list, setList] = 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) - // Tab配置 - const tabList = getStatusOptions(); - // 复制手机号 const copyPhone = (phone: string) => { Taro.setClipboardData({ @@ -61,21 +51,21 @@ const CustomerIndex = () => { }); }; - // 编辑跟进情况 - const editComments = (customer: CustomerUser) => { + // 编辑备注 + const editComments = (patient: PatientUser) => { Taro.showModal({ - title: '编辑跟进情况', + title: '编辑备注', // @ts-ignore editable: true, - placeholderText: '请输入跟进情况', - content: customer.comments || '', + placeholderText: '请输入备注信息', + content: patient.comments || '', success: async (res) => { // @ts-ignore if (res.confirm && res.content !== undefined) { try { - // 更新跟进情况 - await updateShopDealerApply({ - ...customer, + // 更新备注 + await updateClinicPatientUser({ + ...patient, // @ts-ignore comments: res.content.trim() }); @@ -89,9 +79,9 @@ const CustomerIndex = () => { setList([]); setPage(1); setHasMore(true); - fetchCustomerData(activeTab, true); + fetchPatientData(true); } catch (error) { - console.error('更新跟进情况失败:', error); + console.error('更新备注失败:', error); Taro.showToast({ title: '更新失败,请重试', icon: 'error' @@ -102,101 +92,30 @@ const CustomerIndex = () => { }); }; - // 计算剩余保护天数(基于过期时间) - const calculateProtectDays = (expirationTime?: string, applyTime?: string): number => { - try { - // 优先使用过期时间字段 - if (expirationTime) { - const expDate = new Date(expirationTime.replace(' ', 'T')); - const now = new Date(); - - // 计算剩余毫秒数 - const remainingMs = expDate.getTime() - now.getTime(); - - // 转换为天数,向上取整 - const remainingDays = Math.ceil(remainingMs / (1000 * 60 * 60 * 24)); - - console.log('=== 基于过期时间计算 ==='); - console.log('过期时间:', expirationTime); - console.log('当前时间:', now.toLocaleString()); - console.log('剩余天数:', remainingDays); - console.log('======================'); - - return Math.max(0, remainingDays); - } - - // 如果没有过期时间,回退到基于申请时间计算 - if (!applyTime) return 0; - - const protectionPeriod = 7; // 保护期7天 - - // 解析申请时间 - let applyDate: Date; - if (applyTime.includes('T')) { - applyDate = new Date(applyTime); - } else { - applyDate = new Date(applyTime.replace(' ', 'T')); - } - - // 获取当前时间 - const now = new Date(); - - // 只比较日期部分,忽略时间 - const applyDateOnly = new Date(applyDate.getFullYear(), applyDate.getMonth(), applyDate.getDate()); - const currentDateOnly = new Date(now.getFullYear(), now.getMonth(), now.getDate()); - - // 计算已经过去的天数 - const timeDiff = currentDateOnly.getTime() - applyDateOnly.getTime(); - const daysPassed = Math.floor(timeDiff / (1000 * 60 * 60 * 24)); - - // 计算剩余保护天数 - const remainingDays = protectionPeriod - daysPassed; - - console.log('=== 基于申请时间计算 ==='); - console.log('申请时间:', applyTime); - console.log('已过去天数:', daysPassed); - console.log('剩余保护天数:', remainingDays); - console.log('======================'); - - return Math.max(0, remainingDays); - } catch (error) { - console.error('日期计算错误:', error); - return 0; - } - }; - - - // 获取客户数据 - const fetchCustomerData = useCallback(async (statusFilter?: CustomerStatus, resetPage = false, targetPage?: number) => { + // 获取患者数据 + const fetchPatientData = useCallback(async (resetPage = false, targetPage?: number) => { setLoading(true); try { const currentPage = resetPage ? 1 : (targetPage || page); - // 构建API参数,根据状态筛选 + // 构建API参数 const params: any = { - type: 4, page: currentPage }; - const applyStatus = mapCustomerStatusToApplyStatus(statusFilter || activeTab); - if (applyStatus !== undefined) { - params.applyStatus = applyStatus; + + // 添加搜索关键词 + if (displaySearchValue.trim()) { + params.keywords = displaySearchValue.trim(); } - const res = await pageShopDealerApply(params); + const res = await pageClinicPatientUser(params); if (res?.list && res.list.length > 0) { - // 正确映射状态并计算保护天数 - const mappedList = res.list.map(customer => ({ - ...customer, - customerStatus: mapApplyStatusToCustomerStatus(customer.applyStatus || 10), - protectDays: calculateProtectDays(customer.expirationTime, customer.applyTime || customer.createTime || '') - })); - // 如果是重置页面或第一页,直接设置新数据;否则追加数据 if (resetPage || currentPage === 1) { - setList(mappedList); + setList(res.list); } else { - setList(prevList => prevList.concat(mappedList)); + setList(prevList => prevList.concat(res.list)); } // 正确判断是否还有更多数据 @@ -211,7 +130,7 @@ const CustomerIndex = () => { setPage(currentPage); } catch (error) { - console.error('获取客户数据失败:', error); + console.error('获取患者数据失败:', error); Taro.showToast({ title: '加载失败,请重试', icon: 'none' @@ -219,15 +138,14 @@ const CustomerIndex = () => { } finally { setLoading(false); } - }, [activeTab, page]); + }, [page, displaySearchValue]); const reloadMore = async () => { if (loading || !hasMore) return; // 防止重复加载 const nextPage = page + 1; - await fetchCustomerData(activeTab, false, nextPage); + await fetchPatientData(false, nextPage); } - // 防抖搜索功能 useEffect(() => { const timer = setTimeout(() => { @@ -237,153 +155,66 @@ const CustomerIndex = () => { return () => clearTimeout(timer); }, [searchValue]); - // 根据搜索条件筛选数据(状态筛选已在API层面处理) - const getFilteredList = () => { - let filteredList = list; - - // 按搜索关键词筛选 - if (displaySearchValue.trim()) { - const keyword = displaySearchValue.trim().toLowerCase(); - filteredList = filteredList.filter(customer => - (customer.realName && customer.realName.toLowerCase().includes(keyword)) || - (customer.dealerName && customer.dealerName.toLowerCase().includes(keyword)) || - (customer.dealerCode && customer.dealerCode.toLowerCase().includes(keyword)) || - (customer.mobile && customer.mobile.includes(keyword)) || - (customer.userId && customer.userId.toString().includes(keyword)) - ); - } - - return filteredList; - }; - - // 获取各状态的统计数量 - const [statusCounts, setStatusCounts] = useState({ - all: 0, - pending: 0, - signed: 0, - cancelled: 0 - }); - - // 获取所有状态的统计数量 - const fetchStatusCounts = useCallback(async () => { - try { - // 并行获取各状态的数量 - const [allRes, pendingRes, signedRes, cancelledRes] = await Promise.all([ - pageShopDealerApply({type: 4}), // 全部 - pageShopDealerApply({applyStatus: 10, type: 4}), // 跟进中 - pageShopDealerApply({applyStatus: 20, type: 4}), // 已签约 - pageShopDealerApply({applyStatus: 30, type: 4}) // 已取消 - ]); - - setStatusCounts({ - all: allRes?.count || 0, - pending: pendingRes?.count || 0, - signed: signedRes?.count || 0, - cancelled: cancelledRes?.count || 0 - }); - } catch (error) { - console.error('获取状态统计失败:', error); - } - }, []); - - const getStatusCounts = () => statusCounts; - - // 取消操作 - const handleCancel = (customer: ShopDealerApply) => { - updateShopDealerApply({ - ...customer, - applyStatus: 30 - }).then(() => { - Taro.showToast({ - title: '取消成功', - icon: 'success' - }); - // 重新加载当前tab的数据 - setList([]); - setPage(1); - setHasMore(true); - fetchCustomerData(activeTab, true).then(); - fetchStatusCounts().then(); - }) - }; - - // 删除 - const handleDelete = (customer: ShopDealerApply) => { - removeShopDealerApply(customer.applyId).then(() => { + // 删除患者 + const handleDelete = (patient: PatientUser) => { + removeClinicPatientUser(patient.id).then(() => { Taro.showToast({ title: '删除成功', icon: 'success' }); - // 刷新当前tab的数据 + // 刷新数据 setList([]); setPage(1); setHasMore(true); - fetchCustomerData(activeTab, true).then(); - fetchStatusCounts().then(); + fetchPatientData(true).then(); }) } // 初始化数据 useEffect(() => { - fetchCustomerData(activeTab, true).then(); - fetchStatusCounts().then(); - }, []); - - // 当activeTab变化时重新获取数据 - useEffect(() => { - setList([]); // 清空列表 - setPage(1); // 重置页码 - setHasMore(true); // 重置加载状态 - fetchCustomerData(activeTab, true); - }, [activeTab]); + fetchPatientData(true).then(); + }, [displaySearchValue]); // 监听页面显示,当从其他页面返回时刷新数据 useDidShow(() => { - // 刷新当前tab的数据和统计信息 + // 刷新数据 setList([]); setPage(1); setHasMore(true); - fetchCustomerData(activeTab, true); - fetchStatusCounts(); + fetchPatientData(true); }); - // 渲染客户项 - const renderCustomerItem = (customer: CustomerUser) => ( - + // 渲染患者项 + const renderPatientItem = (patient: PatientUser) => ( + - {customer.dealerName} + {patient.realName || '未命名'} - {customer.customerStatus && ( - - {getStatusText(customer.customerStatus)} - - )} - 联系人:{customer.realName} { e.stopPropagation(); - makePhoneCall(customer.mobile || ''); - }}>联系电话:{customer.mobile} + makePhoneCall(patient.phone || ''); + }}>联系电话:{patient.phone || '未提供'} { e.stopPropagation(); - makePhoneCall(customer.mobile || ''); + makePhoneCall(patient.phone || ''); }} /> { e.stopPropagation(); - copyPhone(customer.mobile || ''); + copyPhone(patient.phone || ''); }} > 复制 @@ -391,47 +222,19 @@ const CustomerIndex = () => { - 添加时间:{customer.createTime} + 添加时间:{patient.createTime || '未知'} - {/* 保护天数显示 */} - {customer.applyStatus === 10 && ( - - 保护期: - {customer.protectDays && customer.protectDays > 0 ? ( - - 剩余{customer.protectDays}天 - - ) : ( - - 已过期 - - )} - - )} - - - 报备人:{customer?.nickName} - - {customer?.refereeName} - - {/* 显示 comments 字段 */} - 跟进情况:{customer.comments || '暂无'} + 备注:{patient.comments || '暂无'} { e.stopPropagation(); - editComments(customer); + editComments(patient); }} > 编辑 @@ -440,42 +243,28 @@ const CustomerIndex = () => { - {/* 跟进中状态显示操作按钮 */} - {(customer.applyStatus === 10 && customer.userId == Taro.getStorageSync('UserId')) && ( - - - - - )} - {(customer.applyStatus === 30 && customer.userId == Taro.getStorageSync('UserId')) && ( - - - - )} + {/* 操作按钮 */} + + + + ); - // 渲染客户列表 - const renderCustomerList = () => { - const filteredList = getFilteredList(); + // 渲染患者列表 + const renderPatientList = () => { const isSearching = displaySearchValue.trim().length > 0; return ( @@ -484,7 +273,7 @@ const CustomerIndex = () => { {isSearching && ( - 搜索 "{displaySearchValue}" 的结果,共找到 {filteredList.length} 条记录 + 搜索 "{displaySearchValue}" 的结果,共找到 {list.length} 条记录 )} @@ -510,10 +299,10 @@ const CustomerIndex = () => { } loadMoreText={ - filteredList.length === 0 ? ( + list.length === 0 ? ( ) : ( @@ -522,13 +311,13 @@ const CustomerIndex = () => { ) } > - {loading && filteredList.length === 0 ? ( + {loading && list.length === 0 ? ( 加载中... ) : ( - filteredList.map(renderCustomerItem) + list.map(renderPatientItem) )} @@ -542,7 +331,7 @@ const CustomerIndex = () => { setSearchValue(value)} onClear={() => { setSearchValue(''); @@ -552,32 +341,12 @@ const CustomerIndex = () => { /> - {/* 顶部Tabs */} - - setActiveTab(value as CustomerStatus)} - > - {tabList.map(tab => { - const counts = getStatusCounts(); - const count = counts[tab.value as keyof typeof counts] || 0; - return ( - 0 ? `(${count})` : ''}`} - value={tab.value} - /> - ); - })} - - + {/* 患者列表 */} + {renderPatientList()} - {/* 客户列表 */} - {renderCustomerList()} - - Taro.navigateTo({url: '/doctor/customer/add'})}/> + Taro.navigateTo({url: '/doctor/customer/add'})}/> ); }; -export default CustomerIndex; +export default PatientIndex; diff --git a/src/doctor/orders/add.tsx b/src/doctor/orders/add.tsx index 36126a3..b76e1dd 100644 --- a/src/doctor/orders/add.tsx +++ b/src/doctor/orders/add.tsx @@ -1,23 +1,28 @@ import {useEffect, useState, useRef} from "react"; import {useRouter} from '@tarojs/taro' -import {Loading, CellGroup, Input, Form, Cell, Avatar} from '@nutui/nutui-react-taro' +import {Loading, CellGroup, Input, Form, Cell, Avatar, Tag} from '@nutui/nutui-react-taro' import {ArrowRight} from '@nutui/icons-react-taro' import {View, Text} from '@tarojs/components' import Taro from '@tarojs/taro' import FixedButton from "@/components/FixedButton"; import {addShopChatMessage} from "@/api/shop/shopChatMessage"; -import {ShopChatMessage} from "@/api/shop/shopChatMessage/model"; import navTo from "@/utils/common"; import {getUser} from "@/api/system/user"; import {User} from "@/api/system/user/model"; +import {ClinicPatientUser} from "@/api/clinic/clinicPatientUser/model"; +import {ClinicPrescription} from "@/api/clinic/clinicPrescription/model"; const AddMessage = () => { const {params} = useRouter(); const [toUser, setToUser] = useState() const [loading, setLoading] = useState(true) - const [FormData, _] = useState() + const [FormData, _] = useState() const formRef = useRef(null) + // 患者和处方状态 + const [selectedPatient, setSelectedPatient] = useState(null) + const [selectedPrescription, setSelectedPrescription] = useState(null) + // 判断是编辑还是新增模式 const isEditMode = !!params.id const toUserId = params.id ? Number(params.id) : undefined @@ -30,6 +35,20 @@ const AddMessage = () => { } } + // 设置选中的患者(供其他页面调用) + // @ts-ignore + const setSelectedPatientFunc = (patient: ClinicPatientUser) => { + console.log('患者:', patient) + setSelectedPatient(patient) + console.log(selectedPatient,'selectedPatient') + } + + // 设置选中的处方(供其他页面调用) + // @ts-ignore + const setSelectedPrescriptionFunc = (prescription: ClinicPrescription) => { + setSelectedPrescription(prescription) + } + // 提交表单 const submitSucceed = async (values: any) => { try { @@ -41,9 +60,9 @@ const AddMessage = () => { console.log('提交数据:', submitData) // 参数校验 - if(!toUser){ + if(!toUser && !selectedPatient){ Taro.showToast({ - title: `请选择发送对象`, + title: `请选择发送对象或患者`, icon: 'error' }); return false; @@ -57,12 +76,25 @@ const AddMessage = () => { }); return false; } + + // 如果选择了患者,在消息内容中添加患者信息 + console.log(values,'vals.s..s.s.s.s.s') + let content = values.content; + if (selectedPatient) { + content = `[患者: ${selectedPatient.realName || '未知'}] ${content}`; + } + + // 如果选择了处方,在消息内容中添加处方信息 + if (selectedPrescription) { + content = `[处方: ${selectedPrescription.orderNo || '未知'}] ${content}`; + } + // 执行新增或更新操作 await addShopChatMessage({ - toUserId: toUserId, + toUserId: toUserId || selectedPatient?.userId, formUserId: Taro.getStorageSync('UserId'), type: 'text', - content: values.content + content: content }); Taro.showToast({ @@ -91,6 +123,12 @@ const AddMessage = () => { reload().then(() => { setLoading(false) }) + + // 设置页面实例的方法,供其他页面调用 + // @ts-ignore + Taro.getCurrentInstance().page.setSelectedPatient = setSelectedPatientFunc; + // @ts-ignore + Taro.getCurrentInstance().page.setSelectedPrescription = setSelectedPrescriptionFunc; }, [isEditMode]); if (loading) { @@ -99,18 +137,78 @@ const AddMessage = () => { return ( <> - - - - {toUser.alias || toUser.nickname} - {toUser.mobile} + {/* 显示已选择的用户(如果有的话) */} + {toUser && ( + + + + {toUser.alias || toUser.nickname} + {toUser.mobile} + - - ) : '选择患者'} extra={( - + )} extra={( + + )}/> )} - onClick={() => navTo(`/doctor/customer/index`, true)}/> + + {/* 选择患者 */} + {JSON.stringify(selectedPatient)} + + {selectedPatient.realName || '未知患者'} + + + ) : ( + + )} + onClick={() => navTo(`/doctor/orders/selectPatient`, true)} + /> + + + + + ) : ( + + )} + onClick={() => navTo(`/doctor/orders/selectPatient`, true)} + /> + + {/* 选择处方 */} + + {selectedPrescription.treatmentPlan || '未知处方'} + + + ) : ( + + )} + onClick={() => navTo(`/doctor/orders/selectPrescription`, true)} + /> + + + {selectedPrescription.items?.map(item => ( + {item.medicineName} + ))} + + ) : ( + + )} + onClick={() => navTo(`/doctor/orders/selectPrescription`, true)} + /> + + +
{ onFinishFailed={(errors) => submitFailed(errors)} > - + diff --git a/src/doctor/orders/selectPatient.config.ts b/src/doctor/orders/selectPatient.config.ts new file mode 100644 index 0000000..db136c2 --- /dev/null +++ b/src/doctor/orders/selectPatient.config.ts @@ -0,0 +1,4 @@ +export default definePageConfig({ + navigationBarTitleText: '选择患者', + navigationBarTextStyle: 'black' +}) \ No newline at end of file diff --git a/src/doctor/orders/selectPatient.tsx b/src/doctor/orders/selectPatient.tsx new file mode 100644 index 0000000..773380a --- /dev/null +++ b/src/doctor/orders/selectPatient.tsx @@ -0,0 +1,287 @@ +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([]) + const [loading, setLoading] = useState(false) + const [searchValue, setSearchValue] = useState('') + const [displaySearchValue, setDisplaySearchValue] = useState('') + 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.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]); + + // 初始化数据 + 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); + } + } + Taro.navigateBack(); + }; + + // 渲染患者项 + const renderPatientItem = (patient: PatientUser) => ( + + + + + + {patient.realName || '未命名'} + + + + + + { + e.stopPropagation(); + makePhoneCall(patient.phone || ''); + }}>联系电话:{patient.phone || '未提供'} + + { + e.stopPropagation(); + makePhoneCall(patient.phone || ''); + }} + /> + { + e.stopPropagation(); + copyPhone(patient.phone || ''); + }} + > + 复制 + + + + + 添加时间:{patient.createTime || '未知'} + + + + + {/* 显示 comments 字段 */} + + 备注:{patient.comments || '暂无'} + + + + + {/* 选择按钮 */} + + + ); + + // 渲染患者列表 + const renderPatientList = () => { + const isSearching = displaySearchValue.trim().length > 0; + + return ( + + {/* 搜索结果统计 */} + {isSearching && ( + + + 搜索 "{displaySearchValue}" 的结果,共找到 {list.length} 条记录 + + + )} + + + { + // 滚动事件处理 + }} + onScrollToUpper={() => { + // 滚动到顶部事件处理 + }} + loadingText={ + <> + 加载中... + + } + loadMoreText={ + list.length === 0 ? ( + + ) : ( + + 没有更多了 + + ) + } + > + {loading && list.length === 0 ? ( + + + 加载中... + + ) : ( + list.map(renderPatientItem) + )} + + + + ); + }; + + return ( + + {/* 搜索栏 */} + + setSearchValue(value)} + onClear={() => { + setSearchValue(''); + setDisplaySearchValue(''); + }} + clearable + /> + + + {/* 患者列表 */} + {renderPatientList()} + + ); +}; + +export default SelectPatient; diff --git a/src/doctor/orders/selectPrescription.config.ts b/src/doctor/orders/selectPrescription.config.ts new file mode 100644 index 0000000..22a53f3 --- /dev/null +++ b/src/doctor/orders/selectPrescription.config.ts @@ -0,0 +1,4 @@ +export default definePageConfig({ + navigationBarTitleText: '选择处方', + navigationBarTextStyle: 'black' +}) \ No newline at end of file diff --git a/src/doctor/orders/selectPrescription.tsx b/src/doctor/orders/selectPrescription.tsx new file mode 100644 index 0000000..95b1d04 --- /dev/null +++ b/src/doctor/orders/selectPrescription.tsx @@ -0,0 +1,236 @@ +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([]) + const [loading, setLoading] = useState(false) + const [searchValue, setSearchValue] = useState('') + const [displaySearchValue, setDisplaySearchValue] = useState('') + 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.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 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); + } + } + Taro.navigateBack(); + }; + + // 渲染处方项 + const renderPrescriptionItem = (prescription: ClinicPrescription) => ( + + + + + + 处方编号: {prescription.orderNo || '无编号'} + + + + + + 处方类型: {prescription.prescriptionType === 0 ? '中药' : prescription.prescriptionType === 1 ? '西药' : '未知'} + + + 诊断结果: {prescription.diagnosis || '无'} + + + 创建时间: {prescription.createTime || '未知'} + + + + + {/* 显示备注字段 */} + + 备注: {prescription.comments || '暂无'} + + + + + {/* 选择按钮 */} + + + ); + + // 渲染处方列表 + const renderPrescriptionList = () => { + const isSearching = displaySearchValue.trim().length > 0; + + return ( + + {/* 搜索结果统计 */} + {isSearching && ( + + + 搜索 "{displaySearchValue}" 的结果,共找到 {list.length} 条记录 + + + )} + + + { + // 滚动事件处理 + }} + onScrollToUpper={() => { + // 滚动到顶部事件处理 + }} + loadingText={ + <> + 加载中... + + } + loadMoreText={ + list.length === 0 ? ( + + ) : ( + + 没有更多了 + + ) + } + > + {loading && list.length === 0 ? ( + + + 加载中... + + ) : ( + list.map(renderPrescriptionItem) + )} + + + + ); + }; + + return ( + + {/* 搜索栏 */} + + setSearchValue(value)} + onClear={() => { + setSearchValue(''); + setDisplaySearchValue(''); + }} + clearable + /> + + + {/* 处方列表 */} + {renderPrescriptionList()} + + ); +}; + +export default SelectPrescription; \ No newline at end of file diff --git a/src/doctor/qrcode/index.tsx b/src/doctor/qrcode/index.tsx index 268580a..a5036c6 100644 --- a/src/doctor/qrcode/index.tsx +++ b/src/doctor/qrcode/index.tsx @@ -3,21 +3,21 @@ import {View, Text, Image} from '@tarojs/components' import {Button, Loading} from '@nutui/nutui-react-taro' import {Download, QrCode} from '@nutui/icons-react-taro' import Taro from '@tarojs/taro' -import {useDealerUser} from '@/hooks/useDealerUser' import {generateInviteCode} from '@/api/invite' // import type {InviteStats} from '@/api/invite' import {businessGradients} from '@/styles/gradients' +import {useUser} from "@/hooks/useUser"; const DealerQrcode: React.FC = () => { const [miniProgramCodeUrl, setMiniProgramCodeUrl] = useState('') const [loading, setLoading] = useState(false) // const [inviteStats, setInviteStats] = useState(null) // const [statsLoading, setStatsLoading] = useState(false) - const {dealerUser} = useDealerUser() + const {user} = useUser() // 生成小程序码 const generateMiniProgramCode = async () => { - if (!dealerUser?.userId) { + if (!user?.userId) { return } @@ -25,7 +25,7 @@ const DealerQrcode: React.FC = () => { setLoading(true) // 生成邀请小程序码 - const codeUrl = await generateInviteCode(dealerUser.userId) + const codeUrl = await generateInviteCode(user?.userId) if (codeUrl) { setMiniProgramCodeUrl(codeUrl) @@ -61,11 +61,11 @@ const DealerQrcode: React.FC = () => { // 初始化生成小程序码和获取统计数据 useEffect(() => { - if (dealerUser?.userId) { + if (user?.userId) { generateMiniProgramCode() // fetchInviteStats() } - }, [dealerUser?.userId]) + }, [user?.userId]) // 保存小程序码到相册 const saveMiniProgramCode = async () => { @@ -162,7 +162,7 @@ const DealerQrcode: React.FC = () => { // }) // } - if (!dealerUser) { + if (!user) { return ( @@ -312,84 +312,6 @@ const DealerQrcode: React.FC = () => { - - {/* 邀请统计数据 */} - {/**/} - {/* 我的邀请数据*/} - {/* {statsLoading ? (*/} - {/* */} - {/* */} - {/* 加载中...*/} - {/* */} - {/* ) : inviteStats ? (*/} - {/* */} - {/* */} - {/* */} - {/* */} - {/* {inviteStats.totalInvites || 0}*/} - {/* */} - {/* 总邀请数*/} - {/* */} - {/* */} - {/* */} - {/* {inviteStats.successfulRegistrations || 0}*/} - {/* */} - {/* 成功注册*/} - {/* */} - {/* */} - - {/* */} - {/* */} - {/* */} - {/* {inviteStats.conversionRate ? `${(inviteStats.conversionRate * 100).toFixed(1)}%` : '0%'}*/} - {/* */} - {/* 转化率*/} - {/* */} - {/* */} - {/* */} - {/* {inviteStats.todayInvites || 0}*/} - {/* */} - {/* 今日邀请*/} - {/* */} - {/* */} - - {/* /!* 邀请来源统计 *!/*/} - {/* {inviteStats.sourceStats && inviteStats.sourceStats.length > 0 && (*/} - {/* */} - {/* 邀请来源分布*/} - {/* */} - {/* {inviteStats.sourceStats.map((source, index) => (*/} - {/* */} - {/* */} - {/* */} - {/* {source.source}*/} - {/* */} - {/* */} - {/* {source.count}*/} - {/* */} - {/* {source.conversionRate ? `${(source.conversionRate * 100).toFixed(1)}%` : '0%'}*/} - {/* */} - {/* */} - {/* */} - {/* ))}*/} - {/* */} - {/* */} - {/* )}*/} - {/* */} - {/* ) : (*/} - {/* */} - {/* 暂无邀请数据*/} - {/* */} - {/* 刷新数据*/} - {/* */} - {/* */} - {/* )}*/} - {/**/} ) diff --git a/src/doctor/team/index.tsx b/src/doctor/team/index.tsx index 36f6b14..5df941e 100644 --- a/src/doctor/team/index.tsx +++ b/src/doctor/team/index.tsx @@ -405,7 +405,7 @@ const DealerTeam: React.FC = () => { if (!dealerUser) { return ( - navTo(`/doctor/apply/add`, true)}]} /> diff --git a/src/pages/user/components/IsDealer.tsx b/src/pages/user/components/IsDealer.tsx index 06114dd..68112ae 100644 --- a/src/pages/user/components/IsDealer.tsx +++ b/src/pages/user/components/IsDealer.tsx @@ -64,7 +64,7 @@ const IsDealer = () => { A{config?.vipText || '入驻申请'} + className={'pl-3 text-orange-100 font-medium'}>{config?.vipText || '入驻申请'} {/*门店核销*/} }