This commit is contained in:
chashaobao
2025-12-08 22:08:53 +08:00
parent d4fb682852
commit 6c1e1cfd2d
3 changed files with 197 additions and 150 deletions

View File

@ -1,106 +0,0 @@
import Taro from '@tarojs/taro';
import { Dialog } from '@taroify/core';
import { useCallback, useEffect, useRef, useState } from 'react';
import { DialogStatus, PREFIX } from '@/components/product-dialog/const';
import ContactCustomerService from '@/components/product-dialog/steps-ui/job-contact-customer';
import ContactDirect from '@/components/product-dialog/steps-ui/job-contact-direct';
import UnableUnlockContent from '@/components/product-dialog/steps-ui/job-unable';
import { EventName } from '@/constants/app';
import { DeclarationType, ProductType } from '@/constants/product';
import { JobDetails } from '@/types/job';
import { GetProductIsUnlockResponse, ProductInfo } from '@/types/product';
import { logWithPrefix } from '@/utils/common';
import { requestUseProduct } from '@/utils/product';
import Toast from '@/utils/toast';
import '../index.less';
interface IProps {
data: JobDetails;
productRecord?: GetProductIsUnlockResponse;
productInfo?: ProductInfo;
onClose: () => void;
}
const PRODUCT_CODE = ProductType.VIP;
const log = logWithPrefix('product-contact-dialog');
function ProductContactDialog(props: Omit<IProps, 'visible'>) {
const { data, productInfo: productInfoProps, productRecord, onClose } = props;
const [status, setStatus] = useState<DialogStatus>(DialogStatus.LOADING);
const [publisherAcctNo, setPublisherAcctNo] = useState<string>('');
const initRef = useRef(() => {});
const handleCloseDialog = useCallback(() => {
onClose();
}, [onClose]);
const handleReport = useCallback(() => {
log('report', data.id);
}, [data]);
useEffect(() => {
initRef.current = async () => {
const handleContact = (declarationTypeResult?: ProductInfo['declarationTypeResult']) => {
if (declarationTypeResult?.type === DeclarationType.Direct && declarationTypeResult.publisherAcctNo) {
console.log('set JOB_CONTACT_DIRECT', declarationTypeResult.publisherAcctNo)
setStatus(DialogStatus.JOB_CONTACT_DIRECT);
setPublisherAcctNo(declarationTypeResult.publisherAcctNo);
} else {
console.log('set JOB_CONTACT_CS')
setStatus(DialogStatus.JOB_CONTACT_CS);
}
};
try {
Taro.showLoading();
// if (1 < 2) {
// setStatus(DialogStatus.JOB_CONTACT_CS);
// return;
// }
log('requestProductUseRecord result', productRecord);
if (productRecord) {
handleContact(productRecord.declarationTypeResult);
return;
}
if (!productInfoProps?.balance) {
setStatus(DialogStatus.JOB_UNABLE_UNLOCK);
return;
}
const productInfo = await requestUseProduct(PRODUCT_CODE, { jobId: data.id });
Taro.eventCenter.trigger(EventName.READ_CONTACT);
console.log('开始报单', productInfo);
handleContact(productInfo ? productInfo.declarationTypeResult : undefined);
} catch (e) {
Toast.error('出错了,请重试');
console.log(e);
handleCloseDialog();
} finally {
Taro.hideLoading();
}
};
}, [data, handleCloseDialog, productRecord, productInfoProps?.balance]);
useEffect(() => {
initRef.current();
}, []);
if (status === DialogStatus.LOADING) {
return null;
}
return (
<Dialog className={PREFIX} onClose={onClose} open>
<Dialog.Content>
{status === DialogStatus.JOB_CONTACT_CS && <ContactCustomerService onAfterConfirm={handleCloseDialog} />}
{status === DialogStatus.JOB_CONTACT_DIRECT && (
<ContactDirect publisherAcctNo={publisherAcctNo} onAfterConfirm={handleCloseDialog} onReport={handleReport} />
)}
{status === DialogStatus.JOB_UNABLE_UNLOCK && <UnableUnlockContent onConfirm={handleCloseDialog} />}
</Dialog.Content>
</Dialog>
);
}
export default ProductContactDialog;

View File

