From 6c1e1cfd2d566de2320add163446d123f7685b4a Mon Sep 17 00:00:00 2001 From: chashaobao Date: Mon, 8 Dec 2025 22:08:53 +0800 Subject: [PATCH] featL --- .../product-dialog/contact/index.tsx | 106 ----------- .../product-dialog/job-contact/index.tsx | 175 ++++++++++++++++++ src/pages/job-detail/index.tsx | 66 +++---- 3 files changed, 197 insertions(+), 150 deletions(-) delete mode 100644 src/components/product-dialog/contact/index.tsx create mode 100644 src/components/product-dialog/job-contact/index.tsx diff --git a/src/components/product-dialog/contact/index.tsx b/src/components/product-dialog/contact/index.tsx deleted file mode 100644 index 2519ff2..0000000 --- a/src/components/product-dialog/contact/index.tsx +++ /dev/null @@ -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) { - const { data, productInfo: productInfoProps, productRecord, onClose } = props; - const [status, setStatus] = useState(DialogStatus.LOADING); - const [publisherAcctNo, setPublisherAcctNo] = useState(''); - 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 ( - - - {status === DialogStatus.JOB_CONTACT_CS && } - {status === DialogStatus.JOB_CONTACT_DIRECT && ( - - )} - {status === DialogStatus.JOB_UNABLE_UNLOCK && } - - - ); -} - -export default ProductContactDialog; diff --git a/src/components/product-dialog/job-contact/index.tsx b/src/components/product-dialog/job-contact/index.tsx new file mode 100644 index 0000000..b7b9bff --- /dev/null +++ b/src/components/product-dialog/job-contact/index.tsx @@ -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; + 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.LOADING); + const [publisherAcctNo, setPublisherAcctNo] = useState(''); + + /** + * 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 ( + + + {status === DialogStatus.JOB_CONTACT_CS && } + {status === DialogStatus.JOB_CONTACT_DIRECT && ( + + )} + {status === DialogStatus.JOB_BUY && ( + + )} + {status === DialogStatus.JOB_UNABLE_UNLOCK && } + + + ); +} + +export default ProductJobContactDialog; diff --git a/src/pages/job-detail/index.tsx b/src/pages/job-detail/index.tsx index d019f94..75cda83 100644 --- a/src/pages/job-detail/index.tsx +++ b/src/pages/job-detail/index.tsx @@ -12,9 +12,8 @@ import { JoinGroupHint } from '@/components/join-group-hint'; import LoginButton from '@/components/login-button'; import PageLoading from '@/components/page-loading'; 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 JobBuy from '@/components/product-dialog/steps-ui/job-buy'; import { EventName, PageUrl, RoleType } from '@/constants/app'; import { CertificationStatusType } from '@/constants/company'; import { ReportEventId } from '@/constants/event'; @@ -71,13 +70,13 @@ const getMapCallout = (data: JobDetails): MapProps.callout | undefined => { const AnchorFooter = (props: { data: JobDetails }) => { const { data } = props; const [errorTips, setErrorTips] = useState(''); - const [contactDialogVisible, setContactDialogVisible] = useState(false); + const [showJobContactDialog, setShowJobContactDialog] = useState(false); const [showMaterialGuide, setShowMaterialGuide] = useState(false); - const [showBuyDialog, setShowBuyDialog] = useState(false); const [productInfo, setProductInfo] = useState(); const [productRecord, setProductRecord] = useState(); const userInfo = useUserInfo(); const needPhone = isNeedPhone(userInfo); + const getProductRecord = useCallback(async () => { const result = await requestProductUseRecord(ProductType.VIP, { jobId: data.id }); setProductRecord(result); @@ -86,6 +85,7 @@ const AnchorFooter = (props: { data: JobDetails }) => { const getProductBalance = useCallback(async () => { const [, resp] = await requestProductBalance(ProductType.VIP); setProductInfo(resp); + return resp; }, []); const handleClickContact = useCallback(async () => { @@ -126,17 +126,13 @@ const AnchorFooter = (props: { data: JobDetails }) => { jobId: data.id, }); } else { + // Show material guide if no resume and no VIP and no free balance if (!productInfo?.isCreateResume && !productInfo?.isPaidVip && !productInfo?.freeBalance) { setShowMaterialGuide(true); return; } - - if (!productRecord && !productInfo.isPaidVip && !productInfo.freeBalance) { - setShowBuyDialog(true); - return; - } - - setContactDialogVisible(true); + // Open integrated dialog - it handles buy + contact internally + setShowJobContactDialog(true); } } catch (error) { const e = error as HttpError; @@ -149,33 +145,24 @@ const AnchorFooter = (props: { data: JobDetails }) => { } }, [data, productInfo?.freeBalance, productInfo?.isCreateResume, productInfo?.isPaidVip]); - const handleDialogHidden = useCallback(() => { - setContactDialogVisible(false); - }, []); + const handleDialogClose = useCallback(() => { + setShowJobContactDialog(false); + // Refresh data after dialog closes + getProductRecord(); + }, [getProductRecord]); + const handleConfirmPrejob = useCallback( (type: GET_CONTACT_TYPE) => { setShowMaterialGuide(false); if (GET_CONTACT_TYPE.VIP === type) { - getProductBalance(); - setContactDialogVisible(true); + getProductBalance().then(() => { + setShowJobContactDialog(true); + }); } }, [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(() => { // if (haveSeen) { // return '查看联系方式'; @@ -194,11 +181,8 @@ const AnchorFooter = (props: { data: JobDetails }) => { useEffect(() => { Taro.eventCenter.on(EventName.CREATE_PROFILE, getProductBalance); - Taro.eventCenter.on(EventName.READ_CONTACT, getProductBalance); - return () => { Taro.eventCenter.off(EventName.CREATE_PROFILE); - Taro.eventCenter.off(EventName.READ_CONTACT); }; }, [getProductBalance]); @@ -228,12 +212,13 @@ const AnchorFooter = (props: { data: JobDetails }) => {
- {contactDialogVisible && ( - )} {showMaterialGuide && ( @@ -243,13 +228,6 @@ const AnchorFooter = (props: { data: JobDetails }) => { onConfirm={handleConfirmPrejob} /> )} - {showBuyDialog && ( - - - - - - )}