Compare commits

...

44 Commits

Author SHA1 Message Date
46e3659d98 Merge branch 'main' of http://124.243.245.42:3000/sdz/ks-app-employment-service into dev 2025-10-31 18:25:20 +08:00
361afe206c 修改 2025-10-31 17:49:09 +08:00
3160dad246 修改 2025-10-31 17:47:51 +08:00
7ffab64a82 修改 2025-10-31 17:46:14 +08:00
5a70129c70 修改 2025-10-31 17:39:46 +08:00
45c59a2d36 修改 2025-10-31 17:36:33 +08:00
cc0d8a2f70 视频详情 2025-10-31 17:31:54 +08:00
b963268639 Merge branch 'main' of http://124.243.245.42:3000/sdz/ks-app-employment-service 2025-10-31 17:28:33 +08:00
dc96d856a2 删除unpackage 2025-10-31 17:28:29 +08:00
9a6ad76000 招聘会模块 2025-10-31 17:24:07 +08:00
99d932a453 视频学习 2025-10-31 17:18:43 +08:00
1b1b5f7ac7 培训首页 2025-10-31 11:14:45 +08:00
c98f54ade6 专项练习 2025-10-31 10:18:44 +08:00
2cc5d98763 Merge branch 'main' of http://124.243.245.42:3000/sdz/ks-app-employment-service 2025-10-31 09:28:27 +08:00
lip
438a6f3938 t添加高校毕业生模块 2025-10-30 20:38:17 +08:00
lip
05fd135b3c 添加新图标 2025-10-30 20:32:31 +08:00
25fb1fa2f1 培训 2025-10-30 20:26:34 +08:00
9c22cc4444 configjs回滚 2025-10-30 19:34:57 +08:00
4a1aa68245 添加后端请求接口
分包packageB
2025-10-30 17:09:26 +08:00
冯辉
95b9c1cc29 111 2025-10-30 15:34:42 +08:00
冯辉
e048ba6b0b 企业详细信息页面编辑联系人功能开发 2025-10-27 13:35:02 +08:00
冯辉
55cc52c046 111 2025-10-27 12:39:33 +08:00
冯辉
c71ad5f98c bug修复 2025-10-24 18:49:03 +08:00
冯辉
476e44f400 111 2025-10-24 18:10:16 +08:00
冯辉
d80baf6f0b 首页岗位列表接口调用两次的问题修改 2025-10-24 17:53:53 +08:00
冯辉
5a31d56c9a 首页切换导航bug修复 2025-10-24 17:37:38 +08:00
冯辉
12906b65ec 发布岗位回显企业名称 2025-10-24 17:30:19 +08:00
冯辉
2d6370b796 首页卡片开发 2025-10-24 16:54:52 +08:00
冯辉
92ee5c5311 企业我的页面开发 2025-10-24 11:34:11 +08:00
冯辉
0b339ee061 发布职位网格员模糊查询企业开发 2025-10-23 18:30:40 +08:00
冯辉
4450aa21bc Merge branch 'main' of http://124.243.245.42:3000/sdz/ks-app-employment-service 2025-10-23 17:16:20 +08:00
冯辉
b72b7dd7d6 岗位发布开发 2025-10-23 17:16:16 +08:00
3e58a71658 删除功能 2025-10-23 14:45:08 +08:00
d605e940ee Merge branch 'main' of http://124.243.245.42:3000/sdz/ks-app-employment-service 2025-10-22 14:23:56 +08:00
e287209dcf 静态页面 2025-10-22 14:23:38 +08:00
冯辉
20f2038f8c 接口乱码问题修改 2025-10-22 13:22:06 +08:00
冯辉
4f94090b42 企业信息补全接口联调 2025-10-22 13:15:10 +08:00
冯辉
8bb3c424e2 企业信息补全页面开发 2025-10-21 22:58:47 +08:00
4d87af9c3c 新增静态页面 2025-10-21 17:42:35 +08:00
冯辉
968e6b4091 阿里图标库引入 2025-10-20 16:15:29 +08:00
冯辉
d9c1f83693 微信授权登录功能开发联调 2025-10-20 11:43:44 +08:00
冯辉
ae91ded327 微信登录调试 2025-10-20 11:05:11 +08:00
冯辉
959e9ee9e4 登录注册逻辑开发 2025-10-17 13:15:14 +08:00
冯辉
14dafac147 首页布局更改 2025-10-16 16:44:30 +08:00
84 changed files with 9950 additions and 2753 deletions

View File

