岗位发布开发

This commit is contained in:
冯辉
2025-10-23 17:16:16 +08:00
parent 20f2038f8c
commit b72b7dd7d6
25 changed files with 3402 additions and 327 deletions

View File

@@ -83,7 +83,8 @@
<empty v-else pdTop="200"></empty>
</scroll-view>
</view>
<!-- 统一使用系统tabBar -->
<!-- 自定义tabbar -->
<CustomTabBar :currentPage="1" />
</view>
</view>
</template>
@@ -93,6 +94,7 @@ import { reactive, inject, watch, ref, onMounted } from 'vue';
import { onLoad, onShow } from '@dcloudio/uni-app';
import useLocationStore from '@/stores/useLocationStore';
import { storeToRefs } from 'pinia';
import { tabbarManager } from '@/utils/tabbarManager';
const { longitudeVal, latitudeVal } = storeToRefs(useLocationStore());
const { $api, navTo, cloneDeep } = inject('globalFunction');
const weekList = ref([]);
@@ -124,6 +126,11 @@ onLoad(() => {
getFair('refresh');
});
onShow(() => {
// 更新自定义tabbar选中状态
tabbarManager.updateSelected(1);
});
function toSelectDate() {
navTo('/packageA/pages/selectDate/selectDate', {
query: {

View File

@@ -1,5 +1,7 @@
<template>
<view class="container">
<!-- 自定义tabbar -->
<CustomTabBar :currentPage="2" />
<!-- 抽屉遮罩层 -->
<view v-if="isDrawerOpen" class="overlay" @click="toggleDrawer"></view>
@@ -76,6 +78,7 @@ const { $api, navTo, insertSortData, config } = inject('globalFunction');
import { onLoad, onShow, onHide } from '@dcloudio/uni-app';
import useChatGroupDBStore from '@/stores/userChatGroupStore';
import useUserStore from '@/stores/useUserStore';
import { tabbarManager } from '@/utils/tabbarManager';
import aiPaging from './components/ai-paging.vue';
import { storeToRefs } from 'pinia';
const { isTyping, tabeList, chatSessionID } = storeToRefs(useChatGroupDBStore());
@@ -103,6 +106,8 @@ onShow(() => {
nextTick(() => {
paging.value?.closeFile();
});
// 更新自定义tabbar选中状态
tabbarManager.updateSelected(2);
});
onHide(() => {

View File

@@ -79,7 +79,7 @@
<view class="label">是否是就业见习基地</view>
<view class="input-content">
<text class="input-text" :class="{ placeholder: formData.enterpriseType === null }">
{{ formData.enterpriseType === null ? '请选择' : (formData.enterpriseType ? '是' : '否') }}
{{ formData.enterpriseType === null ? '请选择' : (formData.enterpriseType === 0 ? '是' : '否') }}
</text>
<uni-icons type="arrowright" size="16" color="#999"></uni-icons>
</view>
@@ -241,7 +241,7 @@ const formData = reactive({
legalPersonName: '',
nature: '', // 企业类型
natureText: '', // 企业类型显示文本
enterpriseType: null, // 是否是就业见习基地 (true/false/null)
enterpriseType: null, // 是否是就业见习基地 (0=是, 1=否, null=未选择)
legalIdCard: '', // 法人身份证号
legalPhone: '', // 法人联系方式
industryType: '', // 是否是本地重点发展产业
@@ -445,7 +445,7 @@ const selectEmploymentBase = () => {
uni.showActionSheet({
itemList: ['是', '否'],
success: (res) => {
formData.enterpriseType = res.tapIndex === 0
formData.enterpriseType = res.tapIndex === 0 ? 0 : 1
updateCompletion()
$api.msg('选择成功')
}

View File

@@ -20,7 +20,7 @@
<view class="wxaddress">{{ config.appInfo.areaName }}公共就业和人才服务中心</view>
</view>
</swiper-item>
<swiper-item @touchmove.stop="false">
<swiper-item @touchmove.stop="false">
<view class="content-one">
<view>
<view class="content-title">
@@ -33,17 +33,19 @@
<text>/2</text>
</view>
</view>
<view class="content-input" @click="changeExperience">
<view class="input-titile">工作经验</view>
<input
class="input-con"
v-model="state.experienceText"
disabled
placeholder="请选择您的工作经验"
<view class="content-input" :class="{ 'input-error': nameError }">
<view class="input-titile">姓名</view>
<input
class="input-con2"
v-model="fromValue.name"
maxlength="18"
placeholder="请输入姓名"
@input="validateName"
/>
<view v-if="nameError" class="error-message">{{ nameError }}</view>
</view>
<view class="content-sex">
<view class="sex-titile">求职区域</view>
<view class="content-sex" :class="{ 'input-error': sexError }">
<view class="sex-titile">性别</view>
<view class="sext-ri">
<view
class="sext-box"
@@ -61,6 +63,28 @@
</view>
</view>
</view>
<view v-if="sexError" class="error-message">{{ sexError }}</view>
<view class="content-input" :class="{ 'input-error': ageError }">
<view class="input-titile">年龄</view>
<input
class="input-con2"
v-model="fromValue.age"
maxlength="3"
placeholder="请输入年龄"
@input="validateAge"
/>
<view v-if="ageError" class="error-message">{{ ageError }}</view>
</view>
<view class="content-input" :class="{ 'input-error': experienceError }" @click="changeExperience">
<view class="input-titile">工作经验</view>
<input
class="input-con"
v-model="state.workExperience"
disabled
placeholder="请选择您的工作经验"
/>
<view v-if="experienceError" class="error-message">{{ experienceError }}</view>
</view>
<view class="content-input" @click="changeEducation">
<view class="input-titile">学历</view>
<input class="input-con" v-model="state.educationText" disabled placeholder="本科" />
@@ -69,19 +93,19 @@
<view class="input-titile">身份证</view>
<input
class="input-con2"
v-model="fromValue.idcard"
v-model="fromValue.idCard"
maxlength="18"
placeholder="请输入身份证号码"
@input="validateIdCard"
/>
<view v-if="idCardError" class="error-message">{{ idCardError }}</view>
<view v-if="fromValue.idcard && !idCardError" class="success-message"> 身份证格式正确</view>
<view v-if="fromValue.idCard && !idCardError" class="success-message"> 身份证格式正确</view>
</view>
</view>
<view class="next-btn" @tap="nextStep">下一步</view>
</view>
</swiper-item>
<swiper-item @touchmove.stop="false">
<view class="next-btn" @tap="nextStep">下一步</view>
</view>
</swiper-item>
<swiper-item @touchmove.stop="false">
<view class="content-one">
<view>
<view class="content-title">
@@ -115,6 +139,27 @@
<view class="nx-item" v-for="item in state.jobsText">{{ item }}</view>
</view>
</view>
<view class="content-input" @click="changeSkillLevel">
<view class="input-titile">技能等级</view>
<input
class="input-con"
v-model="state.skillLevelText"
disabled
placeholder="请选择您的技能等级"
/>
</view>
<view class="content-input" @click="changeSkills">
<view class="input-titile">技能名称</view>
<input
class="input-con"
disabled
v-if="!state.skillsText.length"
placeholder="请选择您的技能名称"
/>
<view class="input-nx" @click="changeSkills" v-else>
<view class="nx-item" v-for="(item, index) in state.skillsText" :key="index">{{ item }}</view>
</view>
</view>
<view class="content-input" @click="changeSalay">
<view class="input-titile">期望薪资</view>
<input
@@ -178,23 +223,33 @@ const state = reactive({
risalay: JSON.parse(JSON.stringify(salay)),
areaText: '',
educationText: '',
experienceText: '',
workExperience: '',
salayText: '',
jobsText: [],
skillLevelText: '',
skillsText: [],
});
const fromValue = reactive({
sex: 1,
sex: null,
education: '4',
salaryMin: 2000,
salaryMax: 2000,
area: 0,
jobTitleId: '',
experience: '1',
idcard: '',
workExperience: '1',
idCard: '',
name: '',
age: '',
skillLevel: '',
skills: '',
});
// 身份证校验相关
// 输入校验相关
const idCardError = ref('');
const nameError = ref('');
const ageError = ref('');
const sexError = ref('');
const experienceError = ref('');
onLoad((parmas) => {
getTreeselect();
@@ -204,11 +259,40 @@ onMounted(() => {});
function changeSex(sex) {
fromValue.sex = sex;
// 选择后清除性别错误
sexError.value = '';
}
// 姓名实时校验中文2-18或英文2-30
function validateName() {
const name = (fromValue.name || '').trim();
if (!name) {
nameError.value = '请输入姓名';
return;
}
const cn = /^[\u4e00-\u9fa5·]{2,18}$/;
const en = /^[A-Za-z\s]{2,30}$/;
nameError.value = cn.test(name) || en.test(name) ? '' : '姓名格式不正确';
}
// 年龄实时校验16-65的整数
function validateAge() {
const ageStr = String(fromValue.age || '').trim();
if (!ageStr) {
ageError.value = '请输入年龄';
return;
}
const num = Number(ageStr);
if (!/^\d{1,3}$/.test(ageStr) || Number.isNaN(num)) {
ageError.value = '年龄必须为数字';
return;
}
ageError.value = num >= 16 && num <= 65 ? '' : '年龄需在16-65之间';
}
// 身份证实时校验
function validateIdCard() {
const idCard = fromValue.idcard.trim();
const idCard = (fromValue.idCard || '').trim();
// 如果为空,清除错误信息
if (!idCard) {
@@ -231,8 +315,10 @@ function changeExperience() {
maskClick: true,
data: [oneDictData('experience')],
success: (_, [value]) => {
fromValue.experience = value.value;
state.experienceText = value.label;
fromValue.workExperience = value.value;
state.workExperience = value.label;
// 选择后清除工作经验错误
experienceError.value = '';
},
change(_, [value]) {
// this.setColunm(1, [123, 123]);
@@ -300,20 +386,154 @@ function changeJobs() {
});
}
// 技能等级选择
function changeSkillLevel() {
const skillLevels = [
{ label: '初级', value: '1' },
{ label: '中级', value: '2' },
{ label: '高级', value: '3' }
];
openSelectPopup({
title: '技能等级',
maskClick: true,
data: [skillLevels],
success: (_, [value]) => {
fromValue.skillLevel = value.value;
state.skillLevelText = value.label;
},
});
}
// 技能名称选择
function changeSkills() {
const skills = [
// 前端开发
{ label: 'HTML', value: 'html' },
{ label: 'CSS', value: 'css' },
{ label: 'JavaScript', value: 'javascript' },
{ label: 'TypeScript', value: 'typescript' },
{ label: 'React', value: 'react' },
{ label: 'Vue', value: 'vue' },
{ label: 'Angular', value: 'angular' },
{ label: 'jQuery', value: 'jquery' },
{ label: 'Bootstrap', value: 'bootstrap' },
{ label: 'Sass/Less', value: 'sass' },
{ label: 'Webpack', value: 'webpack' },
{ label: 'Vite', value: 'vite' },
// 后端开发
{ label: 'Java', value: 'java' },
{ label: 'Python', value: 'python' },
{ label: 'Node.js', value: 'nodejs' },
{ label: 'PHP', value: 'php' },
{ label: 'C#', value: 'csharp' },
{ label: 'Go', value: 'go' },
{ label: 'Ruby', value: 'ruby' },
{ label: 'Spring Boot', value: 'springboot' },
{ label: 'Django', value: 'django' },
{ label: 'Express', value: 'express' },
{ label: 'Laravel', value: 'laravel' },
// 数据库
{ label: 'MySQL', value: 'mysql' },
{ label: 'PostgreSQL', value: 'postgresql' },
{ label: 'MongoDB', value: 'mongodb' },
{ label: 'Redis', value: 'redis' },
{ label: 'Oracle', value: 'oracle' },
{ label: 'SQL Server', value: 'sqlserver' },
// 移动开发
{ label: 'React Native', value: 'reactnative' },
{ label: 'Flutter', value: 'flutter' },
{ label: 'iOS开发', value: 'ios' },
{ label: 'Android开发', value: 'android' },
{ label: '微信小程序', value: 'miniprogram' },
{ label: 'uni-app', value: 'uniapp' },
// 云计算与运维
{ label: 'Docker', value: 'docker' },
{ label: 'Kubernetes', value: 'kubernetes' },
{ label: 'AWS', value: 'aws' },
{ label: '阿里云', value: 'aliyun' },
{ label: 'Linux', value: 'linux' },
{ label: 'Nginx', value: 'nginx' },
// 设计工具
{ label: 'Photoshop', value: 'photoshop' },
{ label: 'Figma', value: 'figma' },
{ label: 'Sketch', value: 'sketch' },
{ label: 'Adobe XD', value: 'adobexd' },
// 其他技能
{ label: 'Git', value: 'git' },
{ label: 'Jenkins', value: 'jenkins' },
{ label: 'Jira', value: 'jira' },
{ label: '项目管理', value: 'projectmanagement' },
{ label: '数据分析', value: 'dataanalysis' },
{ label: '人工智能', value: 'ai' },
{ label: '机器学习', value: 'machinelearning' }
];
// 获取当前已选中的技能
const currentSelectedValues = fromValue.skills ? fromValue.skills.split(',') : [];
openSelectPopup({
title: '技能名称',
maskClick: true,
data: [skills],
multiSelect: true,
rowLabel: 'label',
rowKey: 'value',
defaultValues: currentSelectedValues,
success: (selectedValues, selectedItems) => {
const selectedSkills = selectedItems.map(item => item.value);
const selectedLabels = selectedItems.map(item => item.label);
fromValue.skills = selectedSkills.join(',');
state.skillsText = selectedLabels;
},
});
}
function nextStep() {
// 校验身份证号码
const idCard = fromValue.idcard.trim();
if (!idCard) {
$api.msg('请输入身份证号码');
// 统一必填与格式校验
validateName();
validateAge();
validateIdCard();
if (fromValue.sex !== 0 && fromValue.sex !== 1) {
sexError.value = '请选择性别';
}
// 工作经验校验
if (!state.workExperience) {
experienceError.value = '请选择您的工作经验';
} else {
experienceError.value = '';
}
// 学历校验
if (!state.educationText) {
$api.msg('请选择您的学历');
return;
}
const result = IdCardValidator.validate(idCard);
// 检查所有错误状态
if (nameError.value) return;
if (sexError.value) return;
if (ageError.value) return;
if (experienceError.value) return;
if (idCardError.value || !fromValue.idCard) {
if (!fromValue.idCard) $api.msg('请输入身份证号码');
return;
}
const result = IdCardValidator.validate(fromValue.idCard);
if (!result.valid) {
$api.msg(result.message);
return;
}
tabCurrent.value += 1;
}
@@ -359,9 +579,41 @@ function loginTest() {
}
function complete() {
const result = IdCardValidator.validate(fromValue.idcard);
const result = IdCardValidator.validate(fromValue.idCard);
if (result.valid) {
$api.createRequest('/app/user/resume', fromValue, 'post').then((resData) => {
// 构建 experiencesList 数组
const experiencesList = [];
if (fromValue.skills && fromValue.skillLevel) {
const skillsArray = fromValue.skills.split(',');
skillsArray.forEach(skill => {
if (skill.trim()) {
experiencesList.push({
name: skill.trim(),
levels: fromValue.skillLevel
});
}
});
}
// 构建符合要求的请求数据experiencesList 与 appUser 同级)
const requestData = {
appUser: {
name: fromValue.name,
isCompanyUser: 1,
age: fromValue.age,
sex: fromValue.sex,
workExperience: fromValue.workExperience,
education: fromValue.education,
idCard: fromValue.idCard,
area: fromValue.area,
jobTitleId: fromValue.jobTitleId,
salaryMin: fromValue.salaryMin,
salaryMax: fromValue.salaryMax
},
experiencesList: experiencesList
};
$api.createRequest('/app/user/registerUser', requestData, 'post').then((resData) => {
$api.msg('完成');
// 获取用户信息并存储到store中
getUserResume().then((userInfo) => {
@@ -403,32 +655,22 @@ function complete() {
display: flex
flex-wrap: wrap
.nx-item
padding: 20rpx 28rpx
padding: 16rpx 24rpx
width: fit-content
border-radius: 12rpx 12rpx 12rpx 12rpx;
border: 2rpx solid #E8EAEE;
margin-right: 24rpx
margin-top: 24rpx
.nx-item::before
position: absolute;
right: 20rpx;
top: 60rpx;
content: '';
width: 4rpx;
height: 18rpx;
border-radius: 2rpx
background: #697279;
transform: translate(0, -50%) rotate(-45deg) ;
.nx-item::after
position: absolute;
right: 20rpx;
top: 61rpx;
content: '';
width: 4rpx;
height: 18rpx;
border-radius: 2rpx
background: #697279;
transform: rotate(45deg)
border-radius: 20rpx
border: 2rpx solid #E8EAEE
background-color: #f8f9fa
margin-right: 16rpx
margin-top: 16rpx
font-size: 28rpx
color: #333333
transition: all 0.2s ease
&:hover
background-color: #e9ecef
border-color: #256bfa
color: #256bfa
// 移除技能标签的箭头样式,因为技能标签不需要箭头指示
.container
// background: linear-gradient(#4778EC, #002979);
width: 100%;

View File

@@ -16,7 +16,7 @@
</view>
<!-- <view class="chart button-click">职业图谱</view> -->
</view>
<view class="cards" v-if="userInfo.userType !== 0">
<view class="cards" v-if="shouldShowJobSeekerContent">
<view class="card press-button" @click="handleNearbyClick">
<view class="card-title">附近工作</view>
<view class="card-text">好岗职等你来</view>
@@ -27,9 +27,8 @@
</view> -->
</view>
<!-- 服务功能网格 -->
<view class="service-grid" v-if="userInfo.userType !== 0">
<view class="service-grid" v-if="shouldShowJobSeekerContent">
<view class="service-item press-button" @click="handleServiceClick('service-guidance')">
<view class="service-icon service-icon-1">
<uni-icons type="auth-filled" size="32" color="#FFFFFF"></uni-icons>
@@ -84,13 +83,55 @@
</view>
<view class="service-title">AI智能面试</view>
</view>
<view class="service-item press-button" @click="navToTestPage">
<view class="service-icon service-icon-10">
<uni-icons type="gear-filled" size="32" color="#FFFFFF"></uni-icons>
</view>
<view class="service-title">测试页面</view>
</view>
</view>
</view>
<!-- 企业用户内容 -->
<view class="company-content" v-if="shouldShowCompanyContent">
<view class="company-header">
<text class="company-title">企业服务</text>
<text class="company-subtitle">为您提供专业的企业招聘服务</text>
</view>
<view class="company-grid">
<view class="company-item press-button" @click="navTo('/pages/job/publishJob')">
<view class="company-icon company-icon-1">
<uni-icons type="plus-filled" size="32" color="#FFFFFF"></uni-icons>
</view>
<view class="company-title">发布岗位</view>
</view>
<view class="company-item press-button" @click="handleServiceClick('company-management')">
<view class="company-icon company-icon-2">
<uni-icons type="settings-filled" size="32" color="#FFFFFF"></uni-icons>
</view>
<view class="company-title">企业管理</view>
</view>
<view class="company-item press-button" @click="handleServiceClick('recruitment-data')">
<view class="company-icon company-icon-3">
<uni-icons type="bar-chart-filled" size="32" color="#FFFFFF"></uni-icons>
</view>
<view class="company-title">招聘数据</view>
</view>
<view class="company-item press-button" @click="handleServiceClick('talent-pool')">
<view class="company-icon company-icon-4">
<uni-icons type="person-filled" size="32" color="#FFFFFF"></uni-icons>
</view>
<view class="company-title">人才库</view>
</view>
</view>
</view>
<!-- 吸顶筛选区域占位 -->
<view class="filter-placeholder" v-if="shouldStickyFilter && userInfo.userType !== 0"></view>
<view class="filter-placeholder" v-if="shouldStickyFilter && shouldShowJobSeekerContent"></view>
<view class="nav-filter" :class="{ 'sticky-filter': shouldStickyFilter }" v-if="userInfo.userType !== 0">
<view class="nav-filter" :class="{ 'sticky-filter': shouldStickyFilter }" v-if="shouldShowJobSeekerContent">
<view class="filter-top" @touchmove.stop.prevent>
<scroll-view :scroll-x="true" :show-scrollbar="false" class="tab-scroll">
<view class="jobs-left">
@@ -322,7 +363,7 @@
</template>
<script setup>
import { reactive, inject, watch, ref, onMounted, onUnmounted, watchEffect, nextTick } from 'vue';
import { reactive, inject, watch, ref, onMounted, onUnmounted, watchEffect, nextTick, computed } from 'vue';
import img from '@/static/icon/filter.png';
import dictLabel from '@/components/dict-Label/dict-Label.vue';
const { $api, navTo, vacanciesTo, formatTotal, config } = inject('globalFunction');
@@ -330,6 +371,30 @@ import { onLoad, onShow } from '@dcloudio/uni-app';
import { storeToRefs } from 'pinia';
import useUserStore from '@/stores/useUserStore';
const { userInfo, hasLogin, token } = storeToRefs(useUserStore());
// 计算是否显示求职者内容
const shouldShowJobSeekerContent = computed(() => {
// 未登录时默认显示求职者内容
if (!hasLogin.value) {
return true;
}
// 登录后根据用户类型判断
const userType = userInfo.value?.isCompanyUser;
// 企业用户(isCompanyUser=0)不显示求职者内容,其他用户类型显示
return userType !== 0;
});
// 计算是否显示企业用户内容
const shouldShowCompanyContent = computed(() => {
// 未登录时不显示企业内容
if (!hasLogin.value) {
return false;
}
// 只有企业用户(isCompanyUser=0)才显示企业内容
const userType = userInfo.value?.isCompanyUser;
return userType === 0;
});
import useDictStore from '@/stores/useDictStore';
const { getTransformChildren, oneDictData } = useDictStore();
import useLocationStore from '@/stores/useLocationStore';
@@ -346,7 +411,6 @@ const lastScrollTop = ref(0);
const scrollTop = ref(0);
// 当用户与筛选/导航交互时,临时锁定头部显示状态,避免因数据刷新导致回弹显示
const isInteractingWithFilter = ref(false);
// 滚动阈值配置
const HIDE_THRESHOLD = 50; // 隐藏顶部区域的滚动阈值(降低阈值,更容易触发)
const SHOW_THRESHOLD = 5; // 显示顶部区域的滚动阈值(接近顶部)
@@ -461,6 +525,11 @@ const handleServiceClick = (serviceType) => {
}
};
// 跳转到测试页面
const navToTestPage = () => {
navTo('/pages/test/homepage-test');
};
async function loadData() {
try {
if (isLoaded.value) return;
@@ -1015,13 +1084,9 @@ defineExpose({ loadData });
.service-icon-10
background: linear-gradient(135deg, #9C27B0 0%, #BA68C8 100%)
position: relative
&::before
content: '🔍'
position: absolute
top: 50%
left: 50%
transform: translate(-50%, -50%)
font-size: 32rpx
display: flex
align-items: center
justify-content: center
.service-icon-11
background: linear-gradient(135deg, #FF9800 0%, #FFB74D 100%)
position: relative
@@ -1340,6 +1405,71 @@ defineExpose({ loadData });
background-size contain
pointer-events none
filter: blur(3rpx)
// 企业用户内容样式
.company-content
padding: 40rpx 30rpx
background: #ffffff
margin: 20rpx 30rpx
border-radius: 20rpx
box-shadow: 0rpx 4rpx 12rpx 0rpx rgba(0, 0, 0, 0.1)
.company-header
text-align: center
margin-bottom: 40rpx
.company-title
font-size: 36rpx
font-weight: bold
color: #333333
display: block
margin-bottom: 10rpx
.company-subtitle
font-size: 24rpx
color: #666666
display: block
.company-grid
display: grid
grid-template-columns: 1fr 1fr
gap: 30rpx
.company-item
display: flex
flex-direction: column
align-items: center
padding: 30rpx 20rpx
background: #f8f9fa
border-radius: 15rpx
transition: all 0.3s ease
&:active
transform: scale(0.95)
background: #e9ecef
.company-icon
width: 60rpx
height: 60rpx
border-radius: 50%
display: flex
align-items: center
justify-content: center
margin-bottom: 15rpx
&.company-icon-1
background: #256BFA
&.company-icon-2
background: #52c41a
&.company-icon-3
background: #fa8c16
&.company-icon-4
background: #eb2f96
.company-title
font-size: 24rpx
color: #333333
font-weight: 500
.recommend-card
padding 36rpx 24rpx
background: linear-gradient( 360deg, #DFE9FF 0%, #FFFFFF 52%, #FFFFFF 100%);

View File

@@ -6,7 +6,8 @@
<IndexOne @onShowTabbar="changeShowTabbar" />
</view>
<!-- 统一使用系统tabBar -->
<!-- 自定义tabbar -->
<CustomTabBar :currentPage="0" />
</view>
</view>
</template>
@@ -18,11 +19,17 @@ import IndexOne from './components/index-one.vue';
// import IndexTwo from './components/index-two.vue';
import { storeToRefs } from 'pinia';
import { useReadMsg } from '@/stores/useReadMsg';
import { tabbarManager } from '@/utils/tabbarManager';
const { unreadCount } = storeToRefs(useReadMsg());
onLoad(() => {
// useReadMsg().fetchMessages();
});
onShow(() => {
// 更新自定义tabbar选中状态
tabbarManager.updateSelected(0);
});
</script>
<style lang="stylus" scoped>

412
pages/job/companySearch.vue Normal file
View File

@@ -0,0 +1,412 @@
<template>
<view class="company-search-page">
<!-- 头部导航 -->
<view class="header">
<view class="header-left" @click="goBack">
<image src="@/static/icon/back.png" class="back-icon"></image>
</view>
<view class="header-title">选择企业</view>
<view class="header-right"></view>
</view>
<!-- 搜索框 -->
<view class="search-container">
<view class="search-box">
<view class="search-icon">🔍</view>
<input
class="search-input"
placeholder="请输入企业名称进行搜索"
v-model="searchKeyword"
@input="onSearchInput"
@confirm="onSearchConfirm"
/>
<view class="clear-btn" v-if="searchKeyword" @click="clearSearch">
<view class="clear-icon"></view>
</view>
</view>
</view>
<!-- 搜索结果 -->
<scroll-view class="content" scroll-y="true" :style="{ height: scrollViewHeight }">
<!-- 加载状态 -->
<view class="loading-container" v-if="loading">
<view class="loading-text">搜索中...</view>
</view>
<!-- 搜索结果列表 -->
<view class="result-list" v-else-if="searchResults.length > 0">
<view
class="result-item"
v-for="(company, index) in searchResults"
:key="company.id || index"
@click="selectCompany(company)"
>
<view class="company-info">
<view class="company-name">{{ company.name }}</view>
<view class="company-detail" v-if="company.address">
<text class="detail-label">地址</text>
<text class="detail-value">{{ company.address }}</text>
</view>
<view class="company-detail" v-if="company.contact">
<text class="detail-label">联系人</text>
<text class="detail-value">{{ company.contact }}</text>
</view>
</view>
<view class="select-icon">
<view class="arrow-icon">></view>
</view>
</view>
</view>
<!-- 空状态 -->
<view class="empty-container" v-else-if="searchKeyword && !loading">
<view class="empty-icon">🔍</view>
<view class="empty-text">未找到相关企业</view>
<view class="empty-tip">请尝试其他关键词</view>
</view>
<!-- 初始状态 -->
<view class="initial-container" v-else-if="!searchKeyword">
<view class="initial-icon">🔍</view>
<view class="initial-text">请输入企业名称进行搜索</view>
</view>
<!-- 底部安全区域 -->
<view class="bottom-safe-area"></view>
</scroll-view>
</view>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue';
import { createRequest } from '@/utils/request';
// 搜索相关状态
const searchKeyword = ref('');
const searchResults = ref([]);
const loading = ref(false);
const scrollViewHeight = ref('calc(100vh - 200rpx)');
// 防抖定时器
let debounceTimer = null;
// 计算滚动视图高度
const calculateScrollViewHeight = () => {
const systemInfo = uni.getSystemInfoSync();
const windowHeight = systemInfo.windowHeight;
const headerHeight = 100; // 头部高度
const searchHeight = 100; // 搜索框高度
const scrollHeight = windowHeight - headerHeight - searchHeight;
scrollViewHeight.value = `${scrollHeight}px`;
};
// 页面加载时计算高度
onMounted(() => {
calculateScrollViewHeight();
});
// 搜索输入处理(防抖)
const onSearchInput = () => {
// 清除之前的定时器
if (debounceTimer) {
clearTimeout(debounceTimer);
}
// 设置新的定时器500ms后执行搜索
debounceTimer = setTimeout(() => {
if (searchKeyword.value.trim()) {
searchCompanies();
} else {
searchResults.value = [];
}
}, 500);
};
// 搜索确认
const onSearchConfirm = () => {
if (searchKeyword.value.trim()) {
searchCompanies();
}
};
// 搜索企业
const searchCompanies = async () => {
if (!searchKeyword.value.trim()) {
searchResults.value = [];
return;
}
try {
loading.value = true;
const response = await createRequest('/app/company/likeList', {
name: searchKeyword.value.trim()
}, 'GET', false);
if (response.code === 200) {
searchResults.value = response.data || [];
} else {
uni.showToast({
title: response.msg || '搜索失败',
icon: 'none'
});
searchResults.value = [];
}
} catch (error) {
console.error('搜索企业失败:', error);
uni.showToast({
title: '搜索失败,请重试',
icon: 'none'
});
searchResults.value = [];
} finally {
loading.value = false;
}
};
// 选择企业
const selectCompany = (company) => {
// 返回上一页并传递选中的企业信息
uni.navigateBack({
success: () => {
// 通过事件总线或全局数据传递选中的企业信息
getApp().globalData = getApp().globalData || {};
getApp().globalData.selectedCompany = company;
}
});
};
// 清除搜索
const clearSearch = () => {
searchKeyword.value = '';
searchResults.value = [];
if (debounceTimer) {
clearTimeout(debounceTimer);
}
};
// 返回上一页
const goBack = () => {
uni.navigateBack();
};
</script>
<style lang="scss" scoped>
.company-search-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-left {
width: 60rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
.back-icon {
width: 40rpx;
height: 40rpx;
}
}
.header-title {
font-size: 36rpx;
font-weight: 600;
color: #333;
}
.header-right {
width: 60rpx;
}
}
.search-container {
padding: 20rpx 30rpx;
background: #fff;
border-bottom: 1rpx solid #eee;
.search-box {
display: flex;
align-items: center;
background: #f8f8f8;
border-radius: 12rpx;
padding: 0 20rpx;
height: 80rpx;
.search-icon {
font-size: 32rpx;
color: #999;
margin-right: 20rpx;
}
.search-input {
flex: 1;
height: 80rpx;
background: transparent;
border: none;
font-size: 28rpx;
color: #333;
&::placeholder {
color: #999;
}
}
.clear-btn {
width: 40rpx;
height: 40rpx;
display: flex;
align-items: center;
justify-content: center;
margin-left: 20rpx;
.clear-icon {
font-size: 24rpx;
color: #999;
}
}
}
}
.content {
flex: 1;
padding: 0;
overflow: hidden;
}
.loading-container {
display: flex;
justify-content: center;
align-items: center;
height: 200rpx;
.loading-text {
font-size: 28rpx;
color: #999;
}
}
.result-list {
background: #fff;
.result-item {
display: flex;
align-items: center;
padding: 30rpx;
border-bottom: 1rpx solid #f0f0f0;
&:last-child {
border-bottom: none;
}
&:active {
background: #f8f8f8;
}
.company-info {
flex: 1;
.company-name {
font-size: 32rpx;
font-weight: 600;
color: #333;
margin-bottom: 10rpx;
}
.company-detail {
font-size: 24rpx;
color: #666;
margin-bottom: 5rpx;
&:last-child {
margin-bottom: 0;
}
.detail-label {
color: #999;
}
.detail-value {
color: #666;
}
}
}
.select-icon {
width: 40rpx;
height: 40rpx;
display: flex;
align-items: center;
justify-content: center;
.arrow-icon {
font-size: 24rpx;
color: #999;
}
}
}
}
.empty-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 400rpx;
.empty-icon {
font-size: 120rpx;
color: #ccc;
margin-bottom: 30rpx;
}
.empty-text {
font-size: 32rpx;
color: #333;
margin-bottom: 10rpx;
}
.empty-tip {
font-size: 24rpx;
color: #999;
}
}
.initial-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 400rpx;
.initial-icon {
font-size: 120rpx;
color: #ccc;
margin-bottom: 30rpx;
}
.initial-text {
font-size: 28rpx;
color: #999;
}
}
.bottom-safe-area {
height: 120rpx;
background: transparent;
}
</style>

View File

@@ -10,12 +10,11 @@
</view>
<!-- 主要内容 -->
<view class="content">
<!-- 岗位基本信息 -->
<view class="section">
<view class="section-title">岗位基本信息</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>
<view class="label">岗位名称</view>
<input
class="input"
placeholder="请输入岗位名称"
@@ -23,75 +22,41 @@
/>
</view>
<view class="form-group">
<view class="label">岗位类型 *</view>
<picker
mode="selector"
:range="jobTypes"
@change="onJobTypeChange"
class="picker"
>
<view class="picker-text">{{ selectedJobType || '请选择岗位类型' }}</view>
</picker>
</view>
<view class="form-group">
<view class="label">工作地点 *</view>
<view class="label">招聘会公司</view>
<input
class="input"
placeholder="请输入工作地点"
v-model="formData.workLocation"
placeholder="请输入公司名称"
v-model="formData.companyName"
/>
</view>
</view>
<!-- 薪资待遇 -->
<view class="section">
<view class="section-title">薪资待遇</view>
<view class="salary-row">
<view class="form-group">
<view class="label">最低薪资</view>
<input
class="input salary-input"
placeholder="0"
type="number"
v-model="formData.minSalary"
/>
</view>
<view class="salary-separator">-</view>
<view class="form-group">
<view class="label">最高薪资</view>
<input
class="input salary-input"
placeholder="0"
type="number"
v-model="formData.maxSalary"
/>
</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>
<picker
mode="selector"
:range="salaryUnits"
@change="onSalaryUnitChange"
class="picker"
>
<view class="picker-text">{{ selectedSalaryUnit || '请选择薪资单位' }}</view>
</picker>
<view class="label">最大薪资 (/)</view>
<input
class="input"
placeholder="请输入最大薪资"
type="number"
v-model="formData.maxSalary"
/>
</view>
</view>
<!-- 任职要求 -->
<view class="section">
<view class="section-title">任职要求</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">{{ selectedEducation || '请选择学历要求' }}</view>
<view class="picker-text" data-placeholder="请选择学历要求">{{ selectedEducation || '请选择学历要求' }}</view>
</picker>
</view>
<view class="form-group">
@@ -99,10 +64,23 @@
<picker
mode="selector"
:range="experienceLevels"
range-key="label"
@change="onExperienceChange"
class="picker"
>
<view class="picker-text">{{ selectedExperience || '请选择工作经验' }}</view>
<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">
@@ -114,21 +92,49 @@
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="section">
<!-- 岗位描述区块 -->
<view class="form-block">
<view class="section-title">岗位描述</view>
<view class="form-group">
<view class="label">岗位职责</view>
<textarea
class="textarea"
placeholder="请详细描述岗位职责和工作内容"
v-model="formData.jobDescription"
v-model="formData.description"
></textarea>
</view>
</view>
<!-- 任职要求区块 -->
<view class="form-block">
<view class="section-title">任职要求</view>
<view class="form-group">
<view class="label">任职要求</view>
<textarea
class="textarea"
placeholder="请描述对候选人的具体要求"
@@ -137,35 +143,52 @@
</view>
</view>
<!-- 联系方式 -->
<view class="section">
<!-- 联系方式区块 -->
<view class="form-block">
<view class="section-title">联系方式</view>
<view class="form-group">
<view class="label">联系人</view>
<input
class="input"
placeholder="请输入联系人姓名"
v-model="formData.contactPerson"
/>
<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="form-group">
<view class="label">联系电话</view>
<input
class="input"
placeholder="请输入联系电话"
v-model="formData.contactPhone"
/>
</view>
<view class="form-group">
<view class="label">联系邮箱</view>
<input
class="input"
placeholder="请输入联系邮箱"
v-model="formData.contactEmail"
/>
<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>
<!-- 底部安全区域 -->
<view class="bottom-safe-area"></view>
</scroll-view>
<!-- 底部操作按钮 -->
<view class="footer">
@@ -178,49 +201,171 @@
</template>
<script setup>
import { ref, reactive } from 'vue';
import { ref, reactive, onMounted } from 'vue';
import { createRequest } from '@/utils/request';
import useDictStore from '@/stores/useDictStore';
import useUserStore from '@/stores/useUserStore';
// 表单数据
const formData = reactive({
jobTitle: '',
workLocation: '',
companyName: '',
minSalary: '',
maxSalary: '',
recruitCount: '',
jobDescription: '',
recruitCount: '', // 对应接口字段 idCardPictureBackUrl
description: '', // 对应接口字段 description
jobRequirements: '',
contactPerson: '',
contactPhone: '',
contactEmail: ''
jobCategory: '', // 新增:岗位分类
companyId: '', // 新增企业id
latitude: '', // 新增:纬度
longitude: '', // 新增:经度
jobLocation: '', // 新增:工作地点
jobLocationAreaCode: '', // 新增:工作地点区县字典代码
education: '', // 新增:学历要求字典值
experience: '', // 新增:工作经验字典值
contacts: [
{
name: '',
phone: ''
}
]
});
// 字典存储
const dictStore = useDictStore();
const userStore = useUserStore();
// 选择器数据
const jobTypes = ['技术类', '销售类', '管理类', '服务类', '其他'];
const salaryUnits = ['元/月', '元/年', '元/小时'];
const educationLevels = ['不限', '高中', '大专', '本科', '硕士', '博士'];
const experienceLevels = ['不限', '应届毕业生', '1-3年', '3-5年', '5-10年', '10年以上'];
const educationLevels = ref([]);
const experienceLevels = ref([]);
const workDistricts = ref([]);
const workLocations = ref([]);
const jobCategories = ref([]); // 新增:岗位分类选项
// 选中的值
const selectedJobType = ref('');
const selectedSalaryUnit = 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 scrollHeight = windowHeight - headerHeight - footerHeight;
scrollViewHeight.value = `${scrollHeight}px`;
};
// 页面加载时计算高度和初始化数据
onMounted(async () => {
calculateScrollViewHeight();
await initFormData();
});
// 初始化表单数据
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 onJobTypeChange = (e) => {
selectedJobType.value = jobTypes[e.detail.value];
};
const onSalaryUnitChange = (e) => {
selectedSalaryUnit.value = salaryUnits[e.detail.value];
};
const onEducationChange = (e) => {
selectedEducation.value = educationLevels[e.detail.value];
const index = e.detail.value;
const selectedItem = educationLevels.value[index];
selectedEducation.value = selectedItem.label;
formData.education = selectedItem.value;
};
const onExperienceChange = (e) => {
selectedExperience.value = experienceLevels[e.detail.value];
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);
}
};
// 返回上一页
@@ -229,49 +374,138 @@ const goBack = () => {
};
// 发布岗位
const publishJob = () => {
// 单验证
if (!formData.jobTitle) {
uni.showToast({
title: '请输入岗位名称',
icon: 'none'
});
return;
}
if (!formData.workLocation) {
uni.showToast({
title: '请输入工作地点',
icon: 'none'
});
const publishJob = async () => {
// 单验证
if (!validateForm()) {
return;
}
// 模拟发布成功
uni.showLoading({
title: '发布中...'
});
setTimeout(() => {
uni.hideLoading();
uni.showToast({
title: '发布成功',
icon: 'success'
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);
// 延迟返回
setTimeout(() => {
goBack();
}, 1500);
}, 2000);
uni.hideLoading();
if (response.code === 200) {
uni.showToast({
title: '发布成功',
icon: 'success'
});
// 延迟返回
setTimeout(() => {
goBack();
}, 1500);
} 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 {
min-height: 100vh;
height: 100vh;
background-color: #f5f5f5;
padding-bottom: 120rpx;
display: flex;
flex-direction: column;
position: relative;
overflow: hidden;
}
.header {
@@ -307,16 +541,17 @@ const publishJob = () => {
}
.content {
flex: 1;
padding: 0;
overflow: hidden;
}
.section {
.form-block {
background: #fff;
border-radius: 0;
padding: 30rpx;
margin-bottom: 20rpx;
width: 100%;
position: relative;
padding-bottom: 10rpx;
&:last-child {
margin-bottom: 0;
}
@@ -326,23 +561,27 @@ const publishJob = () => {
font-weight: 600;
color: #333;
margin-bottom: 30rpx;
padding-bottom: 20rpx;
padding: 30rpx 30rpx 20rpx 30rpx;
border-bottom: 2rpx solid #f0f0f0;
}
}
.form-group {
margin-bottom: 30rpx;
margin-bottom: 0;
padding: 0 30rpx;
border-bottom: 2rpx solid #f0f0f0;
&:last-child {
margin-bottom: 0;
border-bottom: none;
padding-bottom: 30rpx;
}
.label {
font-size: 28rpx;
color: #333;
color: #000;
margin-bottom: 15rpx;
font-weight: 500;
font-weight: 400;
padding-top: 30rpx;
}
.input {
@@ -350,15 +589,23 @@ const publishJob = () => {
height: 80rpx;
background: #fff;
border: none;
border-bottom: 2rpx solid #e0e0e0;
border-radius: 0;
padding: 0 0 10rpx 0;
padding: 0 0 20rpx 0;
font-size: 28rpx;
color: #333;
position: relative;
z-index: 1;
box-sizing: border-box;
&::placeholder {
color: #999;
font-size: 24rpx;
}
&:focus {
border-bottom-color: #256BFA;
background: #fff;
transform: translateZ(0);
-webkit-transform: translateZ(0);
}
}
@@ -367,15 +614,23 @@ const publishJob = () => {
min-height: 120rpx;
background: #fff;
border: none;
border-bottom: 2rpx solid #e0e0e0;
border-radius: 0;
padding: 20rpx 0 10rpx 0;
padding: 0 0 20rpx 0;
font-size: 28rpx;
color: #333;
position: relative;
z-index: 1;
box-sizing: border-box;
&::placeholder {
color: #999;
font-size: 24rpx;
}
&:focus {
border-bottom-color: #256BFA;
background: #fff;
transform: translateZ(0);
-webkit-transform: translateZ(0);
}
}
@@ -384,38 +639,132 @@ const publishJob = () => {
height: 80rpx;
background: #fff;
border: none;
border-bottom: 2rpx solid #e0e0e0;
border-radius: 0;
display: flex;
align-items: center;
padding: 0 0 10rpx 0;
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;
}
}
}
.salary-row {
// 联系人管理样式
.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;
}
}
}
}
.add-contact-btn {
display: flex;
align-items: center;
gap: 20rpx;
justify-content: center;
margin: 20rpx 30rpx 30rpx 30rpx;
padding: 20rpx;
background: #fff;
border: 2rpx dashed #ddd;
border-radius: 12rpx;
.form-group {
flex: 1;
margin-bottom: 0;
}
.salary-separator {
.add-icon {
font-size: 32rpx;
color: #666;
margin-top: 40rpx;
color: #256BFA;
margin-right: 10rpx;
font-weight: bold;
}
.salary-input {
text-align: center;
.add-text {
font-size: 28rpx;
color: #256BFA;
font-weight: 500;
}
&:active {
background: #e3f2fd;
border-color: #256BFA;
}
}
.bottom-safe-area {
height: 120rpx;
background: transparent;
}
.footer {
@@ -426,6 +775,7 @@ const publishJob = () => {
background: #fff;
padding: 20rpx 30rpx;
border-top: 1rpx solid #eee;
z-index: 100;
.btn-group {
display: flex;
@@ -451,4 +801,28 @@ const publishJob = () => {
}
}
}
/* 防止键盘弹出时页面偏移 */
/* #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>

View File

@@ -1,5 +1,7 @@
<template>
<AppLayout title="我的" back-gorund-color="#F4F4F4">
<!-- 自定义tabbar -->
<CustomTabBar :currentPage="4" />
<view class="mine-userinfo btn-feel" @click="seeDetail">
<view class="userindo-head">
<image class="userindo-head-img" v-if="userInfo.sex === '0'" src="/static/icon/boy.png"></image>
@@ -108,6 +110,7 @@ import { storeToRefs } from 'pinia';
import { onLoad, onShow } from '@dcloudio/uni-app';
const { $api, navTo } = inject('globalFunction');
import useUserStore from '@/stores/useUserStore';
import { tabbarManager } from '@/utils/tabbarManager';
const popup = ref(null);
const { userInfo, Completion } = storeToRefs(useUserStore());
const counts = ref({});
@@ -116,6 +119,8 @@ function logOut() {
}
onShow(() => {
getUserstatistics();
// 更新自定义tabbar选中状态
tabbarManager.updateSelected(4);
});
function close() {

View File

@@ -28,6 +28,9 @@
</swiper>
</view>
<!-- 自定义tabbar -->
<CustomTabBar :currentPage="3" />
<!-- 统一使用系统tabBar -->
</view>
</view>
@@ -39,6 +42,7 @@ import { onLoad, onShow } from '@dcloudio/uni-app';
import Tabbar from '@/components/tabbar/midell-box.vue';
import ReadComponent from './read.vue';
import UnreadComponent from './unread.vue';
import { tabbarManager } from '@/utils/tabbarManager';
const loadedMap = reactive([false, false]);
const swiperRefs = [ref(null), ref(null)];
const components = [ReadComponent, UnreadComponent];
@@ -49,6 +53,8 @@ const { unreadCount } = storeToRefs(useReadMsg());
onShow(() => {
// 获取消息列表
useReadMsg().fetchMessages();
// 更新自定义tabbar选中状态
tabbarManager.updateSelected(3);
});
const state = reactive({
current: 0,

View File

@@ -0,0 +1,170 @@
<template>
<view class="test-page">
<view class="header">
<text class="title">企业搜索功能测试</text>
</view>
<view class="test-section">
<view class="section-title">功能说明</view>
<view class="description">
<text class="desc-text"> 企业用户isCompanyUser=0招聘公司输入框为普通输入框</text>
<text class="desc-text"> 网格员isCompanyUser=2招聘公司输入框为选择器点击跳转到搜索页面</text>
<text class="desc-text"> 搜索页面支持防抖节流500ms延迟</text>
<text class="desc-text"> 搜索接口/app/company/likeList参数name</text>
</view>
</view>
<view class="test-section">
<view class="section-title">当前用户类型</view>
<view class="user-type-info">
<text class="type-label">用户类型</text>
<text class="type-value">{{ getCurrentTypeLabel() }} ({{ currentUserType }})</text>
</view>
<view class="user-type-info">
<text class="type-label">是否企业用户</text>
<text class="type-value">{{ isCompanyUser ? '是' : '否' }}</text>
</view>
</view>
<view class="test-section">
<view class="section-title">测试操作</view>
<view class="button-group">
<button class="test-btn" @click="switchToCompany">切换到企业用户</button>
<button class="test-btn" @click="switchToGrid">切换到网格员</button>
<button class="test-btn" @click="goToPublishJob">进入发布岗位页面</button>
</view>
</view>
</view>
</template>
<script setup>
import { ref, computed } from 'vue';
import { storeToRefs } from 'pinia';
import useUserStore from '@/stores/useUserStore';
const userStore = useUserStore();
const { userInfo } = storeToRefs(userStore);
const userTypes = [
{ value: 0, label: '企业用户' },
{ value: 1, label: '求职者' },
{ value: 2, label: '网格员' },
{ value: 3, label: '政府人员' }
];
const currentUserType = computed(() => userInfo.value?.isCompanyUser !== undefined ? userInfo.value.isCompanyUser : 1);
const isCompanyUser = computed(() => {
return currentUserType.value === 0;
});
const getCurrentTypeLabel = () => {
const type = userTypes.find(t => t.value === currentUserType.value);
return type ? type.label : '未知';
};
const switchToCompany = () => {
userInfo.value.isCompanyUser = 0;
uni.setStorageSync('userInfo', userInfo.value);
uni.showToast({
title: '已切换到企业用户',
icon: 'success'
});
};
const switchToGrid = () => {
userInfo.value.isCompanyUser = 2;
uni.setStorageSync('userInfo', userInfo.value);
uni.showToast({
title: '已切换到网格员',
icon: 'success'
});
};
const goToPublishJob = () => {
uni.navigateTo({
url: '/pages/job/publishJob'
});
};
</script>
<style lang="scss" scoped>
.test-page {
padding: 40rpx;
background: #f5f5f5;
min-height: 100vh;
}
.header {
text-align: center;
margin-bottom: 40rpx;
.title {
font-size: 36rpx;
font-weight: 600;
color: #333;
}
}
.test-section {
background: #fff;
border-radius: 12rpx;
padding: 30rpx;
margin-bottom: 30rpx;
.section-title {
font-size: 32rpx;
font-weight: 600;
color: #333;
margin-bottom: 20rpx;
}
.description {
.desc-text {
display: block;
font-size: 26rpx;
color: #666;
line-height: 1.6;
margin-bottom: 10rpx;
}
}
.user-type-info {
display: flex;
align-items: center;
margin-bottom: 15rpx;
.type-label {
font-size: 28rpx;
color: #333;
margin-right: 10rpx;
}
.type-value {
font-size: 28rpx;
color: #256BFA;
font-weight: 500;
}
}
.button-group {
display: flex;
flex-direction: column;
gap: 20rpx;
.test-btn {
height: 80rpx;
background: #256BFA;
color: #fff;
border: none;
border-radius: 12rpx;
font-size: 28rpx;
font-weight: 500;
&:active {
background: #1e5ce6;
}
}
}
}
</style>

View File

@@ -0,0 +1,296 @@
<template>
<view class="homepage-test">
<view class="header">
<text class="title">首页内容测试</text>
</view>
<view class="content">
<view class="user-info">
<text class="label">当前用户类型</text>
<text class="value">{{ getCurrentUserTypeLabel() }}</text>
</view>
<view class="login-status">
<text class="label">登录状态</text>
<text class="value" :class="{ 'logged-in': hasLogin, 'not-logged-in': !hasLogin }">
{{ hasLogin ? '已登录' : '未登录' }}
</text>
</view>
<view class="debug-info">
<text class="label">调试信息</text>
<text class="value">userType: {{ userInfo?.userType ?? 'undefined' }}</text>
<text class="value">hasLogin: {{ hasLogin }}</text>
<text class="value">shouldShowJobSeeker: {{ shouldShowJobSeekerContent }}</text>
<text class="value">shouldShowCompany: {{ shouldShowCompanyContent }}</text>
<text class="value">完整userInfo: {{ JSON.stringify(userInfo) }}</text>
</view>
<view class="content-preview">
<text class="section-title">首页内容预览</text>
<view class="content-item" v-if="shouldShowJobSeekerContent">
<text class="content-label"> 求职者内容</text>
<text class="content-desc"> 附近工作卡片</text>
<text class="content-desc"> 服务功能网格</text>
<text class="content-desc"> 职位筛选器</text>
</view>
<view class="content-item" v-if="shouldShowCompanyContent">
<text class="content-label"> 企业用户内容</text>
<text class="content-desc"> 企业服务标题</text>
<text class="content-desc"> 发布岗位按钮</text>
<text class="content-desc"> 企业管理功能</text>
</view>
<view class="content-item" v-if="!shouldShowJobSeekerContent && !shouldShowCompanyContent">
<text class="content-label"> 无内容显示</text>
<text class="content-desc">请检查用户类型设置</text>
</view>
</view>
<view class="test-buttons">
<button @click="testLoginAsJobSeeker" class="test-btn">模拟求职者登录</button>
<button @click="testLoginAsCompany" class="test-btn">模拟企业用户登录</button>
<button @click="testLogout" class="test-btn" v-if="hasLogin">模拟登出</button>
<button @click="forceRefreshUserInfo" class="test-btn">强制刷新用户信息</button>
<button @click="clearUserInfo" class="test-btn">清除用户信息</button>
<button @click="refreshTabBar" class="test-btn">刷新TabBar</button>
</view>
</view>
<!-- 自定义tabbar -->
<CustomTabBar :currentPage="0" />
</view>
</template>
<script setup>
import { ref, computed } from 'vue';
import { storeToRefs } from 'pinia';
import useUserStore from '@/stores/useUserStore';
import { tabbarManager } from '@/utils/tabbarManager';
const userStore = useUserStore();
const { userInfo, hasLogin } = storeToRefs(userStore);
const userTypes = [
{ value: 0, label: '企业用户' },
{ value: 1, label: '求职者' },
{ value: 2, label: '网格员' },
{ value: 3, label: '政府人员' }
];
const currentUserType = computed(() => userInfo.value?.userType || 1);
const getCurrentUserTypeLabel = () => {
const type = userTypes.find(t => t.value === currentUserType.value);
return type ? type.label : '未知';
};
// 计算是否显示求职者内容
const shouldShowJobSeekerContent = computed(() => {
if (!hasLogin.value) {
return true;
}
const userType = userInfo.value?.isCompanyUser;
return userType !== 0;
});
// 计算是否显示企业用户内容
const shouldShowCompanyContent = computed(() => {
if (!hasLogin.value) {
return false;
}
const userType = userInfo.value?.isCompanyUser;
return userType === 0;
});
const testLoginAsJobSeeker = () => {
const mockUserInfo = {
userType: 1,
name: '求职者用户',
id: 'jobseeker123'
};
userInfo.value = mockUserInfo;
userStore.hasLogin = true;
uni.setStorageSync('userInfo', mockUserInfo);
uni.showToast({
title: '模拟求职者登录成功',
icon: 'success'
});
};
const testLoginAsCompany = () => {
const mockUserInfo = {
userType: 0,
name: '企业用户',
id: 'company123'
};
userInfo.value = mockUserInfo;
userStore.hasLogin = true;
uni.setStorageSync('userInfo', mockUserInfo);
uni.showToast({
title: '模拟企业用户登录成功',
icon: 'success'
});
};
const testLogout = () => {
userStore.logOut(false);
uni.showToast({
title: '模拟登出成功',
icon: 'success'
});
};
const forceRefreshUserInfo = () => {
// 从本地存储重新加载用户信息
const cachedUserInfo = uni.getStorageSync('userInfo');
if (cachedUserInfo) {
userInfo.value = cachedUserInfo;
userStore.hasLogin = true;
uni.showToast({
title: '用户信息已刷新',
icon: 'success'
});
} else {
uni.showToast({
title: '未找到用户信息',
icon: 'none'
});
}
};
const clearUserInfo = () => {
// 清除所有用户信息
uni.removeStorageSync('userInfo');
uni.removeStorageSync('token');
userInfo.value = {};
userStore.hasLogin = false;
uni.showToast({
title: '用户信息已清除',
icon: 'success'
});
};
const refreshTabBar = () => {
// 刷新tabbar
tabbarManager.refreshTabBar();
uni.showToast({
title: 'TabBar已刷新',
icon: 'success'
});
};
</script>
<style lang="scss" scoped>
.homepage-test {
padding: 40rpx;
min-height: 100vh;
background-color: #f5f5f5;
}
.header {
text-align: center;
margin-bottom: 40rpx;
}
.title {
font-size: 36rpx;
font-weight: bold;
color: #333;
}
.content {
background: white;
border-radius: 20rpx;
padding: 40rpx;
margin-bottom: 40rpx;
}
.user-info, .login-status, .debug-info {
display: flex;
flex-direction: column;
margin-bottom: 20rpx;
padding: 20rpx;
background: #f8f9fa;
border-radius: 10rpx;
}
.debug-info .value {
font-size: 20rpx;
color: #666;
margin: 5rpx 0;
display: block;
word-break: break-all;
}
.label {
font-size: 28rpx;
color: #666;
margin-right: 20rpx;
}
.value {
font-size: 28rpx;
color: #256BFA;
font-weight: bold;
}
.logged-in {
color: #52c41a !important;
}
.not-logged-in {
color: #ff4d4f !important;
}
.content-preview {
margin: 30rpx 0;
}
.section-title {
font-size: 28rpx;
color: #333;
font-weight: bold;
margin-bottom: 20rpx;
display: block;
}
.content-item {
background: #f8f9fa;
padding: 20rpx;
border-radius: 10rpx;
margin-bottom: 15rpx;
}
.content-label {
font-size: 24rpx;
color: #333;
font-weight: bold;
display: block;
margin-bottom: 10rpx;
}
.content-desc {
font-size: 22rpx;
color: #666;
margin: 5rpx 0;
display: block;
}
.test-buttons {
margin-top: 30rpx;
}
.test-btn {
margin: 10rpx 0;
padding: 20rpx 30rpx;
background: #256BFA;
color: white;
border: none;
border-radius: 10rpx;
font-size: 24rpx;
width: 100%;
}
</style>

293
pages/test/tabbar-test.vue Normal file
View File

@@ -0,0 +1,293 @@
<template>
<view class="tabbar-test">
<view class="header">
<text class="title">自定义TabBar测试页面</text>
</view>
<view class="content">
<view class="user-info">
<text class="label">当前用户类型</text>
<text class="value">{{ getCurrentUserTypeLabel() }}</text>
</view>
<view class="login-status">
<text class="label">登录状态</text>
<text class="value" :class="{ 'logged-in': hasLogin, 'not-logged-in': !hasLogin }">
{{ hasLogin ? '已登录' : '未登录' }}
</text>
</view>
<view class="debug-info">
<text class="label">调试信息</text>
<text class="value">userType: {{ userInfo?.userType ?? 'undefined' }}</text>
<text class="value">hasLogin: {{ hasLogin }}</text>
<text class="value">shouldShowJobSeeker: {{ shouldShowJobSeekerContent }}</text>
<text class="value">shouldShowCompany: {{ shouldShowCompanyContent }}</text>
</view>
<view class="switch-section">
<text class="section-title">切换用户类型</text>
<view class="switch-buttons">
<button
v-for="(type, index) in userTypes"
:key="index"
@click="switchUserType(type.value)"
:class="{ active: currentUserType === type.value }"
class="switch-btn"
>
{{ type.label }}
</button>
</view>
</view>
<view class="description">
<text class="desc-title">功能说明</text>
<text class="desc-text"> 未登录状态默认显示求职者tabbar职位 + 招聘会 + AI+ + 消息 + 我的</text>
<text class="desc-text"> 企业用户userType=0显示"发布岗位"导航隐藏"招聘会"</text>
<text class="desc-text"> 求职者用户userType=1,2,3显示"招聘会"导航</text>
<text class="desc-text"> 登录后根据用户角色自动切换对应的tabbar</text>
<text class="desc-text"> 系统默认tabbar已被隐藏使用自定义tabbar</text>
</view>
<view class="test-section">
<text class="section-title">测试功能</text>
<button @click="testHideTabBar" class="test-btn">检查系统TabBar状态</button>
<button @click="testShowTabBar" class="test-btn">临时显示系统TabBar</button>
<button @click="testLogin" class="test-btn" v-if="!hasLogin">模拟登录</button>
<button @click="testLogout" class="test-btn" v-if="hasLogin">模拟登出</button>
</view>
</view>
<!-- 自定义tabbar -->
<CustomTabBar :currentPage="0" />
</view>
</template>
<script setup>
import { ref, computed } from 'vue';
import { storeToRefs } from 'pinia';
import useUserStore from '@/stores/useUserStore';
const userStore = useUserStore();
const { userInfo, hasLogin } = storeToRefs(userStore);
const userTypes = [
{ value: 0, label: '企业用户' },
{ value: 1, label: '求职者' },
{ value: 2, label: '网格员' },
{ value: 3, label: '政府人员' }
];
const currentUserType = computed(() => userInfo.value?.isCompanyUser !== undefined ? userInfo.value.isCompanyUser : 0);
const switchUserType = (userType) => {
console.log('切换用户类型:', userType);
userInfo.value.isCompanyUser = userType;
// 更新到本地存储
uni.setStorageSync('userInfo', userInfo.value);
};
const getCurrentUserTypeLabel = () => {
const type = userTypes.find(t => t.value === currentUserType.value);
return type ? type.label : '未知';
};
// 计算是否显示求职者内容
const shouldShowJobSeekerContent = computed(() => {
if (!hasLogin.value) {
return true;
}
const userType = userInfo.value?.isCompanyUser;
return userType !== 0;
});
// 计算是否显示企业用户内容
const shouldShowCompanyContent = computed(() => {
if (!hasLogin.value) {
return false;
}
const userType = userInfo.value?.isCompanyUser;
return userType === 0;
});
const testHideTabBar = () => {
uni.hideTabBar();
uni.showToast({
title: '系统TabBar已隐藏',
icon: 'success'
});
};
const testShowTabBar = () => {
uni.showTabBar();
uni.showToast({
title: '系统TabBar已显示测试用',
icon: 'none'
});
// 3秒后自动隐藏
setTimeout(() => {
uni.hideTabBar();
}, 3000);
};
const testLogin = () => {
// 模拟登录,设置用户信息
const mockUserInfo = {
userType: 1, // 默认设置为求职者
name: '测试用户',
id: 'test123'
};
userInfo.value = mockUserInfo;
userStore.hasLogin = true;
uni.setStorageSync('userInfo', mockUserInfo);
uni.showToast({
title: '模拟登录成功',
icon: 'success'
});
};
const testLogout = () => {
// 模拟登出,清除用户信息
userStore.logOut(false);
uni.showToast({
title: '模拟登出成功',
icon: 'success'
});
};
</script>
<style lang="scss" scoped>
.tabbar-test {
padding: 40rpx;
min-height: 100vh;
background-color: #f5f5f5;
}
.header {
text-align: center;
margin-bottom: 40rpx;
}
.title {
font-size: 36rpx;
font-weight: bold;
color: #333;
}
.content {
background: white;
border-radius: 20rpx;
padding: 40rpx;
margin-bottom: 40rpx;
}
.user-info, .login-status, .debug-info {
display: flex;
flex-direction: column;
margin-bottom: 20rpx;
padding: 20rpx;
background: #f8f9fa;
border-radius: 10rpx;
}
.debug-info .value {
font-size: 20rpx;
color: #666;
margin: 5rpx 0;
display: block;
}
.label {
font-size: 28rpx;
color: #666;
margin-right: 20rpx;
}
.value {
font-size: 28rpx;
color: #256BFA;
font-weight: bold;
}
.switch-section {
margin-bottom: 40rpx;
}
.section-title {
font-size: 28rpx;
color: #333;
margin-bottom: 20rpx;
display: block;
}
.switch-buttons {
display: flex;
flex-wrap: wrap;
gap: 20rpx;
}
.switch-btn {
padding: 20rpx 30rpx;
background: #f0f0f0;
border: none;
border-radius: 10rpx;
font-size: 24rpx;
color: #666;
transition: all 0.3s;
}
.switch-btn.active {
background: #256BFA;
color: white;
}
.description {
background: #f8f9fa;
padding: 30rpx;
border-radius: 10rpx;
}
.desc-title {
font-size: 26rpx;
color: #333;
font-weight: bold;
margin-bottom: 20rpx;
display: block;
}
.desc-text {
font-size: 24rpx;
color: #666;
line-height: 1.6;
margin-bottom: 10rpx;
display: block;
}
.test-section {
margin-top: 40rpx;
background: #f8f9fa;
padding: 30rpx;
border-radius: 10rpx;
}
.test-btn {
margin: 10rpx 0;
padding: 20rpx 30rpx;
background: #256BFA;
color: white;
border: none;
border-radius: 10rpx;
font-size: 24rpx;
width: 100%;
}
.logged-in {
color: #52c41a !important;
font-weight: bold;
}
.not-logged-in {
color: #ff4d4f !important;
font-weight: bold;
}
</style>

View File

@@ -0,0 +1,232 @@
<template>
<view class="test-page">
<view class="header">
<text class="title">TabBar用户类型切换测试</text>
</view>
<view class="content">
<view class="current-info">
<text class="label">当前用户类型</text>
<text class="value">{{ getCurrentTypeLabel() }} ({{ currentUserType }})</text>
</view>
<view class="type-switcher">
<text class="section-title">切换用户类型</text>
<view class="buttons">
<button
v-for="(type, index) in userTypes"
:key="index"
:class="['btn', { active: currentUserType === type.value }]"
@click="switchUserType(type.value)"
>
{{ type.label }}
</button>
</view>
</view>
<view class="tabbar-preview">
<text class="section-title">TabBar预览</text>
<view class="tabbar-container">
<view
v-for="(item, index) in tabbarConfig"
:key="index"
class="tabbar-item"
>
<text class="tabbar-text">{{ item.text }}</text>
</view>
</view>
</view>
<view class="description">
<text class="desc-title">功能说明</text>
<text class="desc-text"> 企业用户userType=0显示"发布岗位"导航</text>
<text class="desc-text"> 求职者userType=1显示"招聘会"导航</text>
<text class="desc-text"> 网格员userType=2显示"招聘会"导航</text>
<text class="desc-text"> 政府人员userType=3显示"招聘会"导航</text>
<text class="desc-text"> 切换用户类型后底部导航栏会自动更新</text>
</view>
</view>
</view>
</template>
<script setup>
import { ref, computed } from 'vue';
import { storeToRefs } from 'pinia';
import useUserStore from '@/stores/useUserStore';
const { userInfo } = storeToRefs(useUserStore());
const userTypes = [
{ value: 0, label: '企业用户' },
{ value: 1, label: '求职者' },
{ value: 2, label: '网格员' },
{ value: 3, label: '政府人员' }
];
const currentUserType = computed(() => userInfo.value?.isCompanyUser !== undefined ? userInfo.value.isCompanyUser : 1);
const switchUserType = (userType) => {
console.log('切换用户类型:', userType);
userInfo.value.isCompanyUser = userType;
uni.setStorageSync('userInfo', userInfo.value);
uni.showToast({
title: `已切换到${getCurrentTypeLabel()}`,
icon: 'success'
});
};
const getCurrentTypeLabel = () => {
const type = userTypes.find(t => t.value === currentUserType.value);
return type ? type.label : '未知';
};
const tabbarConfig = computed(() => {
const baseItems = [
{ text: '职位' },
{ text: currentUserType.value === 0 ? '发布岗位' : '招聘会' },
{ text: 'AI+' },
{ text: '消息' },
{ text: '我的' }
];
return baseItems;
});
</script>
<style lang="scss" scoped>
.test-page {
padding: 40rpx;
background: #f5f5f5;
min-height: 100vh;
}
.header {
text-align: center;
margin-bottom: 40rpx;
.title {
font-size: 36rpx;
font-weight: bold;
color: #333;
}
}
.content {
.current-info {
background: #fff;
padding: 30rpx;
border-radius: 16rpx;
margin-bottom: 30rpx;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
.label {
font-size: 28rpx;
color: #666;
}
.value {
font-size: 32rpx;
font-weight: bold;
color: #256BFA;
margin-left: 10rpx;
}
}
.type-switcher {
background: #fff;
padding: 30rpx;
border-radius: 16rpx;
margin-bottom: 30rpx;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
.section-title {
font-size: 28rpx;
font-weight: bold;
color: #333;
margin-bottom: 20rpx;
display: block;
}
.buttons {
display: flex;
flex-wrap: wrap;
gap: 20rpx;
.btn {
flex: 1;
min-width: 140rpx;
height: 80rpx;
background: #f8f9fa;
border: 2rpx solid #e9ecef;
border-radius: 12rpx;
font-size: 26rpx;
color: #666;
transition: all 0.3s ease;
&.active {
background: #256BFA;
border-color: #256BFA;
color: #fff;
}
}
}
}
.tabbar-preview {
background: #fff;
padding: 30rpx;
border-radius: 16rpx;
margin-bottom: 30rpx;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
.section-title {
font-size: 28rpx;
font-weight: bold;
color: #333;
margin-bottom: 20rpx;
display: block;
}
.tabbar-container {
display: flex;
background: #f8f9fa;
border-radius: 12rpx;
padding: 20rpx;
.tabbar-item {
flex: 1;
text-align: center;
padding: 20rpx 10rpx;
.tabbar-text {
font-size: 24rpx;
color: #666;
font-weight: 500;
}
}
}
}
.description {
background: #fff;
padding: 30rpx;
border-radius: 16rpx;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
.desc-title {
font-size: 28rpx;
font-weight: bold;
color: #333;
margin-bottom: 20rpx;
display: block;
}
.desc-text {
font-size: 24rpx;
color: #666;
line-height: 1.6;
display: block;
margin-bottom: 10rpx;
}
}
}
</style>