feat: first commit

This commit is contained in:
eleanor.mao
2025-03-31 22:34:22 +08:00
commit d25187c9c8
390 changed files with 57031 additions and 0 deletions

View File

@ -0,0 +1,3 @@
export default definePageConfig({
navigationBarTitleText: '发布通告',
});

View File

@ -0,0 +1,60 @@
@import '@/styles/common.less';
@import '@/styles/variables.less';
.page-job-publish {
padding: 24px;
// 底部高度(paddingTop: 30 + tips: 30 + margin: 40 + button: 80 + margin: 40);
padding-bottom: 220px;
&__reason {
width: 100%;;
padding: 16px 32px;
font-size: 26px;
line-height: 36px;
font-weight: 400;
color: #946724;
background: #FFF4F0;
box-sizing: border-box;
margin-bottom: 40px;
}
&__footer {
position: fixed;
left: 24px;
right: 24px;
bottom: 0;
background: #F5F6FA;
padding-top: 30px;
.flex-column();
&__tips {
font-size: 28px;
line-height: 30px;
font-weight: 400;
color: @blColorG1;
margin-bottom: 40px;
}
&__buttons {
width: 100%;
.flex-row();
margin-bottom: 40px;
}
&__button {
.button(@width: 100%, @height: 80px, @fontSize: 32px, @fontWeight: 400, @borderRadius: 48px);
flex: 1;
margin-left: 24px;
&:first-child {
margin-left: 0;
}
&.lowLight {
color: @blHighlightColor;
background: @blHighlightBg;
}
}
}
}

View File

@ -0,0 +1,312 @@
import { BaseEventOrig, Button, InputProps } from '@tarojs/components';
import Taro, { useLoad } from '@tarojs/taro';
import classNames from 'classnames';
import { useCallback, useEffect, 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 SafeBottomPadding from '@/components/safe-bottom-padding';
import { EventName, PageUrl } from '@/constants/app';
import { CollectEventName } from '@/constants/event';
import { EMPLOY_TYPE_TITLE_MAP, EmployType, JOB_TYPE_SELECT_OPTIONS, JobType } from '@/constants/job';
import { CreateJobInfo, JobDetails } from '@/types/job';
import { logWithPrefix } from '@/utils/common';
import { collectEvent } from '@/utils/event';
import { postCloseJob, postCreateJob, postUpdateJob, postPublishJob, requestJobDetail, isFullTimePriceRequired, isPartTimePriceRequired } from '@/utils/job';
import { getCityValues } from '@/utils/location';
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 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 handleClose = useCallback(async () => {
if (!job) {
Toast.error('数据出错请重试');
return;
}
try {
Taro.showLoading();
await postCloseJob(job.id);
Taro.eventCenter.trigger(EventName.COMPANY_JOB_PUBLISH_CHANGED);
navigateBack();
} catch (e) {
console.error('submit error', e);
Toast.error('关闭失败请重试');
collectEvent(CollectEventName.CLOSE_JOB_FAILED, e);
} finally {
Taro.hideLoading();
}
}, [job]);
const handleSubmit = useCallback(async () => {
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,
};
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 {
await postCreateJob(data);
}
Taro.eventCenter.trigger(EventName.COMPANY_JOB_PUBLISH_CHANGED);
await Toast.success(isUpdateJob ? '更新成功' : '创建成功', 1500, true);
navigateBack();
} catch (e) {
console.error('submit error', e);
Toast.error('审核失败请重试');
collectEvent(CollectEventName.PUBLISH_JOB_FAILED, e);
} finally {
Taro.hideLoading();
}
}, [isUpdate, job, title, employType, category, describe, city, salary, address]);
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)}
/>
</div>
</div>
);
}