392 lines
14 KiB
TypeScript
392 lines
14 KiB
TypeScript
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>
|
||
);
|
||
}
|