@@ -3,17 +3,20 @@ import { reactive, inject, onMounted } from 'vue';
import { onLaunch, onShow, onHide } from '@dcloudio/uni-app';
import useUserStore from './stores/useUserStore';
import useDictStore from './stores/useDictStore';
import { tabbarManager } from './utils/tabbarManager';
const { $api, navTo, appendScriptTagElement } = inject('globalFunction');
import config from '@/config.js';
onLaunch((options) => {
useUserStore().initSeesionId(); //更新
useDictStore().getDictData();
// uni.hideTabBar();
// 尝试从缓存恢复用户信息
// 尝试从缓存恢复用户信息
const restored = useUserStore().restoreUserInfo();
// 用户信息恢复后再初始化自定义tabbar
tabbarManager.initTabBar();
if (restored) {
// 如果成功恢复用户信息验证token是否有效
let token = uni.getStorageSync('token') || '';

View File

@@ -2,7 +2,8 @@ import useUserStore from "../stores/useUserStore";
import {
request,
createRequest,
uploadFile
uploadFile,
myRequest
} from "../utils/request";
import streamRequest, {
chatRequest
@@ -885,7 +886,8 @@ export const $api = {
uploadFile,
formatFileSize,
sendingMiniProgramMessage,
copyText
copyText,
myRequest
}
@@ -916,4 +918,4 @@ export default {
insertSortData,
isInWechatMiniProgramWebview,
isEmptyObject,
}
}

View File

@@ -6,6 +6,7 @@
>
<!-- 顶部头部区域 -->
<view
v-if="title"
class="container-header"
:style="border ? { borderBottom: `2rpx solid ${borderColor}` } : { borderBottom: 'none' }"
>
@@ -49,7 +50,7 @@ const emit = defineEmits(['onScrollBottom']);
defineProps({
title: {
type: String,
default: '标题',
default: '',
},
border: {
type: Boolean,

View File

@@ -0,0 +1,337 @@
<template>
<view class="custom-tabbar">
<view
class="tabbar-item"
v-for="(item, index) in tabbarList"
:key="index"
@click="switchTab(item, index)"
>
<view class="tabbar-icon">
<image
:src="currentItem === item.id ? item.selectedIconPath : item.iconPath"
mode="aspectFit"
/>
</view>
<view class="badge" v-if="item.badge && item.badge > 0">{{ item.badge }}</view>
<view class="tabbar-text" :class="{ 'active': currentItem === item.id }">
{{ item.text }}
</view>
</view>
</view>
</template>
<script setup>
import { ref, computed, watch, onMounted } from 'vue';
import { storeToRefs } from 'pinia';
import useUserStore from '@/stores/useUserStore';
import { useReadMsg } from '@/stores/useReadMsg';
const props = defineProps({
currentPage: {
type: Number,
default: 0
}
});
const userStore = useUserStore();
const { userInfo } = storeToRefs(userStore);
const readMsg = useReadMsg();
const currentItem = ref(props.currentPage);
// 监听props变化
watch(() => props.currentPage, (newPage) => {
currentItem.value = newPage;
});
// 生成tabbar配置的函数
const generateTabbarList = () => {
const baseItems = [
{
id: 0,
text: '职位',
path: '/pages/index/index',
iconPath: '/static/tabbar/calendar.png',
selectedIconPath: '/static/tabbar/calendared.png',
centerItem: false,
badge: readMsg.badges[0]?.count || 0,
},
{
id: 2,
text: 'AI+',
path: '/pages/chat/chat',
iconPath: '/static/tabbar/logo3.png',
selectedIconPath: '/static/tabbar/logo3.png',
centerItem: true,
badge: readMsg.badges[2]?.count || 0,
},
{
id: 3,
text: '消息',
path: '/pages/msglog/msglog',
iconPath: '/static/tabbar/chat4.png',
selectedIconPath: '/static/tabbar/chat4ed.png',
centerItem: false,
badge: readMsg.badges[3]?.count || 0,
},
{
id: 4,
text: '我的',
path: '/pages/mine/mine',
iconPath: '/static/tabbar/mine.png',
selectedIconPath: '/static/tabbar/mined.png',
centerItem: false,
badge: readMsg.badges[4]?.count || 0,
},
];
// 获取用户类型统一使用isCompanyUser字段0=企业用户1=求职者, 3=网格员)
// 优先从store获取如果为空则直接从缓存获取
const cachedUserInfo = uni.getStorageSync('userInfo') || {};
// 获取isCompanyUser字段
const storeIsCompanyUser = userInfo.value?.isCompanyUser;
const cachedIsCompanyUser = cachedUserInfo.isCompanyUser;
// 获取用户类型的逻辑:
// 1. 优先使用store中的isCompanyUser
// 2. 如果store中没有使用缓存中的isCompanyUser
// 3. 最后默认为1求职者
const userType = Number(storeIsCompanyUser !== undefined ? storeIsCompanyUser : (cachedIsCompanyUser !== undefined ? cachedIsCompanyUser : 1));
if (userType === 0 || userType === 2) {
// 企业用户:显示发布岗位
baseItems.splice(1, 0, {
id: 1,
text: '发布岗位',
path: '/pages/job/publishJob',
iconPath: '/static/tabbar/post.png',
selectedIconPath: '/static/tabbar/posted.png',
centerItem: false,
badge: 0,
});
} else {
// 求职者用户(包括未登录状态):显示招聘会
baseItems.splice(1, 0, {
id: 1,
text: '招聘会',
path: '/pages/careerfair/careerfair',
iconPath: '/static/tabbar/post.png',
selectedIconPath: '/static/tabbar/posted.png',
centerItem: false,
badge: readMsg.badges[1]?.count || 0,
});
}
return baseItems;
};
// 根据用户类型生成不同的导航栏配置
const tabbarList = computed(() => {
return generateTabbarList();
});
// 强制刷新tabbar的方法
const forceRefresh = () => {
// 触发响应式更新
const cachedUserInfo = uni.getStorageSync('userInfo') || {};
const currentUserType = userInfo.value?.isCompanyUser !== undefined ? userInfo.value.isCompanyUser : (cachedUserInfo.isCompanyUser !== undefined ? cachedUserInfo.isCompanyUser : 1);
};
// 监听用户类型变化只监听isCompanyUser字段
watch(() => userInfo.value?.isCompanyUser, (newIsCompanyUser, oldIsCompanyUser) => {
if (newIsCompanyUser !== oldIsCompanyUser) {
// 强制触发computed重新计算
forceRefresh();
}
}, { immediate: true });
// 监听用户信息变化(包括登录状态)
watch(() => userInfo.value, (newUserInfo, oldUserInfo) => {
if (newUserInfo !== oldUserInfo) {
// 强制触发computed重新计算
forceRefresh();
}
}, { immediate: true, deep: true });
// 切换tab
const switchTab = (item, index) => {
// 检查是否为"发布岗位"页面,需要判断企业信息是否完整
if (item.path === '/pages/job/publishJob') {
// 检查用户是否已登录
const token = uni.getStorageSync('token') || '';
const hasLogin = userStore.hasLogin;
if (!token || !hasLogin) {
// 未登录,发送事件显示登录弹窗
uni.$emit('showLoginModal');
return; // 不进行页面跳转
}
// 已登录,检查企业信息是否完整
const cachedUserInfo = uni.getStorageSync('userInfo') || {};
const storeUserInfo = userInfo.value || {};
const currentUserInfo = storeUserInfo.id ? storeUserInfo : cachedUserInfo;
// 判断企业信息字段company是否为null或undefined
if (!currentUserInfo.company || currentUserInfo.company === null) {
// 企业信息为空,跳转到企业信息补全页面
uni.navigateTo({
url: '/pages/complete-info/company-info',
});
} else {
// 企业信息完整,跳转到发布岗位页面
uni.navigateTo({
url: '/pages/job/publishJob',
});
}
currentItem.value = item.id;
return;
}
// 检查是否为"我的"页面,需要登录验证和用户类型判断
if (item.path === '/pages/mine/mine') {
// 检查用户是否已登录
const token = uni.getStorageSync('token') || '';
const hasLogin = userStore.hasLogin;
if (!token || !hasLogin) {
// 未登录,发送事件显示登录弹窗
uni.$emit('showLoginModal');
return; // 不进行页面跳转
}
// 已登录,根据用户类型跳转到不同的"我的"页面
const cachedUserInfo = uni.getStorageSync('userInfo') || {};
const storeIsCompanyUser = userInfo.value?.isCompanyUser;
const cachedIsCompanyUser = cachedUserInfo.isCompanyUser;
// 获取用户类型
const userType = Number(storeIsCompanyUser !== undefined ? storeIsCompanyUser : (cachedIsCompanyUser !== undefined ? cachedIsCompanyUser : 1));
let targetPath = '/pages/mine/mine'; // 默认求职者页面
if (userType === 0) {
// 企业用户,跳转到企业我的页面
targetPath = '/pages/mine/company-mine';
} else {
// 求职者或其他用户类型,跳转到普通我的页面
targetPath = '/pages/mine/mine';
}
// 跳转到对应的页面
uni.navigateTo({
url: targetPath,
});
currentItem.value = item.id;
return;
}
// 判断是否为 tabBar 页面
const tabBarPages = [
'/pages/index/index',
'/pages/careerfair/careerfair',
'/pages/chat/chat',
'/pages/msglog/msglog',
'/pages/mine/mine'
];
if (tabBarPages.includes(item.path)) {
// TabBar 页面使用 redirectTo 避免页面栈溢出
uni.redirectTo({
url: item.path,
});
} else {
// 非 TabBar 页面使用 navigateTo
uni.navigateTo({
url: item.path,
});
}
currentItem.value = item.id;
};
onMounted(() => {
currentItem.value = props.currentPage;
// 调试信息显示当前用户状态和tabbar配置
forceRefresh();
});
</script>
<style lang="scss" scoped>
.custom-tabbar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
height: 88rpx;
background-color: #ffffff;
border-top: 1rpx solid #e5e5e5;
display: flex;
align-items: center;
padding-bottom: env(safe-area-inset-bottom);
z-index: 999;
box-shadow: 0 -2rpx 10rpx rgba(0, 0, 0, 0.1);
}
.tabbar-item {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
color: #5E5F60;
font-size: 22rpx;
position: relative;
cursor: pointer;
}
.tabbar-icon {
width: 44rpx;
height: 44rpx;
margin-bottom: 4rpx;
position: relative;
}
.tabbar-icon image {
width: 100%;
height: 100%;
}
.tabbar-text {
font-size: 20rpx;
line-height: 1;
transition: color 0.3s ease;
}
.tabbar-text.active {
color: #256BFA;
font-weight: 500;
}
.badge {
position: absolute;
top: 4rpx;
right: 20rpx;
min-width: 30rpx;
height: 30rpx;
background-color: #ff4444;
color: #fff;
font-size: 18rpx;
border-radius: 15rpx;
text-align: center;
line-height: 30rpx;
padding: 0 10rpx;
transform: scale(0.8);
}
/* 中间按钮特殊样式 */
.tabbar-item:has(.center-item) {
.tabbar-icon {
width: 60rpx;
height: 60rpx;
margin-bottom: 0;
}
}
</style>

View File

@@ -31,13 +31,13 @@ const userTypes = [
{ value: 3, label: '政府人员' }
];
const currentUserType = computed(() => userInfo.value?.userType || 0);
const currentUserType = computed(() => userInfo.value?.isCompanyUser !== undefined ? userInfo.value.isCompanyUser : 0);
const switchUserType = (userType) => {
console.log('切换用户类型:', userType);
console.log('切换前 userInfo:', userInfo.value);
userInfo.value.userType = userType;
userInfo.value.isCompanyUser = userType;
console.log('切换后 userInfo:', userInfo.value);

View File

@@ -13,7 +13,30 @@
<view class="btn-confirm" @click="confirm">确认</view>
</view>
<view class="popup-list">
<!-- 多选模式 -->
<view v-if="multiSelect" class="multi-select-list">
<view v-if="!processedListData[0] || processedListData[0].length === 0" class="empty-tip">
暂无数据
</view>
<view
v-else
class="skill-tags-container"
>
<view
v-for="(item, index) in processedListData[0]"
:key="index"
class="skill-tag"
:class="{ 'skill-tag-active': selectedValues.includes(item[this.rowKey]) }"
@click.stop="toggleSelect(item)"
@touchstart.stop="toggleSelect(item)"
>
<text class="skill-tag-text">{{ getLabel(item) }}</text>
</view>
</view>
</view>
<!-- 单选模式 -->
<picker-view
v-else
indicator-style="height: 84rpx;"
:value="selectedIndex"
@change="bindChange"
@@ -54,6 +77,8 @@ export default {
rowKey: 'value',
selectedItems: [],
unit: '',
multiSelect: false,
selectedValues: [],
};
},
computed: {
@@ -66,6 +91,13 @@ export default {
});
});
},
// 计算选中的项目
computedSelectedItems() {
if (!this.multiSelect) return this.selectedItems;
return this.processedListData[0] ? this.processedListData[0].filter(item =>
this.selectedValues.includes(item[this.rowKey])
) : [];
},
},
methods: {
open(newConfig = {}) {
@@ -80,7 +112,10 @@ export default {
rowKey = 'value',
maskClick = false,
defaultIndex = [],
multiSelect = false,
defaultValues = [],
} = newConfig;
this.reset();
if (title) this.title = title;
if (typeof success === 'function') this.confirmCallback = success;
@@ -92,10 +127,16 @@ export default {
this.rowKey = rowKey;
this.maskClick = maskClick;
this.unit = unit;
this.multiSelect = multiSelect;
this.selectedIndex =
defaultIndex.length === this.listData.length ? defaultIndex : new Array(this.listData.length).fill(0);
this.selectedItems = this.selectedIndex.map((val, index) => this.processedListData[index][val]);
if (multiSelect) {
this.selectedValues = defaultValues || [];
} else {
this.selectedIndex =
defaultIndex.length === this.listData.length ? defaultIndex : new Array(this.listData.length).fill(0);
this.selectedItems = this.selectedIndex.map((val, index) => this.processedListData[index][val]);
}
this.$nextTick(() => {
this.$refs.popup.open();
});
@@ -117,6 +158,22 @@ export default {
getLabel(item) {
return item?.[this.rowLabel] ?? '';
},
toggleSelect(item) {
if (!item || !this.rowKey || !item[this.rowKey]) {
return;
}
const value = item[this.rowKey];
const index = this.selectedValues.indexOf(value);
if (index > -1) {
// 取消选中
this.selectedValues.splice(index, 1);
} else {
// 选中
this.selectedValues.push(value);
}
},
setColunm(index, list) {
if (index > this.listData.length) {
return console.warn('最长' + this.listData.length);
@@ -135,7 +192,14 @@ export default {
}
try {
const result = await callback(this.selectedIndex, this.selectedItems); // 无论是 async 还是返回 Promise 的函数都可以 await
let result;
if (this.multiSelect) {
// 多选模式:传递 selectedValues 和 selectedItems
result = await callback(this.selectedValues, this.computedSelectedItems);
} else {
// 单选模式:传递 selectedIndex 和 selectedItems
result = await callback(this.selectedIndex, this.selectedItems);
}
if (result !== false) {
this.$refs.popup.close();
}
@@ -154,6 +218,8 @@ export default {
this.rowKey = 'value';
this.selectedItems = [];
this.unit = '';
this.multiSelect = false;
this.selectedValues = [];
},
},
};
@@ -224,10 +290,83 @@ export default {
color: #666d7f;
line-height: 38rpx;
}
.btn-confirm {
.btn-confirm {
font-weight: 400;
font-size: 32rpx;
color: #256bfa;
}
}
.multi-select-list {
padding: 20rpx 30rpx;
max-height: calc(60vh - 120rpx);
overflow-y: auto;
}
.empty-tip {
text-align: center;
padding: 60rpx 0;
color: #999999;
font-size: 28rpx;
}
.skill-tags-container {
display: flex;
flex-wrap: wrap;
gap: 16rpx;
align-items: flex-start;
}
.skill-tag {
display: inline-flex;
align-items: center;
padding: 12rpx 20rpx;
border-radius: 20rpx;
background-color: #f8f9fa;
border: 2rpx solid #e8eaee;
cursor: pointer;
transition: all 0.3s ease;
font-size: 24rpx;
color: #333333;
white-space: nowrap;
user-select: none;
&:hover {
background-color: #e9ecef;
border-color: #d0d0d0;
transform: translateY(-1rpx);
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
}
&:active {
transform: translateY(0);
background-color: #dee2e6;
}
.skill-tag-text {
font-size: 24rpx;
color: inherit;
line-height: 1.2;
font-weight: 400;
font-size: 32rpx;
color: #256bfa;
}
&.skill-tag-active {
background-color: #256bfa;
border-color: #256bfa;
color: #ffffff;
box-shadow: 0 2rpx 8rpx rgba(37, 107, 250, 0.3);
.skill-tag-text {
color: #ffffff;
font-weight: 500;
}
&:hover {
background-color: #1e5ce6;
border-color: #1e5ce6;
transform: translateY(-1rpx);
box-shadow: 0 4rpx 12rpx rgba(37, 107, 250, 0.4);
}
}
}
</style>

View File

@@ -10,7 +10,7 @@
>
<image :src="currentItem == item.id ? item.selectedIconPath : item.iconPath"></image>
</view>
<view class="badge" v-if="item.badge">{{ item.badge }}</view>
<view class="badge" v-if="item.badge && item.badge > 0">{{ item.badge }}</view>
<view class="item-bottom" :class="[currentItem == item.id ? 'item-active' : '']">
<text>{{ item.text }}</text>
</view>
@@ -19,7 +19,7 @@
</template>
<script setup>
import { ref, onMounted, computed } from 'vue';
import { ref, onMounted, computed, watch, nextTick } from 'vue';
import { useReadMsg } from '@/stores/useReadMsg';
import { storeToRefs } from 'pinia';
import useUserStore from '@/stores/useUserStore';
@@ -36,35 +36,60 @@ const readMsg = useReadMsg();
const { userInfo } = storeToRefs(useUserStore());
const currentItem = ref(0);
// 根据用户类型生成不同的导航栏配置
const tabbarList = computed(() => {
// 监听用户类型变化重新生成tabbar配置
watch(() => userInfo.value?.isCompanyUser, (newIsCompanyUser, oldIsCompanyUser) => {
console.log('midell-box用户类型变化监听:', { newIsCompanyUser, oldIsCompanyUser, userInfo: userInfo.value });
if (newIsCompanyUser !== oldIsCompanyUser) {
console.log('用户类型发生变化重新生成tabbar:', newIsCompanyUser);
// tabbarList是computed会自动重新计算
// 强制触发响应式更新
nextTick(() => {
console.log('tabbar配置已更新:', tabbarList.value);
});
}
}, { immediate: true });
// 监听用户信息变化(包括登录状态)
watch(() => userInfo.value, (newUserInfo, oldUserInfo) => {
console.log('midell-box用户信息变化监听:', { newUserInfo, oldUserInfo });
if (newUserInfo !== oldUserInfo) {
console.log('用户信息发生变化重新生成tabbar');
// 强制触发响应式更新
nextTick(() => {
console.log('tabbar配置已更新:', tabbarList.value);
});
}
}, { immediate: true, deep: true });
// 生成tabbar配置的函数
const generateTabbarList = () => {
const baseItems = [
{
id: 0,
text: '首页',
text: '职位2',
path: '/pages/index/index',
iconPath: '../../static/tabbar/calendar.png',
selectedIconPath: '../../static/tabbar/calendared.png',
centerItem: false,
badge: readMsg.badges[0].count,
badge: readMsg.badges[0]?.count || 0,
},
{
id: 2,
text: '',
text: 'AI+',
path: '/pages/chat/chat',
iconPath: '../../static/tabbar/logo3.png',
selectedIconPath: '../../static/tabbar/logo3.png',
centerItem: true,
badge: readMsg.badges[2].count,
badge: readMsg.badges[2]?.count || 0,
},
{
id: 3,
text: '消息',
text: '消息2',
path: '/pages/msglog/msglog',
iconPath: '../../static/tabbar/chat4.png',
selectedIconPath: '../../static/tabbar/chat4ed.png',
centerItem: false,
badge: readMsg.badges[3].count,
badge: readMsg.badges[3]?.count || 0,
},
{
id: 4,
@@ -73,12 +98,12 @@ const tabbarList = computed(() => {
iconPath: '../../static/tabbar/mine.png',
selectedIconPath: '../../static/tabbar/mined.png',
centerItem: false,
badge: readMsg.badges[4].count,
badge: readMsg.badges[4]?.count || 0,
},
];
// 根据用户类型添加不同的导航项
const userType = userInfo.value?.userType || 0;
// 根据用户类型添加不同的导航项,未登录时默认为求职者
const userType = userInfo.value?.isCompanyUser !== undefined ? userInfo.value.isCompanyUser : 1;
if (userType === 0) {
// 企业用户:显示发布岗位,隐藏招聘会
@@ -86,13 +111,13 @@ const tabbarList = computed(() => {
id: 1,
text: '发布岗位',
path: '/pages/job/publishJob',
iconPath: '../../static/tabbar/publish-job.svg',
selectedIconPath: '../../static/tabbar/publish-job-selected.svg',
iconPath: '../../static/tabbar/post.png',
selectedIconPath: '../../static/tabbar/posted.png',
centerItem: false,
badge: 0,
});
} else {
// 普通用户、网格员、政府人员:显示招聘会
// 求职者用户(包括未登录状态):显示招聘会
baseItems.splice(1, 0, {
id: 1,
text: '招聘会',
@@ -100,11 +125,16 @@ const tabbarList = computed(() => {
iconPath: '../../static/tabbar/post.png',
selectedIconPath: '../../static/tabbar/posted.png',
centerItem: false,
badge: readMsg.badges[1].count,
badge: readMsg.badges[1]?.count || 0,
});
}
return baseItems;
};
// 根据用户类型生成不同的导航栏配置
const tabbarList = computed(() => {
return generateTabbarList();
});
onMounted(() => {
@@ -114,7 +144,7 @@ onMounted(() => {
});
const changeItem = (item) => {
// 判断是否为 tabBar 页面
// 判断是否为 TabBar 页面
const tabBarPages = [
'/pages/index/index',
'/pages/careerfair/careerfair',
@@ -124,12 +154,12 @@ const changeItem = (item) => {
];
if (tabBarPages.includes(item.path)) {
// tabBar 页面使用 switchTab
uni.switchTab({
// TabBar 页面使用 redirectTo 避免页面栈溢出
uni.redirectTo({
url: item.path,
});
} else {
// 非 tabBar 页面使用 navigateTo
// 非 TabBar 页面使用 navigateTo
uni.navigateTo({
url: item.path,
});

View File

@@ -102,6 +102,7 @@
<script setup>
import { ref, inject } from 'vue';
import useUserStore from '@/stores/useUserStore';
import { tabbarManager } from '@/utils/tabbarManager';
const { $api } = inject('globalFunction');
const { loginSetToken } = useUserStore();
@@ -162,22 +163,33 @@ const getPhoneNumber = (e) => {
userType: userType.value
}, 'post').then((resData) => {
uni.hideLoading();
console.log(resData, 'resume.idCard');
if (resData.token) {
// 登录成功存储token
loginSetToken(resData.token).then((resume) => {
// 更新用户类型到缓存
if (resData.isCompanyUser !== undefined) {
console.log(resData.isCompanyUser, 'resData.isCompanyUser');
const userInfo = uni.getStorageSync('userInfo') || {};
userInfo.isCompanyUser = Number(resData.isCompanyUser); // 0-企业用户1-求职者
uni.setStorageSync('userInfo', userInfo);
}
$api.msg('登录成功');
// 刷新tabbar以显示正确的用户类型
tabbarManager.refreshTabBar();
close();
emit('success');
// 根据用户类型跳转到不同的信息补全页面
if (!resume.data.jobTitleId) {
if (userType.value === 1) {
if (!resume.jobTitleId) {
console.log(resume, 'resume.idCard');
if (userType.value === 1 && !resData.idCard) {
// 求职者跳转到个人信息补全页面
uni.navigateTo({
url: '/pages/complete-info/complete-info?step=1'
});
} else if (userType.value === 0) {
} else if (userType.value === 0 && !resData.idCard) {
// 招聘者跳转到企业信息补全页面
uni.navigateTo({
url: '/pages/complete-info/company-info'
@@ -233,6 +245,15 @@ const wxLogin = () => {
if (resData.token) {
loginSetToken(resData.token).then((resume) => {
console.log(resData, 'resData.isCompanyUser');
// 更新用户类型到缓存
if (resData.isCompanyUser) {
console.log(resData.isCompanyUser, 'resData.isCompanyUser');
const userInfo = uni.getStorageSync('userInfo') || {};
userInfo.isCompanyUser = Number(resData.isCompanyUser); // 0-企业用户1-求职者
uni.setStorageSync('userInfo', userInfo);
}
$api.msg('登录成功');
close();
emit('success');
@@ -285,7 +306,16 @@ const wxLogin = () => {
}, 'post').then((resData) => {
if (resData.token) {
loginSetToken(resData.token).then((resume) => {
// 更新用户类型到缓存
if (resData.isCompanyUser !== undefined) {
const userInfo = uni.getStorageSync('userInfo') || {};
userInfo.isCompanyUser = resData.isCompanyUser ? 0 : 1; // 0-企业用户1-求职者
uni.setStorageSync('userInfo', userInfo);
}
$api.msg('登录成功');
// 刷新tabbar以显示正确的用户类型
tabbarManager.refreshTabBar();
close();
emit('success');
@@ -330,7 +360,16 @@ const testLogin = () => {
uni.hideLoading();
loginSetToken(resData.token).then((resume) => {
// 更新用户类型到缓存
if (resData.isCompanyUser !== undefined) {
const userInfo = uni.getStorageSync('userInfo') || {};
userInfo.isCompanyUser = resData.isCompanyUser ? 0 : 1; // 0-企业用户1-求职者
uni.setStorageSync('userInfo', userInfo);
}
$api.msg('测试登录成功');
// 刷新tabbar以显示正确的用户类型
tabbarManager.refreshTabBar();
close();
emit('success');

View File

@@ -1,6 +1,9 @@
export default {
// baseUrl: 'http://39.98.44.136:8080', // 测试
baseUrl: 'http://ks.zhaopinzao8dian.com/api/ks', // 测试
LCBaseUrl:'http://10.110.145.145:9100',//招聘、培训、帮扶
LCBaseUrlInner:'http://10.110.145.145:10100',//内网端口
// sseAI+
// StreamBaseURl: 'http://39.98.44.136:8000',
StreamBaseURl: 'https://qd.zhaopinzao8dian.com/ai',
@@ -70,4 +73,4 @@ export default {
desc: '融合海量岗位、智能简历匹配、竞争力分析,助你精准锁定理想职位!',
imgUrl: 'https://qd.zhaopinzao8dian.com/file/csn/qd_shareLogo.jpg',
}
}
}

View File

@@ -0,0 +1,117 @@
# 企业我的页面功能说明
## 功能概述
本功能为企业用户提供了专门的"我的"页面和企业信息展示页面,实现了根据用户类型显示不同内容的功能。
## 页面结构
### 1. 企业我的页面 (`pages/mine/company-mine.vue`)
- **功能**: 企业用户的个人中心页面
- **特点**:
- 显示企业头像、名称和信息完整度
- 包含服务专区(实名认证、通知与提醒)
- 提供退出登录功能
- 点击头像区域可跳转到企业信息页面
### 2. 企业信息展示页面 (`pages/mine/company-info.vue`)
- **功能**: 显示详细的企业信息
- **特点**:
- 显示企业头像编辑功能
- 展示完整的企业信息(名称、统一社会代码、注册地点等)
- 支持编辑各项企业信息
- 包含企业联系人和法人信息
### 3. 修改后的我的页面 (`pages/mine/mine.vue`)
- **功能**: 根据用户类型显示不同的内容
- **特点**:
- 企业用户显示企业信息卡片
- 求职者用户显示个人简历信息
- 自动根据 `userInfo.isCompanyUser` 字段判断用户类型
## 用户类型判断
系统通过 `userInfo.isCompanyUser` 字段来判断用户类型:
- `0` = 企业用户
- `1` = 求职者
- `2` = 网格员
- `3` = 政府人员
## 页面跳转逻辑
### 从我的页面跳转
- **企业用户**: 点击头像区域 → 跳转到企业信息页面 (`/pages/mine/company-info`)
- **求职者用户**: 点击头像区域 → 跳转到简历页面 (`/packageA/pages/myResume/myResume`)
### 企业信息页面功能
- 点击头像 → 编辑头像(调用相册选择图片)
- 点击各项信息 → 跳转到对应的编辑页面(需要后续开发)
## 路由配置
新增的路由配置:
```json
{
"path": "pages/mine/company-mine",
"style": {
"navigationBarTitleText": "我的",
"navigationStyle": "custom"
}
},
{
"path": "pages/mine/company-info",
"style": {
"navigationBarTitleText": "企业信息",
"navigationStyle": "custom"
}
}
```
## 测试页面
创建了测试页面 `pages/test/company-mine-test.vue` 用于测试功能:
- 用户类型切换测试
- 页面跳转测试
- 用户信息显示
## 样式特点
### 企业信息卡片
- 白色背景,圆角设计
- 阴影效果现代化UI
- 头像圆形显示
- 信息完整度显示
### 企业信息页面
- 清晰的信息层级
- 可点击的编辑区域
- 统一的视觉风格
## 数据流
1. 用户登录时设置 `userInfo.isCompanyUser` 字段
2. 我的页面根据此字段判断显示内容
3. 企业用户点击头像跳转到企业信息页面
4. 企业信息页面展示详细的企业数据
## 后续开发建议
1. **编辑功能**: 为每个信息项创建对应的编辑页面
2. **数据接口**: 连接真实的企业信息API
3. **头像上传**: 完善头像上传功能
4. **表单验证**: 添加企业信息编辑的表单验证
5. **权限控制**: 根据用户权限控制可编辑的字段
## 使用方法
1. 在测试页面切换用户类型为企业用户
2. 访问我的页面,查看企业信息卡片
3. 点击头像区域跳转到企业信息页面
4. 在企业信息页面查看详细的企业信息
## 注意事项
- 确保用户类型字段正确设置
- 企业信息数据需要从后端API获取
- 头像上传功能需要配置服务器接口
- 编辑页面需要根据实际需求进行开发

View File

@@ -0,0 +1,187 @@
# 企业搜索功能实现说明
## 功能概述
根据用户类型对发布岗位页面的"招聘公司"输入框进行不同的交互处理:
- **企业用户**:直接输入公司名称
- **网格员**:点击输入框跳转到企业搜索页面,支持模糊查询
## 实现细节
### 1. 用户类型判断
通过 `userInfo.isCompanyUser` 字段判断用户类型:
- `0`: 企业用户
- `1`: 求职者
- `2`: 网格员
- `3`: 政府人员
### 2. 页面修改
#### 发布岗位页面 (`pages/job/publishJob.vue`)
**模板修改:**
```vue
<!-- 企业用户直接输入 -->
<input
v-if="isCompanyUser"
class="input"
placeholder="请输入公司名称"
v-model="formData.companyName"
/>
<!-- 网格员点击跳转到搜索页面 -->
<view
v-else
class="company-selector"
@click="openCompanySearch"
>
<view class="selector-text" :class="{ 'placeholder': !formData.companyName }">
{{ formData.companyName || '请选择企业' }}
</view>
<view class="selector-icon">
<view class="arrow-icon">></view>
</view>
</view>
```
**脚本修改:**
- 添加用户类型判断逻辑
- 添加打开企业搜索页面的方法
- 添加页面显示时处理返回数据的逻辑
#### 企业搜索页面 (`pages/job/companySearch.vue`)
**功能特性:**
- 搜索框支持实时输入
- 防抖节流500ms延迟执行搜索
- 调用接口:`/app/company/likeList`,参数:`name`
- 支持企业选择和数据回传
- 空状态和加载状态处理
**核心代码:**
```javascript
// 防抖搜索
const onSearchInput = () => {
if (debounceTimer) {
clearTimeout(debounceTimer);
}
debounceTimer = setTimeout(() => {
if (searchKeyword.value.trim()) {
searchCompanies();
} else {
searchResults.value = [];
}
}, 500);
};
// 搜索企业
const searchCompanies = async () => {
const response = await createRequest('/app/company/likeList', {
name: searchKeyword.value.trim()
}, 'GET', false);
if (response.code === 200) {
searchResults.value = response.data || [];
}
};
// 选择企业
const selectCompany = (company) => {
uni.navigateBack({
success: () => {
getApp().globalData = getApp().globalData || {};
getApp().globalData.selectedCompany = company;
}
});
};
```
### 3. 数据传递
使用全局数据传递选中的企业信息:
**企业搜索页面:**
```javascript
// 选择企业后设置全局数据
getApp().globalData.selectedCompany = company;
```
**发布岗位页面:**
```javascript
// 页面显示时检查全局数据
onShow(() => {
const app = getApp();
if (app.globalData && app.globalData.selectedCompany) {
const selectedCompany = app.globalData.selectedCompany;
formData.companyName = selectedCompany.name;
formData.companyId = selectedCompany.id;
// 清除全局数据
app.globalData.selectedCompany = null;
}
});
```
### 4. 页面配置
`pages.json` 中添加了企业搜索页面配置:
```json
{
"path": "pages/job/companySearch",
"style": {
"navigationBarTitleText": "选择企业",
"navigationStyle": "custom",
"disableScroll": false,
"enablePullDownRefresh": false,
"backgroundColor": "#f5f5f5"
}
}
```
### 5. 测试页面
创建了测试页面 `pages/test/company-search-test.vue` 用于验证功能:
- 用户类型切换
- 功能说明展示
- 直接跳转到发布岗位页面测试
## 使用说明
### 企业用户
1. 进入发布岗位页面
2. 招聘公司输入框为普通输入框
3. 直接输入公司名称
### 网格员
1. 进入发布岗位页面
2. 点击"招聘公司"输入框
3. 跳转到企业搜索页面
4. 输入企业名称进行搜索(支持防抖)
5. 选择企业后自动返回
6. 企业名称显示在输入框中
## 技术特点
1. **防抖节流**搜索输入500ms延迟避免频繁请求
2. **用户类型判断**:根据 `isCompanyUser` 字段动态显示不同交互
3. **数据传递**:使用全局数据实现页面间数据传递
4. **响应式设计**:支持不同屏幕尺寸
5. **错误处理**:完善的错误提示和空状态处理
## 接口说明
**搜索企业接口:**
- 地址:`/app/company/likeList`
- 方法:`GET`
- 参数:`name` (企业名称)
- 返回:企业列表数据
## 注意事项
1. 确保用户类型字段 `isCompanyUser` 正确设置
2. 搜索接口需要支持模糊查询
3. 企业数据需要包含 `id``name` 字段
4. 防抖时间可根据实际需求调整

View File

@@ -0,0 +1,129 @@
# 自定义TabBar使用说明
## 功能概述
本项目实现了基于用户类型的动态自定义TabBar支持根据用户登录状态和类型显示不同的导航项
- **未登录状态**默认显示求职者tabbar职位 + 招聘会 + AI+ + 消息 + 我的)
- **企业用户userType=0**:显示"发布岗位"导航
- **求职者用户userType=1,2,3**:显示"招聘会"导航
## 实现方案
### 1. 微信小程序原生自定义TabBar
`custom-tab-bar/` 目录下创建了微信小程序原生自定义TabBar组件
- `index.js` - 组件逻辑根据用户类型动态生成TabBar配置
- `index.wxml` - 模板文件
- `index.wxss` - 样式文件
- `index.json` - 组件配置文件
### 2. UniApp兼容的自定义TabBar组件
创建了 `components/CustomTabBar/CustomTabBar.vue` 组件,支持多端兼容:
- 支持微信小程序、H5、APP等多端
- 响应式设计,根据用户类型动态显示
- 支持消息徽章显示
- 支持页面跳转逻辑
### 3. 配置修改
`pages.json` 中启用了自定义TabBar
```json
"tabBar": {
"custom": true,
// ... 其他配置
}
```
## 使用方法
### 1. 在页面中引入自定义TabBar
```vue
<template>
<view class="page">
<!-- 页面内容 -->
<!-- 自定义tabbar -->
<CustomTabBar :currentPage="0" />
</view>
</template>
```
### 2. 用户类型判断
组件会自动从 `useUserStore` 中获取用户信息,根据用户登录状态和 `userInfo.userType` 字段判断用户类型:
```javascript
// 用户类型说明
// 未登录: 默认显示求职者tabbar
// 0: 企业用户 - 显示"发布岗位"
// 1: 求职者 - 显示"招聘会"
// 2: 网格员 - 显示"招聘会"
// 3: 政府人员 - 显示"招聘会"
```
### 3. 动态切换用户类型
当用户登录状态或类型发生变化时TabBar会自动更新
```javascript
// 未登录状态自动显示求职者tabbar
// 登录后根据用户类型显示对应tabbar
// 切换用户类型
userInfo.value.userType = 1; // 切换到求职者
uni.setStorageSync('userInfo', userInfo.value);
// 登出时清除用户信息,自动回到未登录状态
userStore.logOut(false);
```
## 页面配置
### 已配置的页面
- `pages/index/index.vue` - 首页currentPage: 0
- `pages/careerfair/careerfair.vue` - 招聘会页面currentPage: 1
- `pages/chat/chat.vue` - AI+页面currentPage: 2
- `pages/msglog/msglog.vue` - 消息页面currentPage: 3
- `pages/mine/mine.vue` - 我的页面currentPage: 4
### 测试页面
- `pages/test/tabbar-test.vue` - TabBar功能测试页面
## 技术特点
1. **响应式设计**:根据用户类型动态显示不同的导航项
2. **多端兼容**支持微信小程序、H5、APP等平台
3. **消息徽章**:支持显示未读消息数量
4. **页面跳转**智能判断tabBar页面和普通页面的跳转方式
5. **用户类型监听**实时监听用户类型变化并更新TabBar
## 注意事项
1. 确保在 `pages.json` 中设置了 `"custom": true`
2. 每个页面的 `currentPage` 参数需要正确设置
3. 用户类型存储在 `userInfo.userType` 字段中
4. 组件会自动监听用户类型变化并更新显示
## 测试方法
1. 访问测试页面:`/pages/test/tabbar-test`
2. 切换不同的用户类型
3. 观察底部TabBar的变化
4. 测试页面跳转功能
## 故障排除
如果TabBar不显示或显示异常
1. 检查 `pages.json` 中的 `custom: true` 配置
2. 确认用户信息是否正确存储
3. 检查组件是否正确引入
4. 查看控制台是否有错误信息

17
main.js
View File

@@ -1,7 +1,7 @@
/*
* @Date: 2025-10-23 14:48:48
* @LastEditors: shirlwang
* @LastEditTime: 2025-10-31 16:51:55
* @LastEditTime: 2025-10-31 18:11:22
*/
import App from './App'
import * as Pinia from 'pinia'
@@ -19,12 +19,13 @@ import { request, get, post, packageRcRequest, packageRcGet, packageRcPost } fro
// 组件
import AppLayout from './components/AppLayout/AppLayout.vue';
import Empty from './components/empty/empty.vue';
import NoBouncePage from './components/NoBouncePage/NoBouncePage.vue'
import MsgTips from './components/MsgTips/MsgTips.vue'
import SelectPopup from './components/selectPopup/selectPopup.vue'
import SelectPopupPlugin from './components/selectPopup/selectPopupPlugin';
import RenderJobs from './components/renderJobs/renderJobs.vue';
import RenderCompanys from './components/renderCompanys/renderCompanys.vue';
import NoBouncePage from '@/components/NoBouncePage/NoBouncePage.vue'
import MsgTips from '@/components/MsgTips/MsgTips.vue'
import SelectPopup from '@/components/selectPopup/selectPopup.vue'
import SelectPopupPlugin from '@/components/selectPopup/selectPopupPlugin';
import RenderJobs from '@/components/renderJobs/renderJobs.vue';
import RenderCompanys from '@/components/renderCompanys/renderCompanys.vue';
// iconfont.css 已在 App.vue 中通过 @import 引入,无需在此处重复引入
// import Tabbar from '@/components/tabbar/midell-box.vue'
// 自动导入 directives 目录下所有指令
const directives = import.meta.glob('./directives/*.js', {
@@ -36,8 +37,6 @@ import { createSSRApp } from 'vue'
import uniIcons from './uni_modules/uni-icons/components/uni-icons/uni-icons.vue'
import uniPopup from './uni_modules/uni-popup/components/uni-popup/uni-popup.vue'
import storeRc from './utilsRc/store/index.js'
// const foldFeature = window.visualViewport && 'segments' in window.visualViewport
// console.log('是否支持多段屏幕:', foldFeature)

View File

@@ -50,7 +50,7 @@
"quickapp" : {},
/* */
"mp-weixin" : {
"appid" : "wxc0f37e549415bb4e",
"appid" : "wx9d1cbc11c8c40ba7",
"setting" : {
"urlCheck" : false,
"es6" : true,

View File

@@ -1,320 +1,329 @@
<template>
<AppLayout title="" :use-scroll-view="false">
<template #headerleft>
<view class="btnback">
<image src="@/static/icon/back.png" @click="navBack"></image>
</view>
</template>
<template #headerright>
<view class="btn mar_ri10">
<image
src="@/static/icon/collect3.png"
v-if="!companyInfo.isCollection"
@click="companyCollection"
></image>
<image src="@/static/icon/collect2.png" v-else @click="companyCollection"></image>
</view>
</template>
<view class="content">
<view class="content-top">
<view class="companyinfo-left">
<image src="@/static/icon/companyIcon.png" mode=""></image>
</view>
<view class="companyinfo-right">
<view class="row1">{{ companyInfo?.name }}</view>
<view class="row2">
<dict-tree-Label
v-if="companyInfo?.industry"
dictType="industry"
:value="companyInfo?.industry"
></dict-tree-Label>
<span v-if="companyInfo?.industry">&nbsp;</span>
<dict-Label dictType="scale" :value="companyInfo?.scale"></dict-Label>
</view>
</view>
</view>
<view class="conetent-info" :class="{ expanded: isExpanded }">
<view class="info-title">公司介绍</view>
<view class="info-desirption">{{ companyInfo.description }}</view>
<!-- <view class="info-title title2">公司地址</view>
<view class="locationCompany"></view> -->
</view>
<view class="expand" @click="expand">
<text>{{ isExpanded ? '收起' : '展开' }}</text>
<image
class="expand-img"
:class="{ 'expand-img-active': !isExpanded }"
src="@/static/icon/downs.png"
></image>
</view>
<scroll-view scroll-y class="Detailscroll-view" @scrolltolower="getJobsList('add')">
<view class="views">
<view class="Detail-title"><text class="title">在招职位</text></view>
<renderJobs
v-if="pageState.list.length"
:list="pageState.list"
:longitude="longitudeVal"
:latitude="latitudeVal"
></renderJobs>
<empty v-else pdTop="200"></empty>
</view>
</scroll-view>
<AppLayout title="" :use-scroll-view="false">
<template #headerleft>
<view class="btnback">
<image src="@/static/icon/back.png" @click="navBack"></image>
</view>
</template>
<template #headerright>
<view class="btn mar_ri10">
<image
src="@/static/icon/collect3.png"
v-if="!companyInfo.isCollection"
></image>
<image src="@/static/icon/collect2.png" v-else></image>
</view>
</template>
<view class="content">
<view class="content-top">
<view class="companyinfo-left">
<image src="@/static/icon/companyIcon.png" mode=""></image>
</view>
</AppLayout>
<view class="companyinfo-right">
<view class="row1">{{ companyInfo?.companyName }}</view>
<view class="row2">
{{ companyInfo?.scale }}
</view>
</view>
</view>
<view class="conetent-info" :class="{ expanded: isExpanded }">
<view class="info-title">公司介绍</view>
<view class="info-desirption">{{
companyInfo.companyIntroduction
}}</view>
<!-- <view class="info-title title2">公司地址</view>
<view class="locationCompany"></view> -->
</view>
<view class="expand" @click="expand">
<text>{{ isExpanded ? "收起" : "展开" }}</text>
<image
class="expand-img"
:class="{ 'expand-img-active': !isExpanded }"
src="@/static/icon/downs.png"
></image>
</view>
<scroll-view scroll-y class="Detailscroll-view">
<view class="views">
<view class="Detail-title"><text class="title">在招职位</text></view>
<template v-if="companyInfo.jobInfoList.length != 0">
<view v-for="job in companyInfo.jobInfoList" :key="job.id">
<view class="cards" @click="navTo(`/packageA/pages/post/post?jobId=${JSON.stringify(job)}`)">
<view class="card-company">
<text class="company">{{ job.jobTitle }}</text>
<view class="salary"> {{ job.salaryRange }} </view>
</view>
<view class="card-companyName">{{ job.companyName }}</view>
<view class="card-companyName">{{ job.jobDescription }}</view>
<view class="card-tags">
<view class="tag">
{{ job.educationRequirement }}
</view>
<view class="tag">
{{ job.experienceRequirement }}
</view>
<!-- <view class="tag">
{{ vacanciesTo(job.vacancies) }}
</view> -->
<view class="tag" v-if="job.jobRequirement">
{{ job.jobRequirement }}
</view>
</view>
<view class="card-bottom">
<view>{{ job.postingDate }}</view>
<view>
<convert-distance
:alat="job.latitude"
:along="job.longitude"
:blat="latitude"
:blong="longitude"
></convert-distance>
<dict-Label
class="mar_le10"
dictType="area"
:value="job.jobLocationAreaCode"
></dict-Label>
</view>
</view>
</view>
</view>
</template>
<empty v-else pdTop="200"></empty>
</view>
</scroll-view>
</view>
</AppLayout>
</template>
<script setup>
import point from '@/static/icon/point.png';
import { reactive, inject, watch, ref, onMounted, computed } from 'vue';
import { onLoad, onShow } from '@dcloudio/uni-app';
import dictLabel from '@/components/dict-Label/dict-Label.vue';
import { storeToRefs } from 'pinia';
import useLocationStore from '@/stores/useLocationStore';
import point from "@/static/icon/point.png";
import { reactive, inject, watch, ref, onMounted, computed } from "vue";
import { onLoad, onShow } from "@dcloudio/uni-app";
import dictLabel from "@/components/dict-Label/dict-Label.vue";
import { storeToRefs } from "pinia";
import useLocationStore from "@/stores/useLocationStore";
const { longitudeVal, latitudeVal } = storeToRefs(useLocationStore());
const { $api, navTo, vacanciesTo, navBack } = inject('globalFunction');
const { $api, navTo, vacanciesTo, navBack } = inject("globalFunction");
const isExpanded = ref(false);
const pageState = reactive({
page: 0,
list: [],
total: 0,
maxPage: 1,
pageSize: 10,
page: 0,
list: [],
total: 0,
maxPage: 1,
pageSize: 10,
});
const companyInfo = ref({});
onLoad((options) => {
console.log(options);
getCompanyInfo(options.companyId || options.bussinessId);
companyInfo.value = JSON.parse(options.job);
console.log(companyInfo.value, "companyInfo.value");
});
function companyCollection() {
const companyId = companyInfo.value.companyId;
if (companyInfo.value.isCollection) {
$api.createRequest(`/app/company/collection/${companyId}`, {}, 'DELETE').then((resData) => {
getCompanyInfo(companyId);
$api.msg('取消收藏成功');
});
} else {
$api.createRequest(`/app/company/collection/${companyId}`, {}, 'POST').then((resData) => {
getCompanyInfo(companyId);
$api.msg('收藏成功');
});
}
}
function getCompanyInfo(id) {
$api.createRequest(`/app/company/${id}`).then((resData) => {
companyInfo.value = resData.data;
getJobsList();
});
}
function getJobsList(type = 'add') {
if (type === 'refresh') {
pageState.page = 1;
pageState.maxPage = 1;
}
if (type === 'add' && pageState.page < pageState.maxPage) {
pageState.page += 1;
}
let params = {
current: pageState.page,
pageSize: pageState.pageSize,
};
$api.createRequest(`/app/company/job/${companyInfo.value.companyId}`, params).then((resData) => {
const { rows, total } = resData;
if (type === 'add') {
const str = pageState.pageSize * (pageState.page - 1);
const end = pageState.list.length;
const reslist = rows;
pageState.list.splice(str, end, ...reslist);
} else {
pageState.list = rows;
}
pageState.total = resData.total;
pageState.maxPage = Math.ceil(pageState.total / pageState.pageSize);
});
}
function expand() {
isExpanded.value = !isExpanded.value;
isExpanded.value = !isExpanded.value;
}
</script>
<style lang="stylus" scoped>
.btnback{
width: 64rpx;
height: 64rpx;
.btnback {
width: 64rpx;
height: 64rpx;
}
.btn {
display: flex;
justify-content: space-between;
align-items: center;
width: 52rpx;
height: 52rpx;
}
image {
height: 100%;
width: 100%;
}
.content{
height: 100%
display: flex;
flex-direction: column
.content-top{
padding: 28rpx
padding-top: 50rpx
display: flex
flex-direction: row
flex-wrap: nowrap
.companyinfo-left{
width: 96rpx;
height: 96rpx;
margin-right: 24rpx
}
.companyinfo-right{
.row1{
font-weight: 500;
font-size: 32rpx;
color: #333333;
}
.row2{
font-weight: 400;
font-size: 28rpx;
color: #6C7282;
line-height: 45rpx;
}
}
.btn {
display: flex;
justify-content: space-between;
align-items: center;
width: 52rpx;
height: 52rpx;
}
image {
height: 100%;
width: 100%;
}
.content {
height: 100%;
display: flex;
flex-direction: column;
.content-top {
padding: 28rpx;
padding-top: 50rpx;
display: flex;
flex-direction: row;
flex-wrap: nowrap;
.companyinfo-left {
width: 96rpx;
height: 96rpx;
margin-right: 24rpx;
}
.conetent-info{
padding: 0 28rpx
overflow: hidden;
max-height: 0rpx;
transition: max-height 0.3s ease;
.info-title{
font-weight: 600;
font-size: 28rpx;
color: #000000;
}
.info-desirption{
margin-top: 12rpx
font-weight: 400;
font-size: 28rpx;
color: #495265;
text-align: justified;
}
.title2{
margin-top: 48rpx
}
}
.expanded {
max-height: 1000rpx; // 足够显示完整内容
}
.expand{
display: flex
flex-wrap: nowrap
white-space: nowrap
justify-content: center
margin-top: 20rpx
margin-bottom: 28rpx
.companyinfo-right {
.row1 {
font-weight: 500;
font-size: 32rpx;
color: #333333;
}
.row2 {
font-weight: 400;
font-size: 28rpx;
color: #256BFA;
.expand-img{
width: 40rpx;
height: 40rpx;
}
.expand-img-active{
transform: rotate(180deg)
}
color: #6C7282;
line-height: 45rpx;
}
}
}
.Detailscroll-view{
flex: 1;
}
.conetent-info {
padding: 0 28rpx;
overflow: hidden;
background: #F4F4F4;
.views{
padding: 28rpx
.Detail-title{
font-weight: 600;
font-size: 32rpx;
color: #000000;
position: relative;
display: flex
justify-content: space-between
.title{
position: relative;
z-index: 2;
}
}
.Detail-title::before{
position: absolute
content: '';
left: -14rpx
bottom: 0
height: 16rpx;
width: 108rpx;
background: linear-gradient(to right, #CBDEFF, #FFFFFF);
border-radius: 8rpx;
z-index: 1;
}
.cards{
padding: 32rpx;
background: #FFFFFF;
box-shadow: 0rpx 0rpx 8rpx 0rpx rgba(0,0,0,0.04);
border-radius: 20rpx 20rpx 20rpx 20rpx;
margin-top: 22rpx;
.card-company{
display: flex
justify-content: space-between
align-items: flex-start
.company{
font-weight: 500;
font-size: 32rpx;
color: #333333;
}
.salary{
font-weight: 500;
font-size: 28rpx;
color: #4C6EFB;
white-space: nowrap
line-height: 48rpx
}
}
.card-companyName{
font-weight: 400;
font-size: 28rpx;
color: #6C7282;
}
.card-tags{
display: flex
flex-wrap: wrap
.tag{
width: fit-content;
height: 30rpx;
background: #F4F4F4;
border-radius: 4rpx;
padding: 6rpx 20rpx;
line-height: 30rpx;
font-weight: 400;
font-size: 24rpx;
color: #6C7282;
text-align: center;
margin-top: 14rpx;
white-space: nowrap
margin-right: 20rpx
}
}
.card-bottom{
margin-top: 32rpx
display: flex
justify-content: space-between
font-size: 28rpx;
color: #6C7282;
}
}
max-height: 0rpx;
transition: max-height 0.3s ease;
.info-title {
font-weight: 600;
font-size: 28rpx;
color: #000000;
}
.info-desirption {
margin-top: 12rpx;
font-weight: 400;
font-size: 28rpx;
color: #495265;
text-align: justified;
}
.title2 {
margin-top: 48rpx;
}
}
.expanded {
max-height: 1000rpx; // 足够显示完整内容
}
.expand {
display: flex;
flex-wrap: nowrap;
white-space: nowrap;
justify-content: center;
margin-top: 20rpx;
margin-bottom: 28rpx;
font-weight: 400;
font-size: 28rpx;
color: #256BFA;
.expand-img {
width: 40rpx;
height: 40rpx;
}
.expand-img-active {
transform: rotate(180deg);
}
}
}
.Detailscroll-view {
flex: 1;
overflow: hidden;
background: #F4F4F4;
.views {
padding: 28rpx;
.Detail-title {
font-weight: 600;
font-size: 32rpx;
color: #000000;
position: relative;
display: flex;
justify-content: space-between;
.title {
position: relative;
z-index: 2;
}
}
.Detail-title::before {
position: absolute;
content: '';
left: -14rpx;
bottom: 0;
height: 16rpx;
width: 108rpx;
background: linear-gradient(to right, #CBDEFF, #FFFFFF);
border-radius: 8rpx;
z-index: 1;
}
.cards {
padding: 32rpx;
background: #FFFFFF;
box-shadow: 0rpx 0rpx 8rpx 0rpx rgba(0, 0, 0, 0.04);
border-radius: 20rpx 20rpx 20rpx 20rpx;
margin-top: 22rpx;
.card-company {
display: flex;
justify-content: space-between;
align-items: flex-start;
.company {
font-weight: 500;
font-size: 32rpx;
color: #333333;
}
.salary {
font-weight: 500;
font-size: 28rpx;
color: #4C6EFB;
white-space: nowrap;
line-height: 48rpx;
}
}
.card-companyName {
font-weight: 400;
font-size: 28rpx;
color: #6C7282;
}
.card-tags {
display: flex;
flex-wrap: wrap;
.tag {
width: fit-content;
height: 30rpx;
background: #F4F4F4;
border-radius: 4rpx;
padding: 6rpx 20rpx;
line-height: 30rpx;
font-weight: 400;
font-size: 24rpx;
color: #6C7282;
text-align: center;
margin-top: 14rpx;
white-space: nowrap;
margin-right: 20rpx;
}
}
.card-bottom {
margin-top: 32rpx;
display: flex;
justify-content: space-between;
font-size: 28rpx;
color: #6C7282;
}
}
}
}
</style>

View File

@@ -1,16 +1,5 @@
<template>
<AppLayout
title="添加工作经历"
border
back-gorund-color="#ffffff"
:show-bg-image="false"
>
<template #headerleft>
<view class="btn mar_le20 button-click" @click="navBack">取消</view>
</template>
<template #headerright>
<view class="btn mar_ri20 button-click" @click="handleConfirm">确认</view>
</template>
<view class="page-container">
<view class="content">
<view class="content-input">
<view class="input-titile">公司名称</view>
@@ -44,8 +33,12 @@
<textarea class="textarea-con" v-model="formData.description" placeholder-style="font-size: 16px" maxlength="500" placeholder="请输入工作描述"/>
</view>
</view>
</AppLayout>
<!-- 底部确认按钮 -->
<view class="bottom-confirm-btn">
<view class="confirm-btn" @click="handleConfirm">确认</view>
</view>
</view>
</template>
<script setup>
@@ -206,7 +199,6 @@
console.log('页面类型:', pageType.value);
let resData;
alert(editData.value.id)
// 根据页面类型调用不同的接口
if (pageType.value === 'edit' && editData.value?.id) {
// 编辑模式:调用更新接口
@@ -234,12 +226,18 @@
</script>
<style lang="stylus" scoped>
.page-container {
min-height: 100vh;
background-color: #ffffff;
position: relative;
}
.content{
padding: 28rpx;
display: flex;
flex-direction: column;
justify-content: flex-start
height: calc(100% - 120rpx)
justify-content: flex-start;
padding-bottom: 120rpx;
}
.content-input
margin-bottom: 52rpx
@@ -328,6 +326,30 @@
line-height: 20rpx
display: flex
align-items: center
// 底部确认按钮样式
.bottom-confirm-btn
position: fixed
bottom: 0
left: 0
right: 0
background-color: #ffffff
padding: 20rpx 28rpx
border-top: 2rpx solid #EBEBEB
z-index: 999
.confirm-btn
width: 100%
height: 90rpx
background: #256BFA
border-radius: 12rpx
font-weight: 500
font-size: 32rpx
color: #FFFFFF
text-align: center
line-height: 90rpx
button-click: true
// .content-sex
// height: 110rpx;
// display: flex

File diff suppressed because it is too large Load Diff

View File

@@ -31,7 +31,7 @@
<input
class="input-con triangle"
disabled
v-if="!state.jobsText.length"
v-if="!state.jobsText || !state.jobsText.length"
placeholder="请选择您的求职岗位"
/>
<view class="input-nx" @click="changeJobs" v-else>
@@ -40,6 +40,7 @@
</view>
</view>
<SelectJobs ref="selectJobsModel"></SelectJobs>
<SelectPopup ref="selectPopupRef"></SelectPopup>
</AppLayout>
</template>
@@ -47,16 +48,24 @@
import { reactive, inject, watch, ref, onMounted, computed } from 'vue';
import { onLoad, onShow } from '@dcloudio/uni-app';
import SelectJobs from '@/components/selectJobs/selectJobs.vue';
import SelectPopup from '@/components/selectPopup/selectPopup.vue';
const { $api, navTo, navBack, config } = inject('globalFunction');
const openSelectPopup = inject('openSelectPopup');
// 创建本地的 openSelectPopup 函数
const openSelectPopup = (config) => {
if (selectPopupRef.value) {
selectPopupRef.value.open(config);
}
};
import { storeToRefs } from 'pinia';
import useUserStore from '@/stores/useUserStore';
import useDictStore from '@/stores/useDictStore';
const { userInfo } = storeToRefs(useUserStore());
const { getUserResume } = useUserStore();
const { dictLabel, oneDictData } = useDictStore();
const { dictLabel, oneDictData, getDictData } = useDictStore();
const selectJobsModel = ref();
const selectPopupRef = ref();
const percent = ref('0%');
const salay = [2, 5, 10, 15, 20, 25, 30, 50, 80, 100];
const state = reactive({
@@ -72,7 +81,9 @@ const fromValue = reactive({
area: '',
jobTitleId: [],
});
onLoad(() => {
onLoad(async () => {
// 初始化字典数据
await getDictData();
initLoad();
});
const confirm = () => {
@@ -99,8 +110,12 @@ function initLoad() {
fromValue.jobTitleId = userInfo.value.jobTitleId;
// 回显
state.areaText = dictLabel('area', Number(userInfo.value.area));
state.salayText = `${userInfo.value.salaryMin}-${userInfo.value.salaryMax}`;
state.jobsText = userInfo.value.jobTitle;
if (userInfo.value.salaryMin && userInfo.value.salaryMax) {
state.salayText = `${userInfo.value.salaryMin}-${userInfo.value.salaryMax}`;
} else {
state.salayText = '';
}
state.jobsText = userInfo.value.jobTitle || [];
const result = getFormCompletionPercent(fromValue);
percent.value = result;
}
@@ -123,7 +138,8 @@ const changeSalary = () => {
const copyri = JSON.parse(JSON.stringify(salay));
const [lf, ri] = e.detail.value;
const risalay = copyri.slice(lf, copyri.length);
this.setColunm(1, risalay);
// 更新右侧选项
state.risalay = risalay;
leftIndex = salayData[0];
}
},

View File

@@ -122,28 +122,23 @@
</view>
<!-- 4. 新增简历上传区域固定在页面底部 -->
<view class="resume-upload-section">
<!-- 上传按钮 -->
<!-- <view class="resume-upload-section">
<button class="upload-btn" @click="handleResumeUpload" :loading="isUploading" :disabled="isUploading">
<uni-icons type="cloud-upload" size="20"></uni-icons>
<!-- <image class="upload-icon" src="/static/icons/upload-file.png" mode="widthFix"></image> -->
<text class="upload-text">
{{ uploadedResumeName || '上传简历' }}
</text>
<!-- 已上传时显示重新上传文字 -->
<text class="reupload-text" v-if="uploadedResumeName">重新上传</text>
</button>
<!-- 上传说明 -->
<text class="upload-tip">支持 PDFWord 格式文件大小不超过 20MB</text>
<!-- 已上传文件信息可选 -->
<view class="uploaded-file-info" v-if="uploadedResumeName">
<image class="file-icon" src="/static/icons/file-icon.png" mode="widthFix"></image>
<text class="file-name">{{ uploadedResumeName }}</text>
<button class="delete-file-btn" size="mini" @click.stop="handleDeleteResume">删除</button>
</view>
</view>
</view> -->
</view>
</template>

View File

@@ -16,27 +16,29 @@
</template>
<view class="content" v-show="!isEmptyObject(jobInfo)">
<view class="content-top btn-feel">
<view class="top-salary">
<Salary-Expectation
:max-salary="jobInfo.maxSalary"
:min-salary="jobInfo.minSalary"
:is-month="true"
></Salary-Expectation>
</view>
<view class="top-name">{{ jobInfo.jobTitle }}</view>
<view class="top-info">
<view class="info-img"><image src="/static/icon/post12.png"></image></view>
<view class="info-text">
<dict-Label dictType="experience" :value="jobInfo.experience"></dict-Label>
<view style="background: #ffffff;padding: 24rpx;box-shadow: 0rpx 0rpx 8rpx 0rpx rgba(0,0,0,0.04);border-radius: 20rpx 20rpx 20rpx 20rpx;position: relative;overflow: hidden;">
<view class="top-salary">
<Salary-Expectation
:max-salary="jobInfo.maxSalary"
:min-salary="jobInfo.minSalary"
:is-month="true"
></Salary-Expectation>
</view>
<view class="info-img mar_le20"><image src="/static/icon/post13.png"></image></view>
<view class="info-text">
<dict-Label dictType="education" :value="jobInfo.education"></dict-Label>
<view class="top-name">{{ jobInfo.jobTitle }}</view>
<view class="top-info">
<view class="info-img"><image src="/static/icon/post12.png"></image></view>
<view class="info-text">
<dict-Label dictType="experience" :value="jobInfo.experience"></dict-Label>
</view>
<view class="info-img mar_le20"><image src="/static/icon/post13.png"></image></view>
<view class="info-text">
<dict-Label dictType="education" :value="jobInfo.education"></dict-Label>
</view>
</view>
<view class="position-source">
<text>来源&nbsp;</text>
{{ jobInfo.dataSource }}
</view>
</view>
<view class="position-source">
<text>来源&nbsp;</text>
{{ jobInfo.dataSource }}
</view>
</view>
<view class="ai-explain" v-if="jobInfo.isExplain">
@@ -98,7 +100,7 @@
></map>
</view>
</view>
<view class="content-card" v-if="!userInfo.isCompanyUser">
<view class="content-card" v-if="currentUserType !== 0">
<view class="card-title">
<text class="title">竞争力分析</text>
</view>
@@ -191,6 +193,12 @@ import RadarMap from './component/radarMap.vue';
import { storeToRefs } from 'pinia';
import useUserStore from '@/stores/useUserStore';
const { userInfo } = storeToRefs(useUserStore());
// 与首页一致的用户类型获取优先store兜底缓存
const currentUserType = computed(() => {
const storeIsCompanyUser = userInfo.value?.isCompanyUser;
const cachedIsCompanyUser = (uni.getStorageSync('userInfo') || {}).isCompanyUser;
return Number(storeIsCompanyUser !== undefined ? storeIsCompanyUser : cachedIsCompanyUser);
});
const { $api, navTo, getLenPx, parseQueryParams, navBack, isEmptyObject } = inject('globalFunction');
import config from '@/config.js';
const matchingDegree = ref(['一般', '良好', '优秀', '极好']);
@@ -263,11 +271,11 @@ function seeExplain() {
function getDetail(jobId) {
return new Promise((reslove, reject) => {
$api.createRequest(`/app/job/${jobId}`).then((resData) => {
const { latitude, longitude, companyName, companyId, isCompanyUser } = resData.data;
const { latitude, longitude, companyName, companyId } = resData.data;
jobInfo.value = resData.data;
reslove(resData.data);
getCompanyIsAJobs(companyId);
if (isCompanyUser) {
if (currentUserType.value !== 0) {
getCompetivetuveness(jobId);
}
// getCompetivetuveness(jobId);
@@ -503,11 +511,11 @@ for i in 0..100
.content{
padding: 0 28rpx
height: 100%
padding-top: 28rpx
.content-top{
background: #FFFFFF;
box-shadow: 0rpx 0rpx 8rpx 0rpx rgba(0,0,0,0.04);
border-radius: 20rpx 20rpx 20rpx 20rpx;
padding: 52rpx 32rpx 34rpx 32rpx
padding: 24rpx
position: relative
overflow: hidden
.top-salary{

559
packageB/jobFair/detail.vue Normal file
View File

@@ -0,0 +1,559 @@
<template>
<AppLayout title="" :use-scroll-view="false">
<template #headerleft>
<view class="btnback">
<image src="@/static/icon/back.png" @click="navBack"></image>
</view>
</template>
<view class="content">
<view class="content-top">
<view class="companyinfo-left">
<image src="@/static/icon/companyIcon.png" mode=""></image>
</view>
<view class="companyinfo-right">
<view class="row1 line_2">{{ fairInfo?.name }}</view>
<view class="row2">
<text>{{ fairInfo.location }}</text>
<convert-distance
:alat="fairInfo.latitude"
:along="fairInfo.longitude"
:blat="latitudeVal"
:blong="longitudeVal"
></convert-distance>
</view>
</view>
</view>
<view class="locations">
<image class="location-img" src="/static/icon/mapLine.png"></image>
<view class="location-info">
<view class="info">
<text class="info-title">{{ fairInfo.address }}</text>
<text class="info-text">位置</text>
</view>
</view>
</view>
<view class="conetent-info" :class="{ expanded: isExpanded }">
<view class="info-title">内容描述</view>
<view class="info-desirption">{{ fairInfo.description }}</view>
<!-- <view class="info-title title2">公司地址</view>
<view class="locationCompany"></view> -->
<view class="company-times">
<view class="info-title">内容描述</view>
<view class="card-times">
<view class="time-left">
<view class="left-date">{{ parseDateTime(fairInfo.startTime).time }}</view>
<view class="left-dateDay">{{ parseDateTime(fairInfo.startTime).date }}</view>
</view>
<view class="line"></view>
<view class="time-center">
<view class="center-date">
{{ getTimeStatus(fairInfo.startTime, fairInfo.endTime).statusText }}
</view>
<view class="center-dateDay">
{{ getHoursBetween(fairInfo.startTime, fairInfo.endTime) }}小时
</view>
</view>
<view class="line"></view>
<view class="time-right">
<view class="left-date">{{ parseDateTime(fairInfo.endTime).time }}</view>
<view class="left-dateDay">{{ parseDateTime(fairInfo.endTime).date }}</view>
</view>
</view>
</view>
</view>
<view class="expand" @click="expand">
<text>{{ isExpanded ? '收起' : '展开' }}</text>
<image
class="expand-img"
:class="{ 'expand-img-active': !isExpanded }"
src="@/static/icon/downs.png"
></image>
</view>
<scroll-view scroll-y class="Detailscroll-view">
<view class="views">
<view class="Detail-title">
<text class="title">参会单位{{ companyList.length }}</text>
</view>
<renderCompanys
v-if="companyList.length"
:list="companyList"
:longitude="longitudeVal"
:latitude="latitudeVal"
></renderCompanys>
<empty v-else pdTop="200"></empty>
</view>
</scroll-view>
</view>
<template #footer>
<view class="footer" v-if="hasnext">
<view
class="btn-wq button-click"
:class="{ 'btn-desbel': fairInfo.isCollection }"
@click="applyExhibitors"
>
{{ fairInfo.isCollection ? '已预约招聘会' : '预约招聘会' }}
</view>
</view>
</template>
</AppLayout>
</template>
<script setup>
import point from '@/static/icon/point.png';
import { reactive, inject, watch, ref, onMounted, computed } from 'vue';
import { onLoad, onShow } from '@dcloudio/uni-app';
import dictLabel from '@/components/dict-Label/dict-Label.vue';
import useLocationStore from '@/stores/useLocationStore';
const { $api, navTo, vacanciesTo, navBack } = inject('globalFunction');
import { storeToRefs } from 'pinia';
const { longitudeVal, latitudeVal } = storeToRefs(useLocationStore());
const isExpanded = ref(false);
const fairInfo = ref({});
const companyList = ref([]);
const hasnext = ref(true);
onLoad((options) => {
getCompanyInfo(options.jobFairId);
});
function getCompanyInfo(id) {
$api.createRequest(`/app/fair/${id}`).then((resData) => {
fairInfo.value = resData.data;
companyList.value = resData.data.companyList;
hasAppointment();
});
}
const hasAppointment = () => {
const isTimePassed = (timeStr) => {
const targetTime = new Date(timeStr.replace(/-/g, '/')).getTime(); // 兼容格式
const now = Date.now();
return now < targetTime;
};
hasnext.value = isTimePassed(fairInfo.value.startTime);
};
function openMap(lat, lng, name = '位置') {
const isConfirmed = window.confirm('是否打开地图查看位置?');
if (!isConfirmed) return;
// 使用高德地图或百度地图的 H5 链接打开
const url = `https://uri.amap.com/marker?position=${lng},${lat}&name=${encodeURIComponent(name)}`;
window.location.href = url;
}
function expand() {
isExpanded.value = !isExpanded.value;
}
// 取消/收藏岗位
function applyExhibitors() {
const fairId = fairInfo.value.jobFairId;
if (fairInfo.value.isCollection) {
// $api.createRequest(`/app/fair/collection/${fairId}`, {}, 'DELETE').then((resData) => {
// getCompanyInfo(fairId);
// $api.msg('取消预约成功');
// });
$api.msg('已预约成功');
} else {
$api.createRequest(`/app/fair/collection/${fairId}`, {}, 'POST').then((resData) => {
getCompanyInfo(fairId);
$api.msg('预约成功');
});
}
}
function toIOSDate(input) {
if (!input) return null;
if (input instanceof Date) return isNaN(input.getTime()) ? null : input;
if (typeof input === 'number') {
const d = new Date(input);
return isNaN(d.getTime()) ? null : d;
}
if (typeof input !== 'string') return null;
let s = input.trim();
if (/^\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}(:\d{2})?$/.test(s)) {
s = s.replace(' ', 'T');
if (/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}$/.test(s)) {
s = s + ':00';
}
}
const d = new Date(s);
return isNaN(d.getTime()) ? null : d;
}
function parseDateTime(datetimeStr) {
if (!datetimeStr) return { time: '', date: '' };
const dateObj = toIOSDate(datetimeStr);
if (isNaN(dateObj.getTime())) return { time: '', date: '' }; // 无效时间
const year = dateObj.getFullYear();
const month = String(dateObj.getMonth() + 1).padStart(2, '0');
const day = String(dateObj.getDate()).padStart(2, '0');
const hours = String(dateObj.getHours()).padStart(2, '0');
const minutes = String(dateObj.getMinutes()).padStart(2, '0');
return {
time: `${hours}:${minutes}`,
date: `${year}${month}${day}`,
};
}
function getTimeStatus(startTimeStr, endTimeStr) {
const now = new Date();
const startTime = toIOSDate(startTimeStr);
const endTime = toIOSDate(endTimeStr);
if (!startTime || !endTime) {
return { status: 1, statusText: '时间异常' };
}
let status = 0;
let statusText = '开始中';
if (now < startTime) {
status = 2;
statusText = '待开始';
} else if (now > endTime) {
status = 1;
statusText = '已过期';
} else {
status = 0;
statusText = '进行中';
}
return { status, statusText };
}
function getHoursBetween(startTimeStr, endTimeStr) {
const start = toIOSDate(startTimeStr);
const end = toIOSDate(endTimeStr);
if (!start || !end) return 0;
const diffMs = end - start;
const diffHours = diffMs / (1000 * 60 * 60);
return +diffHours.toFixed(2);
}
</script>
<style lang="stylus" scoped>
.btnback{
width: 64rpx;
height: 64rpx;
}
.btn {
display: flex;
justify-content: space-between;
align-items: center;
width: 52rpx;
height: 52rpx;
}
image {
height: 100%;
width: 100%;
}
.content{
height: 100%
display: flex;
flex-direction: column
.content-top{
padding: 28rpx
padding-top: 50rpx
display: flex
flex-direction: row
flex-wrap: nowrap
.companyinfo-left{
width: 96rpx;
height: 96rpx;
margin-right: 24rpx
}
.companyinfo-right{
flex: 1
.row1{
font-weight: 500;
font-size: 32rpx;
color: #333333;
font-family: 'PingFangSC-Medium', 'PingFang SC', 'Helvetica Neue', Helvetica, Arial, 'Microsoft YaHei', sans-serif;
}
.row2{
font-weight: 400;
font-size: 28rpx;
color: #6C7282;
line-height: 45rpx;
display: flex
justify-content: space-between
}
}
}
.locations{
padding: 0 28rpx
height: 86rpx;
position: relative
margin-bottom: 36rpx
.location-img{
border-radius: 8rpx 8rpx 8rpx 8rpx;
border: 2rpx solid #EFEFEF;
}
.location-info{
position: absolute
top: 0
left: 0
width: 100%
height: 100%
.info{
padding: 0 60rpx
height: 100%
display: flex
align-items: center
justify-content: space-between
white-space: nowrap
padding-top: rpx
.info-title{
font-weight: 600;
font-size: 28rpx;
color: #333333;
}
.info-text{
font-weight: 400;
font-size: 28rpx;
color: #9B9B9B;
position: relative;
padding-right: 20rpx
}
.info-text::before{
position: absolute;
right: 0;
top: 50%;
content: '';
width: 4rpx;
height: 16rpx;
border-radius: 2rpx
background: #9B9B9B;
transform: translate(0, -75%) rotate(-45deg)
}
.info-text::after {
position: absolute;
right: 0;
top: 50%;
content: '';
width: 4rpx;
height: 16rpx;
border-radius: 2rpx
background: #9B9B9B;
transform: translate(0, -25%) rotate(45deg)
}
}
}
}
.conetent-info{
padding: 0 28rpx
overflow: hidden;
max-height: 0rpx;
transition: max-height 0.3s ease;
.info-title{
font-weight: 600;
font-size: 28rpx;
color: #000000;
}
.info-desirption{
margin-top: 12rpx
font-weight: 400;
font-size: 28rpx;
color: #495265;
text-align: justified;
}
.title2{
margin-top: 48rpx
}
}
.company-times{
padding-top: 40rpx
.info-title{
font-weight: 600;
font-size: 28rpx;
color: #000000;
}
}
.expanded {
max-height: 1000rpx; // 足够显示完整内容
}
.expand{
display: flex
flex-wrap: nowrap
white-space: nowrap
justify-content: center
margin-bottom: 46rpx
font-weight: 400;
font-size: 28rpx;
color: #256BFA;
.expand-img{
width: 40rpx;
height: 40rpx;
}
.expand-img-active{
transform: rotate(180deg)
}
}
}
.Detailscroll-view{
flex: 1;
overflow: hidden;
background: #F4F4F4;
.views{
padding: 28rpx
.Detail-title{
font-weight: 600;
font-size: 32rpx;
color: #000000;
position: relative;
display: flex
justify-content: space-between
.title{
position: relative;
z-index: 2;
}
}
.Detail-title::before{
position: absolute
content: '';
left: -14rpx
bottom: 0
height: 16rpx;
width: 108rpx;
background: linear-gradient(to right, #CBDEFF, #FFFFFF);
border-radius: 8rpx;
z-index: 1;
}
.cards{
padding: 32rpx;
background: #FFFFFF;
box-shadow: 0rpx 0rpx 8rpx 0rpx rgba(0,0,0,0.04);
border-radius: 20rpx 20rpx 20rpx 20rpx;
margin-top: 22rpx;
.card-company{
display: flex
justify-content: space-between
align-items: flex-start
.company{
font-weight: 500;
font-size: 32rpx;
color: #333333;
}
.salary{
font-weight: 500;
font-size: 28rpx;
color: #4C6EFB;
white-space: nowrap
line-height: 48rpx
}
}
.card-companyName{
font-weight: 400;
font-size: 28rpx;
color: #6C7282;
}
.card-tags{
display: flex
flex-wrap: wrap
.tag{
width: fit-content;
height: 30rpx;
background: #F4F4F4;
border-radius: 4rpx;
padding: 6rpx 20rpx;
line-height: 30rpx;
font-weight: 400;
font-size: 24rpx;
color: #6C7282;
text-align: center;
margin-top: 14rpx;
white-space: nowrap
margin-right: 20rpx
}
}
.card-bottom{
margin-top: 32rpx
display: flex
justify-content: space-between
font-size: 28rpx;
color: #6C7282;
}
}
}
}
.card-times{
display: flex;
justify-content: space-between
align-items: center
padding: 24rpx 30rpx 10rpx 30rpx
.time-left,
.time-right{
text-align: center
.left-date{
font-weight: 500;
font-size: 48rpx;
color: #333333;
}
.left-dateDay{
font-weight: 400;
font-size: 24rpx;
color: #333333;
margin-top: 12rpx
}
}
.line{
width: 40rpx;
height: 0rpx;
border: 2rpx solid #D4D4D4;
margin-top: 64rpx
}
.time-center{
text-align: center;
display: flex
flex-direction: column
justify-content: center
align-items: center
.center-date{
font-weight: 400;
font-size: 28rpx;
color: #FF881A;
padding-top: 10rpx
}
.center-dateDay{
font-weight: 400;
font-size: 24rpx;
color: #333333;
margin-top: 6rpx
line-height: 48rpx;
width: 104rpx;
height: 48rpx;
background: #F9F9F9;
border-radius: 8rpx 8rpx 8rpx 8rpx;
}
}
}
.footer{
background: #FFFFFF;
box-shadow: 0rpx -4rpx 24rpx 0rpx rgba(11,44,112,0.12);
border-radius: 0rpx 0rpx 0rpx 0rpx;
padding: 40rpx 28rpx 20rpx 28rpx
.btn-wq{
height: 90rpx;
background: #256BFA;
border-radius: 12rpx 12rpx 12rpx 12rpx;
font-weight: 500;
font-size: 32rpx;
color: #FFFFFF;
text-align: center;
line-height: 90rpx
}
.btn-desbel{
background: #6697FB;
box-shadow: 0rpx -4rpx 24rpx 0rpx rgba(11,44,112,0.12);
}
}
</style>

237
packageB/login.vue Normal file
View File

@@ -0,0 +1,237 @@
<template>
<AppLayout title="" :use-scroll-view="false">
<view class="wrap">
<view class="login_index">
<input class="input" placeholder="请输入账号" placeholder-class="inputplace" v-model="form.username" />
<view class="login_yzm">
<input class="input" type="password" placeholder="请输入密码" placeholder-class="inputplace"
v-model="form.password" />
</view>
<view class="login_yzm">
<input class="input" placeholder="请输入验证码" placeholder-class="inputplace" v-model="form.code" />
<image class="yzm" :src="codeUrl" @click="getCodeImg"></image>
</view>
<button class="com-btn" @click="register"> </button>
</view>
</view>
</AppLayout>
</template>
<script setup>
import {
reactive,
inject,
watch,
ref,
onMounted,
onUnmounted
} from 'vue'
import {
onLoad,
onShow
} from '@dcloudio/uni-app';
const {
$api,
navTo,
vacanciesTo,
navBack
} = inject("globalFunction");
const placeholderStyle = 'font-size:30rpx'
const checked = ref(true)
const codeUrl = ref('')
const form = reactive({
username: 'langchaojituan',
password: 'Aa123456?',
rememberMe: false,
code: '',
uuid: ''
})
onLoad(() => {
})
onMounted(() => {
getCodeImg()
})
function register() {
if (!form.username) {
uni.showToast({
icon: 'none',
title: '请输入用户名'
})
return
}
if (!form.password) {
uni.showToast({
icon: 'none',
title: '请输入密码'
})
return
}
if (!form.uuid) {
uni.showToast({
icon: 'none',
title: '请输入验证码'
})
return
}
uni.showLoading({
title: '登录中...',
mask: true
})
$api.myRequest('/auth/login',form,'post',10100).then((res) => {
console.log(res, 'res')
uni.setStorageSync('Padmin-Token', res.data.access_token)
uni.reLaunch({
url: '/pages/index/index'
})
codeUrl.value = 'data:image/gif;base64,' + res.img
}).catch(() => {
uni.hideLoading()
uni.showToast({
icon: 'none',
title: '登录失败,请重试'
})
})
}
function getCodeImg() {
$api.myRequest('/code',{},'get',10100).then((resData) => {
codeUrl.value = 'data:image/gif;base64,' + resData.img
form.uuid = resData.uuid
});
}
</script>
<style scoped lang="stylus">
.wrap {
background-color: #ffffff;
height: 100vh;
position: relative;
.lg-head {
height: 480rpx;
background: #46ca98;
position: relative;
.view_logo {
text-align: center;
.login_logo {
width: 300rpx;
height: 300rpx;
margin-top: 100rpx;
}
}
.bg-cover {
position: absolute;
bottom: -4rpx;
left: 0;
right: 0;
height: 30rpx;
background-size: 100% 100%;
z-index: 1;
}
}
}
.login_index {
font-size: 36rpx;
font-weight: 500;
width: 596rpx;
margin: 0 auto;
::v-deep .is-input-border {
border: 0;
border-bottom: 1px solid #dcdfe6 !important;
border-radius: 0;
}
::v-deep .uni-input-input {
font-size: 32rpx;
padding-left: 10rpx;
}
::v-deep .uniui-contact-filled:before {
color: #46ca98;
font-size: 50rpx;
}
::v-deep .uniui-locked-filled:before {
color: #46ca98;
font-size: 50rpx;
}
.login_yzm {
margin-top: 40rpx;
display: flex;
align-items: center;
.yzm {
width: 200rpx;
height: 80rpx;
}
}
.com-btn {
height: 100rpx;
background: #46ca98;
border-radius: 50rpx;
color: #fff;
margin-top: 100rpx;
}
.login_wt {
margin: 0 auto;
text-align: right;
font-size: 24rpx;
color: rgba(134, 134, 136, 1);
}
}
.lg-bottom {
position: absolute;
bottom: -3px;
left: 0;
width: 100%;
.bottom-svg {
position: absolute;
bottom: -3px;
left: 0;
width: 100%;
}
}
.login_tongyi {
font-size: 26rpx;
color: rgba(196, 196, 196, 1);
width: 620rpx;
margin: 32rpx auto;
text-align: center;
text {
color: rgba(86, 176, 236, 1);
}
}
.input {
padding: 0 30rpx 0 80rpx;
height: 80rpx;
background: #FFFFFF;
border-radius: 75rpx 75rpx 75rpx 75rpx;
font-size: 28rpx;
}
.inputplace {
font-weight: 400;
font-size: 28rpx;
color: #B5B5B5;
}
</style>

138
packageB/train/index.vue Normal file
View File

@@ -0,0 +1,138 @@
<template>
<!-- <AppLayout title=""> -->
<view class="tab-container">
<image src="../../static/images/train/bj.jpg" mode=""></image>
<view>
<view class="btns" @click="jumps('/packageB/train/video/videoList')">
<image src="/static/images/train/spxx-k.png" mode=""></image>
<view>
<text>培训视频</text>
<view class="btn">
<text>立即查看</text>
<image src="/static/images/train/arrow.png" mode=""></image>
</view>
</view>
</view>
<view class="btns" @click="jumps('/packageB/train/practice/startPracticing')">
<image src="/static/images/train/zxxl-k.png" mode=""></image>
<view>
<text>专项练习</text>
<view class="btn">
<text>立即查看</text>
<image src="/static/images/train/arrow.png" mode=""></image>
</view>
</view>
</view>
<view class="btns" @click="jumps('')">
<image src="/static/images/train/mnks-k.png" mode=""></image>
<view>
<text>模拟考试</text>
<view class="btn">
<text>立即查看</text>
<image src="/static/images/train/arrow.png" mode=""></image>
</view>
</view>
</view>
<view class="btns" @click="jumps('')">
<image src="/static/images/train/ctb-k.png" mode=""></image>
<view>
<text>错题本 </text>
<view class="btn" style="margin-left: 13%;">
<text>立即查看</text>
<image src="/static/images/train/arrow.png" mode=""></image>
</view>
</view>
</view>
</view>
</view>
<!-- </AppLayout> -->
</template>
<script setup>
import { reactive, inject, watch, ref, onMounted } from 'vue';
import { onLoad, onShow } from '@dcloudio/uni-app';
const { $api, navTo, vacanciesTo, formatTotal, config } = inject('globalFunction');
import useUserStore from '@/stores/useUserStore';
import useDictStore from '@/stores/useDictStore';
function jumps(url){
navTo(url);
}
</script>
<style lang="stylus" scoped>
.tab-container{
height: 100vh;
width: 100%;
position: relative;
// background: url('@/static/images/train/bj.jpg') center center no-repeat;
// background-size: 100% 100%;
image{
position: absolute;
width: 100%;
height: 100%;
}
view{
padding: 100rpx 28rpx 0;
.btns{
width: 100%
height: 170rpx;
border-radius: 5rpx
padding: 20rpx;
box-sizing: border-box
color: #000
text-align: center
margin-bottom: 50rpx
position: relative
image{
position: absolute;
width: 100%;
height: 100%;
left: 0;
top: 0;
}
view{
padding: 0 0 0 10%;
box-sizing: border-box;
position: absolute
width: 100%;
height: 100%;
left: 0;
top: 0;
z-index: 100;
display: flex;
align-items: center
font-weight: bold;
font-size: 36rpx;
color: #595959;
.btn{
margin-left: 8%;
background-color: #3A92FF;
position: static;
color: #fff;
font-size: 22rpx;
display: flex;
align-items: center;
justify-content: center
width: 140rpx;
height: 50rpx;
border-radius: 5rpx
font-weight: 500;
padding: 0;
image{
position: static;
width: 14rpx;
height: 22rpx;
margin-left: 10rpx;
}
}
}
}
}
}
</style>

View File

@@ -0,0 +1,8 @@
<template>
</template>
<script>
</script>
<style>
</style>

View File

@@ -0,0 +1,361 @@
<template>
<div class="app-box">
<image src="../../../static/images/train/bj.jpg" class="bjImg" mode=""></image>
<div class="con-box">
<div class="header">
<div>正确率0%</div>
<div>用时00:00</div>
<div class="headBtn">暂停</div>
</div>
<div class="problemCard">
<div v-for="(item,index) in problemData" :key="index">
<template v-if="questionIndex==(index+1)">
<div class="problemTitle">
<span class="titleType" v-if="item.type=='single'">单选题</span>
<span class="titleType" v-if="item.type=='multiple'">多选题</span>
<span class="titleType" v-if="item.type=='judge'">判断题</span>
<span>{{item.content}}</span>
</div>
<div class="options" v-if="item.type=='single'">
<div class="opt" @click="selected(i)" :class="radio!==''&&i==radio?'active':''" v-for="(val,i) in parseOptions(item.trainChooses)">
<div class="optLab">{{indexToLetter(i)}}</div>
<span>{{val}}</span>
</div>
</div>
<div class="options" v-if="item.type=='multiple'">
<div class="opt" @click="selected2(i)" :class="judgment(i)?'active':''" v-for="(val,i) in parseOptions(item.trainChooses)">
<div class="optLab">{{indexToLetter(i)}}</div>
<span>{{val}}</span>
</div>
</div>
<div class="options" v-if="item.type=='judge'">
<div class="opt" @click="selected3('正确')" :class="radio2=='正确'?'active':''">
<span>正确</span>
</div>
<div class="opt" @click="selected3('错误')" :class="radio2=='错误'?'active':''">
<span>错误</span>
</div>
</div>
<div class="analysis">
<div class="analysisHead correct">
<div>回答正确</div>
<div></div>
</div>
<!-- <div class="analysisHead errors" v-if="judgWhether=='错误'">
<div>回答错误</div>
<div></div>
</div> -->
<div class="analysisCon">
<div class="parse1">正确答案</div>
<div class="parse2" v-if="item.type=='single'" style="color: #30A0FF;font-weight: bold;">{{String.fromCharCode(65 + Number(item.answer))}}.</div>
<div class="parse2" v-if="item.type=='multiple'" style="color: #30A0FF;font-weight: bold;">
<span v-for="(val,i) in parseOptions(item.answer)">{{indexToLetter(val-1)}}.</span>
</div>
<div class="parse2" v-if="item.type=='judge'" style="color: #30A0FF;font-weight: bold;">{{item.answer}}</div>
</div>
<div class="analysisCon">
<div class="parse1">答案解析</div>
<div class="parse2">{{item.answerDesc}}</div>
</div>
<div class="analysisCon">
<div class="parse1">知识点</div>
<div>
<el-tag style="margin-right: 10px;">{{item.knowledgePoint}}</el-tag>
</div>
</div>
</div>
<div class="problemBtns">
<div class="events">提交答案</div>
<div>下一题</div>
</div>
</template>
</div>
</div>
<div class="footer">
<div class="footerLeft">
<div class="zuo events"></div>
<div class="you"></div>
<div style="text-align: center;font-size: 24rpx;">
<div></div>
<div>收藏</div>
</div>
</div>
<div></div>
<div class="footer">
</div>
</div>
</div>
</div>
</template>
<script setup>
import { reactive, inject, watch, ref, onMounted } from 'vue';
import { onLoad, onShow } from '@dcloudio/uni-app';
const { $api, navTo, vacanciesTo, formatTotal, config } = inject('globalFunction');
import useUserStore from '@/stores/useUserStore';
import useDictStore from '@/stores/useDictStore';
const radio = ref('');
const radio2 = ref('');
const checkList = ref([]);
const questionIndex = ref(1);
const correctIndex = ref(0);
const errorsIndex = ref(0);
const accuracyRate = ref(0);
const problemData = reactive([
{
type:'single',
content:"君不见黄河之水天上来,下一句是?",
fraction:5,
trainChooses:"奔流到海不复回,朝如青丝暮成雪,人生得意须尽欢,莫使金樽空对月",
},{
type:'multiple',
content:"以下哪些是欧姆定律的适用条件?",
fraction:8,
trainChooses:"线性电阻,恒定温度,金属导体,半导体材料",
},{
type:'judge',
content:"功率越大的电器,其电阻值越小",
fraction:2,
trainChooses:[
"正确",
"错误",
]
}
]);
function selected(i){
radio.value=i
};
//多选
function selected2(i){
let arr=checkList.value.join(",")
if(arr.indexOf(i) !== -1){
const index = checkList.value.indexOf(i);
if (index !== -1) {
checkList.value.splice(index, 1);
}
}else{
checkList.value.push(i)
}
};
function judgment(i){
let arr=checkList.value.join(",")
if(arr.indexOf(i) !== -1){
return true
}else{
return false
}
};
function selected3(i){
radio2.value=i
};
// 解析选项
function parseOptions(options) {
if (!options) return [];
// 假设options是字符串格式以分号分隔
if (typeof options === 'string') {
return options.split(',').filter(opt => opt.trim());
}
// 如果是数组,直接返回
if (Array.isArray(options)) {
return options;
}
return [];
};
function indexToLetter(index) {
// 将索引转换为对应的字母
return String.fromCharCode(65 + index);
};
</script>
<style lang="stylus" scoped>
.app-box{
width: 100%;
height: 100vh;
position: relative;
.bjImg{
position: absolute;
width: 100%;
height: 100%;
}
.con-box{
position: absolute;
width: 100%;
height: 100%;
left: 0;
top:0;
z-index: 10;
padding: 20rpx 28rpx;
box-sizing: border-box;
.header{
height: 100rpx;
padding: 0 10rpx;
display: flex;
align-items: center;
justify-content: space-between;
background: linear-gradient(0deg, #4285EC 0%, #0BBAFB 100%);
color: #fff;
font-size: 26rpx;
border-radius: 10rpx
.headBtn{
background: #499FFF;
border-radius: 4px;
width: 100rpx;
text-align: center;
height: 50rpx;
line-height: 50rpx;
}
}
.problemCard{
margin-top: 30rpx;
.problemTitle{
font-size: 30rpx;
.titleType{
display: inline-block;
background-color: #499FFF;
border-radius: 10rpx 10rpx 10rpx 0;
padding: 8rpx 12rpx;
color: #fff;
font-size: 26rpx;
margin-right: 20rpx;
}
}
.options{
margin-top: 30rpx;
.opt{
height: 60rpx;
/* background-color: #F8F9FA; */
border-radius: 5px;
margin-bottom: 15px;
display: flex;
align-items: center;
padding-left: 30rpx;
box-sizing: border-box;
color: #808080;
font-size: 30rpx;
.optLab{
width: 40rpx;
height: 40rpx;
text-align: center;
line-height: 40rpx;
border-radius: 50%;
background-color: #8C8C8C;
color: #fff;
font-weight: 600;
font-size: 32rpx;
margin-right: 20rpx;
}
}
.active{
background: linear-gradient(90deg, #25A9F5 0%, #B1DBFF 100%);
color: #fff!important;
font-weight: bold;
}
.active>view{
background-color: #fff!important;
color: #25A9F5!important;
}
}
.analysis{
margin-top: 30rpx;
background-color: #fff;
border-radius: 6px;
margin-bottom: 15rpx;
border: 1px solid #10A8FF;
padding: 20rpx;
box-sizing: border-box;
.analysisHead{
display: flex;
align-items: center;
justify-content: space-between;
font-size: 32rpx;
font-family: Microsoft YaHei;
font-weight: bold;
letter-spacing: 2px;
}
.correct{
color: #67C23A;
}
.errors{
color: #F06A6A;
}
.analysisCon{
margin-top: 30rpx;
.parse1{
font-size: 30rpx;
font-family: Microsoft YaHei;
font-weight: bold;
}
.parse2{
font-size: 26rpx;
color: #333;
margin-top: 10px;
}
}
}
.problemBtns{
display: flex
align-items: center
justify-content: center
view{
width: 140rpx
height: 50rpx
text-align: center
line-height: 50rpx
background-color: #10A8FF
color: #fff
font-size: 28rpx
border-radius: 5rpx
margin-right: 10rpx;
}
}
}
.footer{
width: 100%;
height: 120rpx;
border-top: 1px solid #ddd
position: fixed
bottom: 0
left: 0
display: flex
align-items: center
justify-content: space-between
.footerLeft{
display: flex
align-items: center
font-size: 30rpx;
.zuo{
width: 24rpx;
height: 24rpx;
border-top: 2px solid #666
border-left: 2px solid #666
transform: rotate(-45deg); /* 鼠标悬停时旋转360度 */
margin-left: 50rpx;
}
.you{
width: 24rpx;
height: 24rpx;
border-right: 2px solid #666
border-bottom: 2px solid #666
transform: rotate(-45deg); /* 鼠标悬停时旋转360度 */
margin-left: 30rpx;
margin-right: 50rpx;
}
}
}
}
}
.events{
pointer-events: none; /* 这会禁用所有指针事件 */
opacity: 0.5; /* 可选:改变透明度以视觉上表示不可点击 */
cursor: not-allowed; /* 可选:改变鼠标光标样式 */
}
</style>

View File

@@ -0,0 +1,316 @@
<template>
<AppLayout :title="title" :show-bg-image="false" @onScrollBottom="getDataList('add')">
<!-- <template #headerleft>
<view class="btnback">
<image src="@/static/icon/back.png" @click="navBack"></image>
</view>
</template> -->
<template #headContent>
<view class="collection-search">
<view class="search-content">
<view class="header-input button-click">
<uni-icons class="iconsearch" color="#6A6A6A" type="search" size="22"></uni-icons>
<input
class="input"
v-model="searchKeyword"
@confirm="searchVideo"
placeholder="输入视频名称"
placeholder-class="inputplace"
/>
<uni-icons
v-if="searchKeyword"
class="clear-icon"
type="clear"
size="24"
color="#999"
@click="clearSearch"
/>
</view>
</view>
</view>
</template>
<view class="main-list">
<view class="list-title">
<text>视频列表</text>
<view class="title-line"></view>
</view>
<view class="video-grid" v-if="pageState.list.length">
<view
v-for="video in pageState.list"
:key="video.id || video.videoId"
class="video-item"
:style="getItemBackgroundStyle('video-bg.png')"
@click="playVideo(video)"
>
<view class="video-cover">
<image
:src="video.coverImage || video.videoCover || '/static/icon/video.png'"
mode="aspectFill"
></image>
</view>
<view class="video-info">
{{ video.title || video.videoName || '未命名视频' }}
</view>
</view>
</view>
<empty v-else pdTop="200"></empty>
</view>
</AppLayout>
</template>
<script setup>
import { inject, ref, reactive } from 'vue';
import { onLoad } from '@dcloudio/uni-app';
const { $api, navTo, navBack } = inject('globalFunction');
// state
const title = ref('');
const searchKeyword = ref('');
const pageState = reactive({
page: 0,
list: [],
total: 0,
maxPage: 1,
pageSize: 12,
search: {},
});
const baseUrl = 'http://10.110.145.145/images/train/';
const getItemBackgroundStyle = (imageName) => ({
backgroundImage: `url(${baseUrl + imageName})`,
backgroundSize: 'cover', // 覆盖整个容器
backgroundPosition: 'center', // 居中
backgroundRepeat: 'no-repeat'
});
// 模拟视频数据
const mockVideoData = [
{
id: '1',
title: '职业技能培训基础课程',
coverImage: '/static/icon/server1.png',
videoUrl: ''
},
{
id: '2',
title: '面试技巧分享',
coverImage: '/static/icon/server2.png',
videoUrl: ''
},
{
id: '3',
title: '简历制作指南',
coverImage: '/static/icon/server3.png',
videoUrl: ''
},
{
id: '4',
title: '职场沟通技巧',
coverImage: '/static/icon/server4.png',
videoUrl: ''
},
{
id: '5',
title: '职业规划讲座',
coverImage: '/static/icon/flame.png',
videoUrl: ''
},
{
id: '6',
title: '行业趋势分析',
coverImage: '/static/icon/flame2.png',
videoUrl: ''
}
];
onLoad(() => {
getDataList('refresh');
});
// 搜索视频
function searchVideo() {
getDataList('refresh');
}
// 清除搜索内容
function clearSearch() {
searchKeyword.value = '';
getDataList('refresh');
}
// 获取视频列表
function getDataList(type = 'add') {
if (type === 'refresh') {
pageState.page = 1;
pageState.maxPage = 1;
}
if (type === 'add' && pageState.page < pageState.maxPage) {
pageState.page += 1;
}
// 模拟API请求延迟
setTimeout(() => {
// 在实际项目中这里应该调用真实的API接口
// 目前使用模拟数据
let filteredList = [...mockVideoData];
// 如果有搜索关键词,进行过滤
if (searchKeyword.value.trim()) {
filteredList = filteredList.filter(video =>
video.title.toLowerCase().includes(searchKeyword.value.toLowerCase())
);
}
const start = 0;
const end = pageState.pageSize;
const pageData = filteredList.slice(start, end);
if (type === 'add') {
pageState.list = [...pageState.list, ...pageData];
} else {
pageState.list = pageData;
}
pageState.total = filteredList.length;
pageState.maxPage = Math.ceil(pageState.total / pageState.pageSize);
}, 300);
}
// 播放视频
function playVideo(video) {
// 在实际项目中,这里应该导航到视频播放页面
// 或者调用视频播放组件
console.log('播放视频:', video.title);
// 示例navTo(`/pages/videoPlayer/videoPlayer?id=${video.id}`);
$api.msg(`准备播放: ${video.title}`);
}
</script>
<style lang="stylus" scoped>
.btnback{
width: 64rpx;
height: 64rpx;
}
.btn {
display: flex;
justify-content: space-between;
align-items: center;
width: 52rpx;
height: 52rpx;
}
image {
height: 100%;
width: 100%;
}
.collection-search{
padding: 10rpx 20rpx;
.search-content{
position: relative
display: flex
align-items: center
padding: 14rpx 0
.header-input{
padding: 0
width: calc(100%);
position: relative
.iconsearch{
position: absolute
left: 30rpx;
top: 50%
transform: translate(0, -50%)
z-index: 1
}
.input{
padding: 0 80rpx 0 80rpx
height: 80rpx;
background: #FFFFFF;
border-radius: 75rpx 75rpx 75rpx 75rpx;
border: 2rpx solid #ECECEC
font-size: 28rpx;
}
.clear-icon{
position: absolute
right: 30rpx;
top: 50%
transform: translate(0, -50%)
z-index: 1
cursor: pointer
}
.inputplace{
font-weight: 400;
font-size: 28rpx;
color: #B5B5B5;
}
}
}
}
.main-list{
background-color: #ffffff;
padding: 20rpx 20rpx 28rpx 20rpx;
margin:10rpx 30rpx ;
box-shadow: 0px 3px 20px 0px rgba(0,105,234,0.1);
border-radius: 12px;
}
.list-title{
font-weight: bold;
font-size: 36rpx;
color: #404040;
position: relative;
margin-bottom: 20px;
}
.title-line{
position: absolute;
bottom: -10rpx;
left: 36rpx;
width: 70rpx;
height: 8rpx;
background: linear-gradient(90deg, #FFAD58 0%, #FF7A5B 100%);
border-radius: 2px;
}
.video-grid{
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 20rpx;
}
.video-item{
border-radius: 12rpx;
overflow: hidden;
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
transition: transform 0.2s;
padding: 20rpx
}
.video-item:active{
transform: scale(0.98);
}
.video-cover{
position: relative;
width: 100%;
padding-top: 56.25%; /* 16:9 比例 */
background: #f0f0f0;
border-radius: 4rpx;
}
.video-cover image{
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.video-info{
padding: 16rpx 16rpx 0 16rpx;
font-size: 26rpx;
color: #333;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
text-align: center;
}
.video-title{
font-size: 28rpx;
color: #333;
line-height: 40rpx;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
}
</style>

View File

@@ -0,0 +1,316 @@
<template>
<AppLayout :title="title" :show-bg-image="false" @onScrollBottom="getDataList('add')">
<!-- <template #headerleft>
<view class="btnback">
<image src="@/static/icon/back.png" @click="navBack"></image>
</view>
</template> -->
<template #headContent>
<view class="collection-search">
<view class="search-content">
<view class="header-input button-click">
<uni-icons class="iconsearch" color="#6A6A6A" type="search" size="22"></uni-icons>
<input
class="input"
v-model="searchKeyword"
@confirm="searchVideo"
placeholder="输入视频名称"
placeholder-class="inputplace"
/>
<uni-icons
v-if="searchKeyword"
class="clear-icon"
type="clear"
size="24"
color="#999"
@click="clearSearch"
/>
</view>
</view>
</view>
</template>
<view class="main-list">
<view class="list-title">
<text>视频列表</text>
<view class="title-line"></view>
</view>
<view class="video-grid" v-if="pageState.list.length">
<view
v-for="video in pageState.list"
:key="video.id || video.videoId"
class="video-item"
:style="getItemBackgroundStyle('video-bg.png')"
@click="playVideo(video)"
>
<view class="video-cover">
<image
:src="video.coverImage || video.videoCover || '/static/icon/video.png'"
mode="aspectFill"
></image>
</view>
<view class="video-info">
{{ video.title || video.videoName || '未命名视频' }}
</view>
</view>
</view>
<empty v-else pdTop="200"></empty>
</view>
</AppLayout>
</template>
<script setup>
import { inject, ref, reactive } from 'vue';
import { onLoad } from '@dcloudio/uni-app';
const { $api, navTo, navBack } = inject('globalFunction');
// state
const title = ref('');
const searchKeyword = ref('');
const pageState = reactive({
page: 0,
list: [],
total: 0,
maxPage: 1,
pageSize: 12,
search: {},
});
const baseUrl = 'http://10.110.145.145/images/train/';
const getItemBackgroundStyle = (imageName) => ({
backgroundImage: `url(${baseUrl + imageName})`,
backgroundSize: 'cover', // 覆盖整个容器
backgroundPosition: 'center', // 居中
backgroundRepeat: 'no-repeat'
});
// 模拟视频数据
const mockVideoData = [
{
id: '1',
title: '职业技能培训基础课程',
coverImage: '/static/icon/server1.png',
videoUrl: ''
},
{
id: '2',
title: '面试技巧分享',
coverImage: '/static/icon/server2.png',
videoUrl: ''
},
{
id: '3',
title: '简历制作指南',
coverImage: '/static/icon/server3.png',
videoUrl: ''
},
{
id: '4',
title: '职场沟通技巧',
coverImage: '/static/icon/server4.png',
videoUrl: ''
},
{
id: '5',
title: '职业规划讲座',
coverImage: '/static/icon/flame.png',
videoUrl: ''
},
{
id: '6',
title: '行业趋势分析',
coverImage: '/static/icon/flame2.png',
videoUrl: ''
}
];
onLoad(() => {
getDataList('refresh');
});
// 搜索视频
function searchVideo() {
getDataList('refresh');
}
// 清除搜索内容
function clearSearch() {
searchKeyword.value = '';
getDataList('refresh');
}
// 获取视频列表
function getDataList(type = 'add') {
if (type === 'refresh') {
pageState.page = 1;
pageState.maxPage = 1;
}
if (type === 'add' && pageState.page < pageState.maxPage) {
pageState.page += 1;
}
// 模拟API请求延迟
setTimeout(() => {
// 在实际项目中这里应该调用真实的API接口
// 目前使用模拟数据
let filteredList = [...mockVideoData];
// 如果有搜索关键词,进行过滤
if (searchKeyword.value.trim()) {
filteredList = filteredList.filter(video =>
video.title.toLowerCase().includes(searchKeyword.value.toLowerCase())
);
}
const start = 0;
const end = pageState.pageSize;
const pageData = filteredList.slice(start, end);
if (type === 'add') {
pageState.list = [...pageState.list, ...pageData];
} else {
pageState.list = pageData;
}
pageState.total = filteredList.length;
pageState.maxPage = Math.ceil(pageState.total / pageState.pageSize);
}, 300);
}
// 播放视频
function playVideo(video) {
// 在实际项目中,这里应该导航到视频播放页面
// 或者调用视频播放组件
console.log('播放视频:', video.title);
// 示例navTo(`/pages/videoPlayer/videoPlayer?id=${video.id}`);
$api.msg(`准备播放: ${video.title}`);
}
</script>
<style lang="stylus" scoped>
.btnback{
width: 64rpx;
height: 64rpx;
}
.btn {
display: flex;
justify-content: space-between;
align-items: center;
width: 52rpx;
height: 52rpx;
}
image {
height: 100%;
width: 100%;
}
.collection-search{
padding: 10rpx 20rpx;
.search-content{
position: relative
display: flex
align-items: center
padding: 14rpx 0
.header-input{
padding: 0
width: calc(100%);
position: relative
.iconsearch{
position: absolute
left: 30rpx;
top: 50%
transform: translate(0, -50%)
z-index: 1
}
.input{
padding: 0 80rpx 0 80rpx
height: 80rpx;
background: #FFFFFF;
border-radius: 75rpx 75rpx 75rpx 75rpx;
border: 2rpx solid #ECECEC
font-size: 28rpx;
}
.clear-icon{
position: absolute
right: 30rpx;
top: 50%
transform: translate(0, -50%)
z-index: 1
cursor: pointer
}
.inputplace{
font-weight: 400;
font-size: 28rpx;
color: #B5B5B5;
}
}
}
}
.main-list{
background-color: #ffffff;
padding: 20rpx 20rpx 28rpx 20rpx;
margin:10rpx 30rpx ;
box-shadow: 0px 3px 20px 0px rgba(0,105,234,0.1);
border-radius: 12px;
}
.list-title{
font-weight: bold;
font-size: 36rpx;
color: #404040;
position: relative;
margin-bottom: 20px;
}
.title-line{
position: absolute;
bottom: -10rpx;
left: 36rpx;
width: 70rpx;
height: 8rpx;
background: linear-gradient(90deg, #FFAD58 0%, #FF7A5B 100%);
border-radius: 2px;
}
.video-grid{
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 20rpx;
}
.video-item{
border-radius: 12rpx;
overflow: hidden;
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
transition: transform 0.2s;
padding: 20rpx
}
.video-item:active{
transform: scale(0.98);
}
.video-cover{
position: relative;
width: 100%;
padding-top: 56.25%; /* 16:9 比例 */
background: #f0f0f0;
border-radius: 4rpx;
}
.video-cover image{
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.video-info{
padding: 16rpx 16rpx 0 16rpx;
font-size: 26rpx;
color: #333;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
text-align: center;
}
.video-title{
font-size: 28rpx;
color: #333;
line-height: 40rpx;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
}
</style>

View File

@@ -3,16 +3,18 @@
{
"path": "pages/index/index",
"style": {
"navigationBarTitleText": "青岛智慧就业平台",
// #ifdef H5
"navigationBarTitleText": "喀什智慧就业平台",
"navigationBarTitleTextSize": "30rpx",
// #ifdef H5
"navigationStyle": "custom"
// #endif
// #endif
}
},
{
"path": "pages/mine/mine",
"style": {
"navigationBarTitleText": "我的",
"navigationBarTitleTextSize": "30rpx",
"navigationStyle": "custom"
}
},
@@ -20,28 +22,32 @@
"path": "pages/msglog/msglog",
"style": {
"navigationBarTitleText": "消息",
"navigationStyle": "custom",
"enablePullDownRefresh": false
"navigationBarTitleTextSize": "30rpx"
// "navigationStyle": "custom",
// "enablePullDownRefresh": false
}
},
{
"path": "pages/careerfair/careerfair",
"style": {
"navigationBarTitleText": "招聘会"
"navigationBarTitleText": "招聘会",
"navigationBarTitleTextSize": "30rpx"
// "navigationStyle": "custom"
}
},
{
"path": "pages/complete-info/complete-info",
"style": {
"navigationBarTitleText": "补全信息"
"navigationBarTitleText": "补全信息",
"navigationBarTitleTextSize": "30rpx"
// "navigationStyle": "custom"
}
},
{
"path": "pages/complete-info/company-info",
"style": {
"navigationBarTitleText": "企业信息"
"navigationBarTitleText": "企业信息",
"navigationBarTitleTextSize": "30rpx"
// "navigationStyle": "custom"
}
},
@@ -68,30 +74,102 @@
"navigationStyle": "custom"
}
},
{
"path": "pages/test/tabbar-test",
"style": {
"navigationBarTitleText": "TabBar测试",
"navigationStyle": "custom"
}
},
{
"path": "pages/test/homepage-test",
"style": {
"navigationBarTitleText": "首页内容测试",
"navigationStyle": "custom"
}
},
{
"path": "pages/test/tabbar-user-type-test",
"style": {
"navigationBarTitleText": "TabBar用户类型测试",
"navigationStyle": "custom"
}
},
{
"path": "pages/test/company-search-test",
"style": {
"navigationBarTitleText": "企业搜索功能测试",
"navigationStyle": "custom"
}
},
{
"path": "pages/test/company-mine-test",
"style": {
"navigationBarTitleText": "企业我的页面测试",
"navigationStyle": "custom"
}
},
{
"path": "pages/job/publishJob",
"style": {
"navigationBarTitleText": "发布岗位",
"navigationStyle": "custom"
"navigationBarTitleText": "发布岗位"
// "navigationStyle": "custom",
// "disableScroll": false,
// "enablePullDownRefresh": false,
// "onReachBottomDistance": 50,
// "backgroundColor": "#f5f5f5"
}
},
{
"path": "pages/job/companySearch",
"style": {
"navigationBarTitleText": "选择企业",
"navigationStyle": "custom",
"disableScroll": false,
"enablePullDownRefresh": false,
"backgroundColor": "#f5f5f5"
}
},
{
"path": "pages/chat/chat",
"style": {
"navigationBarTitleText": "AI+",
"navigationBarTitleTextSize": "30rpx",
"navigationBarBackgroundColor": "#4778EC",
"navigationBarTextStyle": "white",
"enablePullDownRefresh": false,
"enablePullDownRefresh": false
// #ifdef H5
"navigationStyle": "custom"
// "navigationStyle": "custom"
//#endif
}
},
{
"path": "pages/search/search",
"style": {
"navigationBarTitleText": "",
"navigationStyle": "custom"
"navigationBarTitleText": ""
// "navigationStyle": "custom"
}
},
{
"path": "pages/mine/company-mine",
"style": {
"navigationBarTitleText": "我的",
"navigationBarTitleTextSize": "30rpx"
// "navigationStyle": "custom"
}
},
{
"path": "pages/mine/company-info",
"style": {
"navigationBarTitleText": "企业信息",
"navigationBarTitleTextSize": "30rpx"
}
},
{
"path": "pages/mine/edit-company-contacts",
"style": {
"navigationBarTitleText": "编辑联系人",
"navigationBarTitleTextSize": "30rpx"
}
}
@@ -101,10 +179,11 @@
"pages": [
{
"path" : "pages/addWorkExperience/addWorkExperience",
"style" :
"style" :
{
"navigationBarTitleText" : "添加工作经历",
"navigationStyle": "custom"
"navigationBarTitleTextSize": "30rpx"
// "navigationStyle": "custom"
}
},{
"path": "pages/choiceness/choiceness",
@@ -119,41 +198,43 @@
"style": {
"navigationBarTitleText": "职位详情",
"navigationBarBackgroundColor": "#4778EC",
"navigationBarTextStyle": "white",
"navigationStyle": "custom"
"navigationBarTextStyle": "white"
}
}, {
"path": "pages/UnitDetails/UnitDetails",
"style": {
"navigationBarTitleText": "单位详情",
"navigationBarBackgroundColor": "#4778EC",
"navigationBarTextStyle": "white",
"navigationStyle": "custom"
"navigationBarTitleText": "单位详情"
// "navigationBarBackgroundColor": "#4778EC",
// "navigationBarTextStyle": "white"
// "navigationStyle": "custom"
}
}, {
"path": "pages/exhibitors/exhibitors",
"style": {
"navigationBarTitleText": "参展单位",
"navigationBarBackgroundColor": "#4778EC",
"navigationBarTextStyle": "white",
"navigationStyle": "custom"
"navigationBarBackgroundColor": "#FFFFFF",
"navigationBarTextStyle": "black"
// "navigationStyle": "custom"
}
}, {
}, {
"path": "pages/myResume/myResume",
"style": {
"navigationBarTitleText": "我的简历",
"navigationBarTitleTextSize": "30rpx",
"navigationBarBackgroundColor": "#FFFFFF"
}
}, {
}, {
"path": "pages/Intendedposition/Intendedposition",
"style": {
"navigationBarTitleText": "投递记录",
"navigationBarTitleTextSize": "30rpx",
"navigationBarBackgroundColor": "#FFFFFF"
}
}, {
}, {
"path": "pages/collection/collection",
"style": {
"navigationBarTitleText": "我的收藏",
"navigationBarTitleTextSize": "30rpx",
"navigationBarBackgroundColor": "#FFFFFF",
"navigationStyle": "custom"
}
@@ -162,6 +243,7 @@
"path": "pages/browseJob/browseJob",
"style": {
"navigationBarTitleText": "我的浏览",
"navigationBarTitleTextSize": "30rpx",
"navigationBarBackgroundColor": "#FFFFFF",
"navigationStyle": "custom"
}
@@ -170,6 +252,7 @@
"path": "pages/addPosition/addPosition",
"style": {
"navigationBarTitleText": "添加岗位",
"navigationBarTitleTextSize": "30rpx",
"navigationStyle": "custom"
}
},
@@ -184,6 +267,7 @@
"path": "pages/personalInfo/personalInfo",
"style": {
"navigationBarTitleText": "个人信息",
"navigationBarTitleTextSize": "30rpx",
"navigationStyle": "custom"
}
},
@@ -191,13 +275,15 @@
"path": "pages/jobExpect/jobExpect",
"style": {
"navigationBarTitleText": "求职期望",
"navigationStyle": "custom"
"navigationBarTitleTextSize": "30rpx"
// "navigationStyle": "custom"
}
},
{
"path": "pages/reservation/reservation",
"style": {
"navigationBarTitleText": "我的预约",
"navigationBarTitleTextSize": "30rpx",
"navigationBarBackgroundColor": "#FFFFFF"
}
},
@@ -205,6 +291,7 @@
"path": "pages/choicenessList/choicenessList",
"style": {
"navigationBarTitleText": "精选企业",
"navigationBarTitleTextSize": "30rpx",
"navigationBarBackgroundColor": "#FFFFFF",
"navigationStyle": "custom"
}
@@ -213,6 +300,7 @@
"path": "pages/newJobPosition/newJobPosition",
"style": {
"navigationBarTitleText": "新职位推荐",
"navigationBarTitleTextSize": "30rpx",
"navigationBarBackgroundColor": "#FFFFFF"
}
},
@@ -220,6 +308,7 @@
"path": "pages/systemNotification/systemNotification",
"style": {
"navigationBarTitleText": "系统通知",
"navigationBarTitleTextSize": "30rpx",
"navigationBarBackgroundColor": "#FFFFFF"
}
},
@@ -235,6 +324,7 @@
"path": "pages/moreJobs/moreJobs",
"style": {
"navigationBarTitleText": "更多岗位",
"navigationBarTitleTextSize": "30rpx",
"navigationBarBackgroundColor": "#FFFFFF"
}
},
@@ -242,6 +332,7 @@
"path": "pages/collection/compare",
"style": {
"navigationBarTitleText": " 岗位对比",
"navigationBarTitleTextSize": "30rpx",
"navigationBarBackgroundColor": "#FFFFFF"
}
},
@@ -249,150 +340,71 @@
"path": "pages/myResume/corporateInformation",
"style": {
"navigationBarTitleText": " 企业详情",
"navigationBarTitleTextSize": "30rpx",
"navigationBarBackgroundColor": "#FFFFFF"
}
}
]
},
{
"root": "packageB",
"pages": [
{
"path" : "pages/search/search",
"style" : {
"navigationBarTitleText" : "生涯规划",
"navigationStyle": "custom"
}
},
"root": "packageB",
"pages": [
{
"path" : "jobFair/detail",
"style" :
{
"navigationBarTitleText" : "招聘会详情",
"navigationBarTitleTextSize": "30rpx"
// "navigationStyle": "custom"
}
},
{
"path" : "login",
"style" :
{
"navigationBarTitleText" : "登录",
"navigationBarTitleTextSize": "30rpx"
// "navigationStyle": "custom"
}
},
{
"path": "pages/job/index",
"style": {
"navigationBarTitleText": "职业库"
}
"path" : "train/index",
"style" :
{
"navigationBarTitleText" : "技能评价",
"navigationBarTitleTextSize": "30rpx"
// "navigationStyle": "custom"
}
},
{
"path": "pages/job/midList",
"style": {
"navigationBarTitleText": "职业库-中类"
}
"path" : "train/practice/startPracticing",
"style" :
{
"navigationBarTitleText" : "专项训练",
"navigationBarTitleTextSize": "30rpx"
// "navigationStyle": "custom"
}
},
{
"path": "pages/job/smallList",
"style": {
"navigationBarTitleText": "职业库-小类"
}
"path" : "train/video/videoList",
"style" :
{
"navigationBarTitleText" : "视频学习",
"navigationBarTitleTextSize": "30rpx"
// "navigationStyle": "custom"
}
},
{
"path": "pages/job/details",
"style": {
"navigationBarTitleText": "职业详情"
}
"path" : "train/video/videoDetail",
"style" :
{
"navigationBarTitleText" : "视频详情",
"navigationBarTitleTextSize": "30rpx"
// "navigationStyle": "custom"
}
},
{
"path": "pages/userCenter/personDocument",
"style": {
"navigationBarTitleText": "生涯档案",
"navigationStyle": "custom"
}
},
{
"path": "pages/userCenter/careerCompass",
"style": {
"navigationBarTitleText": "生涯罗盘",
"navigationStyle": "custom"
}
},
{
"path": "pages/userCenter/learningPlan",
"style": {
"navigationBarTitleText": "学业规划",
"navigationStyle": "custom"
}
},
{
"path": "pages/userCenter/smartTarget",
"style": {
"navigationBarTitleText": "学业规划",
"navigationStyle": "custom"
}
},
{
"path": "pages/userCenter/fillInInformation",
"style": {
"navigationBarTitleText": "完善个人信息"
}
},
{
"path": "pages/pagesTest/testList",
"style": {
"navigationBarTitleText": "生涯测评",
"navigationStyle": "custom"
}
},
{
"path": "pages/pagesTest/customTestTitle",
"style": {
"navigationBarTitleText": "自定义测评",
"navigationStyle": "custom"
}
},
{
"path": "pages/pagesTest/interestTestTitle",
"style": {
"navigationBarTitleText": "职业兴趣测评",
"navigationStyle": "custom"
}
},
{
"path": "pages/pagesTest/workValuesTestTitle",
"style": {
"navigationBarTitleText": "工作价值观测评",
"navigationStyle": "custom"
}
},
{
"path": "pages/pagesTest/personalTestTitle",
"style": {
"navigationBarTitleText": "人格测评",
"navigationStyle": "custom"
}
},
{
"path": "pages/testReport/workValuesTestReport",
"style": {
"navigationBarTitleText": "工作价值观测评报告",
"navigationStyle": "custom"
}
},
{
"path": "pages/testReport/multipleAbilityTestReport",
"style": {
"navigationBarTitleText": "多元能力测评报告",
"navigationStyle": "custom"
}
},
{
"path": "pages/testReport/generalCareerTestReport",
"style": {
"navigationBarTitleText": "通用职业能力测评报告",
"navigationStyle": "custom"
}
},
{
"path": "pages/testReport/personalTestReport",
"style": {
"navigationBarTitleText": "人格测评报告",
"navigationStyle": "custom"
}
},
{
"path": "pages/testReport/interestTestReport",
"style": {
"navigationBarTitleText": "职业兴趣测评报告",
"navigationStyle": "custom"
}
}
]
]
},{
"root": "packageRc",
"pages": [
@@ -492,13 +504,14 @@
"selectedIconPath": "static/tabbar/mined.png",
"text": "我的"
}
]
],
},
"globalStyle": {
"navigationBarTextStyle": "black",
"navigationBarTitleText": "uni-app",
"navigationBarBackgroundColor": "#F8F8F8",
"backgroundColor": "#F8F8F8"
"backgroundColor": "#F8F8F8",
"navigationBarTitleTextSize": "18px"
// "enablePullDownRefresh": false,
// "navigationStyle": "custom"
},
@@ -508,8 +521,9 @@
"^uni-(.*)": "@dcloudio/uni-ui/lib/uni-$1/uni-$1.vue",
"^IconfontIcon$": "@/components/IconfontIcon/IconfontIcon.vue",
"^WxAuthLogin$": "@/components/WxAuthLogin/WxAuthLogin.vue",
"^AppLayout$": "@/components/AppLayout/AppLayout.vue"
"^AppLayout$": "@/components/AppLayout/AppLayout.vue",
"^CustomTabBar$": "@/components/CustomTabBar/CustomTabBar.vue"
}
},
"uniIdRouter": {}
}
}

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>

View File

@@ -54,6 +54,18 @@
<div class="content unicode" style="display: block;">
<ul class="icon_lists dib-box">
<li class="dib">
<span class="icon iconfont">&#xe63f;</span>
<div class="name">gxbys</div>
<div class="code-name">&amp;#xe63f;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe60f;</span>
<div class="name">个人中心</div>
<div class="code-name">&amp;#xe60f;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe607;</span>
<div class="name">素质测评</div>
@@ -120,9 +132,9 @@
<pre><code class="language-css"
>@font-face {
font-family: 'iconfont';
src: url('iconfont.woff2?t=1760941852498') format('woff2'),
url('iconfont.woff?t=1760941852498') format('woff'),
url('iconfont.ttf?t=1760941852498') format('truetype');
src: url('iconfont.woff2?t=1761826914823') format('woff2'),
url('iconfont.woff?t=1761826914823') format('woff'),
url('iconfont.ttf?t=1761826914823') format('truetype');
}
</code></pre>
<h3 id="-iconfont-">第二步:定义使用 iconfont 的样式</h3>
@@ -148,6 +160,24 @@
<div class="content font-class">
<ul class="icon_lists dib-box">
<li class="dib">
<span class="icon iconfont icon-Graduation-simple-"></span>
<div class="name">
gxbys
</div>
<div class="code-name">.icon-Graduation-simple-
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-gerenzhongxin"></span>
<div class="name">
个人中心
</div>
<div class="code-name">.icon-gerenzhongxin
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-suzhicepingtiku"></span>
<div class="name">
@@ -247,6 +277,22 @@
<div class="content symbol">
<ul class="icon_lists dib-box">
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-Graduation-simple-"></use>
</svg>
<div class="name">gxbys</div>
<div class="code-name">#icon-Graduation-simple-</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-gerenzhongxin"></use>
</svg>
<div class="name">个人中心</div>
<div class="code-name">#icon-gerenzhongxin</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-suzhicepingtiku"></use>

View File

@@ -1,8 +1,8 @@
@font-face {
font-family: "iconfont"; /* Project id 5044714 */
src: url('iconfont.woff2?t=1760941852498') format('woff2'),
url('iconfont.woff?t=1760941852498') format('woff'),
url('iconfont.ttf?t=1760941852498') format('truetype');
src: url('iconfont.woff2?t=1761826914823') format('woff2'),
url('iconfont.woff?t=1761826914823') format('woff'),
url('iconfont.ttf?t=1761826914823') format('truetype');
}
.iconfont {
@@ -13,6 +13,14 @@
-moz-osx-font-smoothing: grayscale;
}
.icon-Graduation-simple-:before {
content: "\e63f";
}
.icon-gerenzhongxin:before {
content: "\e60f";
}
.icon-suzhicepingtiku:before {
content: "\e607";
}

File diff suppressed because one or more lines are too long

View File

@@ -5,6 +5,20 @@
"css_prefix_text": "icon-",
"description": "",
"glyphs": [
{
"icon_id": "5122382",
"name": "gxbys",
"font_class": "Graduation-simple-",
"unicode": "e63f",
"unicode_decimal": 58943
},
{
"icon_id": "6311925",
"name": "个人中心",
"font_class": "gerenzhongxin",
"unicode": "e60f",
"unicode_decimal": 58895
},
{
"icon_id": "28808301",
"name": "素质测评",

Binary file not shown.

After

Width:  |  Height:  |  Size: 219 B

BIN
static/images/train/bj.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 309 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 464 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 455 B

BIN
static/images/train/wsc.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 632 B

BIN
static/images/train/xl.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 242 B

BIN
static/images/train/zs.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 297 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

BIN
static/imgs/avatar.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

View File

@@ -48,31 +48,22 @@ export const useReadMsg = defineStore('readMsg', () => {
const count = unreadCount.value
const index = 3
const countVal = count > 99 ? '99+' : String(count)
if (count === 0) {
uni.removeTabBarBadge({
index
}) // 替换为你消息页面的 TabBar index
badges.value[index] = {
count: 0
}
} else {
badges.value[index] = {
count: countVal
}
uni.setTabBarBadge({
index,
text: countVal
})
// 更新徽章数据,不直接调用 uni.removeTabBarBadge 和 uni.setTabBarBadge
// 因为项目使用的是自定义 TabBar这些方法只能在原生 TabBar 页面使用
badges.value[index] = {
count: count === 0 ? 0 : countVal
}
// 如果需要使用原生 TabBar 的徽章功能,需要确保在 TabBar 页面中调用
// 这里只更新数据,让自定义 TabBar 组件根据数据来显示徽章
}
// 拉取消息列表
async function fetchMessages() {
try {
$api.createRequest('/app/notice/info', {
isRead: 1
}, "GET").then((res) => {
$api.createRequest('/app/notice/info', {}, "GET").then((res) => {
msgList.value = res.data || []
updateTabBarBadge()
})

View File

@@ -64,7 +64,7 @@ const useUserStore = defineStore("user", () => {
});
}
const logOut = (redirect = true) => {
const logOut = (showLoginModal = true) => {
hasLogin.value = false;
token.value = ''
resume.value = {}
@@ -73,11 +73,10 @@ const useUserStore = defineStore("user", () => {
uni.removeStorageSync('userInfo')
uni.removeStorageSync('token')
// 只有在明确需要跳转时才跳转到登录页
if (redirect) {
uni.redirectTo({
url: '/pages/complete-info/complete-info',
});
// 如果需要显示登录弹窗,则通过事件通知页面显示微信登录弹窗
if (showLoginModal) {
// 通过 uni.$emit 发送全局事件,通知页面显示登录弹窗
uni.$emit('showLoginModal');
}
}
@@ -117,9 +116,9 @@ const useUserStore = defineStore("user", () => {
hasLogin.value = true;
// 模拟添加用户类型字段,实际项目中应该从接口获取
if (!userInfo.value.userType) {
userInfo.value.userType = 0; // 默认设置为企业用户
}
// if (!userInfo.value.userType) {
// userInfo.value.userType = 1; // 默认设置为求职者用户
// }
// 持久化存储用户信息到本地缓存
uni.setStorageSync('userInfo', values.data);

View File

@@ -229,7 +229,13 @@ export default {
query
.select(`#waterfalls_flow_column_${i}`)
.boundingClientRect((data) => {
heightArr.push({ column: i, height: data.height });
// 添加 null 检查,防止 data 为 null 时访问 height 属性
if (data && data.height !== undefined) {
heightArr.push({ column: i, height: data.height });
} else {
// 如果元素不存在或高度未定义,使用默认高度 0
heightArr.push({ column: i, height: 0 });
}
})
.exec(() => {
if (this.data.column <= heightArr.length) {

BIN
unpackage/.DS_Store vendored

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -1,15 +0,0 @@
{
"hash": "92569f0a",
"configHash": "2d3ccd01",
"lockfileHash": "63211de0",
"browserHash": "8f10a447",
"optimized": {
"element-plus": {
"src": "../../../../../../../node_modules/element-plus/es/index.mjs",
"file": "element-plus.js",
"fileHash": "beb69334",
"needsInterop": false
}
},
"chunks": {}
}

View File

@@ -1,3 +0,0 @@
{
"type": "module"
}

View File

@@ -1,7 +1,8 @@
{
"description": "项目配置文件。",
"packOptions": {
"ignore": []
"ignore": [],
"include": []
},
"setting": {
"urlCheck": false,
@@ -9,28 +10,20 @@
"postcss": true,
"minified": true,
"newFeature": true,
"bigPackageSizeSupport": true
"bigPackageSizeSupport": true,
"babelSetting": {
"ignore": [],
"disablePlugins": [],
"outputPath": ""
}
},
"compileType": "miniprogram",
"libVersion": "3.5.7",
"appid": "wxc0f37e549415bb4e",
"libVersion": "3.10.3",
"appid": "wx9d1cbc11c8c40ba7",
"projectname": "qingdao-employment-service",
"condition": {
"search": {
"current": -1,
"list": []
},
"conversation": {
"current": -1,
"list": []
},
"game": {
"current": -1,
"list": []
},
"miniprogram": {
"current": -1,
"list": []
}
"condition": {},
"editorSetting": {
"tabIndent": "insertSpaces",
"tabSize": 2
}
}

View File

@@ -1,91 +0,0 @@
{
"description": "项目私有配置文件。此文件中的内容将覆盖 project.config.json 中的相同字段。项目的改动优先同步到此文件中。详见文档https://developers.weixin.qq.com/miniprogram/dev/devtools/projectconfig.html",
"projectname": "qingdao-employment-service",
"setting": {
"compileHotReLoad": true,
"autoAudits": false
},
"condition": {
"miniprogram": {
"list": [
{
"name": "packageRc/pages/personalList/personalList",
"pathName": "packageRc/pages/personalList/personalList",
"query": "",
"launchMode": "default",
"scene": null
},
{
"name": "packageRc/pages/daiban/daiban",
"pathName": "packageRc/pages/daiban/daiban",
"query": "",
"launchMode": "default",
"scene": null
},
{
"name": "packageRc/pages/index/index",
"pathName": "packageRc/pages/index/index",
"query": "",
"launchMode": "default",
"scene": null
},
{
"name": "packageB/pages/userCenter/personDocument",
"pathName": "packageB/pages/userCenter/personDocument",
"query": "",
"launchMode": "default",
"scene": null
},
{
"name": "packageB/pages/testReport/personalTestReport",
"pathName": "packageB/pages/testReport/personalTestReport",
"query": "year=2025",
"launchMode": "default",
"scene": null
},
{
"name": "packageB/pages/testReport/interestTestReport",
"pathName": "packageB/pages/testReport/interestTestReport",
"query": "",
"launchMode": "default",
"scene": null
},
{
"name": "packageB/pages/testReport/multipleAbilityTestReport",
"pathName": "packageB/pages/testReport/multipleAbilityTestReport",
"query": "id=969306",
"launchMode": "default",
"scene": null
},
{
"name": "packageB/pages/testReport/workValuesTestReport",
"pathName": "packageB/pages/testReport/workValuesTestReport",
"query": "",
"launchMode": "default",
"scene": null
},
{
"name": "packageB/pages/userCenter/personDocument",
"pathName": "packageB/pages/userCenter/personDocument",
"query": "",
"launchMode": "default",
"scene": null
},
{
"name": "packageB/pages/pagesTest/testList",
"pathName": "packageB/pages/pagesTest/testList",
"query": "",
"launchMode": "default",
"scene": null
},
{
"name": "packageB/pages/search/search",
"pathName": "packageB/pages/search/search",
"query": "",
"launchMode": "default",
"scene": null
}
]
}
}
}

View File

@@ -166,67 +166,67 @@ export function packageRcPost(config) {
// ======================================
export function request({
url,
method = 'GET',
data = {},
load = false,
header = {}
url,
method = 'GET',
data = {},
load = false,
header = {}
} = {}) {
return new Promise((resolve, reject) => {
if (load) {
uni.showLoading({
title: '请稍候',
mask: true
});
}
let Authorization = ''
if (useUserStore().token) {
Authorization = `${useUserStore().userInfo.token}${useUserStore().token}`
}
uni.request({
url: config.baseUrl + url,
method,
data: data,
header: {
'Authorization': Authorization || '',
},
success: resData => {
// 响应拦截
if (resData.statusCode === 200) {
const {
code,
msg
} = resData.data
if (code === 200) {
resolve(resData.data)
return
}
uni.showToast({
title: msg,
icon: 'none'
})
}
if (resData.data?.code === 401 || resData.data?.code === 402) {
useUserStore().logOut()
uni.showToast({
title: '登录过期,请重新登录',
icon: 'none'
})
return
}
const err = new Error('请求出现异常,请联系工作人员')
err.error = resData
reject(err)
},
fail: err => reject(err),
complete() {
if (load) {
uni.hideLoading();
}
}
})
})
return new Promise((resolve, reject) => {
if (load) {
uni.showLoading({
title: '请稍候',
mask: true
});
}
let Authorization = ''
if (useUserStore().token) {
Authorization = `${useUserStore().userInfo.token}${useUserStore().token}`
}
uni.request({
url: config.baseUrl + url,
method,
data: data,
header: {
'Authorization': Authorization || '',
},
success: resData => {
// 响应拦截
if (resData.statusCode === 200) {
const {
code,
msg
} = resData.data
if (code === 200) {
resolve(resData.data)
return
}
uni.showToast({
title: msg,
icon: 'none'
})
}
if (resData.data?.code === 401 || resData.data?.code === 402) {
useUserStore().logOut()
uni.showToast({
title: '登录过期,请重新登录',
icon: 'none'
})
return
}
const err = new Error('请求出现异常,请联系工作人员')
err.error = resData
reject(err)
},
fail: err => reject(err),
complete() {
if (load) {
uni.hideLoading();
}
}
})
})
}
@@ -239,146 +239,172 @@ export function request({
* @returns promise
**/
export function createRequest(url, data = {}, method = 'GET', loading = false, headers = {}) {
if (loading) {
uni.showLoading({
title: '请稍后',
mask: true
})
}
let Authorization = ''
if (useUserStore().token) {
Authorization = `${useUserStore().token}`
}
if (loading) {
uni.showLoading({
title: '请稍后',
mask: true
})
}
let Authorization = ''
if (useUserStore().token) {
Authorization = `${useUserStore().token}`
}
const header = headers || {};
header["Authorization"] = encodeURIComponent(Authorization);
return new Promise((resolve, reject) => {
uni.request({
url: config.baseUrl + url,
method: method,
data: data,
header,
success: resData => {
// 响应拦截
if (resData.statusCode === 200) {
const {
code,
msg
} = resData.data
if (code === 200) {
resolve(resData.data)
return
}
// 处理业务错误
if (resData.data?.code === 401 || resData.data?.code === 402) {
useUserStore().logOut()
}
// 显示具体的错误信息
const errorMsg = msg || '请求出现异常,请联系工作人员'
uni.showToast({
title: errorMsg,
icon: 'none'
})
const err = new Error(errorMsg)
err.error = resData
reject(err)
return
}
// HTTP状态码不是200的情况
const err = new Error('网络请求失败,请检查网络连接')
err.error = resData
reject(err)
},
fail: (err) => {
reject(err)
},
complete: () => {
if (loading) {
uni.hideLoading();
}
}
});
})
const header = headers || {};
header["Authorization"] = encodeURIComponent(Authorization);
return new Promise((resolve, reject) => {
uni.request({
url: config.baseUrl + url,
method: method,
data: data,
header,
success: resData => {
// 响应拦截
if (resData.statusCode === 200) {
const {
code,
msg
} = resData.data
if (code === 200) {
resolve(resData.data)
return
}
// 处理业务错误
if (resData.data?.code === 401 || resData.data?.code === 402) {
useUserStore().logOut()
}
// 显示具体的错误信息
const errorMsg = msg || '请求出现异常,请联系工作人员'
uni.showToast({
title: errorMsg,
icon: 'none'
})
const err = new Error(errorMsg)
err.error = resData
reject(err)
return
}
// HTTP状态码不是200的情况
const err = new Error('网络请求失败,请检查网络连接')
err.error = resData
reject(err)
},
fail: (err) => {
reject(err)
},
complete: () => {
if (loading) {
uni.hideLoading();
}
}
});
})
}
export function uploadFile(tempFilePaths, loading = false) {
if (loading) {
uni.showLoading({
title: '请稍后',
mask: true
})
}
let Authorization = ''
if (useUserStore().token) {
Authorization = `${useUserStore().token}`
}
if (loading) {
uni.showLoading({
title: '请稍后',
mask: true
})
}
let Authorization = ''
if (useUserStore().token) {
Authorization = `${useUserStore().token}`
}
const header = {};
header["Authorization"] = encodeURIComponent(Authorization);
return new Promise((resolve, reject) => {
uni.uploadFile({
url: config.baseUrl + '/app/file/upload',
filePath: tempFilePaths,
name: 'file',
header,
success: (uploadFileRes) => {
if (uploadFileRes.statusCode === 200) {
return resolve(uploadFileRes.data)
}
},
fail: (err) => {
reject(err)
},
complete: () => {
if (loading) {
uni.hideLoading();
}
}
})
})
const header = {};
header["Authorization"] = encodeURIComponent(Authorization);
return new Promise((resolve, reject) => {
uni.uploadFile({
url: config.baseUrl + '/app/file/upload',
filePath: tempFilePaths,
name: 'file',
header,
success: (uploadFileRes) => {
if (uploadFileRes.statusCode === 200) {
return resolve(uploadFileRes.data)
}
},
fail: (err) => {
reject(err)
},
complete: () => {
if (loading) {
uni.hideLoading();
}
}
})
})
}
// 主包的GET请求函数 - 调用request函数使用动态token和配置的baseURL
// 此函数已经集成到主包中,与主包其他请求函数保持一致的行为
// 封装GET请求
export function get(config) {
if (typeof config === 'string') {
// 兼容旧的调用方式: get(url, params, options)
const params = arguments[1] || {};
const options = arguments[2] || {};
return request({
url: config,
method: 'GET',
data: params,
...options
})
}
// 支持配置对象的调用方式: get({url, data, ...})
return request({
method: 'GET',
...config
})
export function myRequest(url, data = {}, method = 'GET', port = 9100, headers = {}, loading = false) {
let LCBaseUrl = config.LCBaseUrl
if (port != 9100) {
LCBaseUrl = config.LCBaseUrlInner
}
const header = headers || {};
// 上下文
// /jobfair-api/jobfair/public 招聘会
// /dashboard-api/dashboard 用户登录相关
// /dashboard-api 获取验证码、登录
if (loading) {
uni.showLoading({
title: '请稍后',
mask: true
})
}
return new Promise((resolve, reject) => {
uni.request({
url: LCBaseUrl + url,
method: method,
data: data,
header,
success: resData => {
if(url=='/dashboard/auth/heart'){
resolve(resData.data)
return
}else{
// 响应拦截
if (resData.statusCode === 200) {
const {
code,
msg
} = resData.data
if (code === 200) {
resolve(resData.data)
return
}
// 处理业务错误
if (resData.data?.code === 401 || resData.data?.code === 402) {
useUserStore().logOut()
}
// 显示具体的错误信息
const errorMsg = msg || '请求出现异常,请联系工作人员'
uni.showToast({
title: errorMsg,
icon: 'none'
})
const err = new Error(errorMsg)
err.error = resData
reject(err)
return
}
// HTTP状态码不是200的情况
const err = new Error('网络请求失败,请检查网络连接')
err.error = resData
reject(err)
}
},
fail: (err) => {
reject(err)
},
complete: () => {
if (loading) {
uni.hideLoading();
}
}
})
})
}
// 主包的POST请求函数 - 调用request函数使用动态token和配置的baseURL
// 此函数已经集成到主包中,与主包其他请求函数保持一致的行为
// 封装POST请求
export function post(config) {
if (typeof config === 'string') {
// 兼容旧的调用方式: post(url, data, options)
const data = arguments[1] || {};
const options = arguments[2] || {};
return request({
url: config,
method: 'POST',
data,
...options
})
}
// 支持配置对象的调用方式: post({url, data, ...})
return request({
method: 'POST',
...config
})
}

45
utils/tabbarManager.js Normal file
View File

@@ -0,0 +1,45 @@
// 自定义tabbar管理器
export const tabbarManager = {
// 显示tabbar
showTabBar() {
if (typeof getTabBar === 'function' && getTabBar()) {
getTabBar().setData({
selected: 0
});
}
},
// 更新tabbar选中状态
updateSelected(index) {
if (typeof getTabBar === 'function' && getTabBar()) {
getTabBar().setData({
selected: index
});
}
},
// 根据用户类型更新tabbar
updateTabBarByUserType() {
if (typeof getTabBar === 'function' && getTabBar()) {
const userInfo = uni.getStorageSync('userInfo') || {};
const userType = userInfo.isCompanyUser !== undefined ? userInfo.isCompanyUser : 1;
getTabBar().generateTabbarList(userType);
}
},
// 初始化tabbar
initTabBar() {
// 延迟初始化自定义tabbar
setTimeout(() => {
this.updateTabBarByUserType();
}, 200);
},
// 强制刷新tabbar登录后调用
refreshTabBar() {
// 延迟刷新,确保用户信息已更新
setTimeout(() => {
this.updateTabBarByUserType();
}, 100);
}
};