This commit is contained in:
2025-10-31 18:25:20 +08:00
84 changed files with 9950 additions and 2753 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,10 @@
<template>
<view class="container">
<!-- 自定义tabbar -->
<CustomTabBar :currentPage="2" />
<!-- 微信授权登录弹窗 -->
<WxAuthLogin ref="wxAuthLoginRef" @success="handleLoginSuccess"></WxAuthLogin>
<!-- 抽屉遮罩层 -->
<view v-if="isDrawerOpen" class="overlay" @click="toggleDrawer"></view>
@@ -71,13 +76,15 @@
</template>
<script setup>
import { ref, inject, nextTick, computed } from 'vue';
import { ref, inject, nextTick, computed, onMounted, onUnmounted } from 'vue';
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';
import WxAuthLogin from '@/components/WxAuthLogin/WxAuthLogin.vue';
const { isTyping, tabeList, chatSessionID } = storeToRefs(useChatGroupDBStore());
const { userInfo } = storeToRefs(useUserStore());
const isDrawerOpen = ref(false);
@@ -85,6 +92,7 @@ const scrollIntoView = ref(false);
const searchText = ref('');
const paging = ref(null);
const wxAuthLoginRef = ref(null);
// 实时过滤
const filteredList = computed(() => {
@@ -103,8 +111,27 @@ onShow(() => {
nextTick(() => {
paging.value?.closeFile();
});
// 更新自定义tabbar选中状态
tabbarManager.updateSelected(2);
});
onMounted(() => {
// 监听退出登录事件,显示微信登录弹窗
uni.$on('showLoginModal', () => {
wxAuthLoginRef.value?.open();
});
});
onUnmounted(() => {
uni.$off('showLoginModal');
});
// 登录成功回调
const handleLoginSuccess = () => {
console.log('登录成功');
// 可以在这里添加登录成功后的处理逻辑
};
onHide(() => {
paging.value?.handleTouchCancel();
if (isDrawerOpen.value) {

View File

@@ -1,11 +1,7 @@
<template>
<view class="chat-container">
<!-- #ifdef MP-WEIXIN -->
<view class="chat-background">
<!-- #endif -->
<!-- #ifndef MP-WEIXIN -->
<view class="chat-background" v-fade:600="!messages.length">
<!-- #endif -->
<view class="chat-background" v-if="!messages.length">
<image class="backlogo" src="/static/icon/backAI.png"></image>
<view class="back-rowTitle">欢迎使用{{ config.appInfo.areaName }}AI智能求职</view>
<view class="back-rowText">
@@ -24,6 +20,28 @@
<view class="message">{{ recognizedText }} {{ lastFinalText }}</view>
</view>
</view>
<!-- #endif -->
<!-- #ifndef MP-WEIXIN -->
<view class="chat-background" v-if="!messages.length">
<image class="backlogo" src="/static/icon/backAI.png"></image>
<view class="back-rowTitle">欢迎使用{{ config.appInfo.areaName }}AI智能求职</view>
<view class="back-rowText">
我可以根据您的简历和求职需求帮你精准匹配{{ config.appInfo.areaName }}互联网招聘信息对比招聘信息的优缺点提供面试指导等请把你的任务交给我吧~
</view>
<view class="back-rowh3">猜你所想</view>
<view
class="back-rowmsg button-click"
v-for="(item, index) in queries"
:key="index"
@click="sendMessageGuess(item)"
>
{{ item }}
</view>
<view class="chat-item self" v-if="isRecording">
<view class="message">{{ recognizedText }} {{ lastFinalText }}</view>
</view>
</view>
<!-- #endif -->
<scroll-view class="chat-list scrollView" :scroll-top="scrollTop" :scroll-y="true" scroll-with-animation>
<!-- #ifdef MP-WEIXIN -->
<view class="chat-list list-content">
@@ -946,19 +964,19 @@ image-margin-top = 40rpx
white-space: pre-wrap;
}
.list-content {
padding: 0 44rpx 44rpx 44rpx;
padding: 0 44rpx 10rpx 44rpx;
}
.chat-item {
display: flex;
align-items: flex-start;
margin-bottom: 20rpx;
margin-bottom: 10rpx;
width: 100%;
}
.chat-item.self {
justify-content: flex-end;
}
.message
margin-top: 40rpx
margin-top: 0rpx
// max-width: 80%;
width: 100%;
word-break: break-word;
@@ -1003,10 +1021,12 @@ image-margin-top = 40rpx
}
.input-area {
padding: 32rpx 28rpx 24rpx 28rpx;
padding-bottom: calc(24rpx + env(safe-area-inset-bottom) + 40rpx - 40rpx);
position: relative;
background: #FFFFFF;
box-shadow: 0rpx -4rpx 10rpx 0rpx rgba(11,44,112,0.06);
transition: height 2s ease-in-out;
z-index: 1001;
}
.input-area::after
position: absolute

View File

@@ -1,5 +1,5 @@
<template>
<AppLayout title="企业信息">
<AppLayout>
<view class="company-info-container">
<!-- 头部信息 -->
<view class="header-info">
@@ -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>
@@ -131,6 +131,15 @@
</view>
</view>
<!-- 企业规模 -->
<view class="form-item clickable" @click="selectScale">
<view class="label">企业规模</view>
<view class="input-content">
<input class="input-con" v-model="formData.scaleText" disabled placeholder="请选择企业规模" />
<uni-icons type="arrowright" size="16" color="#999"></uni-icons>
</view>
</view>
<!-- 联系人信息列表 -->
<view class="contact-section">
<view class="section-title">联系人信息</view>
@@ -225,9 +234,11 @@ import { onLoad } from '@dcloudio/uni-app'
import AreaCascadePicker from '@/components/area-cascade-picker/area-cascade-picker.vue'
import SelectPopup from '@/components/selectPopup/selectPopup.vue'
import useDictStore from '@/stores/useDictStore'
import useUserStore from '@/stores/useUserStore'
const { $api } = inject('globalFunction')
const dictStore = useDictStore()
const userStore = useUserStore()
// 表单数据
const formData = reactive({
@@ -241,11 +252,13 @@ const formData = reactive({
legalPersonName: '',
nature: '', // 企业类型
natureText: '', // 企业类型显示文本
enterpriseType: null, // 是否是就业见习基地 (true/false/null)
enterpriseType: null, // 是否是就业见习基地 (0=是, 1=否, null=未选择)
legalIdCard: '', // 法人身份证号
legalPhone: '', // 法人联系方式
industryType: '', // 是否是本地重点发展产业
isLocalCompany: null, // 是否是本地企业 (true/false/null)
scale: '', // 企业规模
scaleText: '', // 企业规模显示文本
companyContactList: [
{ contactPerson: '', contactPersonPhone: '' }
]
@@ -311,7 +324,8 @@ const completionPercentage = computed(() => {
formData.legalIdCard,
formData.legalPhone,
formData.industryType,
formData.isLocalCompany !== null ? 'filled' : ''
formData.isLocalCompany !== null ? 'filled' : '',
formData.scale
]
// 检查联系人信息
@@ -445,7 +459,7 @@ const selectEmploymentBase = () => {
uni.showActionSheet({
itemList: ['是', '否'],
success: (res) => {
formData.enterpriseType = res.tapIndex === 0
formData.enterpriseType = res.tapIndex === 0 ? 0 : 1
updateCompletion()
$api.msg('选择成功')
}
@@ -464,6 +478,73 @@ const selectLocalCompany = () => {
})
}
// 企业规模选项数据从字典中获取
const scaleOptions = computed(() => {
const scaleData = dictStore.state?.scale || []
console.log('企业规模选项数据:', scaleData)
return scaleData
})
// 选择企业规模
const selectScale = () => {
console.log('点击企业规模,当前数据:', scaleOptions.value)
console.log('字典store状态:', dictStore.state.scale)
// 获取企业规模选项,优先使用字典数据,失败时使用备用数据
let options = scaleOptions.value
if (!options || !options.length) {
console.log('企业规模数据为空,尝试重新加载')
// 尝试重新加载字典数据
dictStore.getDictData().then(() => {
if (dictStore.state.scale && dictStore.state.scale.length > 0) {
selectScale() // 递归调用
} else {
// 使用备用数据
console.log('使用备用企业规模数据')
options = fallbackScaleOptions
showScaleSelector(options)
}
}).catch(() => {
// 使用备用数据
console.log('字典加载失败,使用备用企业规模数据')
options = fallbackScaleOptions
showScaleSelector(options)
})
return
}
showScaleSelector(options)
}
// 备用企业规模选项(当字典数据加载失败时使用)
const fallbackScaleOptions = [
{ label: '1-10人', value: '1' },
{ label: '11-50人', value: '2' },
{ label: '51-100人', value: '3' },
{ label: '101-500人', value: '4' },
{ label: '501-1000人', value: '5' },
{ label: '1000人以上', value: '6' }
]
// 显示企业规模选择器
const showScaleSelector = (options) => {
console.log('企业规模选项列表:', options)
openSelectPopup({
title: '企业规模',
maskClick: true,
data: [options],
success: (_, [value]) => {
console.log('选择的企业规模:', value)
formData.scale = value.value
formData.scaleText = value.label
updateCompletion()
$api.msg('企业规模选择成功')
}
})
}
// 添加联系人
const addContact = () => {
@@ -573,6 +654,11 @@ const confirm = () => {
return
}
if (!formData.scale.trim()) {
$api.msg('请选择企业规模')
return
}
// 验证身份证号格式
const idCardRegex = /^[1-9]\d{5}(18|19|20)\d{2}((0[1-9])|(1[0-2]))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/
if (!idCardRegex.test(formData.legalIdCard)) {
@@ -623,6 +709,7 @@ const confirm = () => {
legalPhone: formData.legalPhone,
industryType: formData.industryType,
isLocalCompany: formData.isLocalCompany,
scale: formData.scaleText,
companyContactList: formData.companyContactList.filter(contact => contact.contactPerson.trim() && contact.contactPersonPhone.trim())
}
@@ -631,11 +718,30 @@ const confirm = () => {
company: companyData
}
$api.createRequest('/app/user/registerUser', submitData, 'post')
.then((resData) => {
$api.createRequest('/registerUser', submitData, 'post')
.then(async (resData) => {
uni.hideLoading()
$api.msg('企业信息保存成功')
// 如果接口返回了token需要重新保存token
if (resData.token) {
try {
await userStore.loginSetToken(resData.token)
console.log('Token已更新:', resData.token)
} catch (error) {
console.error('更新Token失败:', error)
}
}
// 保存成功后,重新获取用户信息并更新缓存
try {
await userStore.getUserResume()
console.log('用户信息已更新到缓存')
} catch (error) {
console.error('获取用户信息失败:', error)
// 即使获取用户信息失败,也不影响页面跳转
}
// 跳转到首页或企业相关页面
uni.reLaunch({
url: '/pages/index/index'
@@ -720,10 +826,12 @@ defineExpose({
font-size: 28rpx
color: #333
text-align: right
line-height: 1.4
&::placeholder
color: #999
font-size: 26rpx
font-size: 28rpx
line-height: 1.4
.input-con
flex: 1
@@ -733,10 +841,12 @@ defineExpose({
background: transparent
border: none
outline: none
line-height: 1.4
&::placeholder
color: #999
font-size: 26rpx
font-size: 28rpx
line-height: 1.4
.input-content
flex: 1
@@ -752,7 +862,8 @@ defineExpose({
&.placeholder
color: #999
font-size: 26rpx
font-size: 28rpx
line-height: 1.4
&.intro-text
max-height: 80rpx

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,153 @@ 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,16 +578,65 @@ 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('/registerUser', requestData, 'post').then(async (resData) => {
$api.msg('完成');
// 获取用户信息并存储到store中
getUserResume().then((userInfo) => {
console.log('用户信息已存储到store:', userInfo);
uni.reLaunch({
url: '/pages/index/index',
});
// 如果接口返回了token需要重新保存token
if (resData.token) {
try {
await loginSetToken(resData.token);
console.log('Token已更新:', resData.token);
} catch (error) {
console.error('更新Token失败:', error);
}
}
// 保存成功后,重新获取用户信息并更新缓存
try {
await getUserResume();
console.log('用户信息已更新到缓存');
} catch (error) {
console.error('获取用户信息失败:', error);
// 即使获取用户信息失败,也不影响页面跳转
}
// 跳转到首页
uni.reLaunch({
url: '/pages/index/index',
});
});
} else {
@@ -403,32 +671,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

@@ -5,18 +5,53 @@
<image class="mp-background" src="/static/icon/background2.png" mode="aspectFill"></image>
<!-- #endif -->
<!-- 企业账号显示卡片 -->
<view class="enterprise-card btn-feel" v-if="shouldShowCompanyCard" @click="goToCompanyInfo">
<view class="card-content">
<!-- 企业图标 -->
<view class="company-icon">
<image
v-if="companyInfo.avatar"
:src="companyInfo.avatar"
class="logo-image"
mode="aspectFit"
/>
<view v-else class="default-logo">
<uni-icons type="home-filled" size="32" color="#256BFA"></uni-icons>
</view>
</view>
<!-- 企业信息 -->
<view class="company-info">
<view class="company-name">{{ companyInfo.name || '企业名称' }}</view>
<view class="company-details">
<text class="industry">{{ companyInfo.industry || '互联网' }}</text>
<text class="separator">·</text>
<text class="size">{{ companyInfo.scale || '100-999人' }}</text>
</view>
</view>
</view>
</view>
<view
class="nav-hidden hidden-animation"
:class="{ 'hidden-height': shouldHideTop }"
>
<view class="container-search">
<view class="container-search" v-if="shouldShowJobSeekerContent">
<view class="search-input button-click" @click="navTo('/pages/search/search')">
<uni-icons class="iconsearch" color="#666666" type="search" size="18"></uni-icons>
<text class="inpute">职位名称薪资要求等</text>
</view>
<!-- 直播入口按钮 -->
<view class="live-button press-button" @click="handleLiveClick">
<view class="live-icon">
<uni-icons type="videocam-filled" size="16" color="#FFFFFF"></uni-icons>
</view>
<view class="live-text">直播</view>
</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 +62,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,6 +118,66 @@
</view>
<view class="service-title">AI智能面试</view>
</view>
<view class="service-item press-button" @click="handleServiceClick('ai-interview')">
<view class="service-icon service-icon-9">
<IconfontIcon name="Graduation-simple-" :size="32" color="#FFFFFF" />
</view>
<view class="service-title" style="overflow:none">高校毕业生<br/>智慧就业服务</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 class="service-item press-button" @click="handleServiceClick('public-recruitment')">
<view class="service-icon service-icon-2">
<IconfontIcon name="zhengfulou" :size="48" color="#FFFFFF" />
</view>
<view class="service-title">事业单位招录</view>
</view>
<view class="service-item press-button" @click="handleServiceClick('resume-creation')">
<view class="service-icon service-icon-3">
<IconfontIcon name="jianli" :size="48" color="#FFFFFF" />
</view>
<view class="service-title">简历制作</view>
</view>
<view class="service-item press-button" @click="handleServiceClick('labor-policy')">
<view class="service-icon service-icon-4">
<IconfontIcon name="zhengce" :size="48" color="#FFFFFF" />
</view>
<view class="service-title">劳动政策指引</view>
</view>
<view class="service-item press-button" @click="handleServiceClick('skill-training')">
<view class="service-icon service-icon-5">
<IconfontIcon name="jinengpeixun" :size="48" color="#FFFFFF" />
</view>
<view class="service-title">技能培训信息</view>
</view>
<view class="service-item press-button" @click="handleServiceClick('skill-evaluation')">
<view class="service-icon service-icon-6">
<IconfontIcon name="jinengpingjia" :size="48" color="#FFFFFF" />
</view>
<view class="service-title">技能评价指引</view>
</view>
<view class="service-item press-button" @click="handleServiceClick('question-bank')">
<view class="service-icon service-icon-7">
<IconfontIcon name="suzhicepingtiku" :size="48" color="#FFFFFF" />
</view>
<view class="service-title">题库和考试</view>
</view>
<view class="service-item press-button" @click="handleServiceClick('quality-assessment')">
<view class="service-icon service-icon-8">
<IconfontIcon name="suzhicepingtiku" :size="48" color="#FFFFFF" />
</view>
<view class="service-title">素质测评</view>
</view>
<view class="service-item press-button" @click="handleServiceClick('ai-interview')">
<view class="service-icon service-icon-9">
<IconfontIcon name="ai" :size="68" color="#FFFFFF" />
</view>
<view class="service-title">AI智能面试</view>
</view>
<view class="service-item press-button" @click="navTo('/packageB/pages/search/search')">
<view class="service-icon service-icon-9">
<IconfontIcon name="ai" :size="68" color="#FFFFFF" />
@@ -92,11 +186,47 @@
</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">
@@ -328,7 +458,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');
@@ -336,6 +466,81 @@ 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;
}
// 优先从store获取如果为空则从缓存获取
const storeIsCompanyUser = userInfo.value?.isCompanyUser;
const cachedUserInfo = uni.getStorageSync('userInfo') || {};
const cachedIsCompanyUser = cachedUserInfo.isCompanyUser;
// 获取用户类型优先使用store中的isCompanyUser如果store中没有使用缓存中的isCompanyUser
// 缓存中的值可能是字符串,需要转换为数值类型
const userType = storeIsCompanyUser !== undefined ? Number(storeIsCompanyUser) : Number(cachedIsCompanyUser);
// 企业用户(isCompanyUser=0)不显示求职者内容,其他用户类型显示
return userType !== 0;
});
// 企业信息数据
const companyInfo = reactive({
name: '',
avatar: '',
industry: '',
scale: '',
isVerified: false
});
// 计算是否显示企业卡片
const shouldShowCompanyCard = computed(() => {
// 未登录时不显示
if (!hasLogin.value) {
return false;
}
// 优先从store获取如果为空则从缓存获取
const storeIsCompanyUser = userInfo.value?.isCompanyUser;
const cachedUserInfo = uni.getStorageSync('userInfo') || {};
const cachedIsCompanyUser = cachedUserInfo.isCompanyUser;
// 获取用户类型优先使用store中的isCompanyUser如果store中没有使用缓存中的isCompanyUser
// 缓存中的值可能是字符串,需要转换为数值类型
const userType = storeIsCompanyUser !== undefined ? Number(storeIsCompanyUser) : Number(cachedIsCompanyUser);
// 只有企业用户(isCompanyUser=0)才显示企业卡片
if (userType !== 0) {
return false;
}
// 检查企业信息是否已完善
return companyInfo.name && companyInfo.name.trim() !== '';
});
// 计算是否显示企业用户内容
const shouldShowCompanyContent = computed(() => {
// 未登录时不显示企业内容
if (!hasLogin.value) {
return false;
}
// 优先从store获取如果为空则从缓存获取
const storeIsCompanyUser = userInfo.value?.isCompanyUser;
const cachedUserInfo = uni.getStorageSync('userInfo') || {};
const cachedIsCompanyUser = cachedUserInfo.isCompanyUser;
// 获取用户类型优先使用store中的isCompanyUser如果store中没有使用缓存中的isCompanyUser
// 缓存中的值可能是字符串,需要转换为数值类型
const userType = storeIsCompanyUser !== undefined ? Number(storeIsCompanyUser) : Number(cachedIsCompanyUser);
// 只有企业用户(isCompanyUser=0)才显示企业内容
return userType === 0;
});
import useDictStore from '@/stores/useDictStore';
const { getTransformChildren, oneDictData } = useDictStore();
import useLocationStore from '@/stores/useLocationStore';
@@ -345,6 +550,7 @@ import { useScrollDirection } from '@/hook/useScrollDirection';
import { useColumnCount } from '@/hook/useColumnCount';
import WxAuthLogin from '@/components/WxAuthLogin/WxAuthLogin.vue';
import IconfontIcon from '@/components/IconfontIcon/IconfontIcon.vue'
// 企业卡片组件已内联到模板中
// 滚动状态管理
const shouldHideTop = ref(false);
const shouldStickyFilter = ref(false);
@@ -352,7 +558,6 @@ const lastScrollTop = ref(0);
const scrollTop = ref(0);
// 当用户与筛选/导航交互时,临时锁定头部显示状态,避免因数据刷新导致回弹显示
const isInteractingWithFilter = ref(false);
// 滚动阈值配置
const HIDE_THRESHOLD = 50; // 隐藏顶部区域的滚动阈值(降低阈值,更容易触发)
const SHOW_THRESHOLD = 5; // 显示顶部区域的滚动阈值(接近顶部)
@@ -421,21 +626,101 @@ const rangeOptions = ref([
{ value: 3, text: '疆外' },
]);
const isLoaded = ref(false);
const isInitialized = ref(false); // 添加初始化标志
const { columnCount, columnSpace } = useColumnCount(() => {
pageState.pageSize = 10 * (columnCount.value - 1);
getJobRecommend('refresh');
// 只在首次初始化时调用,避免重复调用
if (!isInitialized.value) {
isInitialized.value = true;
getJobRecommend('refresh');
}
nextTick(() => {
waterfallsFlowRef.value?.refresh?.();
useLocationStore().getLocation();
});
});
// 获取企业信息
const getCompanyInfo = () => {
try {
const cachedUserInfo = uni.getStorageSync('userInfo') || {};
console.log('缓存中的userInfo:', cachedUserInfo);
// 重置企业信息
companyInfo.name = '';
companyInfo.avatar = '';
companyInfo.industry = '';
companyInfo.scale = '';
companyInfo.isVerified = false;
// 检查是否有company字段
if (cachedUserInfo.company) {
const company = cachedUserInfo.company;
// 基本信息
companyInfo.name = company.name || '';
companyInfo.avatar = company.avatar || '';
companyInfo.industry = company.industryType || '互联网';
companyInfo.scale = company.scale || '100-999人';
// 判断是否实名legalIdCard字段有值则表示已实名
companyInfo.isVerified = !!(company.legalIdCard && company.legalIdCard.trim());
console.log('企业信息已更新:', {
name: companyInfo.name,
industry: companyInfo.industry,
size: companyInfo.scale,
isVerified: companyInfo.isVerified
});
} else {
console.log('缓存中没有company字段企业信息已重置');
}
} catch (error) {
console.error('获取企业信息失败:', error);
// 重置为默认值
companyInfo.name = '';
companyInfo.avatar = '';
companyInfo.industry = '';
companyInfo.scale = '';
companyInfo.isVerified = false;
}
};
// 跳转到企业信息详情页面
const goToCompanyInfo = () => {
navTo('/pages/mine/company-info');
};
// 组件初始化时加载数据
onMounted(() => {
getJobRecommend('refresh');
// 获取企业信息
getCompanyInfo();
// 监听退出登录事件,显示微信登录弹窗
uni.$on('showLoginModal', () => {
wxAuthLoginRef.value?.open();
});
});
onUnmounted(() => {
uni.$off('showLoginModal');
});
onShow(() => {
// 获取最新的企业信息
getCompanyInfo();
});
// 监听用户信息变化,当登录状态改变时重新获取企业信息
watch([hasLogin, userInfo], () => {
if (hasLogin.value) {
getCompanyInfo();
}
}, { deep: true });
// 登录检查函数
const checkLogin = () => {
const tokenValue = uni.getStorageSync('token') || '';
@@ -451,6 +736,9 @@ const checkLogin = () => {
const handleLoginSuccess = () => {
// 登录成功后刷新数据
getJobRecommend('refresh');
// 重新获取企业信息
getCompanyInfo();
};
// 处理附近工作点击
@@ -467,6 +755,16 @@ const handleServiceClick = (serviceType) => {
}
};
// 处理直播按钮点击
const handleLiveClick = () => {
$api.msg('该功能正在开发中');
};
// 跳转到测试页面
const navToTestPage = () => {
navTo('/pages/test/homepage-test');
};
async function loadData() {
try {
if (isLoaded.value) return;
@@ -478,7 +776,9 @@ async function loadData() {
}
function scrollBottom() {
loadmoreRef.value.change('loading');
if (loadmoreRef.value && typeof loadmoreRef.value.change === 'function') {
loadmoreRef.value.change('loading');
}
if (state.tabIndex === 'all') {
getJobRecommend();
} else {
@@ -556,7 +856,7 @@ function navToService(serviceType) {
'resume-creation': '/packageA/pages/myResume/myResume',
'labor-policy': '/pages/service/labor-policy',
'skill-training': '/pages/service/skill-training',
'skill-evaluation': '/pages/service/skill-evaluation',
// 'skill-evaluation': '/pages/service/skill-evaluation',
'question-bank': '/pages/service/question-bank',
'quality-assessment': '/pages/service/quality-assessment',
'ai-interview': '/pages/chat/chat',
@@ -566,7 +866,8 @@ function navToService(serviceType) {
'company-info': '/pages/service/company-info',
'interview-tips': '/pages/service/interview-tips',
'employment-news': '/pages/service/employment-news',
'more-services': '/pages/service/more-services'
'more-services': '/pages/service/more-services',
'skill-evaluation': '/packageB/train/index'
};
const route = serviceRoutes[serviceType];
@@ -655,6 +956,7 @@ function getJobRecommend(type = 'add') {
sessionId: useUserStore().seesionId,
...pageState.search,
...conditionSearch.value,
isPublish: 1,
};
let comd = { recommend: true, jobCategory: '', tip: '确认你的兴趣,为您推荐更多合适的岗位' };
$api.createRequest('/app/job/recommend', params).then((resData) => {
@@ -882,13 +1184,14 @@ defineExpose({ loadData });
padding: 16rpx 24rpx
display: flex
justify-content: space-between
align-items: center
.search-input
display: flex
align-items: center;
width: 100%
flex: 1
height: 80rpx;
line-height: 80rpx
margin-right: 24rpx
margin-right: 16rpx
background: #FFFFFF;
border-radius: 75rpx 75rpx 75rpx 75rpx;
.iconsearch
@@ -899,6 +1202,35 @@ defineExpose({ loadData });
font-size: 28rpx;
color: #B5B5B5;
width: 100%
.live-button
display: flex
align-items: center
justify-content: center
width: 90rpx
height: 42rpx
background: linear-gradient(135deg, #FF6B6B 0%, #FF8E8E 100%)
border-radius: 40rpx
box-shadow: 0 3rpx 8rpx rgba(255, 107, 107, 0.2)
transition: all 0.2s ease
flex-shrink: 0
&:active
transform: scale(0.96)
box-shadow: 0 2rpx 6rpx rgba(255, 107, 107, 0.25)
.live-icon
margin-right: 8rpx
display: flex
align-items: center
justify-content: center
.live-text
font-family: 'PingFangSC-Medium', 'PingFang SC', 'Helvetica Neue', Helvetica, Arial, 'Microsoft YaHei', sans-serif
font-weight: 500
font-size: 24rpx
color: #FFFFFF
text-align: center
white-space: nowrap
.chart
font-family: 'PingFangSC-Medium', 'PingFang SC', 'Helvetica Neue', Helvetica, Arial, 'Microsoft YaHei', sans-serif;
width: 170rpx;
@@ -1021,13 +1353,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
@@ -1100,6 +1428,8 @@ defineExpose({ loadData });
text-overflow: ellipsis
width: 100%
max-width: 100%
.service-title-special
overflow: none;
// 吸顶筛选区域占位
.filter-placeholder
@@ -1346,6 +1676,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%);
@@ -1424,4 +1819,133 @@ defineExpose({ loadData });
.isBut{
filter: grayscale(100%);
}
// 企业账号显示卡片样式
.enterprise-card
margin: 20rpx 28rpx
padding: 28rpx
background: rgba(255, 255, 255, 0.9)
backdrop-filter: blur(20rpx)
-webkit-backdrop-filter: blur(20rpx)
border-radius: 24rpx
border: 1rpx solid rgba(255, 255, 255, 0.4)
box-shadow: 0 8rpx 40rpx rgba(0, 0, 0, 0.08), 0 2rpx 8rpx rgba(0, 0, 0, 0.04)
position: relative
overflow: hidden
cursor: pointer
transition: all 0.3s ease
&:active
transform: scale(0.98)
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.12), 0 1rpx 4rpx rgba(0, 0, 0, 0.08)
// 玻璃态效果 - 更强烈的渐变
&::before
content: ''
position: absolute
top: 0
left: 0
right: 0
bottom: 0
background: linear-gradient(135deg,
rgba(255, 255, 255, 0.2) 0%,
rgba(255, 255, 255, 0.1) 50%,
rgba(255, 255, 255, 0.05) 100%)
border-radius: 24rpx
pointer-events: none
z-index: 0
// 添加微妙的边框高光
&::after
content: ''
position: absolute
top: 0
left: 0
right: 0
bottom: 0
border-radius: 24rpx
padding: 1rpx
background: linear-gradient(135deg,
rgba(255, 255, 255, 0.6) 0%,
rgba(255, 255, 255, 0.2) 100%)
mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0)
-webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0)
mask-composite: xor
-webkit-mask-composite: xor
pointer-events: none
z-index: 1
.card-content
display: flex
align-items: center
position: relative
z-index: 2
.company-icon
width: 88rpx
height: 88rpx
border-radius: 18rpx
background: linear-gradient(135deg, #256BFA 0%, #4A90E2 100%)
display: flex
align-items: center
justify-content: center
margin-right: 28rpx
flex-shrink: 0
box-shadow: 0 6rpx 16rpx rgba(37, 107, 250, 0.25)
position: relative
// 添加图标内部高光
&::before
content: ''
position: absolute
top: 4rpx
left: 4rpx
right: 4rpx
height: 20rpx
background: linear-gradient(180deg, rgba(255, 255, 255, 0.3) 0%, transparent 100%)
border-radius: 18rpx 18rpx 0 0
pointer-events: none
.default-logo
display: flex
align-items: center
justify-content: center
color: #ffffff
.company-info
flex: 1
min-width: 0
z-index: 2
position: relative
.company-name
font-size: 34rpx
font-weight: 600
color: #000000
line-height: 1.3
margin-bottom: 10rpx
overflow: hidden
text-overflow: ellipsis
white-space: nowrap
font-family: 'PingFangSC-Medium', 'PingFang SC', 'Helvetica Neue', Helvetica, Arial, 'Microsoft YaHei', sans-serif
.company-details
display: flex
align-items: center
font-size: 26rpx
color: #666666
line-height: 1.2
.industry
color: #666666
font-weight: 400
.separator
margin: 0 10rpx
color: #CCCCCC
font-weight: 300
.size
color: #666666
font-weight: 400
</style>

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>

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

@@ -0,0 +1,474 @@
<template>
<view class="company-search-page">
<!-- 头部导航 -->
<view class="header">
<view class="header-left" @click="goBack">
<view class="back-arrow"><uni-icons type="search" size="32" color="#FFFFFF"></uni-icons></view>
</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"
/>
<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="search-tip" v-if="!searchKeyword">
<text class="tip-text">请输入企业名称进行搜索</text>
</view>
<!-- 加载中 -->
<view class="loading-container" v-if="loading">
<view class="loading-text">搜索中...</view>
</view>
<!-- 搜索结果列表 -->
<view class="result-list" v-if="searchResults.length > 0">
<view
class="result-item"
v-for="(company, index) in searchResults"
:key="company.companyId"
@click="selectCompany(company)"
>
<view class="company-info">
<view class="company-name">{{ company.name }}</view>
<view class="company-location" v-if="company.location">
<text class="location-icon">📍</text>
<text class="location-text">{{ company.location }}</text>
</view>
<view class="company-scale" v-if="company.scale">
<text class="scale-label">规模</text>
<text class="scale-text">{{ getScaleText(company.scale) }}</text>
</view>
<view class="company-nature" v-if="company.nature">
<text class="nature-label">性质</text>
<text class="nature-text">{{ getNatureText(company.nature) }}</text>
</view>
<view class="company-description" v-if="company.description">
<text class="desc-text">{{ company.description }}</text>
</view>
</view>
<view class="select-icon">
<view class="arrow-icon"></view>
</view>
</view>
</view>
<!-- 无结果提示 -->
<view class="no-result" v-if="searchKeyword && !loading && searchResults.length === 0">
<view class="empty-icon">📭</view>
<view class="no-result-text">未找到相关企业</view>
<view class="no-result-tip">请尝试其他关键词</view>
</view>
<!-- 底部安全区域 -->
<view class="bottom-safe-area"></view>
</scroll-view>
</view>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import { createRequest } from '@/utils/request';
// 搜索关键词
const searchKeyword = ref('');
// 搜索结果
const searchResults = ref([]);
// 加载状态
const loading = ref(false);
// 防抖定时器
let debounceTimer = null;
// 滚动视图高度
const scrollViewHeight = ref('calc(100vh - 200rpx)');
// 计算滚动视图高度
const calculateScrollViewHeight = () => {
const systemInfo = uni.getSystemInfoSync();
const windowHeight = systemInfo.windowHeight;
const headerHeight = 100; // 头部高度
const searchHeight = 120; // 搜索框高度
const scrollHeight = windowHeight - headerHeight - searchHeight;
scrollViewHeight.value = `${scrollHeight}px`;
};
// 页面加载时计算高度
onMounted(() => {
calculateScrollViewHeight();
});
// 搜索输入处理(防抖)
const onSearchInput = () => {
// 清除之前的定时器
if (debounceTimer) {
clearTimeout(debounceTimer);
}
// 设置新的定时器
debounceTimer = setTimeout(() => {
if (searchKeyword.value.trim()) {
searchCompanies();
} else {
searchResults.value = [];
}
}, 500); // 500ms 防抖延迟
};
// 搜索企业
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.rows || [];
} 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 clearSearch = () => {
searchKeyword.value = '';
searchResults.value = [];
if (debounceTimer) {
clearTimeout(debounceTimer);
}
};
// 获取企业规模文本
const getScaleText = (scale) => {
const scaleMap = {
'1': '1-20人',
'2': '21-50人',
'3': '51-100人',
'4': '101-200人',
'5': '201-500人',
'6': '500人以上'
};
return scaleMap[scale] || '未知';
};
// 获取企业性质文本
const getNatureText = (nature) => {
const natureMap = {
'1': '有限责任公司',
'2': '股份有限公司',
'3': '个人独资企业',
'4': '合伙企业',
'5': '外商投资企业',
'6': '其他'
};
return natureMap[nature] || '未知';
};
// 选择企业
const selectCompany = (company) => {
// 返回上一页并传递选中的企业信息
uni.navigateBack({
delta: 1,
success: () => {
// 通过事件总线或全局状态传递数据
uni.$emit('companySelected', {
id: company.companyId,
name: company.name,
address: company.location
});
}
});
};
// 返回上一页
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-arrow {
font-size: 40rpx;
color: #333;
font-weight: bold;
}
}
.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: #f5f5f5;
border-radius: 12rpx;
padding: 0 20rpx;
height: 80rpx;
.search-icon {
font-size: 32rpx;
margin-right: 20rpx;
color: #999;
}
.search-input {
flex: 1;
height: 100%;
background: transparent;
border: none;
font-size: 28rpx;
color: #333;
line-height: 1.4;
display: flex;
align-items: center;
&::placeholder {
color: #999;
font-size: 28rpx;
line-height: 1.4;
}
}
.clear-btn {
width: 32rpx;
height: 32rpx;
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;
}
.search-tip {
display: flex;
justify-content: center;
align-items: center;
height: 400rpx;
.tip-text {
font-size: 28rpx;
color: #999;
}
}
.loading-container {
display: flex;
justify-content: center;
align-items: center;
height: 200rpx;
.loading-text {
font-size: 28rpx;
color: #666;
}
}
.result-list {
background: #fff;
.result-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 30rpx;
border-bottom: 1rpx solid #f0f0f0;
&:last-child {
border-bottom: none;
}
&:active {
background: #f8f8f8;
}
.company-info {
flex: 1;
.company-name {
font-size: 30rpx;
color: #333;
font-weight: 500;
margin-bottom: 15rpx;
line-height: 1.4;
}
.company-location {
display: flex;
align-items: center;
margin-bottom: 10rpx;
.location-icon {
font-size: 20rpx;
margin-right: 8rpx;
}
.location-text {
font-size: 24rpx;
color: #666;
flex: 1;
}
}
.company-scale, .company-nature {
display: flex;
align-items: center;
margin-bottom: 8rpx;
.scale-label, .nature-label {
font-size: 22rpx;
color: #999;
margin-right: 8rpx;
}
.scale-text, .nature-text {
font-size: 22rpx;
color: #666;
}
}
.company-description {
margin-top: 10rpx;
.desc-text {
font-size: 22rpx;
color: #888;
line-height: 1.4;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
line-clamp: 2;
overflow: hidden;
text-overflow: ellipsis;
}
}
}
.select-icon {
width: 40rpx;
height: 40rpx;
display: flex;
align-items: center;
justify-content: center;
.arrow-icon {
font-size: 24rpx;
color: #999;
}
}
}
}
.no-result {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 400rpx;
.empty-icon {
font-size: 120rpx;
margin-bottom: 30rpx;
}
.no-result-text {
font-size: 28rpx;
color: #666;
margin-bottom: 10rpx;
}
.no-result-tip {
font-size: 24rpx;
color: #999;
}
}
.bottom-safe-area {
height: 120rpx;
background: transparent;
}
</style>

File diff suppressed because it is too large Load Diff

View File

@@ -1,75 +0,0 @@
<template>
<view class="tab-container">
<view class="uni-margin-wrap">
<swiper
class="swiper"
:current="current"
:circular="false"
:indicator-dots="false"
:autoplay="false"
:duration="500"
>
<swiper-item @touchmove.stop="false">
<slot name="tab0"></slot>
</swiper-item>
<swiper-item @touchmove.stop="false">
<slot name="tab1"></slot>
</swiper-item>
<swiper-item @touchmove.stop="false">
<slot name="tab2"></slot>
</swiper-item>
<swiper-item @touchmove.stop="false">
<slot name="tab3"></slot>
</swiper-item>
<swiper-item @touchmove.stop="false">
<slot name="tab3"></slot>
</swiper-item>
<swiper-item @touchmove.stop="false">
<slot name="tab4"></slot>
</swiper-item>
<swiper-item @touchmove.stop="false">
<slot name="tab5"></slot>
</swiper-item>
<swiper-item @touchmove.stop="false">
<slot name="tab6"></slot>
</swiper-item>
</swiper>
</view>
</view>
</template>
<script>
export default {
name: 'tab',
data() {
return {};
},
props: {
current: {
type: Number,
default: 0,
},
},
};
</script>
<style lang="stylus" scoped>
.tab-container
// flex: 1
height: 100%
width: 100%
display: flex
align-items: center
justify-content: center
flex-direction: row
.uni-margin-wrap
width: 100%
height: 100%
.swiper
width: 100%
height: 100%
.swiper-item
display: block;
width: 100%
height: 100%
</style>

View File

@@ -1,490 +0,0 @@
<template>
<AppLayout title="AI+就业服务程序">
<tabcontrolVue :current="tabCurrent">
<template v-slot:tab0>
<view class="login-content">
<image class="logo" src="@/static/logo.png"></image>
<view class="logo-title">就业</view>
</view>
<view class="btns">
<button class="wxlogin" @click="loginTest">内测登录</button>
<view class="wxaddress">{{ config.appInfo.areaName }}公共就业和人才服务中心</view>
</view>
</template>
<template v-slot:tab1>
<view class="content-one">
<view>
<view class="content-title">
<view class="title-lf">
<view class="lf-h1">请您完善求职名片</view>
<view class="lf-text">个人信息仅用于推送优质内容</view>
</view>
<view class="title-ri">
<text style="color: #256bfa">1</text>
<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>
<view class="content-sex">
<view class="sex-titile">求职区域</view>
<view class="sext-ri">
<view
class="sext-box"
:class="{ 'sext-boxactive': fromValue.sex === 0 }"
@click="changeSex(0)"
>
</view>
<view
class="sext-box"
:class="{ 'sext-boxactive': fromValue.sex === 1 }"
@click="changeSex(1)"
>
</view>
</view>
</view>
<view class="content-input" @click="changeEducation">
<view class="input-titile">学历</view>
<input class="input-con" v-model="state.educationText" disabled placeholder="本科" />
</view>
<view class="content-input">
<view class="input-titile">身份证</view>
<input class="input-con2" v-model="fromValue.idcard" maxlength="18" placeholder="本科" />
</view>
</view>
<view class="next-btn" @tap="nextStep">下一步</view>
</view>
</template>
<template v-slot:tab2>
<view class="content-one">
<view>
<view class="content-title">
<view class="title-lf">
<view class="lf-h1">请您完善求职名片</view>
<view class="lf-text">个人信息仅用于推送优质内容</view>
</view>
<view class="title-ri">
<text style="color: #256bfa">2</text>
<text>/2</text>
</view>
</view>
<view class="content-input" @click="changeArea">
<view class="input-titile">求职区域</view>
<input
class="input-con"
v-model="state.areaText"
disabled
placeholder="请选择您的求职区域"
/>
</view>
<view class="content-input" @click="changeJobs">
<view class="input-titile">求职岗位</view>
<input
class="input-con"
disabled
v-if="!state.jobsText.length"
placeholder="请选择您的求职岗位"
/>
<view class="input-nx" @click="changeJobs" v-else>
<view class="nx-item" v-for="item in state.jobsText">{{ item }}</view>
</view>
</view>
<view class="content-input" @click="changeSalay">
<view class="input-titile">期望薪资</view>
<input
class="input-con"
v-model="state.salayText"
disabled
placeholder="请选择您的期望薪资"
/>
</view>
</view>
<view class="next-btn" @tap="complete">开启求职之旅</view>
</view>
</template>
</tabcontrolVue>
<SelectJobs ref="selectJobsModel"></SelectJobs>
</AppLayout>
</template>
<script setup>
import tabcontrolVue from './components/tabcontrol.vue';
import SelectJobs from '@/components/selectJobs/selectJobs.vue';
import { reactive, inject, watch, ref, onMounted } from 'vue';
import { onLoad, onShow } from '@dcloudio/uni-app';
import useUserStore from '@/stores/useUserStore';
import useDictStore from '@/stores/useDictStore';
const { $api, navTo, config, IdCardValidator } = inject('globalFunction');
const { loginSetToken, getUserResume } = useUserStore();
const { getDictSelectOption, oneDictData } = useDictStore();
const openSelectPopup = inject('openSelectPopup');
// console.log(config.appInfo.areaName);
// status
const selectJobsModel = ref();
const tabCurrent = ref(0);
const salay = [2, 5, 10, 15, 20, 25, 30, 50, 80, 100];
const state = reactive({
station: [],
stationCateLog: 1,
lfsalay: [2, 5, 10, 15, 20, 25, 30, 50],
risalay: JSON.parse(JSON.stringify(salay)),
areaText: '',
educationText: '',
experienceText: '',
salayText: '',
jobsText: [],
});
const fromValue = reactive({
sex: 1,
education: '4',
salaryMin: 2000,
salaryMax: 2000,
area: 0,
jobTitleId: '',
experience: '1',
idcard: '',
});
onLoad((parmas) => {
getTreeselect();
});
onMounted(() => {});
function changeSex(sex) {
fromValue.sex = sex;
}
function changeExperience() {
openSelectPopup({
title: '工作经验',
maskClick: true,
data: [oneDictData('experience')],
success: (_, [value]) => {
fromValue.experience = value.value;
state.experienceText = value.label;
},
change(_, [value]) {
// this.setColunm(1, [123, 123]);
console.log(this);
},
});
}
function changeEducation() {
openSelectPopup({
title: '学历',
maskClick: true,
data: [oneDictData('education')],
success: (_, [value]) => {
fromValue.area = value.value;
state.educationText = value.label;
},
});
}
function changeArea() {
openSelectPopup({
title: '区域',
maskClick: true,
data: [oneDictData('area')],
success: (_, [value]) => {
fromValue.area = value.value;
state.areaText = config.appInfo.areaName + '-' + value.label;
},
});
}
function changeSalay() {
let leftIndex = 0;
openSelectPopup({
title: '薪资',
maskClick: true,
data: [state.lfsalay, state.risalay],
unit: 'k',
success: (_, [min, max]) => {
fromValue.salaryMin = min.value * 1000;
fromValue.salaryMax = max.value * 1000;
state.salayText = `${fromValue.salaryMin}-${fromValue.salaryMax}`;
},
change(e) {
const salayData = e.detail.value;
if (leftIndex !== salayData[0]) {
const copyri = JSON.parse(JSON.stringify(salay));
const [lf, ri] = e.detail.value;
const risalay = copyri.slice(lf, copyri.length);
this.setColunm(1, risalay);
leftIndex = salayData[0];
}
},
});
}
function changeJobs() {
selectJobsModel.value?.open({
title: '添加岗位',
success: (ids, labels) => {
fromValue.jobTitleId = ids;
state.jobsText = labels.split(',');
},
});
}
function nextStep() {
tabCurrent.value += 1;
}
// 获取职位
function getTreeselect() {
$api.createRequest('/app/common/jobTitle/treeselect', {}, 'GET').then((resData) => {
state.station = resData.data;
});
}
// 登录
function loginTest() {
// uni.share({
// provider: 'weixin',
// scene: 'WXSceneSession',
// type: 2,
// imageUrl: 'https://qiniu-web-assets.dcloud.net.cn/unidoc/zh/uni@2x.png',
// success: function (res) {
// console.log('success:' + JSON.stringify(res));
// },
// fail: function (err) {
// console.log('fail:' + JSON.stringify(err));
// },
// });
const params = {
username: 'test',
password: 'test',
};
$api.createRequest('/app/login', params, 'post').then((resData) => {
$api.msg('模拟帐号密码测试登录成功');
loginSetToken(resData.token).then((resume) => {
if (resume.data.jobTitleId) {
// 设置推荐列表,每次退出登录都需要更新
useUserStore().initSeesionId();
uni.reLaunch({
url: '/packageRc/pages/daiban/daiban',
});
} else {
nextStep();
}
});
});
}
function complete() {
const result = IdCardValidator.validate(fromValue.idcard);
if (result.valid) {
$api.createRequest('/app/user/resume', fromValue, 'post').then((resData) => {
$api.msg('完成');
// 获取用户信息并存储到store中
getUserResume().then((userInfo) => {
console.log('用户信息已存储到store:', userInfo);
uni.reLaunch({
url: '/packageRc/pages/daiban/daiban',
});
});
});
} else {
$api.msg('身份证校验失败');
console.log('验证失败:', result.message);
}
}
</script>
<style lang="stylus" scoped>
.input-nx
position: relative
border-bottom: 2rpx solid #EBEBEB
padding-bottom: 30rpx
display: flex
flex-wrap: wrap
.nx-item
padding: 20rpx 28rpx
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)
.container
// background: linear-gradient(#4778EC, #002979);
width: 100%;
height: calc(100vh - var(--window-top) - var(--status-bar-height) - var(--window-bottom));
position: fixed;
background: url('@/static/icon/background2.png') 0 0 no-repeat;
background-size: 100% 728rpx;
display: flex;
flex-direction: column
.container-hader
height: 88rpx;
text-align: center;
line-height: 88rpx;
color: #000000;
font-weight: bold
font-size: 32rpx
.login-content
position: absolute;
left: 50%;
top: 40%;
transform: translate(-50%, -50%);
display: flex;
align-items: flex-end;
flex-wrap: nowrap;
.logo
width: 266rpx;
height: 182rpx;
.logo-title
font-size: 88rpx;
color: #22c984;
width: 180rpx;
.btns
position: absolute;
top: 70%;
left: 50%;
transform: translate(-50%, 0)
.wxlogin
width: 562rpx;
height: 140rpx;
border-radius: 70rpx;
background-color: #13C57C;
color: #FFFFFF;
text-align: center;
line-height: 140rpx;
font-size: 70rpx;
.wxaddress
color: #BBBBBB;
margin-top: 70rpx;
text-align: center;
.content-one
padding: 60rpx 28rpx;
display: flex;
flex-direction: column;
justify-content: space-between
height: calc(100% - 120rpx)
.content-title
display: flex
justify-content: space-between;
align-items: center
margin-bottom: 70rpx
.title-lf
font-size: 44rpx;
color: #000000;
font-weight: 600;
.lf-text
font-weight: 400;
font-size: 28rpx;
color: #6C7282;
.title-ri
font-size: 36rpx;
color: #000000;
font-weight: 600;
.content-input
margin-bottom: 52rpx
.input-titile
font-weight: 400;
font-size: 28rpx;
color: #6A6A6A;
.input-con2
font-weight: 400;
font-size: 32rpx;
color: #333333;
line-height: 80rpx;
height: 80rpx;
border-bottom: 2rpx solid #EBEBEB
.input-con
font-weight: 400;
font-size: 32rpx;
color: #333333;
line-height: 80rpx;
height: 80rpx;
border-bottom: 2rpx solid #EBEBEB
position: relative;
.input-con::before
position: absolute;
right: 20rpx;
top: calc(50% - 2rpx);
content: '';
width: 4rpx;
height: 18rpx;
border-radius: 2rpx
background: #697279;
transform: translate(0, -50%) rotate(-45deg) ;
.input-con::after
position: absolute;
right: 20rpx;
top: 50%;
content: '';
width: 4rpx;
height: 18rpx;
border-radius: 2rpx
background: #697279;
transform: rotate(45deg)
.content-sex
height: 110rpx;
display: flex
justify-content: space-between;
align-items: flex-start;
border-bottom: 2rpx solid #EBEBEB
margin-bottom: 52rpx
.sex-titile
line-height: 80rpx;
.sext-ri
display: flex
align-items: center;
.sext-box
height: 76rpx;
width: 152rpx;
text-align: center;
line-height: 80rpx;
border-radius: 12rpx 12rpx 12rpx 12rpx
border: 2rpx solid #E8EAEE;
margin-left: 28rpx
font-weight: 400;
font-size: 28rpx;
.sext-boxactive
color: #256BFA
background: rgba(37,107,250,0.1);
border: 2rpx solid #256BFA;
.next-btn
width: 100%;
height: 90rpx;
background: #256BFA;
border-radius: 12rpx 12rpx 12rpx 12rpx;
font-weight: 500;
font-size: 32rpx;
color: #FFFFFF;
text-align: center;
line-height: 90rpx
</style>

363
pages/mine/company-info.vue Normal file
View File

@@ -0,0 +1,363 @@
<template>
<AppLayout back-gorund-color="#F4F4F4">
<!-- 编辑头像 -->
<view class="avatar-section btn-feel" @click="editAvatar">
<view class="avatar-label">编辑信息</view>
<view class="avatar-container">
<image class="company-avatar" :src="companyInfo.avatar || '/static/imgs/avatar.jpg'"></image>
<uni-icons color="#A2A2A2" type="right" size="16"></uni-icons>
</view>
</view>
<!-- 企业详细信息 -->
<view class="info-section">
<view class="info-item btn-feel" @click="editInfo('name')">
<view class="info-label">企业名称</view>
<view class="info-content">
<text class="info-value">{{ companyInfo.name || '暂无公司名称' }}</text>
<uni-icons color="#A2A2A2" type="right" size="16"></uni-icons>
</view>
</view>
<view class="info-item btn-feel" @click="editInfo('code')">
<view class="info-label">统一社会代码</view>
<view class="info-content">
<text class="info-value">{{ companyInfo.socialCode || '暂无统一社会代码' }}</text>
<uni-icons color="#A2A2A2" type="right" size="16"></uni-icons>
</view>
</view>
<view class="info-item btn-feel" @click="editInfo('location')">
<view class="info-label">企业注册地点</view>
<view class="info-content">
<text class="info-value">{{ companyInfo.location || '暂无注册地点' }}</text>
<uni-icons color="#A2A2A2" type="right" size="16"></uni-icons>
</view>
</view>
<view class="info-item btn-feel" @click="editInfo('description')">
<view class="info-label">企业信息介绍</view>
<view class="info-content">
<text class="info-value">{{ companyInfo.description || '暂无企业介绍' }}</text>
<uni-icons color="#A2A2A2" type="right" size="16"></uni-icons>
</view>
</view>
<view class="info-item btn-feel" @click="editInfo('legalPerson')">
<view class="info-label">企业法人姓名</view>
<view class="info-content">
<text class="info-value">{{ companyInfo.legalPerson || '暂无法人信息' }}</text>
<uni-icons color="#A2A2A2" type="right" size="16"></uni-icons>
</view>
</view>
<!-- 企业联系人管理 -->
<view class="contact-management-section">
<view class="contact-section-header">
<view class="contact-section-title">企业联系人</view>
<view class="edit-contacts-btn btn-feel" @click="editContacts">
<uni-icons type="compose" size="16" color="#256BFA"></uni-icons>
<text class="edit-text">编辑联系人</text>
</view>
</view>
<!-- 联系人列表展示 -->
<view v-for="(contact, index) in companyInfo.companyContactList" :key="contact.id || index" class="contact-display-item">
<view class="contact-display-title">联系人{{ index + 1 }}</view>
<view class="contact-display-content">
<view class="contact-field">
<text class="field-label">姓名</text>
<text class="field-value">{{ contact.contactPerson || '暂无' }}</text>
</view>
<view class="contact-field">
<text class="field-label">电话</text>
<text class="field-value">{{ contact.contactPersonPhone || '暂无' }}</text>
</view>
</view>
</view>
<!-- 如果没有联系人显示提示 -->
<view v-if="!companyInfo.companyContactList || companyInfo.companyContactList.length === 0" class="no-contacts">
<text class="no-contacts-text">暂无联系人信息</text>
</view>
</view>
</view>
</AppLayout>
</template>
<script setup>
import { reactive, inject, onMounted } from 'vue';
import { onLoad, onShow } from '@dcloudio/uni-app';
import AppLayout from '@/components/AppLayout/AppLayout.vue';
const { $api, navTo } = inject('globalFunction');
// 企业信息数据
const companyInfo = reactive({
name: '',
avatar: '/static/imgs/avatar.jpg',
completeness: '65%',
socialCode: '',
location: '',
description: '',
legalPerson: '',
companyContactList: [], // 企业联系人列表
isVerified: false // 实名状态
});
function editAvatar() {
// 编辑头像逻辑
// uni.chooseImage({
// count: 1,
// success: (res) => {
// // 上传头像
// uploadAvatar(res.tempFilePaths[0]);
// }
// });
}
function uploadAvatar(filePath) {
// 上传头像到服务器
uni.uploadFile({
url: '/api/upload/avatar',
filePath: filePath,
name: 'avatar',
success: (res) => {
const data = JSON.parse(res.data);
if (data.success) {
companyInfo.avatar = data.data.url;
uni.showToast({
title: '头像更新成功',
icon: 'success'
});
}
}
});
}
function editInfo(type) {
// 根据类型跳转到不同的编辑页面
const editPages = {
name: '/pages/mine/edit-company-name',
code: '/pages/mine/edit-company-code',
location: '/pages/mine/edit-company-location',
description: '/pages/mine/edit-company-description',
legalPerson: '/pages/mine/edit-legal-person'
};
if (editPages[type]) {
navTo(editPages[type]);
}
}
function editContacts() {
// 跳转到联系人编辑页面
navTo('/pages/mine/edit-company-contacts');
}
onShow(() => {
// 获取企业信息
getCompanyInfo();
});
// 从缓存获取公司信息
function getCompanyInfo() {
try {
const cachedUserInfo = uni.getStorageSync('userInfo') || {};
console.log('缓存中的userInfo:', cachedUserInfo);
// 检查是否有company字段
if (cachedUserInfo.company) {
const company = cachedUserInfo.company;
// 基本信息
companyInfo.name = company.name || '';
companyInfo.socialCode = company.code || '';
companyInfo.location = company.registeredAddress || '';
companyInfo.description = company.description || '';
companyInfo.legalPerson = company.legalPerson || '';
// 联系人信息 - 直接使用companyContactList数组
companyInfo.companyContactList = company.companyContactList || [];
// 判断是否实名legalIdCard字段有值则表示已实名
companyInfo.isVerified = !!(company.legalIdCard && company.legalIdCard.trim());
console.log('公司名称:', companyInfo.name);
console.log('实名状态:', companyInfo.isVerified);
console.log('legalIdCard值:', company.legalIdCard);
} else {
console.log('缓存中没有company字段');
// 保持默认值
}
} catch (error) {
console.error('获取公司信息失败:', error);
// 保持默认值
}
}
function goBack() {
uni.navigateBack();
}
</script>
<style lang="stylus" scoped>
.avatar-section {
display: flex;
align-items: center;
justify-content: space-between;
padding: 30rpx;
background: #FFFFFF;
margin: 20rpx;
border-radius: 20rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.08);
.avatar-label {
font-size: 28rpx;
color: #333333;
}
.avatar-container {
display: flex;
align-items: center;
.company-avatar {
width: 80rpx;
height: 80rpx;
border-radius: 50%;
margin-right: 16rpx;
}
}
}
.info-section {
background: #FFFFFF;
margin: 0 20rpx 40rpx;
border-radius: 20rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.08);
.info-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 30rpx;
border-bottom: 1rpx solid #F5F5F5;
&:last-child {
border-bottom: none;
}
.info-label {
font-size: 28rpx;
color: #6C7282;
min-width: 200rpx;
}
.info-content {
display: flex;
align-items: center;
flex: 1;
.info-value {
font-size: 28rpx;
color: #333333;
flex: 1;
text-align: right;
margin-right: 16rpx;
word-break: break-all;
}
}
}
}
.btn-feel {
transition: transform 0.2s ease;
&:active {
transform: scale(0.98);
}
}
.contact-management-section {
border-top: 20rpx solid #F4F4F4;
.contact-section-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 20rpx 30rpx;
background: #F8F8F8;
border-bottom: 1rpx solid #F5F5F5;
.contact-section-title {
font-size: 24rpx;
color: #999999;
}
.edit-contacts-btn {
display: flex;
align-items: center;
padding: 8rpx 16rpx;
background: #E6F7FF;
border-radius: 8rpx;
.edit-text {
font-size: 24rpx;
color: #256BFA;
margin-left: 8rpx;
}
}
}
.contact-display-item {
padding: 20rpx 30rpx;
border-bottom: 1rpx solid #F5F5F5;
&:last-child {
border-bottom: none;
}
.contact-display-title {
font-size: 26rpx;
font-weight: 600;
color: #333333;
margin-bottom: 16rpx;
}
.contact-display-content {
.contact-field {
display: flex;
align-items: center;
margin-bottom: 12rpx;
&:last-child {
margin-bottom: 0;
}
.field-label {
font-size: 26rpx;
color: #6C7282;
min-width: 120rpx;
}
.field-value {
font-size: 26rpx;
color: #333333;
flex: 1;
}
}
}
}
.no-contacts {
padding: 40rpx 30rpx;
text-align: center;
.no-contacts-text {
font-size: 26rpx;
color: #999999;
}
}
}
</style>

285
pages/mine/company-mine.vue Normal file
View File

@@ -0,0 +1,285 @@
<template>
<AppLayout back-gorund-color="#F4F4F4">
<!-- 自定义tabbar -->
<CustomTabBar :currentPage="4" />
<!-- 企业信息卡片 -->
<view class="company-info-card btn-feel" @click="goToCompanyInfo">
<view class="company-avatar">
<image class="company-avatar-img" :src="companyInfo.avatar || '/static/imgs/avatar.jpg'"></image>
</view>
<view class="company-details">
<view class="company-name">{{ companyInfo.name || '暂无公司名称' }}</view>
<view class="company-completeness">
信息完整度 {{ companyInfo.completeness || '100%' }}
<text class="verification-status" :class="{ 'verified': companyInfo.isVerified, 'unverified': !companyInfo.isVerified }">
{{ companyInfo.isVerified ? '已实名' : '未实名' }}
</text>
</view>
</view>
<view class="company-arrow">
<uni-icons color="#A2A2A2" type="right" size="16"></uni-icons>
</view>
</view>
<!-- 服务专区 -->
<view class="service-zone-card">
<view class="service-title">服务专区</view>
<view class="service-item btn-feel">
<view class="service-left">
<uni-icons type="contact" size="20" color="#256BFA"></uni-icons>
<text class="service-text">实名认证</text>
</view>
<view class="service-status" :class="{ 'verified': companyInfo.isVerified, 'unverified': !companyInfo.isVerified }">
{{ companyInfo.isVerified ? '已通过' : '未认证' }}
</view>
</view>
<view class="service-item btn-feel">
<view class="service-left">
<uni-icons type="notification" size="20" color="#256BFA"></uni-icons>
<text class="service-text">通知与提醒</text>
</view>
<view class="service-status">已开启</view>
</view>
</view>
<!-- 退出登录按钮 -->
<view class="logout-btn btn-feel" @click="logOut">
退出登录
</view>
<!-- 退出确认弹窗 -->
<uni-popup ref="popup" type="dialog">
<uni-popup-dialog
mode="base"
title="确定退出登录吗?"
type="info"
:duration="2000"
:before-close="true"
@confirm="confirm"
@close="close"
></uni-popup-dialog>
</uni-popup>
</AppLayout>
</template>
<script setup>
import { reactive, inject, ref, onMounted, onUnmounted } from 'vue';
import { onLoad, onShow } from '@dcloudio/uni-app';
import useUserStore from '@/stores/useUserStore';
const { $api, navTo } = inject('globalFunction');
const popup = ref(null);
// 企业信息数据
const companyInfo = reactive({
name: '',
avatar: '/static/imgs/avatar.jpg',
completeness: '100%',
isVerified: false // 实名状态
});
function goToCompanyInfo() {
navTo('/pages/mine/company-info');
}
function logOut() {
popup.value.open();
}
function close() {
popup.value.close();
}
function confirm() {
// 调用退出登录
useUserStore().logOut();
// 关闭弹窗
popup.value.close();
// 跳转到首页
uni.reLaunch({
url: '/pages/index/index'
});
}
onShow(() => {
// 获取企业信息
getCompanyInfo();
});
// 监听退出登录事件,显示微信登录弹窗
onMounted(() => {
uni.$on('showLoginModal', () => {
// 这里可以显示微信登录弹窗
// 由于这个页面没有 WxAuthLogin 组件,我们跳转到首页让首页处理
uni.reLaunch({
url: '/pages/index/index'
});
});
});
onUnmounted(() => {
uni.$off('showLoginModal');
});
// 从缓存获取公司信息
function getCompanyInfo() {
try {
const cachedUserInfo = uni.getStorageSync('userInfo') || {};
console.log('缓存中的userInfo:', cachedUserInfo);
// 检查是否有company字段
if (cachedUserInfo.company) {
companyInfo.name = cachedUserInfo.company.name || '';
// 判断是否实名legalIdCard字段有值则表示已实名
companyInfo.isVerified = !!(cachedUserInfo.company.legalIdCard && cachedUserInfo.company.legalIdCard.trim());
console.log('公司名称:', companyInfo.name);
console.log('实名状态:', companyInfo.isVerified);
console.log('legalIdCard值:', cachedUserInfo.company.legalIdCard);
} else {
console.log('缓存中没有company字段');
companyInfo.name = '';
companyInfo.isVerified = false;
}
} catch (error) {
console.error('获取公司信息失败:', error);
companyInfo.name = '';
companyInfo.isVerified = false;
}
}
</script>
<style lang="stylus" scoped>
.company-info-card {
display: flex;
align-items: center;
padding: 30rpx;
background: #FFFFFF;
margin: 20rpx;
border-radius: 20rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.08);
.company-avatar {
width: 100rpx;
height: 100rpx;
border-radius: 50%;
overflow: hidden;
margin-right: 24rpx;
.company-avatar-img {
width: 100%;
height: 100%;
}
}
.company-details {
flex: 1;
.company-name {
font-size: 36rpx;
font-weight: 600;
color: #333333;
margin-bottom: 8rpx;
}
.company-completeness {
font-size: 28rpx;
color: #6C7282;
display: flex;
align-items: center;
gap: 16rpx;
.verification-status {
font-size: 24rpx;
padding: 4rpx 12rpx;
border-radius: 12rpx;
&.verified {
background-color: #E8F5E8;
color: #52C41A;
}
&.unverified {
background-color: #FFF2E8;
color: #FA8C16;
}
}
}
}
.company-arrow {
margin-left: 20rpx;
}
}
.service-zone-card {
background: #FFFFFF;
margin: 0 20rpx 20rpx;
border-radius: 20rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.08);
padding: 32rpx;
.service-title {
font-size: 32rpx;
font-weight: 600;
color: #000000;
margin-bottom: 32rpx;
}
.service-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 24rpx 0;
border-bottom: 1rpx solid #F5F5F5;
&:last-child {
border-bottom: none;
}
.service-left {
display: flex;
align-items: center;
.service-text {
font-size: 28rpx;
color: #333333;
margin-left: 16rpx;
}
}
.service-status {
font-size: 28rpx;
color: #6E6E6E;
&.verified {
color: #52C41A;
}
&.unverified {
color: #FA8C16;
}
}
}
}
.logout-btn {
height: 96rpx;
background: #FFFFFF;
margin: 0 20rpx 40rpx;
border-radius: 20rpx;
text-align: center;
line-height: 96rpx;
font-size: 28rpx;
color: #256BFA;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.08);
}
.btn-feel {
transition: transform 0.2s ease;
&:active {
transform: scale(0.98);
}
}
</style>

View File

@@ -0,0 +1,470 @@
<template>
<AppLayout back-gorund-color="#F4F4F4">
<!-- 联系人列表 -->
<view class="contacts-section">
<view v-for="(contact, index) in contactList" :key="contact.tempId || contact.id" class="contact-item">
<view class="contact-header">
<text class="contact-title">联系人{{ index + 1 }}</text>
<view class="contact-actions" v-if="contactList.length > 1">
<view class="action-btn delete-btn" @click="deleteContact(index)">
<uni-icons type="trash" size="16" color="#FF4D4F"></uni-icons>
<text class="action-text">删除</text>
</view>
</view>
</view>
<view class="contact-form">
<view class="form-item">
<view class="form-label">联系人姓名</view>
<input
class="form-input"
v-model="contact.contactPerson"
placeholder="请输入联系人姓名"
@blur="validateContactName(index)"
/>
</view>
<view class="form-item">
<view class="form-label">联系电话</view>
<input
class="form-input"
v-model="contact.contactPersonPhone"
placeholder="请输入联系电话"
type="number"
@blur="validateContactPhone(index)"
/>
</view>
</view>
</view>
</view>
<!-- 添加联系人按钮 -->
<view class="add-contact-section" v-if="contactList.length < 3">
<view class="add-contact-btn btn-feel" @click="addContact">
<uni-icons type="plus" size="20" color="#256BFA"></uni-icons>
<text class="add-text">添加联系人</text>
</view>
</view>
<!-- 保存按钮 -->
<view class="save-section">
<view class="save-btn btn-feel" @click="saveContacts">
<text class="save-text">保存联系人</text>
</view>
</view>
<!-- 提示信息 -->
<view class="tips-section">
<view class="tips-title">温馨提示</view>
<view class="tips-content">
<text class="tips-item"> 至少需要保留一个联系人最多可添加3个联系人</text>
<text class="tips-item"> 联系人姓名和电话为必填项</text>
<text class="tips-item"> 联系电话请填写正确的手机号码</text>
</view>
</view>
</AppLayout>
</template>
<script setup>
import { reactive, inject, ref, onMounted } from 'vue';
import { onLoad, onShow } from '@dcloudio/uni-app';
import AppLayout from '@/components/AppLayout/AppLayout.vue';
import { createRequest } from '@/utils/request.js';
const { navTo } = inject('globalFunction');
// 联系人列表数据
const contactList = reactive([]);
let tempIdCounter = 0; // 用于生成临时ID
// 页面加载时获取联系人数据
onLoad((options) => {
// 从缓存获取企业联系人信息
loadContactsFromCache();
});
// 从缓存加载联系人数据
function loadContactsFromCache() {
try {
const cachedUserInfo = uni.getStorageSync('userInfo') || {};
if (cachedUserInfo.company && cachedUserInfo.company.companyContactList) {
const contacts = cachedUserInfo.company.companyContactList;
// 如果联系人列表为空,至少添加一个空联系人
if (contacts.length === 0) {
contactList.push(createEmptyContact());
} else {
// 为每个联系人添加临时ID如果没有ID的话
contacts.forEach(contact => {
if (!contact.tempId) {
contact.tempId = `temp_${++tempIdCounter}`;
}
contactList.push({ ...contact });
});
}
} else {
// 如果没有联系人数据,创建一个空联系人
contactList.push(createEmptyContact());
}
} catch (error) {
console.error('加载联系人数据失败:', error);
// 出错时至少创建一个空联系人
contactList.push(createEmptyContact());
}
}
// 创建空联系人
function createEmptyContact() {
return {
id: '',
contactPerson: '',
contactPersonPhone: '',
tempId: `temp_${++tempIdCounter}`
};
}
// 添加联系人
function addContact() {
if (contactList.length >= 3) {
uni.showToast({
title: '最多只能添加3个联系人',
icon: 'none'
});
return;
}
contactList.push(createEmptyContact());
}
// 删除联系人
function deleteContact(index) {
if (contactList.length <= 1) {
uni.showToast({
title: '至少需要保留一个联系人',
icon: 'none'
});
return;
}
uni.showModal({
title: '确认删除',
content: '确定要删除这个联系人吗?',
success: (res) => {
if (res.confirm) {
contactList.splice(index, 1);
}
}
});
}
// 验证联系人姓名
function validateContactName(index) {
const contact = contactList[index];
if (!contact.contactPerson || contact.contactPerson.trim() === '') {
uni.showToast({
title: '请输入联系人姓名',
icon: 'none'
});
return false;
}
return true;
}
// 验证联系人电话
function validateContactPhone(index) {
const contact = contactList[index];
if (!contact.contactPersonPhone || contact.contactPersonPhone.trim() === '') {
uni.showToast({
title: '请输入联系电话',
icon: 'none'
});
return false;
}
// 简单的手机号验证
const phoneRegex = /^1[3-9]\d{9}$/;
if (!phoneRegex.test(contact.contactPersonPhone)) {
uni.showToast({
title: '请输入正确的手机号码',
icon: 'none'
});
return false;
}
return true;
}
// 验证所有联系人
function validateAllContacts() {
for (let i = 0; i < contactList.length; i++) {
if (!validateContactName(i) || !validateContactPhone(i)) {
return false;
}
}
return true;
}
// 保存联系人
async function saveContacts() {
// 验证所有联系人信息
if (!validateAllContacts()) {
return;
}
try {
uni.showLoading({
title: '保存中...',
mask: true
});
// 获取companyId
const companyId = getCompanyIdFromCache();
if (!companyId) {
uni.showToast({
title: '获取企业信息失败,请重新登录',
icon: 'none'
});
return;
}
// 准备API数据移除临时ID添加companyId
const apiData = contactList.map(contact => ({
id: contact.id || '',
contactPerson: contact.contactPerson.trim(),
contactPersonPhone: contact.contactPersonPhone.trim(),
companyId: companyId
}));
// 调用API保存联系人
const response = await createRequest(
'/app/companycontact/batchInsertUpdate',
{
companyContactList: apiData
},
'POST',
false
);
if (response.code === 200) {
// 保存成功,更新本地缓存
updateLocalCache(apiData);
uni.showToast({
title: '保存成功',
icon: 'success'
});
// 延迟返回上一页
setTimeout(() => {
goBack();
}, 1500);
} else {
uni.showToast({
title: response.msg || '保存失败',
icon: 'none'
});
}
} catch (error) {
console.error('保存联系人失败:', error);
uni.showToast({
title: '保存失败,请重试',
icon: 'none'
});
} finally {
uni.hideLoading();
}
}
// 从缓存获取companyId
function getCompanyIdFromCache() {
try {
const cachedUserInfo = uni.getStorageSync('userInfo') || {};
if (cachedUserInfo.company && cachedUserInfo.company.companyId) {
return cachedUserInfo.company.companyId;
}
return null;
} catch (error) {
console.error('获取companyId失败:', error);
return null;
}
}
// 更新本地缓存
function updateLocalCache(contactData) {
try {
const cachedUserInfo = uni.getStorageSync('userInfo') || {};
if (cachedUserInfo.company) {
cachedUserInfo.company.companyContactList = contactData;
uni.setStorageSync('userInfo', cachedUserInfo);
}
} catch (error) {
console.error('更新本地缓存失败:', error);
}
}
// 返回上一页
function goBack() {
uni.navigateBack();
}
</script>
<style lang="stylus" scoped>
.save-section {
margin: 0 20rpx 20rpx;
.save-btn {
display: flex;
align-items: center;
justify-content: center;
height: 88rpx;
background: #256BFA;
border-radius: 20rpx;
box-shadow: 0 4rpx 20rpx rgba(37, 107, 250, 0.3);
.save-text {
font-size: 32rpx;
color: #FFFFFF;
font-weight: 600;
}
}
}
.contacts-section {
margin: 20rpx;
.contact-item {
background: #FFFFFF;
border-radius: 20rpx;
margin-bottom: 20rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.08);
.contact-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 30rpx 30rpx 20rpx;
border-bottom: 1rpx solid #F5F5F5;
.contact-title {
font-size: 28rpx;
font-weight: 600;
color: #333333;
}
.contact-actions {
display: flex;
align-items: center;
.action-btn {
display: flex;
align-items: center;
padding: 8rpx 16rpx;
border-radius: 8rpx;
&.delete-btn {
background: #FFF2F0;
.action-text {
font-size: 24rpx;
color: #FF4D4F;
margin-left: 8rpx;
}
}
}
}
}
.contact-form {
padding: 20rpx 30rpx 30rpx;
.form-item {
margin-bottom: 30rpx;
&:last-child {
margin-bottom: 0;
}
.form-label {
font-size: 26rpx;
color: #6C7282;
margin-bottom: 16rpx;
}
.form-input {
width: 100%;
height: 80rpx;
background: #F8F9FA;
border: 1rpx solid #E9ECEF;
border-radius: 12rpx;
padding: 0 20rpx;
font-size: 28rpx;
color: #333333;
box-sizing: border-box;
&:focus {
border-color: #256BFA;
background: #FFFFFF;
}
}
}
}
}
}
.add-contact-section {
margin: 0 20rpx 20rpx;
.add-contact-btn {
display: flex;
align-items: center;
justify-content: center;
height: 88rpx;
background: #FFFFFF;
border: 2rpx dashed #256BFA;
border-radius: 20rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.08);
.add-text {
font-size: 28rpx;
color: #256BFA;
margin-left: 12rpx;
}
}
}
.tips-section {
margin: 0 20rpx 40rpx;
padding: 30rpx;
background: #F8F9FA;
border-radius: 20rpx;
.tips-title {
font-size: 26rpx;
font-weight: 600;
color: #333333;
margin-bottom: 20rpx;
}
.tips-content {
.tips-item {
display: block;
font-size: 24rpx;
color: #6C7282;
line-height: 1.6;
margin-bottom: 8rpx;
&:last-child {
margin-bottom: 0;
}
}
}
}
.btn-feel {
transition: transform 0.2s ease;
&:active {
transform: scale(0.98);
}
}
</style>

View File

@@ -1,6 +1,23 @@
<template>
<AppLayout title="我的" back-gorund-color="#F4F4F4">
<view class="mine-userinfo btn-feel" @click="seeDetail">
<!-- 自定义tabbar -->
<CustomTabBar :currentPage="4" />
<!-- 企业用户信息卡片 -->
<view v-if="userType === 0" class="company-info-card btn-feel" @click="seeDetail">
<view class="company-avatar">
<image class="company-avatar-img" :src="companyInfo.avatar || '/static/icon/company-default.png'"></image>
</view>
<view class="company-details">
<view class="company-name">{{ companyInfo.name || '科里喀什分公司' }}</view>
<view class="company-completeness">信息完整度 {{ companyInfo.completeness || '100%' }}</view>
</view>
<view class="company-arrow">
<uni-icons color="#A2A2A2" type="right" size="16"></uni-icons>
</view>
</view>
<!-- 求职者用户信息卡片 -->
<view v-else 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>
<image class="userindo-head-img" v-else src="/static/icon/girl.png"></image>
@@ -83,7 +100,8 @@
<view class="row-right">已开启</view>
</view>
</view>
<view class="card-back button-click" @click="logOut">退出登录</view>
<view v-if="userType === 2" class="card-help button-click" @click="goToJobHelper">求职帮</view>
<view class="card-back button-click" @click="logOut">退出登录</view>
<uni-popup ref="popup" type="dialog">
<uni-popup-dialog
mode="base"
@@ -103,19 +121,56 @@
</template>
<script setup>
import { reactive, inject, watch, ref, onMounted } from 'vue';
import { reactive, inject, watch, ref, onMounted, onUnmounted, computed } from 'vue';
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({});
// 获取用户类型,参考首页的实现方式
const userType = computed(() => {
// 优先从store获取如果为空则从缓存获取
const storeIsCompanyUser = userInfo.value?.isCompanyUser;
const cachedUserInfo = uni.getStorageSync('userInfo') || {};
const cachedIsCompanyUser = cachedUserInfo.isCompanyUser;
// 获取用户类型优先使用store中的isCompanyUser如果store中没有使用缓存中的isCompanyUser
// 缓存中的值可能是字符串,需要转换为数值类型
return storeIsCompanyUser !== undefined ? Number(storeIsCompanyUser) : Number(cachedIsCompanyUser);
});
// 企业信息数据
const companyInfo = reactive({
name: '科里喀什分公司',
avatar: '/static/icon/company-avatar.png',
completeness: '100%'
});
function logOut() {
popup.value.open();
}
onShow(() => {
getUserstatistics();
// 更新自定义tabbar选中状态
tabbarManager.updateSelected(4);
});
// 监听退出登录事件,显示微信登录弹窗
onMounted(() => {
uni.$on('showLoginModal', () => {
// 这里可以显示微信登录弹窗
// 由于这个页面没有 WxAuthLogin 组件,我们跳转到首页让首页处理
uni.reLaunch({
url: '/pages/index/index'
});
});
});
onUnmounted(() => {
uni.$off('showLoginModal');
});
function close() {
@@ -135,12 +190,19 @@ function getUserstatistics() {
});
}
function seeDetail() {
if (userInfo.isCompanyUser) {
navTo('/packageA/pages/myResume/corporateInformation');
if (userType === 0) {
// 企业用户跳转到企业信息页面
navTo('/pages/mine/company-info');
} else {
// 求职者用户跳转到简历页面
navTo('/packageA/pages/myResume/myResume');
}
}
function goToJobHelper() {
// 跳转到求职者信息补全页面
navTo('/pages/complete-info/complete-info');
}
</script>
<style lang="stylus" scoped>
@@ -240,6 +302,16 @@ function seeDetail() {
margin: 0
}
}
.card-help{
height: 96rpx;
background: #FFFFFF;
border-radius: 20rpx 20rpx 20rpx 20rpx;
text-align: center;
line-height: 96rpx;
font-size: 28rpx;
color: #256BFA;
margin-bottom: 20rpx;
}
.card-back{
height: 96rpx;
background: #FFFFFF;
@@ -321,4 +393,48 @@ function seeDetail() {
border-radius: 2rpx
background: #A2A2A2;
transform: rotate(45deg)
// 企业信息卡片样式
.company-info-card {
display: flex;
align-items: center;
padding: 30rpx;
background: #FFFFFF;
margin: 20rpx;
border-radius: 20rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.08);
.company-avatar {
width: 100rpx;
height: 100rpx;
border-radius: 50%;
overflow: hidden;
margin-right: 24rpx;
.company-avatar-img {
width: 100%;
height: 100%;
}
}
.company-details {
flex: 1;
.company-name {
font-size: 36rpx;
font-weight: 600;
color: #333333;
margin-bottom: 8rpx;
}
.company-completeness {
font-size: 28rpx;
color: #6C7282;
}
}
.company-arrow {
margin-left: 20rpx;
}
}
</style>

View File

@@ -21,34 +21,45 @@
<component :is="components[index]" :ref="(el) => handelComponentsRef(el, index)" />
<!-- #endif -->
<!-- #ifdef MP-WEIXIN -->
<ReadComponent v-show="currentIndex === 0" :ref="(el) => handelComponentsRef(el, index)" />
<UnreadComponent v-show="currentIndex === 1" :ref="(el) => handelComponentsRef(el, index)" />
<ReadComponent v-show="state.current === 0" :ref="(el) => handelComponentsRef(el, index)" />
<UnreadComponent v-show="state.current === 1" :ref="(el) => handelComponentsRef(el, index)" />
<!-- #endif -->
</swiper-item>
</swiper>
</view>
<!-- 自定义tabbar -->
<CustomTabBar :currentPage="3" />
<!-- 微信授权登录弹窗 -->
<WxAuthLogin ref="wxAuthLoginRef" @success="handleLoginSuccess"></WxAuthLogin>
<!-- 统一使用系统tabBar -->
</view>
</view>
</template>
<script setup>
import { reactive, inject, watch, ref, onMounted } from 'vue';
import { reactive, inject, watch, ref, onMounted, onUnmounted } from 'vue';
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';
import WxAuthLogin from '@/components/WxAuthLogin/WxAuthLogin.vue';
const loadedMap = reactive([false, false]);
const swiperRefs = [ref(null), ref(null)];
const components = [ReadComponent, UnreadComponent];
import { storeToRefs } from 'pinia';
import { useReadMsg } from '@/stores/useReadMsg';
const { unreadCount } = storeToRefs(useReadMsg());
const wxAuthLoginRef = ref(null);
onShow(() => {
// 获取消息列表
useReadMsg().fetchMessages();
// 更新自定义tabbar选中状态
tabbarManager.updateSelected(3);
});
const state = reactive({
current: 0,
@@ -57,8 +68,23 @@ const state = reactive({
onMounted(() => {
handleTabChange(state.current);
// 监听退出登录事件,显示微信登录弹窗
uni.$on('showLoginModal', () => {
wxAuthLoginRef.value?.open();
});
});
onUnmounted(() => {
uni.$off('showLoginModal');
});
// 登录成功回调
const handleLoginSuccess = () => {
console.log('登录成功');
// 可以在这里添加登录成功后的处理逻辑
};
const handelComponentsRef = (el, index) => {
if (el) {
swiperRefs[index].value = el;

View File

@@ -1,6 +1,7 @@
<template>
<scroll-view scroll-y class="main-scroll">
<view class="scrollmain">
<!-- 消息列表 -->
<view
class="list-card press-button"
v-for="(item, index) in msgList"
@@ -35,6 +36,13 @@
<view class="info-text line_2">{{ item.subTitle || '消息' }}</view>
</view>
</view>
<!-- 暂无消息提示 -->
<view class="empty-state" v-if="msgList.length === 0">
<image class="empty-icon" src="/static/icon/empty.png" mode="aspectFit"></image>
<text class="empty-text">暂无消息</text>
<text class="empty-desc">您还没有收到任何消息</text>
</view>
</view>
</scroll-view>
</template>
@@ -146,4 +154,26 @@ defineExpose({ loadData });
font-size: 28rpx;
color: #6C7282;
margin-top: 4rpx;
// 空状态样式
.empty-state
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 120rpx 40rpx;
.empty-icon
width: 200rpx;
height: 200rpx;
margin-bottom: 40rpx;
opacity: 0.6;
.empty-text
font-size: 32rpx;
color: #999999;
font-weight: 500;
margin-bottom: 16rpx;
.empty-desc
font-size: 28rpx;
color: #CCCCCC;
font-weight: 400;
</style>

View File

@@ -1,6 +1,7 @@
<template>
<scroll-view scroll-y class="main-scroll">
<view class="scrollmain">
<!-- 未读消息列表 -->
<view
class="list-card press-button"
v-for="(item, index) in unreadMsgList"
@@ -33,6 +34,13 @@
<view class="info-text line_2">{{ item.subTitle || '消息' }}</view>
</view>
</view>
<!-- 暂无未读消息提示 -->
<view class="empty-state" v-if="unreadMsgList.length === 0">
<image class="empty-icon" src="/static/icon/empty.png" mode="aspectFit"></image>
<text class="empty-text">暂无未读消息</text>
<text class="empty-desc">您没有未读的消息</text>
</view>
</view>
</scroll-view>
</template>
@@ -132,4 +140,26 @@ defineExpose({ loadData });
font-size: 28rpx;
color: #6C7282;
margin-top: 4rpx;
// 空状态样式
.empty-state
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 120rpx 40rpx;
.empty-icon
width: 200rpx;
height: 200rpx;
margin-bottom: 40rpx;
opacity: 0.6;
.empty-text
font-size: 32rpx;
color: #999999;
font-weight: 500;
margin-bottom: 16rpx;
.empty-desc
font-size: 28rpx;
color: #CCCCCC;
font-weight: 400;
</style>

View File

@@ -0,0 +1,156 @@
<template>
<AppLayout title="企业我的页面测试" back-gorund-color="#F4F4F4">
<view class="test-container">
<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="switchToJobSeeker">切换到求职者</button>
</view>
<view class="current-type">
当前用户类型{{ getCurrentTypeLabel() }} ({{ currentUserType }})
</view>
</view>
<view class="test-section">
<view class="section-title">页面跳转测试</view>
<view class="button-group">
<button class="test-btn" @click="goToCompanyMine">企业我的页面</button>
<button class="test-btn" @click="goToCompanyInfo">企业信息页面</button>
<button class="test-btn" @click="goToMine">普通我的页面</button>
</view>
</view>
<view class="test-section">
<view class="section-title">用户信息显示</view>
<view class="info-display">
<text>用户类型{{ userInfo.isCompanyUser }}</text>
<text>用户名{{ userInfo.name || '未设置' }}</text>
</view>
</view>
</view>
</AppLayout>
</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 switchToCompany = () => {
userInfo.value.isCompanyUser = 0;
userInfo.value.name = '科里喀什分公司';
uni.setStorageSync('userInfo', userInfo.value);
uni.showToast({
title: '已切换到企业用户',
icon: 'success'
});
};
const switchToJobSeeker = () => {
userInfo.value.isCompanyUser = 1;
userInfo.value.name = '求职者用户';
uni.setStorageSync('userInfo', userInfo.value);
uni.showToast({
title: '已切换到求职者',
icon: 'success'
});
};
const getCurrentTypeLabel = () => {
const type = userTypes.find(t => t.value === currentUserType.value);
return type ? type.label : '未知';
};
const goToCompanyMine = () => {
uni.navigateTo({
url: '/pages/mine/company-mine'
});
};
const goToCompanyInfo = () => {
uni.navigateTo({
url: '/pages/mine/company-info'
});
};
const goToMine = () => {
uni.navigateTo({
url: '/pages/mine/mine'
});
};
</script>
<style lang="scss" scoped>
.test-container {
padding: 40rpx;
background: #f5f5f5;
min-height: 100vh;
}
.test-section {
background: #fff;
border-radius: 12rpx;
padding: 30rpx;
margin-bottom: 30rpx;
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.1);
}
.section-title {
font-size: 32rpx;
font-weight: 600;
color: #333;
margin-bottom: 20rpx;
}
.button-group {
display: flex;
flex-direction: column;
gap: 20rpx;
margin-bottom: 20rpx;
}
.test-btn {
background: #256BFA;
color: #fff;
border: none;
border-radius: 8rpx;
padding: 20rpx;
font-size: 28rpx;
text-align: center;
}
.current-type {
font-size: 28rpx;
color: #666;
padding: 20rpx;
background: #f8f8f8;
border-radius: 8rpx;
}
.info-display {
display: flex;
flex-direction: column;
gap: 10rpx;
text {
font-size: 28rpx;
color: #333;
padding: 10rpx;
background: #f8f8f8;
border-radius: 4rpx;
}
}
</style>

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>