新增加项需求
This commit is contained in:
@@ -82,6 +82,8 @@ import type {
|
|||||||
CustomSettlementApproveStatusGetResponse,
|
CustomSettlementApproveStatusGetResponse,
|
||||||
InputCustomSettlementApply,
|
InputCustomSettlementApply,
|
||||||
CustomSettlementApplyResponse,
|
CustomSettlementApplyResponse,
|
||||||
|
InputCustomSettlementApplyCancel,
|
||||||
|
CustomSettlementApplyCancelResponse,
|
||||||
} from './types';
|
} from './types';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -633,3 +635,15 @@ export const customSettlementApply = (
|
|||||||
data
|
data
|
||||||
).then(res => res.data);
|
).then(res => res.data);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 体检加项自定义结算申请取消
|
||||||
|
*/
|
||||||
|
export const customSettlementApplyCancel = (
|
||||||
|
data: InputCustomSettlementApplyCancel
|
||||||
|
): Promise<CustomSettlementApplyCancelResponse> => {
|
||||||
|
return request.post<CustomSettlementApplyCancelResponse>(
|
||||||
|
`${MEDICAL_EXAM_BASE_PATH}/custom-settlement-apply-cancel`,
|
||||||
|
data
|
||||||
|
).then(res => res.data);
|
||||||
|
};
|
||||||
|
|||||||
@@ -1461,3 +1461,26 @@ export interface OutputCustomSettlementApply {
|
|||||||
* 体检加项自定义结算申请响应
|
* 体检加项自定义结算申请响应
|
||||||
*/
|
*/
|
||||||
export type CustomSettlementApplyResponse = CommonActionResult<OutputCustomSettlementApply>;
|
export type CustomSettlementApplyResponse = CommonActionResult<OutputCustomSettlementApply>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 体检加项自定义结算申请取消入参
|
||||||
|
*/
|
||||||
|
export interface InputCustomSettlementApplyCancel {
|
||||||
|
/** 体检ID */
|
||||||
|
physical_exam_id: number;
|
||||||
|
/** 体检加项组合ID(多个逗号分隔,例如:123,456) */
|
||||||
|
add_item_id: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 体检加项自定义结算申请取消出参
|
||||||
|
*/
|
||||||
|
export interface OutputCustomSettlementApplyCancel {
|
||||||
|
/** 是否成功(1-成功 0-失败) */
|
||||||
|
is_success?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 体检加项自定义结算申请取消响应
|
||||||
|
*/
|
||||||
|
export type CustomSettlementApplyCancelResponse = CommonActionResult<OutputCustomSettlementApplyCancel>;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { useEffect, useMemo, useState, useRef } from 'react';
|
import { useEffect, useMemo, useState, useRef, useCallback } from 'react';
|
||||||
|
|
||||||
import type { ExamClient } from '../../data/mockData';
|
import type { ExamClient } from '../../data/mockData';
|
||||||
import { searchPhysicalExamAddItem, getAddItemCustomerInfo, getChannelCompanyList, createNativePaymentQrcode, checkNativePaymentStatus, getAddItemBillPdf } from '../../api';
|
import { searchPhysicalExamAddItem, getAddItemCustomerInfo, getChannelCompanyList, createNativePaymentQrcode, checkNativePaymentStatus, getAddItemBillPdf, getCustomSettlementApproveStatus, customSettlementApply, customSettlementApplyCancel } from '../../api';
|
||||||
import { Button, Input } from '../ui';
|
import { Button, Input } from '../ui';
|
||||||
import { cls } from '../../utils/cls';
|
import { cls } from '../../utils/cls';
|
||||||
import nozImage from '../../assets/image/noz.png';
|
import nozImage from '../../assets/image/noz.png';
|
||||||
@@ -72,6 +72,24 @@ export const ExamAddonPanel = ({ client, onGoToSign }: ExamAddonPanelProps) => {
|
|||||||
const [paymentLoading, setPaymentLoading] = useState(false);
|
const [paymentLoading, setPaymentLoading] = useState(false);
|
||||||
const [paymentMessage, setPaymentMessage] = useState<string | null>(null);
|
const [paymentMessage, setPaymentMessage] = useState<string | null>(null);
|
||||||
const pollingTimerRef = useRef<number | null>(null);
|
const pollingTimerRef = useRef<number | null>(null);
|
||||||
|
// 跟踪客户信息是否已加载(用于避免重复请求加项列表)
|
||||||
|
const customerInfoLoadedRef = useRef<boolean>(false);
|
||||||
|
// 自定义结算相关状态
|
||||||
|
const customSettlementPollingTimerRef = useRef<number | null>(null);
|
||||||
|
const lastFetchStatusTimeRef = useRef<number>(0); // 上次获取审批状态的时间戳
|
||||||
|
const [showCustomSettlementModal, setShowCustomSettlementModal] = useState(false);
|
||||||
|
const [customSettlementStatus, setCustomSettlementStatus] = useState<{
|
||||||
|
apply_status?: number;
|
||||||
|
apply_status_name?: string | null;
|
||||||
|
final_settlement_price?: number | null;
|
||||||
|
} | null>(null);
|
||||||
|
const [customSettlementLoading, setCustomSettlementLoading] = useState(false);
|
||||||
|
const [customSettlementType, setCustomSettlementType] = useState<1 | 2>(1); // 1-按比例折扣 2-自定义结算价
|
||||||
|
const [customDiscountRatio, setCustomDiscountRatio] = useState<number>(100); // 折扣比例(如100代表10折,即原价)
|
||||||
|
const [customFinalPrice, setCustomFinalPrice] = useState<number>(0); // 最终结算价
|
||||||
|
const [customApplyReason, setCustomApplyReason] = useState<string>(''); // 申请理由
|
||||||
|
const [waitingSeconds, setWaitingSeconds] = useState<number>(0); // 等待审核的秒数
|
||||||
|
const waitingTimerRef = useRef<number | null>(null); // 等待计时器
|
||||||
|
|
||||||
// 点击外部关闭下拉框
|
// 点击外部关闭下拉框
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -124,6 +142,7 @@ export const ExamAddonPanel = ({ client, onGoToSign }: ExamAddonPanelProps) => {
|
|||||||
scrm_account_id,
|
scrm_account_id,
|
||||||
scrm_account_name,
|
scrm_account_name,
|
||||||
});
|
});
|
||||||
|
customerInfoLoadedRef.current = true;
|
||||||
// 设置挂账公司默认值
|
// 设置挂账公司默认值
|
||||||
const companyName = res.Data.customerInfo.company_name;
|
const companyName = res.Data.customerInfo.company_name;
|
||||||
if (companyName && companyName.trim() !== '') {
|
if (companyName && companyName.trim() !== '') {
|
||||||
@@ -149,9 +168,14 @@ export const ExamAddonPanel = ({ client, onGoToSign }: ExamAddonPanelProps) => {
|
|||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('获取加项客户信息失败', err);
|
console.error('获取加项客户信息失败', err);
|
||||||
|
} finally {
|
||||||
|
// 无论成功或失败,都标记为已尝试加载
|
||||||
|
customerInfoLoadedRef.current = true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 当 client.id 变化时,重置加载状态
|
||||||
|
customerInfoLoadedRef.current = false;
|
||||||
fetchCustomerInfo();
|
fetchCustomerInfo();
|
||||||
}, [client.id]);
|
}, [client.id]);
|
||||||
|
|
||||||
@@ -205,6 +229,10 @@ export const ExamAddonPanel = ({ client, onGoToSign }: ExamAddonPanelProps) => {
|
|||||||
|
|
||||||
// 拉取加项列表
|
// 拉取加项列表
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (!customerInfoLoadedRef.current && customerInfo === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const fetchList = async () => {
|
const fetchList = async () => {
|
||||||
setAddonLoading(true);
|
setAddonLoading(true);
|
||||||
setAddonError(null);
|
setAddonError(null);
|
||||||
@@ -249,7 +277,7 @@ export const ExamAddonPanel = ({ client, onGoToSign }: ExamAddonPanelProps) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
fetchList();
|
fetchList();
|
||||||
}, [debouncedAddonSearch, customerInfo?.scrm_account_id, customerInfo?.scrm_account_name, client.id]);
|
}, [debouncedAddonSearch, customerInfo?.scrm_account_id, customerInfo?.scrm_account_name, client.id, customerInfo]);
|
||||||
|
|
||||||
const allAddons = useMemo(() => addonList, [addonList]);
|
const allAddons = useMemo(() => addonList, [addonList]);
|
||||||
|
|
||||||
@@ -279,9 +307,15 @@ export const ExamAddonPanel = ({ client, onGoToSign }: ExamAddonPanelProps) => {
|
|||||||
const totalOriginal = selectedItems.reduce((sum, item) => {
|
const totalOriginal = selectedItems.reduce((sum, item) => {
|
||||||
return sum + parseFloat(item.originalPrice || item.currentPrice || '0');
|
return sum + parseFloat(item.originalPrice || item.currentPrice || '0');
|
||||||
}, 0);
|
}, 0);
|
||||||
const totalCurrent = selectedItems.reduce((sum, item) => {
|
const totalCurrentBase = selectedItems.reduce((sum, item) => {
|
||||||
return sum + parseFloat(item.currentPrice || item.originalPrice || '0');
|
return sum + parseFloat(item.currentPrice || item.originalPrice || '0');
|
||||||
}, 0);
|
}, 0);
|
||||||
|
|
||||||
|
// 如果自定义结算审核通过,使用审核后的金额
|
||||||
|
const totalCurrent = (customSettlementStatus?.apply_status === 3 && typeof customSettlementStatus.final_settlement_price === 'number')
|
||||||
|
? customSettlementStatus.final_settlement_price
|
||||||
|
: totalCurrentBase;
|
||||||
|
|
||||||
const discount = totalOriginal - totalCurrent;
|
const discount = totalOriginal - totalCurrent;
|
||||||
|
|
||||||
// 获取标签样式
|
// 获取标签样式
|
||||||
@@ -382,6 +416,277 @@ export const ExamAddonPanel = ({ client, onGoToSign }: ExamAddonPanelProps) => {
|
|||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
// 获取自定义结算审批状态(带节流,最多每秒一次)
|
||||||
|
const fetchCustomSettlementStatus = useCallback(async () => {
|
||||||
|
const physical_exam_id = Number(client.id);
|
||||||
|
const currentSelectedItems = allAddons.filter(item => selectedIds.has(item.id || item.name));
|
||||||
|
|
||||||
|
if (!physical_exam_id || currentSelectedItems.length === 0) {
|
||||||
|
setCustomSettlementStatus(null);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const add_item_id = currentSelectedItems
|
||||||
|
.map(item => item.combinationItemCode)
|
||||||
|
.filter((code): code is number => code !== null && code !== undefined)
|
||||||
|
.join(',');
|
||||||
|
|
||||||
|
if (!add_item_id) {
|
||||||
|
setCustomSettlementStatus(null);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 节流:如果距离上次调用不足0.8秒,则跳过
|
||||||
|
const now = Date.now();
|
||||||
|
const timeSinceLastCall = now - lastFetchStatusTimeRef.current;
|
||||||
|
if (timeSinceLastCall < 800) {
|
||||||
|
// 返回当前状态是否需要轮询
|
||||||
|
return customSettlementStatus?.apply_status === 1;
|
||||||
|
}
|
||||||
|
lastFetchStatusTimeRef.current = now;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await getCustomSettlementApproveStatus({
|
||||||
|
physical_exam_id,
|
||||||
|
add_item_id,
|
||||||
|
});
|
||||||
|
if (res.Status === 200 && res.Data) {
|
||||||
|
const status = {
|
||||||
|
apply_status: res.Data.apply_status,
|
||||||
|
apply_status_name: res.Data.apply_status_name,
|
||||||
|
final_settlement_price: res.Data.final_settlement_price,
|
||||||
|
};
|
||||||
|
setCustomSettlementStatus(status);
|
||||||
|
// 如果状态变为审核中,重置等待时间
|
||||||
|
if (res.Data.apply_status === 1 && customSettlementStatus?.apply_status !== 1) {
|
||||||
|
setWaitingSeconds(0);
|
||||||
|
}
|
||||||
|
// 返回是否需要继续轮询(1-审核中 需要轮询)
|
||||||
|
return res.Data.apply_status === 1;
|
||||||
|
} else {
|
||||||
|
setCustomSettlementStatus(null);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('获取自定义结算审批状态失败', err);
|
||||||
|
setCustomSettlementStatus(null);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}, [client.id, selectedIds, allAddons, customSettlementStatus]);
|
||||||
|
|
||||||
|
// 当选中加项变化时,获取审批状态
|
||||||
|
useEffect(() => {
|
||||||
|
const currentSelectedItems = allAddons.filter(item => selectedIds.has(item.id || item.name));
|
||||||
|
if (currentSelectedItems.length > 0) {
|
||||||
|
fetchCustomSettlementStatus().then((shouldPoll) => {
|
||||||
|
// 如果需要轮询(审核中),开始轮询
|
||||||
|
if (shouldPoll) {
|
||||||
|
startCustomSettlementPolling();
|
||||||
|
} else {
|
||||||
|
stopCustomSettlementPolling();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
setCustomSettlementStatus(null);
|
||||||
|
stopCustomSettlementPolling();
|
||||||
|
}
|
||||||
|
}, [selectedIds, client.id, allAddons, fetchCustomSettlementStatus]);
|
||||||
|
|
||||||
|
// 开始轮询自定义结算审批状态
|
||||||
|
const startCustomSettlementPolling = useCallback(() => {
|
||||||
|
// 清除之前的定时器
|
||||||
|
stopCustomSettlementPolling();
|
||||||
|
|
||||||
|
// 每1秒轮询一次
|
||||||
|
customSettlementPollingTimerRef.current = window.setInterval(() => {
|
||||||
|
fetchCustomSettlementStatus().then((shouldPoll) => {
|
||||||
|
// 如果不再需要轮询(审核完成),停止轮询
|
||||||
|
if (!shouldPoll) {
|
||||||
|
stopCustomSettlementPolling();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, 1000);
|
||||||
|
}, [fetchCustomSettlementStatus]);
|
||||||
|
|
||||||
|
// 停止轮询自定义结算审批状态
|
||||||
|
const stopCustomSettlementPolling = useCallback(() => {
|
||||||
|
if (customSettlementPollingTimerRef.current) {
|
||||||
|
clearInterval(customSettlementPollingTimerRef.current);
|
||||||
|
customSettlementPollingTimerRef.current = null;
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// 清理轮询定时器
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
stopCustomSettlementPolling();
|
||||||
|
};
|
||||||
|
}, [stopCustomSettlementPolling]);
|
||||||
|
|
||||||
|
// 等待计时器:当审核中时开始计时
|
||||||
|
useEffect(() => {
|
||||||
|
if (customSettlementStatus?.apply_status === 1) {
|
||||||
|
// 开始计时
|
||||||
|
setWaitingSeconds(0);
|
||||||
|
waitingTimerRef.current = window.setInterval(() => {
|
||||||
|
setWaitingSeconds((prev) => prev + 1);
|
||||||
|
}, 1000);
|
||||||
|
} else {
|
||||||
|
// 停止计时并重置
|
||||||
|
if (waitingTimerRef.current) {
|
||||||
|
clearInterval(waitingTimerRef.current);
|
||||||
|
waitingTimerRef.current = null;
|
||||||
|
}
|
||||||
|
setWaitingSeconds(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (waitingTimerRef.current) {
|
||||||
|
clearInterval(waitingTimerRef.current);
|
||||||
|
waitingTimerRef.current = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [customSettlementStatus?.apply_status]);
|
||||||
|
|
||||||
|
// 提交自定义结算申请
|
||||||
|
const handleSubmitCustomSettlement = async () => {
|
||||||
|
const physical_exam_id = Number(client.id);
|
||||||
|
if (!physical_exam_id || selectedItems.length === 0) {
|
||||||
|
setPaymentMessage('请先选择加项项目');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!customApplyReason.trim()) {
|
||||||
|
setPaymentMessage('请输入申请理由');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setCustomSettlementLoading(true);
|
||||||
|
setPaymentMessage(null);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 构建加项项目明细列表
|
||||||
|
const listAddItemDetail = selectedItems
|
||||||
|
.map(item => {
|
||||||
|
const combinationItemCode = item.combinationItemCode;
|
||||||
|
if (combinationItemCode === null || combinationItemCode === undefined) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const originalPrice = parseFloat(item.originalPrice || '0');
|
||||||
|
let settlementPrice = originalPrice;
|
||||||
|
|
||||||
|
if (customSettlementType === 1) {
|
||||||
|
// 按比例折扣
|
||||||
|
settlementPrice = originalPrice * (customDiscountRatio / 100);
|
||||||
|
} else {
|
||||||
|
// 自定义结算价
|
||||||
|
settlementPrice = customFinalPrice / selectedItems.length; // 平均分配
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
combination_item_code: String(combinationItemCode),
|
||||||
|
combination_item_name: item.name,
|
||||||
|
original_price: originalPrice,
|
||||||
|
settlement_price: settlementPrice,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.filter((item): item is { combination_item_code: string; combination_item_name: string; original_price: number; settlement_price: number } => item !== null);
|
||||||
|
|
||||||
|
const original_settlement_price = selectedItems.reduce((sum, item) => {
|
||||||
|
return sum + parseFloat(item.currentPrice || item.originalPrice || '0');
|
||||||
|
}, 0);
|
||||||
|
|
||||||
|
const final_settlement_price = customSettlementType === 1
|
||||||
|
? original_settlement_price * (customDiscountRatio / 100)
|
||||||
|
: customFinalPrice;
|
||||||
|
|
||||||
|
const res = await customSettlementApply({
|
||||||
|
physical_exam_id,
|
||||||
|
listAddItemDetail,
|
||||||
|
original_settlement_price,
|
||||||
|
settlement_type: customSettlementType,
|
||||||
|
discount_ratio: customSettlementType === 1 ? customDiscountRatio : undefined,
|
||||||
|
final_settlement_price,
|
||||||
|
apply_reason: customApplyReason.trim(),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.Status === 200) {
|
||||||
|
setPaymentMessage('自定义结算申请提交成功');
|
||||||
|
setShowCustomSettlementModal(false);
|
||||||
|
// 重置表单
|
||||||
|
setCustomSettlementType(1);
|
||||||
|
setCustomDiscountRatio(100);
|
||||||
|
setCustomFinalPrice(0);
|
||||||
|
setCustomApplyReason('');
|
||||||
|
// 重新获取审批状态并开始轮询
|
||||||
|
setTimeout(() => {
|
||||||
|
fetchCustomSettlementStatus().then((shouldPoll) => {
|
||||||
|
if (shouldPoll) {
|
||||||
|
startCustomSettlementPolling();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, 500);
|
||||||
|
} else {
|
||||||
|
setPaymentMessage(res.Message || '提交失败,请稍后重试');
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('提交自定义结算申请失败', err);
|
||||||
|
setPaymentMessage('提交失败,请稍后重试');
|
||||||
|
} finally {
|
||||||
|
setCustomSettlementLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 取消自定义结算申请
|
||||||
|
const handleCancelCustomSettlement = async () => {
|
||||||
|
const physical_exam_id = Number(client.id);
|
||||||
|
if (!physical_exam_id || selectedItems.length === 0) {
|
||||||
|
setPaymentMessage('请先选择加项项目');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!window.confirm('确定要取消自定义结算申请吗?')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setCustomSettlementLoading(true);
|
||||||
|
setPaymentMessage(null);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const add_item_id = selectedItems
|
||||||
|
.map(item => item.combinationItemCode)
|
||||||
|
.filter((code): code is number => code !== null && code !== undefined)
|
||||||
|
.join(',');
|
||||||
|
|
||||||
|
if (!add_item_id) {
|
||||||
|
setPaymentMessage('无法获取加项项目ID');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await customSettlementApplyCancel({
|
||||||
|
physical_exam_id,
|
||||||
|
add_item_id,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.Status === 200 && res.Data?.is_success === 1) {
|
||||||
|
setPaymentMessage('取消申请成功');
|
||||||
|
// 停止轮询
|
||||||
|
stopCustomSettlementPolling();
|
||||||
|
// 重新获取审批状态
|
||||||
|
setTimeout(() => {
|
||||||
|
fetchCustomSettlementStatus();
|
||||||
|
}, 500);
|
||||||
|
} else {
|
||||||
|
setPaymentMessage(res.Message || '取消申请失败,请稍后重试');
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('取消自定义结算申请失败', err);
|
||||||
|
setPaymentMessage('取消申请失败,请稍后重试');
|
||||||
|
} finally {
|
||||||
|
setCustomSettlementLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// 获取加项PDF(按本次支付的组合代码生成对应的加项单)
|
// 获取加项PDF(按本次支付的组合代码生成对应的加项单)
|
||||||
const fetchAddItemBillPdf = async (examId: number, combinationItemCodes: string) => {
|
const fetchAddItemBillPdf = async (examId: number, combinationItemCodes: string) => {
|
||||||
try {
|
try {
|
||||||
@@ -797,9 +1102,46 @@ export const ExamAddonPanel = ({ client, onGoToSign }: ExamAddonPanelProps) => {
|
|||||||
{/* <div className='text-xs text-gray-500'>结算方式: 个人支付 (微信 / 支付宝)</div> */}
|
{/* <div className='text-xs text-gray-500'>结算方式: 个人支付 (微信 / 支付宝)</div> */}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* 添加自定义结算 */}
|
||||||
|
{localStorage.getItem('authCode')?.includes('HisTijianPad_Btn_Tongji') && selectedItems.length > 0 && (
|
||||||
|
<div className='flex flex-col items-end gap-2 relative'>
|
||||||
|
{customSettlementStatus && (
|
||||||
|
<div className='text-xs text-gray-600'>
|
||||||
|
审批状态: <span className={cls(
|
||||||
|
'font-semibold',
|
||||||
|
customSettlementStatus.apply_status === 1 && 'text-blue-600', // 审核中
|
||||||
|
customSettlementStatus.apply_status === 3 && 'text-green-600', // 审核通过
|
||||||
|
customSettlementStatus.apply_status === 4 && 'text-red-600', // 审核不通过
|
||||||
|
customSettlementStatus.apply_status === 2 && 'text-gray-600' // 取消申请
|
||||||
|
)}>
|
||||||
|
{customSettlementStatus.apply_status_name || '未知'}
|
||||||
|
{customSettlementStatus.apply_status === 1 && ' (审核中...)'}
|
||||||
|
</span>
|
||||||
|
{customSettlementStatus.final_settlement_price !== null && customSettlementStatus.final_settlement_price !== undefined && (
|
||||||
|
<span className='ml-2'>
|
||||||
|
结算价: <span className='font-semibold text-red-600'>¥{customSettlementStatus.final_settlement_price.toFixed(2)}</span>
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<Button
|
||||||
|
onClick={() => setShowCustomSettlementModal(true)}
|
||||||
|
disabled={customSettlementStatus?.apply_status === 1}
|
||||||
|
className={cls(
|
||||||
|
'px-3 py-1.5 text-xs text-white',
|
||||||
|
customSettlementStatus?.apply_status === 1
|
||||||
|
? 'bg-gray-400 cursor-not-allowed'
|
||||||
|
: 'bg-blue-600 hover:bg-blue-700'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
自定义结算
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
|
||||||
{paymentMessage && (
|
{paymentMessage && (
|
||||||
<div className={`text-sm text-center mt-2 ${paymentMessage.includes('成功') ? 'text-green-600' : 'text-amber-600'}`}>
|
<div className={`text-sm text-center mt-2 ${paymentMessage.includes('成功') ? 'text-green-600' : 'text-amber-600'}`}>
|
||||||
{paymentMessage}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -959,6 +1301,191 @@ export const ExamAddonPanel = ({ client, onGoToSign }: ExamAddonPanelProps) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* 自定义结算弹窗 */}
|
||||||
|
{showCustomSettlementModal && (
|
||||||
|
<div className='fixed inset-0 z-50 flex items-center justify-center bg-black/30' onClick={() => setShowCustomSettlementModal(false)}>
|
||||||
|
<div
|
||||||
|
className='w-[500px] max-w-[95vw] bg-white rounded-2xl shadow-xl overflow-hidden'
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
>
|
||||||
|
<div className='px-4 py-3 border-b flex items-center justify-between'>
|
||||||
|
<div className='font-semibold'>自定义结算申请</div>
|
||||||
|
<button
|
||||||
|
className='text-xs text-gray-500 hover:text-gray-700'
|
||||||
|
onClick={() => setShowCustomSettlementModal(false)}
|
||||||
|
>
|
||||||
|
关闭
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='px-4 py-6 space-y-4'>
|
||||||
|
{/* 选中项目信息 */}
|
||||||
|
<div className='text-sm text-gray-700'>
|
||||||
|
<div className='font-medium mb-2'>选中项目 ({selectedItems.length}项):</div>
|
||||||
|
<div className='max-h-32 overflow-y-auto space-y-1'>
|
||||||
|
{selectedItems.map((item, idx) => (
|
||||||
|
<div key={idx} className='text-xs text-gray-600 flex justify-between'>
|
||||||
|
<span>{item.name}</span>
|
||||||
|
<span>¥{parseFloat(item.currentPrice || item.originalPrice || '0').toFixed(2)}</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div className='mt-2 pt-2 border-t text-sm font-semibold flex justify-between'>
|
||||||
|
<span>渠道折扣价合计:</span>
|
||||||
|
<span className='text-red-600'>¥{totalCurrent.toFixed(2)}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 结算方式 */}
|
||||||
|
<div className='space-y-2'>
|
||||||
|
<label className='text-sm text-gray-700 font-medium'>结算方式</label>
|
||||||
|
<div className='flex gap-3'>
|
||||||
|
<button
|
||||||
|
type='button'
|
||||||
|
onClick={() => setCustomSettlementType(1)}
|
||||||
|
className={cls(
|
||||||
|
'flex-1 px-4 py-2 rounded-lg border text-sm transition-colors',
|
||||||
|
customSettlementType === 1
|
||||||
|
? 'bg-blue-50 border-blue-500 text-blue-700 font-medium'
|
||||||
|
: 'bg-white border-gray-300 text-gray-700 hover:border-gray-400'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
按比例折扣
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type='button'
|
||||||
|
onClick={() => setCustomSettlementType(2)}
|
||||||
|
className={cls(
|
||||||
|
'flex-1 px-4 py-2 rounded-lg border text-sm transition-colors',
|
||||||
|
customSettlementType === 2
|
||||||
|
? 'bg-blue-50 border-blue-500 text-blue-700 font-medium'
|
||||||
|
: 'bg-white border-gray-300 text-gray-700 hover:border-gray-400'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
自定义结算价
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 折扣比例或最终结算价 */}
|
||||||
|
{customSettlementType === 1 ? (
|
||||||
|
<div className='space-y-3'>
|
||||||
|
<div className='flex items-center justify-between'>
|
||||||
|
<label className='text-sm text-gray-700 font-medium'>折扣比例</label>
|
||||||
|
<span className='text-sm font-semibold text-blue-600'>{customDiscountRatio / 10}折</span>
|
||||||
|
</div>
|
||||||
|
<div className='relative'>
|
||||||
|
<input
|
||||||
|
type='range'
|
||||||
|
min='10'
|
||||||
|
max='100'
|
||||||
|
step='5'
|
||||||
|
value={customDiscountRatio}
|
||||||
|
onChange={(e) => {
|
||||||
|
setCustomDiscountRatio(Number(e.target.value));
|
||||||
|
}}
|
||||||
|
className='w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer accent-blue-600'
|
||||||
|
style={{
|
||||||
|
background: `linear-gradient(to right, #3b82f6 0%, #3b82f6 ${((customDiscountRatio - 10) / (100 - 10)) * 100}%, #e5e7eb ${((customDiscountRatio - 10) / (100 - 10)) * 100}%, #e5e7eb 100%)`
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{/* 刻度标记 */}
|
||||||
|
<div className='flex justify-between mt-1 px-1'>
|
||||||
|
{[10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95, 100].map((value) => (
|
||||||
|
<span
|
||||||
|
key={value}
|
||||||
|
className='text-[10px] text-gray-400'
|
||||||
|
style={{ width: '10px' }}
|
||||||
|
>
|
||||||
|
{value / 10}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='text-xs text-gray-500'>
|
||||||
|
最终结算价: <span className='font-semibold text-red-600'>¥{(totalCurrent * (customDiscountRatio / 100)).toFixed(2)}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className='space-y-2'>
|
||||||
|
<label className='text-sm text-gray-700 font-medium'>最终结算价 (¥)</label>
|
||||||
|
<Input
|
||||||
|
type='number'
|
||||||
|
min='0'
|
||||||
|
step='0.01'
|
||||||
|
value={customFinalPrice || ''}
|
||||||
|
onChange={(e) => {
|
||||||
|
const val = Number(e.target.value);
|
||||||
|
if (val >= 0) {
|
||||||
|
setCustomFinalPrice(val);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
placeholder='请输入最终结算价'
|
||||||
|
className='text-sm'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* 申请理由 */}
|
||||||
|
<div className='space-y-2'>
|
||||||
|
<label className='text-sm text-gray-700 font-medium'>申请理由 *</label>
|
||||||
|
<textarea
|
||||||
|
value={customApplyReason}
|
||||||
|
onChange={(e) => setCustomApplyReason(e.target.value)}
|
||||||
|
placeholder='请输入申请理由'
|
||||||
|
rows={3}
|
||||||
|
className='w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:outline-none focus:ring-1 focus:ring-blue-500 focus:border-blue-500 resize-none'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 提交按钮 */}
|
||||||
|
<div className='flex gap-3 pt-2'>
|
||||||
|
<Button
|
||||||
|
onClick={() => setShowCustomSettlementModal(false)}
|
||||||
|
className='flex-1 px-4 py-2 bg-gray-100 hover:bg-gray-200 text-gray-700'
|
||||||
|
>
|
||||||
|
取消
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={handleSubmitCustomSettlement}
|
||||||
|
disabled={customSettlementLoading || !customApplyReason.trim()}
|
||||||
|
className={cls(
|
||||||
|
'flex-1 px-4 py-2 text-white font-medium',
|
||||||
|
customSettlementLoading || !customApplyReason.trim()
|
||||||
|
? 'bg-gray-400 cursor-not-allowed'
|
||||||
|
: 'bg-blue-600 hover:bg-blue-700'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{customSettlementLoading ? '提交中...' : '提交申请'}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* 审核中全屏遮罩 */}
|
||||||
|
{customSettlementStatus?.apply_status === 1 && (
|
||||||
|
<div className='fixed inset-0 bg-black/50 backdrop-blur-sm flex items-center justify-center z-50'>
|
||||||
|
<div className='bg-white rounded-2xl p-6 max-w-md w-full mx-4 shadow-xl'>
|
||||||
|
<div className='text-center mb-6'>
|
||||||
|
<div className='text-lg font-semibold text-blue-600 mb-2'>审核中...</div>
|
||||||
|
<div className='text-sm text-gray-500 mb-1'>正在等待审核结果,请稍候</div>
|
||||||
|
<div className='text-xs text-gray-400'>已等待 {waitingSeconds} 秒</div>
|
||||||
|
</div>
|
||||||
|
<div className='flex justify-center'>
|
||||||
|
<Button
|
||||||
|
onClick={handleCancelCustomSettlement}
|
||||||
|
disabled={customSettlementLoading}
|
||||||
|
className='px-6 py-2 bg-gray-500 hover:bg-gray-600 text-white disabled:opacity-50'
|
||||||
|
>
|
||||||
|
{customSettlementLoading ? '取消中...' : '取消申请'}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user