完善加项样式
This commit is contained in:
@@ -3,6 +3,7 @@ import { useEffect, useMemo, useState, useRef } from 'react';
|
|||||||
import type { ExamClient } from '../../data/mockData';
|
import type { ExamClient } from '../../data/mockData';
|
||||||
import { searchPhysicalExamAddItem, getAddItemCustomerInfo } from '../../api';
|
import { searchPhysicalExamAddItem, getAddItemCustomerInfo } from '../../api';
|
||||||
import { Button, Input } from '../ui';
|
import { Button, Input } from '../ui';
|
||||||
|
import { cls } from '../../utils/cls';
|
||||||
|
|
||||||
interface AddonTag {
|
interface AddonTag {
|
||||||
title: string;
|
title: string;
|
||||||
@@ -31,7 +32,46 @@ export const ExamAddonPanel = ({ client }: ExamAddonPanelProps) => {
|
|||||||
const debounceTimerRef = useRef<number | null>(null);
|
const debounceTimerRef = useRef<number | null>(null);
|
||||||
const [addonLoading, setAddonLoading] = useState(false);
|
const [addonLoading, setAddonLoading] = useState(false);
|
||||||
const [addonError, setAddonError] = useState<string | null>(null);
|
const [addonError, setAddonError] = useState<string | null>(null);
|
||||||
|
// 折扣比例(1 = 100%)
|
||||||
const [discountRatio, setDiscountRatio] = useState<number>(1);
|
const [discountRatio, setDiscountRatio] = useState<number>(1);
|
||||||
|
// 渠道折扣列表
|
||||||
|
const [channelDiscounts, setChannelDiscounts] = useState<
|
||||||
|
{ channel_id?: string | null; channel_name?: string | null; discount_rate?: number | null; discount_name?: string | null }[]
|
||||||
|
>([]);
|
||||||
|
// 下拉框状态
|
||||||
|
const [isDiscountDropdownOpen, setIsDiscountDropdownOpen] = useState(false);
|
||||||
|
const discountDropdownRef = useRef<HTMLDivElement>(null);
|
||||||
|
// 结算方式:'self' 自费, 'account' 挂账
|
||||||
|
const [paymentMethod, setPaymentMethod] = useState<'self' | 'account'>('self');
|
||||||
|
const [isPaymentMethodDropdownOpen, setIsPaymentMethodDropdownOpen] = useState(false);
|
||||||
|
const paymentMethodDropdownRef = useRef<HTMLDivElement>(null);
|
||||||
|
// 挂账公司
|
||||||
|
const [accountCompany, setAccountCompany] = useState<string>('圆和');
|
||||||
|
const [isAccountCompanyDropdownOpen, setIsAccountCompanyDropdownOpen] = useState(false);
|
||||||
|
const accountCompanyDropdownRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
// 点击外部关闭下拉框
|
||||||
|
useEffect(() => {
|
||||||
|
const handleClickOutside = (event: MouseEvent) => {
|
||||||
|
if (discountDropdownRef.current && !discountDropdownRef.current.contains(event.target as Node)) {
|
||||||
|
setIsDiscountDropdownOpen(false);
|
||||||
|
}
|
||||||
|
if (paymentMethodDropdownRef.current && !paymentMethodDropdownRef.current.contains(event.target as Node)) {
|
||||||
|
setIsPaymentMethodDropdownOpen(false);
|
||||||
|
}
|
||||||
|
if (accountCompanyDropdownRef.current && !accountCompanyDropdownRef.current.contains(event.target as Node)) {
|
||||||
|
setIsAccountCompanyDropdownOpen(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isDiscountDropdownOpen || isPaymentMethodDropdownOpen || isAccountCompanyDropdownOpen) {
|
||||||
|
document.addEventListener('mousedown', handleClickOutside);
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener('mousedown', handleClickOutside);
|
||||||
|
};
|
||||||
|
}, [isDiscountDropdownOpen, isPaymentMethodDropdownOpen, isAccountCompanyDropdownOpen]);
|
||||||
|
|
||||||
// 获取体检加项客户信息(用于拿到渠道折扣等信息)
|
// 获取体检加项客户信息(用于拿到渠道折扣等信息)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -42,10 +82,13 @@ export const ExamAddonPanel = ({ client }: ExamAddonPanelProps) => {
|
|||||||
try {
|
try {
|
||||||
const res = await getAddItemCustomerInfo({ physical_exam_id });
|
const res = await getAddItemCustomerInfo({ physical_exam_id });
|
||||||
if (res.Status === 200 && res.Data?.listChannelDiscount && res.Data.listChannelDiscount.length > 0) {
|
if (res.Status === 200 && res.Data?.listChannelDiscount && res.Data.listChannelDiscount.length > 0) {
|
||||||
|
setChannelDiscounts(res.Data.listChannelDiscount);
|
||||||
const rate = res.Data.listChannelDiscount[0]?.discount_rate;
|
const rate = res.Data.listChannelDiscount[0]?.discount_rate;
|
||||||
if (typeof rate === 'number' && rate > 0) {
|
if (typeof rate === 'number' && rate > 0) {
|
||||||
setDiscountRatio(rate);
|
setDiscountRatio(rate);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
setChannelDiscounts([]);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('获取加项客户信息失败', err);
|
console.error('获取加项客户信息失败', err);
|
||||||
@@ -176,12 +219,103 @@ export const ExamAddonPanel = ({ client }: ExamAddonPanelProps) => {
|
|||||||
return '渠道价';
|
return '渠道价';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 构建折扣选项列表
|
||||||
|
const discountOptions = useMemo(() => {
|
||||||
|
const options: Array<{ value: number; label: string }> = [
|
||||||
|
{ value: 1, label: '100%(无折扣)' },
|
||||||
|
];
|
||||||
|
channelDiscounts.forEach((item) => {
|
||||||
|
const rate = typeof item.discount_rate === 'number' && item.discount_rate > 0 ? item.discount_rate : 1;
|
||||||
|
const percent = Math.round(rate * 100);
|
||||||
|
const label = item.discount_name || `${percent}%`;
|
||||||
|
options.push({ value: rate, label });
|
||||||
|
});
|
||||||
|
return options;
|
||||||
|
}, [channelDiscounts]);
|
||||||
|
|
||||||
|
// 获取当前选中的标签
|
||||||
|
const currentDiscountLabel = useMemo(() => {
|
||||||
|
const option = discountOptions.find(opt => opt.value === discountRatio);
|
||||||
|
return option?.label || '100%(无折扣)';
|
||||||
|
}, [discountRatio, discountOptions]);
|
||||||
|
|
||||||
|
// 处理折扣选择
|
||||||
|
const handleDiscountSelect = (value: number) => {
|
||||||
|
setDiscountRatio(value);
|
||||||
|
setIsDiscountDropdownOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 处理结算方式选择
|
||||||
|
const handlePaymentMethodSelect = (value: 'self' | 'account') => {
|
||||||
|
setPaymentMethod(value);
|
||||||
|
setIsPaymentMethodDropdownOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 处理挂账公司选择
|
||||||
|
const handleAccountCompanySelect = (value: string) => {
|
||||||
|
setAccountCompany(value);
|
||||||
|
setIsAccountCompanyDropdownOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 结算方式选项
|
||||||
|
const paymentMethodOptions: Array<{ value: 'self' | 'account'; label: string }> = [
|
||||||
|
{ value: 'self', label: '自费' },
|
||||||
|
{ value: 'account', label: '挂账' },
|
||||||
|
];
|
||||||
|
|
||||||
|
// 挂账公司选项(可以根据实际需求从接口获取)
|
||||||
|
const accountCompanyOptions: Array<{ value: string; label: string }> = [
|
||||||
|
{ value: '圆和', label: '圆和' },
|
||||||
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='space-y-4'>
|
<div className='space-y-4'>
|
||||||
{/* 标题和说明 */}
|
{/* 标题和说明 */}
|
||||||
<div>
|
<div>
|
||||||
<div className='flex items-center justify-between mb-2 gap-3'>
|
<div className='flex items-center justify-between mb-3 gap-4'>
|
||||||
<h3 className='text-lg font-semibold text-gray-900'>体检套餐加项选择</h3>
|
<div className='flex items-center gap-2'>
|
||||||
|
<h3 className='text-lg font-semibold text-gray-900 whitespace-nowrap'>体检套餐加项选择</h3>
|
||||||
|
<div className='flex items-center gap-2 text-xs text-gray-600'>
|
||||||
|
{/* <span className='whitespace-nowrap'>折扣比例</span> */}
|
||||||
|
<div ref={discountDropdownRef} className='relative'>
|
||||||
|
<button
|
||||||
|
type='button'
|
||||||
|
onClick={() => setIsDiscountDropdownOpen(!isDiscountDropdownOpen)}
|
||||||
|
className={cls(
|
||||||
|
'border-gray-300 rounded-lg px-3 py-1.5 text-xs text-gray-700 bg-white',
|
||||||
|
'focus:outline-none focus:ring-1 focus:ring-blue-500 focus:border-blue-500',
|
||||||
|
'min-w-[140px] flex items-center justify-between gap-2',
|
||||||
|
'hover:border-gray-400 transition-colors'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<span>{currentDiscountLabel}</span>
|
||||||
|
<span className={cls('text-gray-400 transition-transform text-[10px]', isDiscountDropdownOpen && 'rotate-180')}>
|
||||||
|
▼
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
{isDiscountDropdownOpen && (
|
||||||
|
<div className='absolute z-50 w-full mt-1 bg-white border border-gray-300 rounded-lg shadow-lg overflow-hidden'>
|
||||||
|
{discountOptions.map((option) => (
|
||||||
|
<button
|
||||||
|
key={option.value}
|
||||||
|
type='button'
|
||||||
|
onClick={() => handleDiscountSelect(option.value)}
|
||||||
|
className={cls(
|
||||||
|
'w-full px-3 py-2 text-xs text-left transition-colors',
|
||||||
|
'hover:bg-gray-50',
|
||||||
|
discountRatio === option.value && 'bg-blue-50 text-blue-700 font-medium',
|
||||||
|
discountRatio !== option.value && 'text-gray-700'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{option.label}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className='w-[260px] flex items-center gap-2'>
|
<div className='w-[260px] flex items-center gap-2'>
|
||||||
<Input
|
<Input
|
||||||
placeholder='搜索 加项名称'
|
placeholder='搜索 加项名称'
|
||||||
@@ -291,14 +425,100 @@ export const ExamAddonPanel = ({ client }: ExamAddonPanelProps) => {
|
|||||||
<span className='text-gray-500 ml-1'>已优惠 ¥{discount.toFixed(0)}</span>
|
<span className='text-gray-500 ml-1'>已优惠 ¥{discount.toFixed(0)}</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className='text-xs text-gray-500'>结算方式: 个人支付 (微信 / 支付宝)</div>
|
{/* <div className='text-xs text-gray-500'>结算方式: 个人支付 (微信 / 支付宝)</div> */}
|
||||||
|
</div>
|
||||||
|
<div className='flex items-center gap-3'>
|
||||||
|
{/* 结算方式 */}
|
||||||
|
<div className='flex items-center gap-2 text-xs text-gray-600'>
|
||||||
|
<span className='whitespace-nowrap'>结算方式</span>
|
||||||
|
<div ref={paymentMethodDropdownRef} className='relative'>
|
||||||
|
<button
|
||||||
|
type='button'
|
||||||
|
onClick={() => setIsPaymentMethodDropdownOpen(!isPaymentMethodDropdownOpen)}
|
||||||
|
className={cls(
|
||||||
|
'border border-gray-300 rounded-lg px-3 py-1.5 text-xs text-gray-700 bg-white',
|
||||||
|
'focus:outline-none focus:ring-1 focus:ring-blue-500 focus:border-blue-500',
|
||||||
|
'min-w-[100px] flex items-center justify-between gap-2',
|
||||||
|
'hover:border-gray-400 transition-colors'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<span>{paymentMethodOptions.find(opt => opt.value === paymentMethod)?.label || '自费'}</span>
|
||||||
|
<span className={cls('text-gray-400 transition-transform text-[10px]', isPaymentMethodDropdownOpen && 'rotate-180')}>
|
||||||
|
▼
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
{isPaymentMethodDropdownOpen && (
|
||||||
|
<div className='absolute z-50 w-full bottom-full mb-1 bg-white border border-gray-300 rounded-lg shadow-lg overflow-hidden'>
|
||||||
|
{paymentMethodOptions.map((option) => (
|
||||||
|
<button
|
||||||
|
key={option.value}
|
||||||
|
type='button'
|
||||||
|
onClick={() => handlePaymentMethodSelect(option.value)}
|
||||||
|
className={cls(
|
||||||
|
'w-full px-3 py-2 text-xs text-left transition-colors',
|
||||||
|
'hover:bg-gray-50',
|
||||||
|
paymentMethod === option.value && 'bg-blue-50 text-blue-700 font-medium',
|
||||||
|
paymentMethod !== option.value && 'text-gray-700'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{option.label}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 挂账公司 */}
|
||||||
|
{paymentMethod === 'account' && (
|
||||||
|
<div className='flex items-center gap-2 text-xs text-gray-600'>
|
||||||
|
<span className='whitespace-nowrap'>挂账公司</span>
|
||||||
|
<div ref={accountCompanyDropdownRef} className='relative'>
|
||||||
|
<button
|
||||||
|
type='button'
|
||||||
|
onClick={() => setIsAccountCompanyDropdownOpen(!isAccountCompanyDropdownOpen)}
|
||||||
|
className={cls(
|
||||||
|
'border border-gray-300 rounded-lg px-3 py-1.5 text-xs text-gray-700 bg-white',
|
||||||
|
'focus:outline-none focus:ring-1 focus:ring-blue-500 focus:border-blue-500',
|
||||||
|
'min-w-[100px] flex items-center justify-between gap-2',
|
||||||
|
'hover:border-gray-400 transition-colors'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<span>{accountCompany}</span>
|
||||||
|
<span className={cls('text-gray-400 transition-transform text-[10px]', isAccountCompanyDropdownOpen && 'rotate-180')}>
|
||||||
|
▼
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
{isAccountCompanyDropdownOpen && (
|
||||||
|
<div className='absolute z-50 w-full bottom-full mb-1 bg-white border border-gray-300 rounded-lg shadow-lg overflow-hidden'>
|
||||||
|
{accountCompanyOptions.map((option) => (
|
||||||
|
<button
|
||||||
|
key={option.value}
|
||||||
|
type='button'
|
||||||
|
onClick={() => handleAccountCompanySelect(option.value)}
|
||||||
|
className={cls(
|
||||||
|
'w-full px-3 py-2 text-xs text-left transition-colors',
|
||||||
|
'hover:bg-gray-50',
|
||||||
|
accountCompany === option.value && 'bg-blue-50 text-blue-700 font-medium',
|
||||||
|
accountCompany !== option.value && 'text-gray-700'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{option.label}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Button
|
||||||
|
className='bg-[#269745] hover:bg-[#269745]/80 rounded-3xl text-white px-6 py-3 text-base font-medium'
|
||||||
|
disabled={selectedCount === 0}
|
||||||
|
>
|
||||||
|
确认支付 ¥{totalCurrent.toFixed(0)}
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
|
||||||
className='bg-[#269745] hover:bg-[#269745]/80 rounded-3xl text-white px-6 py-3 text-base font-medium'
|
|
||||||
disabled={selectedCount === 0}
|
|
||||||
>
|
|
||||||
确认支付 ¥{totalCurrent.toFixed(0)}
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user