Files
boluo-app-main/src/pages/job-publish/index.tsx
chashaobao 34f9c8d0e6 feat:
2025-08-16 13:02:46 +08:00

392 lines
14 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { BaseEventOrig, Button, InputProps } from '@tarojs/components';
import Taro, { useLoad } from '@tarojs/taro';
import { Dialog } from '@taroify/core';
import classNames from 'classnames';
import { useCallback, useEffect, useRef, useState } from 'react';
import BlFormCell from '@/components/bl-form-cell';
import BlFormInput from '@/components/bl-form-input';
import BlFormItem from '@/components/bl-form-item';
import { BlFormRadio, BlFormRadioGroup } from '@/components/bl-form-radio';
import BlFormSelect from '@/components/bl-form-select';
import BlSalaryInput, { BlSalaryValue } from '@/components/bl-salary-input';
import { CityPickerPopup } from '@/components/city-picker';
import PageLoading from '@/components/page-loading';
import CompanyPublishJobBuy from '@/components/product-dialog/steps-ui/company-publish-job-buy';
import SafeBottomPadding from '@/components/safe-bottom-padding';
import { EventName, PageUrl } from '@/constants/app';
import { CertificationStatusType } from '@/constants/company';
import { CollectEventName } from '@/constants/event';
import { EMPLOY_TYPE_TITLE_MAP, EmployType, JOB_TYPE_SELECT_OPTIONS, JobType } from '@/constants/job';
import { ProductType } from '@/constants/product';
import useUserInfo from '@/hooks/use-user-info';
import store from '@/store';
import { cacheJobId } from '@/store/actions';
import { CreateJobInfo, JobDetails } from '@/types/job';
import { logWithPrefix } from '@/utils/common';
import { collectEvent } from '@/utils/event';
import {
postCloseJob,
postCreateJob,
postUpdateJob,
requestJobDetail,
isFullTimePriceRequired,
isPartTimePriceRequired,
postPublishJob,
} from '@/utils/job';
import { getCityValues } from '@/utils/location';
import { requestProductBalance } from '@/utils/product';
import { getPageQuery, navigateBack, navigateTo } from '@/utils/route';
import Toast from '@/utils/toast';
import './index.less';
const PREFIX = 'page-job-publish';
const log = logWithPrefix(PREFIX);
const isInvalidCreateJobInfo = (data: CreateJobInfo) => {
const {
title,
category,
employType,
provinceCode,
cityCode,
countyCode,
address,
jobDescription,
lowPriceForFullTime,
highPriceForFullTime,
lowPriceForPartyTime,
highPriceForPartyTime,
} = data;
if (!category) {
return '请选择通告品类';
}
if (!title) {
return '请选择通告标题';
}
if (!jobDescription) {
return '请输入通告描述';
}
if (isFullTimePriceRequired(employType)) {
if (!lowPriceForFullTime || !highPriceForFullTime) {
return '薪资范围不能为空';
}
if (lowPriceForFullTime > highPriceForFullTime) {
return '薪资最高值不能低于最低值';
}
}
if (isPartTimePriceRequired(employType)) {
if (!lowPriceForPartyTime || !highPriceForPartyTime) {
return '薪资范围不能为空';
}
if (lowPriceForPartyTime > highPriceForPartyTime) {
return '薪资最高值不能低于最低值';
}
}
if (!provinceCode || !cityCode || !countyCode) {
return '请输入工作地址';
}
if (!address) {
return '请输入详细地址';
}
};
export default function JobPublish() {
const [loading, setLoading] = useState(false);
const [isUpdate, setIsUpdate] = useState(false);
const [job, setJob] = useState<JobDetails | null>(null);
const [reason, setReason] = useState('');
const [employType, setEmployType] = useState(EmployType.Full);
const [category, setCategory] = useState<JobType>(JobType.Finery);
const [title, setTitle] = useState('');
const [describe, setDescribe] = useState('');
const [salary, setSalary] = useState<BlSalaryValue>(['', '', '', ''] as unknown as BlSalaryValue);
const [city, setCity] = useState<string[] | undefined>();
const [showCityPicker, setShowCityPicker] = useState(false);
const [address, setAddress] = useState('');
const [showBuy, setShowBuy] = useState(false);
const createdJobIdRef = useRef('');
const userInfo = useUserInfo();
const handleEmployTypeChange = useCallback((value: EmployType) => {
setEmployType(value);
}, []);
const handleSelectCategory = useCallback((value: JobType) => {
setCategory(value);
}, []);
const handleInputTitle = useCallback((e: BaseEventOrig<InputProps.inputEventDetail>) => {
const value = e.detail.value || '';
setTitle(value);
}, []);
const handleClickEditDescribe = useCallback(() => navigateTo(PageUrl.JobPublishDescribe, { describe }), [describe]);
const handleSalaryChange = useCallback((value: BlSalaryValue) => {
setSalary(value);
}, []);
const handleClickCity = useCallback(() => setShowCityPicker(true), []);
const handleConfirmCityPicker = useCallback((areaValues: string[]) => {
log('handleConfirmCityPicker', areaValues);
setShowCityPicker(false);
setCity(areaValues);
setAddress('');
}, []);
const handleInputAddress = useCallback((e: BaseEventOrig<InputProps.inputEventDetail>) => {
const value = e.detail.value || '';
setAddress(value);
}, []);
const refreshJobPublishList = useCallback(() => {
Taro.eventCenter.trigger(EventName.COMPANY_JOB_PUBLISH_CHANGED);
setTimeout(() => {
Taro.eventCenter.trigger(EventName.COMPANY_JOB_PUBLISH_CHANGED);
}, 300);
}, []);
const handleClose = useCallback(async () => {
if (!job) {
Toast.error('数据出错请重试');
return;
}
try {
Taro.showLoading();
await postCloseJob(job.id);
refreshJobPublishList();
navigateBack();
} catch (e) {
console.error('submit error', e);
Toast.error('关闭失败请重试');
collectEvent(CollectEventName.CLOSE_JOB_FAILED, e);
} finally {
Taro.hideLoading();
}
}, [job, refreshJobPublishList]);
const getCreateJobInfo = useCallback((): [CreateJobInfo, string[]] => {
const cityCodes = city || [];
const data: CreateJobInfo = {
title,
employType,
category: category!,
jobDescription: describe,
provinceCode: cityCodes[0],
cityCode: cityCodes[1],
countyCode: cityCodes[2],
lowPriceForFullTime: !isFullTimePriceRequired(employType) ? 0 : salary[0] * 1000,
highPriceForFullTime: !isFullTimePriceRequired(employType) ? 0 : salary[1] * 1000,
lowPriceForPartyTime: !isPartTimePriceRequired(employType) ? 0 : salary[2],
highPriceForPartyTime: !isPartTimePriceRequired(employType) ? 0 : salary[3],
address: address,
};
return [data, cityCodes];
}, [address, category, city, describe, employType, salary, title]);
const handleSubmit = useCallback(async () => {
const [data, cityCodes] = getCreateJobInfo();
const errMsg = isInvalidCreateJobInfo(data);
if (errMsg) {
Toast.info(errMsg);
return;
}
try {
Taro.showLoading();
// 将省市区拼到最前面
const cityValues = getCityValues(cityCodes);
if (!data.address.startsWith(cityValues)) {
data.address = `${cityValues}${data.address}`;
}
const isUpdateJob = isUpdate && job;
if (isUpdateJob) {
data.jobId = job!.id;
await postUpdateJob(data);
Taro.eventCenter.trigger(EventName.JOB_UPDATE, job!.id);
} else {
const jobId = await postCreateJob(data);
createdJobIdRef.current = jobId;
refreshJobPublishList();
if (userInfo.bossAuthStatus !== CertificationStatusType.Success) {
// 去认证
store.dispatch(cacheJobId(jobId));
navigateTo(PageUrl.CertificationStart);
Taro.hideLoading();
return;
}
const [time] = await requestProductBalance(ProductType.CompanyPublishJob);
if (time <= 0) {
// 付钱
setShowBuy(true);
Taro.hideLoading();
return;
}
await postPublishJob(jobId);
}
refreshJobPublishList();
await Toast.success(isUpdateJob ? '更新成功' : '创建并发布成功', 1500, true);
navigateBack();
} catch (e) {
createdJobIdRef.current = '';
console.error('submit error', e);
Toast.error('审核失败请重试');
collectEvent(CollectEventName.PUBLISH_JOB_FAILED, e);
} finally {
Taro.hideLoading();
}
}, [getCreateJobInfo, isUpdate, job, userInfo.bossAuthStatus, refreshJobPublishList]);
const handleNext = useCallback(async () => {
Taro.showLoading();
try {
await postPublishJob(createdJobIdRef.current);
refreshJobPublishList();
await Toast.success('发布成功', 1500, true);
navigateBack();
} catch (e) {
console.error('submit error', e);
Toast.error('审核失败请重试');
collectEvent(CollectEventName.PUBLISH_JOB_FAILED, e);
} finally {
Taro.hideLoading();
}
}, [refreshJobPublishList]);
const handleClosePublishJob = useCallback(() => {
setShowBuy(false);
navigateBack(-1);
}, []);
useEffect(() => {
const callback = (d: string) => setDescribe(d);
Taro.eventCenter.on(EventName.EDIT_JOB_DESCRIBE, callback);
return () => {
Taro.eventCenter.off(EventName.EDIT_JOB_DESCRIBE, callback);
};
}, []);
useLoad(async () => {
const query = getPageQuery<{ jobId: string }>();
const jobId = query?.jobId;
if (!jobId) {
return;
}
try {
setLoading(true);
setIsUpdate(true);
const details = await requestJobDetail(jobId);
setJob(details);
details.title && setTitle(details.title);
details.employType && setEmployType(details.employType);
details.category && setCategory(details.category);
details.jobDescription && setDescribe(details.jobDescription);
details.jobLocation.provinceCode &&
details.jobLocation.cityCode &&
details.jobLocation.countyCode &&
setCity([details.jobLocation.provinceCode, details.jobLocation.cityCode, details.jobLocation.countyCode]);
details.lowPriceForFullTime &&
details.highPriceForFullTime &&
details.lowPriceForPartyTime &&
details.highPriceForPartyTime &&
setSalary([
details.lowPriceForFullTime / 1000,
details.highPriceForFullTime / 1000,
details.lowPriceForPartyTime,
details.highPriceForPartyTime,
]);
details.jobLocation.address && setAddress(details.jobLocation.address);
details.verifyFailReason && setReason(details.verifyFailReason);
} catch (e) {
console.error(e);
Toast.error('出错了,请重试');
} finally {
setLoading(false);
}
});
if (loading) {
return <PageLoading />;
}
return (
<div className={PREFIX}>
{reason && <div className={`${PREFIX}__reason`}>{reason}</div>}
<BlFormItem title="通告标题" subTitle="不能填写微信、电话等联系方式">
<BlFormInput
value={title}
maxlength={20}
placeholder="如“招聘女装主播”"
onInput={handleInputTitle}
maxLengthTips
/>
</BlFormItem>
<BlFormItem title="工作类型" subTitle={false}>
<BlFormRadioGroup direction="horizontal" value={employType} onChange={handleEmployTypeChange}>
<BlFormRadio name={EmployType.Full} text={EMPLOY_TYPE_TITLE_MAP[EmployType.Full]} value={employType} />
<BlFormRadio name={EmployType.Part} text={EMPLOY_TYPE_TITLE_MAP[EmployType.Part]} value={employType} />
<BlFormRadio name={EmployType.All} text={EMPLOY_TYPE_TITLE_MAP[EmployType.All]} value={employType} />
</BlFormRadioGroup>
</BlFormItem>
<BlFormItem title="品类" subTitle={false}>
<BlFormSelect title="品类" value={category} options={JOB_TYPE_SELECT_OPTIONS} onSelect={handleSelectCategory} />
</BlFormItem>
<BlFormItem title="薪资范围" subTitle={false} dynamicHeight>
<BlSalaryInput value={salary} employType={employType} onChange={handleSalaryChange} />
</BlFormItem>
<BlFormItem title="通告描述" subTitle={false}>
<BlFormCell text={describe} placeholder="介绍工作内容、通告要求、加分项" onClick={handleClickEditDescribe} />
</BlFormItem>
<BlFormItem title="工作城市" subTitle={false}>
<BlFormCell text={getCityValues(city, '-')} placeholder="工作所在省-城-区" onClick={handleClickCity} />
</BlFormItem>
<BlFormItem title="详细地址" subTitle={false}>
<BlFormInput value={address} placeholder="请填写详细地址" onInput={handleInputAddress} />
</BlFormItem>
<SafeBottomPadding />
<div className={`${PREFIX}__footer`}>
<div className={`${PREFIX}__footer__tips`}></div>
<div className={`${PREFIX}__footer__buttons`}>
{!isUpdate && (
<Button className={`${PREFIX}__footer__button`} onClick={handleSubmit}>
</Button>
)}
{isUpdate && (
<>
<Button className={classNames(`${PREFIX}__footer__button`, 'lowLight')} onClick={handleClose}>
</Button>
<Button className={`${PREFIX}__footer__button`} onClick={handleSubmit}>
</Button>
</>
)}
</div>
<SafeBottomPadding />
</div>
<div>
<CityPickerPopup
areaValues={city}
open={showCityPicker}
onConfirm={handleConfirmCityPicker}
onCancel={() => setShowCityPicker(false)}
/>
<Dialog open={showBuy} onClose={handleClosePublishJob}>
<Dialog.Content>
<CompanyPublishJobBuy onNext={handleNext} />
</Dialog.Content>
</Dialog>
</div>
</div>
);
}