Files
ks-app-employment-service/pages/job/publishJob.vue
2025-10-24 16:54:52 +08:00

908 lines
26 KiB
Vue
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.

<template>
<view class="publish-job-page">
<!-- 头部导航 -->
<view class="header">
<view class="header-title">发布岗位</view>
</view>
<!-- 主要内容 -->
<scroll-view class="content" scroll-y="true" :style="{ height: scrollViewHeight }" :scroll-with-animation="true">
<!-- 基本信息区块 -->
<view class="form-block">
<view class="form-group">
<view class="label">岗位名称</view>
<input
class="input"
placeholder="请输入岗位名称"
v-model="formData.jobTitle"
/>
</view>
<view class="form-group" v-if="userType === 0">
<view class="label">招聘公司</view>
<input
class="input"
placeholder="请输入招聘公司"
v-model="formData.companyName"
/>
</view>
<view class="form-group" v-if="userType === 2">
<view class="label">招聘公司</view>
<!-- 企业用户直接输入 -->
<input
v-if="userType === 1"
class="input"
placeholder="请输入公司名称"
v-model="formData.companyName"
/>
<!-- 网格员点击跳转搜索 -->
<view
v-else
class="company-selector"
@click="goToCompanySearch"
>
<view class="selector-text" :class="{ 'placeholder': !formData.companyName }">
{{ formData.companyName || '请选择企业' }}
</view>
<view class="selector-arrow">
<view class="arrow-icon"></view>
</view>
</view>
</view>
<view class="form-group">
<view class="label">最小薪资 (/)</view>
<input
class="input"
placeholder="请输入最小薪资"
type="number"
v-model="formData.minSalary"
/>
</view>
<view class="form-group">
<view class="label">最大薪资 (/)</view>
<input
class="input"
placeholder="请输入最大薪资"
type="number"
v-model="formData.maxSalary"
/>
</view>
<view class="form-group">
<view class="label">学历要求</view>
<picker
mode="selector"
:range="educationLevels"
range-key="label"
@change="onEducationChange"
class="picker"
>
<view class="picker-text" data-placeholder="请选择学历要求">{{ selectedEducation || '请选择学历要求' }}</view>
</picker>
</view>
<view class="form-group">
<view class="label">工作经验</view>
<picker
mode="selector"
:range="experienceLevels"
range-key="label"
@change="onExperienceChange"
class="picker"
>
<view class="picker-text" data-placeholder="请选择工作经验">{{ selectedExperience || '请选择工作经验' }}</view>
</picker>
</view>
<view class="form-group">
<view class="label">工作区县</view>
<picker
mode="selector"
:range="workDistricts"
range-key="label"
@change="onWorkDistrictChange"
class="picker"
>
<view class="picker-text" data-placeholder="请选择工作区县">{{ selectedWorkDistrict || '请选择工作区县' }}</view>
</picker>
</view>
<view class="form-group">
<view class="label">招聘人数</view>
<input
class="input"
placeholder="请输入招聘人数"
type="number"
v-model="formData.recruitCount"
/>
</view>
<view class="form-group">
<view class="label">工作地点</view>
<view class="location-input-container">
<input
class="input location-input"
placeholder="请输入具体工作地址"
v-model="formData.jobLocation"
/>
<view class="location-btn" @click="chooseLocation">
<text class="location-btn-text">选择位置</text>
</view>
</view>
</view>
<view class="form-group">
<view class="label">岗位分类</view>
<picker
mode="selector"
:range="jobCategories"
range-key="label"
@change="onJobCategoryChange"
class="picker"
>
<view class="picker-text" data-placeholder="请选择岗位分类">{{ selectedJobCategory || '请选择岗位分类' }}</view>
</picker>
</view>
</view>
<!-- 岗位描述区块 -->
<view class="form-block">
<view class="section-title">岗位描述</view>
<view class="form-group">
<textarea
class="textarea"
placeholder="请详细描述岗位职责和工作内容"
v-model="formData.description"
></textarea>
</view>
</view>
<!-- 任职要求区块 -->
<view class="form-block">
<view class="section-title">任职要求</view>
<view class="form-group">
<textarea
class="textarea"
placeholder="请描述对候选人的具体要求"
v-model="formData.jobRequirements"
></textarea>
</view>
</view>
<!-- 联系方式区块 -->
<view class="form-block">
<view class="section-title">联系方式</view>
<view class="contacts-container">
<view
class="contact-item"
v-for="(contact, index) in formData.contacts"
:key="index"
>
<view class="contact-header">
<view class="contact-title">联系人 {{ index + 1 }}</view>
<view
class="delete-btn"
v-if="formData.contacts.length > 1"
@click="removeContact(index)"
>
删除
</view>
</view>
<view class="form-group">
<view class="label">联系人姓名</view>
<input
class="input"
placeholder="请输入联系人姓名"
v-model="contact.name"
/>
</view>
<view class="form-group">
<view class="label">联系电话</view>
<input
class="input"
placeholder="请输入联系电话"
v-model="contact.phone"
/>
</view>
</view>
</view>
<view class="add-contact-btn" v-if="formData.contacts.length < 3" @click="addContact">
<view class="add-icon">+</view>
<view class="add-text">添加联系人</view>
</view>
</view>
<!-- 底部安全区域 -->
<view class="bottom-safe-area"></view>
</scroll-view>
<!-- 底部操作按钮 -->
<view class="footer">
<view class="btn-group">
<button class="btn btn-publish" @click="publishJob">发布岗位</button>
</view>
</view>
<!-- 自定义tabbar -->
<CustomTabBar :currentPage="1" />
</view>
</template>
<script setup>
import { ref, reactive, onMounted, onUnmounted } from 'vue';
import { storeToRefs } from 'pinia';
import { createRequest } from '@/utils/request';
import useDictStore from '@/stores/useDictStore';
import useUserStore from '@/stores/useUserStore';
const userStore = useUserStore();
const cachedUserInfo = uni.getStorageSync('userInfo') || {};
// 表单数据
const formData = reactive({
jobTitle: '',
companyName: '',
minSalary: '',
maxSalary: '',
recruitCount: '', // 对应接口字段 idCardPictureBackUrl
description: '', // 对应接口字段 description
jobRequirements: '',
jobCategory: '', // 新增:岗位分类
companyId: '', // 新增企业id
latitude: '', // 新增:纬度
longitude: '', // 新增:经度
jobLocation: '', // 新增:工作地点
jobLocationAreaCode: '', // 新增:工作地点区县字典代码
education: '', // 新增:学历要求字典值
experience: '', // 新增:工作经验字典值
contacts: [
{
name: '',
phone: ''
}
]
});
// 字典存储
const dictStore = useDictStore();
// 选择器数据
const educationLevels = ref([]);
const experienceLevels = ref([]);
const workDistricts = ref([]);
const workLocations = ref([]);
const jobCategories = ref([]); // 新增:岗位分类选项
// 选中的值
const selectedEducation = ref('');
const selectedExperience = ref('');
const selectedWorkDistrict = ref('');
const selectedWorkLocation = ref('');
const selectedJobCategory = ref('');
// 滚动视图高度
const scrollViewHeight = ref('calc(100vh - 200rpx)');
// 计算滚动视图高度
const calculateScrollViewHeight = () => {
const systemInfo = uni.getSystemInfoSync();
const windowHeight = systemInfo.windowHeight;
const headerHeight = 100; // 头部高度
const footerHeight = 120; // 底部按钮高度
const tabbarHeight = 88; // 自定义tabbar高度
const extraPadding = 50; // 额外间距,确保内容不被遮挡
const scrollHeight = windowHeight - headerHeight - footerHeight - tabbarHeight - extraPadding;
scrollViewHeight.value = `${scrollHeight}px`;
};
const userType = ref(Number(userStore.userInfo.isCompanyUser));
console.log('完整userInfo对象:---223--', userType.value);
// 页面加载时计算高度和初始化数据
onMounted(async () => {
console.log('完整userInfo对象:-----', );
console.log('缓存中的userInfo:----', cachedUserInfo);
calculateScrollViewHeight();
await initFormData();
// 监听企业选择事件
uni.$on('companySelected', handleCompanySelected);
});
// 页面卸载时移除事件监听
onUnmounted(() => {
uni.$off('companySelected', handleCompanySelected);
});
// 初始化表单数据
const initFormData = async () => {
try {
// 获取字典数据
await dictStore.getDictData();
// 设置学历选项
educationLevels.value = dictStore.state.education;
// 设置工作经验选项
experienceLevels.value = dictStore.state.experience;
// 设置区县选项(从字典获取)
workDistricts.value = dictStore.state.area;
// 设置岗位分类选项
jobCategories.value = [
{ label: '普通', value: '1' },
{ label: '零工', value: '2' },
{ label: '实习实训', value: '3' }
];
// 设置企业ID从用户信息获取
if (userStore.userInfo && userStore.userInfo.id) {
formData.companyId = userStore.userInfo.id;
}
} catch (error) {
console.error('初始化表单数据失败:', error);
uni.showToast({
title: '数据加载失败',
icon: 'none'
});
}
};
// 选择器事件处理
const onEducationChange = (e) => {
const index = e.detail.value;
const selectedItem = educationLevels.value[index];
selectedEducation.value = selectedItem.label;
formData.education = selectedItem.value;
};
const onExperienceChange = (e) => {
const index = e.detail.value;
const selectedItem = experienceLevels.value[index];
selectedExperience.value = selectedItem.label;
formData.experience = selectedItem.value;
};
const onWorkDistrictChange = (e) => {
const index = e.detail.value;
const selectedItem = workDistricts.value[index];
selectedWorkDistrict.value = selectedItem.label;
formData.jobLocationAreaCode = selectedItem.value;
};
const onJobCategoryChange = (e) => {
const index = e.detail.value;
const selectedItem = jobCategories.value[index];
selectedJobCategory.value = selectedItem.label;
formData.jobCategory = selectedItem.value;
};
// 选择位置
const chooseLocation = () => {
uni.chooseLocation({
success: (res) => {
formData.jobLocation = res.address;
formData.latitude = res.latitude.toString();
formData.longitude = res.longitude.toString();
},
fail: (err) => {
console.error('选择位置失败:', err);
uni.showToast({
title: '获取位置失败',
icon: 'none'
});
}
});
};
// 添加联系人
const addContact = () => {
if (formData.contacts.length < 3) {
formData.contacts.push({
name: '',
phone: ''
});
}
};
// 删除联系人
const removeContact = (index) => {
if (formData.contacts.length > 1) {
formData.contacts.splice(index, 1);
}
};
// 跳转到企业搜索页面
const goToCompanySearch = () => {
uni.navigateTo({
url: '/pages/job/companySearch'
});
};
// 处理企业选择
const handleCompanySelected = (company) => {
formData.companyName = company.name;
formData.companyId = company.id;
};
// 发布岗位
const publishJob = async () => {
// 表单验证
if (!validateForm()) {
return;
}
try {
uni.showLoading({
title: '发布中...'
});
// 构建请求数据
const requestData = {
jobTitle: formData.jobTitle,
minSalary: formData.minSalary,
maxSalary: formData.maxSalary,
education: formData.education,
experience: formData.experience,
jobLocation: formData.jobLocation,
jobLocationAreaCode: formData.jobLocationAreaCode,
idCardPictureBackUrl: formData.recruitCount, // 招聘人数
latitude: formData.latitude,
longitude: formData.longitude,
description: formData.description,
jobCategory: formData.jobCategory,
companyId: formData.companyId,
companyName: formData.companyName,
jobContactList: formData.contacts.filter(contact => contact.name.trim() && contact.phone.trim())
};
// 调用发布接口
const response = await createRequest('/app/job/publishJob', requestData, 'POST', false);
uni.hideLoading();
if (response.code === 200) {
uni.showToast({
title: '发布成功',
icon: 'success'
});
uni.redirectTo({
url: '/pages/index/index'
});
} else {
uni.showToast({
title: response.msg || '发布失败',
icon: 'none'
});
}
} catch (error) {
uni.hideLoading();
console.error('发布岗位失败:', error);
uni.showToast({
title: '发布失败,请重试',
icon: 'none'
});
}
};
// 表单验证
const validateForm = () => {
// 必填字段验证
const requiredFields = [
{ field: 'jobTitle', message: '请输入岗位名称' },
{ field: 'companyName', message: '请输入公司名称' },
{ field: 'minSalary', message: '请输入最小薪资' },
{ field: 'maxSalary', message: '请输入最大薪资' },
{ field: 'education', message: '请选择学历要求' },
{ field: 'experience', message: '请选择工作经验' },
{ field: 'jobLocation', message: '请选择工作地点' },
{ field: 'jobLocationAreaCode', message: '请选择工作区县' },
{ field: 'recruitCount', message: '请输入招聘人数' },
{ field: 'description', message: '请输入岗位描述' },
{ field: 'jobCategory', message: '请选择岗位分类' }
];
for (const { field, message } of requiredFields) {
if (!formData[field] || formData[field].toString().trim() === '') {
uni.showToast({
title: message,
icon: 'none'
});
return false;
}
}
// 薪资验证
const minSalary = parseFloat(formData.minSalary);
const maxSalary = parseFloat(formData.maxSalary);
if (minSalary >= maxSalary) {
uni.showToast({
title: '最大薪资必须大于最小薪资',
icon: 'none'
});
return false;
}
// 验证联系人信息
for (let i = 0; i < formData.contacts.length; i++) {
const contact = formData.contacts[i];
if (!contact.name.trim()) {
uni.showToast({
title: `请输入第${i + 1}个联系人姓名`,
icon: 'none'
});
return false;
}
if (!contact.phone.trim()) {
uni.showToast({
title: `请输入第${i + 1}个联系人电话`,
icon: 'none'
});
return false;
}
}
return true;
};
</script>
<style lang="scss" scoped>
.publish-job-page {
height: 100vh;
background-color: #f5f5f5;
display: flex;
flex-direction: column;
position: relative;
overflow: hidden;
}
.header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 20rpx 30rpx;
background: #fff;
border-bottom: 1rpx solid #eee;
.header-title {
font-size: 36rpx;
font-weight: 600;
color: #333;
}
.header-right {
width: 60rpx;
}
}
.content {
flex: 1;
padding: 0;
overflow: hidden;
}
.form-block {
background: #fff;
margin-bottom: 20rpx;
width: 100%;
position: relative;
padding-bottom: 10rpx;
&:last-child {
margin-bottom: 0;
}
.section-title {
font-size: 32rpx;
font-weight: 600;
color: #333;
margin-bottom: 30rpx;
padding: 30rpx 30rpx 20rpx 30rpx;
border-bottom: 2rpx solid #f0f0f0;
}
}
.form-group {
margin-bottom: 0;
padding: 0 30rpx;
border-bottom: 2rpx solid #f0f0f0;
&:last-child {
border-bottom: none;
padding-bottom: 30rpx;
}
.label {
font-size: 28rpx;
color: #000;
margin-bottom: 15rpx;
font-weight: 400;
padding-top: 30rpx;
}
.input {
width: 100%;
height: 80rpx;
background: #fff;
border: none;
border-radius: 0;
padding: 0 0 20rpx 0;
font-size: 28rpx;
color: #333;
position: relative;
z-index: 1;
box-sizing: border-box;
line-height: 1.4;
display: flex;
align-items: center;
&::placeholder {
color: #999;
font-size: 28rpx;
line-height: 1.4;
}
&:focus {
background: #fff;
transform: translateZ(0);
-webkit-transform: translateZ(0);
}
}
.textarea {
width: 100%;
min-height: 120rpx;
background: #fff;
border: none;
border-radius: 0;
padding: 0 0 20rpx 0;
font-size: 28rpx;
color: #333;
position: relative;
z-index: 1;
box-sizing: border-box;
line-height: 1.4;
&::placeholder {
color: #999;
font-size: 28rpx;
line-height: 1.4;
}
&:focus {
background: #fff;
transform: translateZ(0);
-webkit-transform: translateZ(0);
}
}
.picker {
width: 100%;
height: 80rpx;
background: #fff;
border: none;
border-radius: 0;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 0 20rpx 0;
box-sizing: border-box;
.picker-text {
font-size: 28rpx;
color: #333;
&:empty::before {
content: attr(data-placeholder);
color: #999;
font-size: 24rpx;
}
}
&::after {
content: '';
width: 0;
height: 0;
border-left: 8rpx solid transparent;
border-right: 8rpx solid transparent;
border-top: 8rpx solid #999;
}
}
}
// 企业选择器样式
.company-selector {
display: flex;
align-items: center;
justify-content: space-between;
height: 80rpx;
background: #fff;
border: none;
border-radius: 0;
padding: 0 0 20rpx 0;
box-sizing: border-box;
.selector-text {
font-size: 28rpx;
color: #333;
&.placeholder {
color: #999;
font-size: 24rpx;
}
}
.selector-arrow {
width: 40rpx;
height: 40rpx;
display: flex;
align-items: center;
justify-content: center;
.arrow-icon {
font-size: 24rpx;
color: #999;
}
}
&:active {
background: #f8f8f8;
}
}
// 联系人管理样式
.contacts-container {
.contact-item {
margin-bottom: 30rpx;
padding: 0 30rpx;
background: #fff;
border-radius: 12rpx;
&:last-child {
margin-bottom: 0;
}
.contact-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20rpx 0 10rpx 0;
border-bottom: 1rpx solid #eee;
margin-bottom: 20rpx;
.contact-title {
font-size: 28rpx;
font-weight: 600;
color: #333;
}
.delete-btn {
font-size: 24rpx;
color: #ff4757;
padding: 8rpx 16rpx;
background: #fff;
border: 1rpx solid #ff4757;
border-radius: 6rpx;
&:active {
background: #ff4757;
color: #fff;
}
}
}
.form-group {
background: transparent;
border-bottom: 1rpx solid #eee;
margin-bottom: 0;
&:last-child {
border-bottom: none;
}
.label {
font-size: 26rpx;
color: #666;
margin-bottom: 10rpx;
padding-top: 20rpx;
}
.input {
font-size: 26rpx;
padding-bottom: 15rpx;
line-height: 1.4;
&::placeholder {
font-size: 26rpx;
line-height: 1.4;
}
}
}
}
}
.add-contact-btn {
display: flex;
align-items: center;
justify-content: center;
margin: 20rpx 30rpx 30rpx 30rpx;
padding: 20rpx;
background: #fff;
border: 2rpx dashed #ddd;
border-radius: 12rpx;
.add-icon {
font-size: 32rpx;
color: #256BFA;
margin-right: 10rpx;
font-weight: bold;
}
.add-text {
font-size: 28rpx;
color: #256BFA;
font-weight: 500;
}
&:active {
background: #e3f2fd;
border-color: #256BFA;
}
}
.bottom-safe-area {
height: 250rpx;
background: transparent;
}
.footer {
position: fixed;
bottom: 140rpx;
left: 0;
right: 0;
background: #fff;
padding: 25rpx 30rpx;
border-top: 1rpx solid #eee;
z-index: 100;
.btn-group {
display: flex;
.btn {
flex: 1;
height: 80rpx;
border-radius: 12rpx;
font-size: 32rpx;
font-weight: 500;
border: none;
&.btn-publish {
background: #256BFA;
color: #fff;
}
}
}
}
/* 防止键盘弹出时页面偏移 */
/* #ifdef H5 */
.publish-job-page {
-webkit-overflow-scrolling: touch;
-webkit-transform: translateZ(0);
transform: translateZ(0);
}
.publish-job-page * {
-webkit-transform: translateZ(0);
transform: translateZ(0);
}
/* #endif */
/* #ifdef MP-WEIXIN */
.publish-job-page {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
/* #endif */
</style>