@ -0,0 +1,175 @@
import Taro from '@tarojs/taro';
import { Dialog } from '@taroify/core';
import { useCallback, useEffect, useState } from 'react';
import { DialogStatus, PREFIX } from '@/components/product-dialog/const';
import JobBuy from '@/components/product-dialog/steps-ui/job-buy';
import ContactCustomerService from '@/components/product-dialog/steps-ui/job-contact-customer';
import ContactDirect from '@/components/product-dialog/steps-ui/job-contact-direct';
import UnableUnlockContent from '@/components/product-dialog/steps-ui/job-unable';
import { DeclarationType, ProductType } from '@/constants/product';
import { JobDetails } from '@/types/job';
import { GetProductIsUnlockResponse, ProductInfo } from '@/types/product';
import { logWithPrefix } from '@/utils/common';
import { requestUseProduct } from '@/utils/product';
import Toast from '@/utils/toast';
import '../index.less';
interface IProps {
data: JobDetails;
/** Product use record from parent - if exists, user has already unlocked this job */
productRecord?: GetProductIsUnlockResponse | null;
/** Product balance info from parent */
productInfo?: ProductInfo;
/** Callback to refresh product balance in parent after purchase */
onRefreshBalance?: () => Promise<ProductInfo | undefined>;
onClose: () => void;
}
const PRODUCT_CODE = ProductType.VIP;
const log = logWithPrefix('product-job-contact-dialog');
/**
* Integrated dialog component for job contact flow
* Handles: balance check -> buy if needed -> use product -> show contact info
*
* @param productRecord - Pass from parent to avoid duplicate API calls
* @param productInfo - Pass from parent to avoid duplicate API calls
* @param onRefreshBalance - Callback to refresh balance in parent after purchase
*/
function ProductJobContactDialog(props: IProps) {
const { data, productRecord, productInfo, onRefreshBalance, onClose } = props;
const [status, setStatus] = useState<DialogStatus>(DialogStatus.LOADING);
const [publisherAcctNo, setPublisherAcctNo] = useState<string>('');
/**
* Handle contact display based on declaration type
*/
const showContactResult = useCallback((declarationTypeResult?: ProductInfo['declarationTypeResult']) => {
if (declarationTypeResult?.type === DeclarationType.Direct && declarationTypeResult.publisherAcctNo) {
log('show JOB_CONTACT_DIRECT', declarationTypeResult.publisherAcctNo);
setPublisherAcctNo(declarationTypeResult.publisherAcctNo);
setStatus(DialogStatus.JOB_CONTACT_DIRECT);
} else {
log('show JOB_CONTACT_CS');
setStatus(DialogStatus.JOB_CONTACT_CS);
}
}, []);
/**
* Use product and show contact info
*/
const consumeProductAndShowContact = useCallback(async () => {
const productResult = await requestUseProduct(PRODUCT_CODE, { jobId: data.id });
log('consumeProductAndShowContact result', productResult);
// Refresh balance in parent after consuming product
await onRefreshBalance?.();
showContactResult(productResult?.declarationTypeResult);
}, [data.id, showContactResult, onRefreshBalance]);
/**
* Callback after successful purchase
* Refresh balance via parent callback and use product to show contact info
*/
const handleAfterBuy = useCallback(async () => {
log('handleAfterBuy - start');
try {
Taro.showLoading({ mask: true, title: '加载中...' });
// Refresh balance via parent callback
await onRefreshBalance?.();
// Use product and show contact info after purchase
await consumeProductAndShowContact();
} catch (e) {
log('handleAfterBuy error', e);
Toast.error('出错了,请重试');
onClose();
} finally {
Taro.hideLoading();
}
}, [consumeProductAndShowContact, onRefreshBalance, onClose]);
const handleReport = useCallback(() => {
log('report', data.id);
}, [data.id]);
/**
* Initialize dialog on mount
*/
useEffect(() => {
let isMounted = true;
const init = async () => {
try {
Taro.showLoading({ mask: true, title: '加载中...' });
log('init with productRecord', productRecord);
log('init with productInfo', productInfo);
// Step 1: Already unlocked - show contact directly
if (productRecord) {
log('show JOB_CONTACT_DIRECT from productRecord', productRecord.declarationTypeResult);
showContactResult(productRecord.declarationTypeResult);
return;
}
// Step 2: No productInfo - error state
if (!productInfo) {
log('no productInfo provided, closing');
Toast.error('出错了,请重试');
onClose();
return;
}
// Step 3: Determine status based on balance
if (!productInfo.isPaidVip && !productInfo.freeBalance) {
log('show JOB_BUY');
if (isMounted) setStatus(DialogStatus.JOB_BUY);
} else if (!productInfo.balance) {
log('show JOB_UNABLE_UNLOCK');
if (isMounted) setStatus(DialogStatus.JOB_UNABLE_UNLOCK);
} else {
await consumeProductAndShowContact();
}
} catch (e) {
log('init error', e);
Toast.error('出错了,请重试');
onClose();
} finally {
Taro.hideLoading();
}
};
init();
return () => {
isMounted = false;
};
// Only run on mount - props are captured at dialog open time
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
if (status === DialogStatus.LOADING) {
return null;
}
return (
<Dialog className={PREFIX} onClose={onClose} open>
<Dialog.Content>
{status === DialogStatus.JOB_CONTACT_CS && <ContactCustomerService onAfterConfirm={onClose} />}
{status === DialogStatus.JOB_CONTACT_DIRECT && (
<ContactDirect publisherAcctNo={publisherAcctNo} onAfterConfirm={onClose} onReport={handleReport} />
)}
{status === DialogStatus.JOB_BUY && (
<JobBuy onConfirm={handleAfterBuy} isCreateResume={productInfo?.isCreateResume} />
)}
{status === DialogStatus.JOB_UNABLE_UNLOCK && <UnableUnlockContent onConfirm={onClose} />}
</Dialog.Content>
</Dialog>
);
}
export default ProductJobContactDialog;

View File

@ -12,9 +12,8 @@ import { JoinGroupHint } from '@/components/join-group-hint';
import LoginButton from '@/components/login-button'; import LoginButton from '@/components/login-button';
import PageLoading from '@/components/page-loading'; import PageLoading from '@/components/page-loading';
import { PrejobPopup } from '@/components/prejob-popup'; import { PrejobPopup } from '@/components/prejob-popup';
import ProductContactDialog from '@/components/product-dialog/contact'; import ProductJobContactDialog from '@/components/product-dialog/job-contact';
import CompanyPublishJobBuy from '@/components/product-dialog/steps-ui/company-publish-job-buy'; import CompanyPublishJobBuy from '@/components/product-dialog/steps-ui/company-publish-job-buy';
import JobBuy from '@/components/product-dialog/steps-ui/job-buy';
import { EventName, PageUrl, RoleType } from '@/constants/app'; import { EventName, PageUrl, RoleType } from '@/constants/app';
import { CertificationStatusType } from '@/constants/company'; import { CertificationStatusType } from '@/constants/company';
import { ReportEventId } from '@/constants/event'; import { ReportEventId } from '@/constants/event';
@ -71,13 +70,13 @@ const getMapCallout = (data: JobDetails): MapProps.callout | undefined => {
const AnchorFooter = (props: { data: JobDetails }) => { const AnchorFooter = (props: { data: JobDetails }) => {
const { data } = props; const { data } = props;
const [errorTips, setErrorTips] = useState<string>(''); const [errorTips, setErrorTips] = useState<string>('');
const [contactDialogVisible, setContactDialogVisible] = useState(false); const [showJobContactDialog, setShowJobContactDialog] = useState(false);
const [showMaterialGuide, setShowMaterialGuide] = useState(false); const [showMaterialGuide, setShowMaterialGuide] = useState(false);
const [showBuyDialog, setShowBuyDialog] = useState(false);
const [productInfo, setProductInfo] = useState<undefined | ProductInfo>(); const [productInfo, setProductInfo] = useState<undefined | ProductInfo>();
const [productRecord, setProductRecord] = useState<undefined | GetProductIsUnlockResponse>(); const [productRecord, setProductRecord] = useState<undefined | GetProductIsUnlockResponse>();
const userInfo = useUserInfo(); const userInfo = useUserInfo();
const needPhone = isNeedPhone(userInfo); const needPhone = isNeedPhone(userInfo);
const getProductRecord = useCallback(async () => { const getProductRecord = useCallback(async () => {
const result = await requestProductUseRecord(ProductType.VIP, { jobId: data.id }); const result = await requestProductUseRecord(ProductType.VIP, { jobId: data.id });
setProductRecord(result); setProductRecord(result);
@ -86,6 +85,7 @@ const AnchorFooter = (props: { data: JobDetails }) => {
const getProductBalance = useCallback(async () => { const getProductBalance = useCallback(async () => {
const [, resp] = await requestProductBalance(ProductType.VIP); const [, resp] = await requestProductBalance(ProductType.VIP);
setProductInfo(resp); setProductInfo(resp);
return resp;
}, []); }, []);
const handleClickContact = useCallback(async () => { const handleClickContact = useCallback(async () => {
@ -126,17 +126,13 @@ const AnchorFooter = (props: { data: JobDetails }) => {
jobId: data.id, jobId: data.id,
}); });
} else { } else {
// Show material guide if no resume and no VIP and no free balance
if (!productInfo?.isCreateResume && !productInfo?.isPaidVip && !productInfo?.freeBalance) { if (!productInfo?.isCreateResume && !productInfo?.isPaidVip && !productInfo?.freeBalance) {
setShowMaterialGuide(true); setShowMaterialGuide(true);
return; return;
} }
// Open integrated dialog - it handles buy + contact internally
if (!productRecord && !productInfo.isPaidVip && !productInfo.freeBalance) { setShowJobContactDialog(true);
setShowBuyDialog(true);
return;
}
setContactDialogVisible(true);
} }
} catch (error) { } catch (error) {
const e = error as HttpError; const e = error as HttpError;
@ -149,33 +145,24 @@ const AnchorFooter = (props: { data: JobDetails }) => {
} }
}, [data, productInfo?.freeBalance, productInfo?.isCreateResume, productInfo?.isPaidVip]); }, [data, productInfo?.freeBalance, productInfo?.isCreateResume, productInfo?.isPaidVip]);
const handleDialogHidden = useCallback(() => { const handleDialogClose = useCallback(() => {
setContactDialogVisible(false); setShowJobContactDialog(false);
}, []); // Refresh data after dialog closes
getProductRecord();
}, [getProductRecord]);
const handleConfirmPrejob = useCallback( const handleConfirmPrejob = useCallback(
(type: GET_CONTACT_TYPE) => { (type: GET_CONTACT_TYPE) => {
setShowMaterialGuide(false); setShowMaterialGuide(false);
if (GET_CONTACT_TYPE.VIP === type) { if (GET_CONTACT_TYPE.VIP === type) {
getProductBalance(); getProductBalance().then(() => {
setContactDialogVisible(true); setShowJobContactDialog(true);
});
} }
}, },
[getProductBalance] [getProductBalance]
); );
const handleAfterBuy = useCallback(async () => {
setShowBuyDialog(false);
Taro.showLoading({ mask: true, title: '加载中...' });
await getProductBalance();
console.log('购买后重新获取次数');
setContactDialogVisible(true);
Taro.hideLoading();
}, [getProductBalance]);
const handleCancel = useCallback(() => {
setShowBuyDialog(false);
}, []);
// const unAuthedButtonText = useMemo(() => { // const unAuthedButtonText = useMemo(() => {
// if (haveSeen) { // if (haveSeen) {
// return '查看联系方式'; // return '查看联系方式';
@ -194,11 +181,8 @@ const AnchorFooter = (props: { data: JobDetails }) => {
useEffect(() => { useEffect(() => {
Taro.eventCenter.on(EventName.CREATE_PROFILE, getProductBalance); Taro.eventCenter.on(EventName.CREATE_PROFILE, getProductBalance);
Taro.eventCenter.on(EventName.READ_CONTACT, getProductBalance);
return () => { return () => {
Taro.eventCenter.off(EventName.CREATE_PROFILE); Taro.eventCenter.off(EventName.CREATE_PROFILE);
Taro.eventCenter.off(EventName.READ_CONTACT);
}; };
}, [getProductBalance]); }, [getProductBalance]);
@ -228,12 +212,13 @@ const AnchorFooter = (props: { data: JobDetails }) => {
</LoginButton> </LoginButton>
</div> </div>
<div> <div>
{contactDialogVisible && ( {showJobContactDialog && (
<ProductContactDialog <ProductJobContactDialog
productInfo={productInfo}
productRecord={productRecord}
data={data} data={data}
onClose={handleDialogHidden} productRecord={productRecord}
productInfo={productInfo}
onRefreshBalance={getProductBalance}
onClose={handleDialogClose}
/> />
)} )}
{showMaterialGuide && ( {showMaterialGuide && (
@ -243,13 +228,6 @@ const AnchorFooter = (props: { data: JobDetails }) => {
onConfirm={handleConfirmPrejob} onConfirm={handleConfirmPrejob}
/> />
)} )}
{showBuyDialog && (
<Dialog open onClose={handleCancel}>
<Dialog.Content>
<JobBuy onConfirm={handleAfterBuy} isCreateResume={productInfo?.isCreateResume} />
</Dialog.Content>
</Dialog>
)}
<CommonDialog <CommonDialog
content={errorTips} content={errorTips}
confirm="确定" confirm="确定"