Files
ks-app-employment-service/packageB/jobFair/detailCom.vue

1028 lines
23 KiB
Vue
Raw Normal View History

2025-11-05 17:21:01 +08:00
<template>
<view class="job-detail">
<view class="company-header">
<view class="company-img">
<image :src="`${imgBaseUrl}/jobfair/company.png`" mode=""></image>
</view>
<view>
<view class="company-name">
{{ fair.jobFairTitle }}
<view class="job-type">
{{ fair.jobFairType == 1 ? "线上" : "线下" }}
</view>
</view>
<view class="company-Address">
{{ fair.jobFairAddress }}
<view class="activity-status">
{{
getActivityStatus(fair.jobFairStartTime, fair.jobFairEndTime).text
}}
</view>
</view>
</view>
</view>
<template v-if="isExpanded">
<view class="jobfair-title"> 招聘会单位 </view>
<view class="jobfair-content">
<view> 1招聘会协办单位{{ fair.jobFairHelpUnit }} </view>
<view> 2招聘会主办单位{{ fair.jobFairHostUnit }} </view>
<view> 3招聘会承办单位{{ fair.jobFairOrganizeUnit }} </view>
</view>
<view class="jobfair-title"> 内容描述 </view>
<view class="jobfair-content">
{{ fair.jobFairIntroduction }}
</view>
<view class="jobfair-title"> 联系电话 </view>
<view class="jobfair-content">
{{ fair.jobFairPhone }}
</view>
<view class="jobfair-title"> 招聘会备注 </view>
<view class="jobfair-content">
{{ fair.jobFairRemark }}
</view>
<view class="jobfair-content">
<view class="card-times">
<view class="time-left">
<view class="left-date">{{
parseDateTime(fair.jobFairStartTime).time
}}</view>
<view class="left-dateDay">{{
parseDateTime(fair.jobFairStartTime).date
}}</view>
</view>
<view class="line"></view>
<view class="time-center">
<view class="center-date">
{{
getTimeStatus(fair.jobFairStartTime, fair.jobFairEndTime)
.statusText
}}
</view>
<view class="center-dateDay">
{{
getHoursBetween(fair.jobFairStartTime, fair.jobFairEndTime)
}}小时
</view>
</view>
<view class="line"></view>
<view class="time-right">
<view class="left-date">{{
parseDateTime(fair.jobFairEndTime).time
}}</view>
<view class="left-dateDay">{{
parseDateTime(fair.jobFairEndTime).date
}}</view>
</view>
</view>
</view>
<view class="jobfair-title"> 招聘会照片 </view>
<view class="jobfair-content">
<image :src="publicUrl + '/file/file/minio' + fair.jobFairImage" alt="" v-if="fair.jobFairImage" />
<text v-else>暂无照片</text>
</view>
</template>
<view class="expand" @click="expand">
<text>{{ isExpanded ? "收起" : "展开" }}</text>
<image class="expand-img" :class="{ 'expand-img-active': !isExpanded }" src="@/static/icon/downs.png">
</image>
</view>
<view class="num-box">
<view class="num-item">
<view class="num">
{{ fair.myJobNum?fair.myJobNum : 0 }}
</view>
<view class="num-desc">
企业招聘岗位
</view>
</view>
<view class="num-item">
<view class="num">
{{fair.resumeCount?fair.resumeCount : 0}}
</view>
<view class="num-desc">
收到简历
</view>
</view>
</view>
<!-- 使用scroll-view实现分页加载 -->
<scroll-view scroll-y class="detail-box" @scrolltolower="onReachBottom" lower-threshold="50">
<view class="detail-item" v-for="(item, index) in detail.list" :key="index">
<view class="gw">
岗位名称<text>{{ item.jobInfo.jobTitle }}</text>
</view>
<view class="name">
{{ item.personInfo.name }}
<!-- 应聘状态1已投递2已邀请面试3已录用4不录用 -->
<view class="status" :style="getItemBackgroundStyle('wcl.png')">
{{ getStatusText(item.status) }}
</view>
</view>
<view class="bottom">
<view class="tag">
<view class="tag-item">性别{{ item.personInfo.sex == 0 ? "男" : "女" }}</view>
<view class="tag-item success">年龄{{ item.personInfo.age }}</view>
</view>
</view>
<view class="btn">
<button type="primary" plain>查看</button>
<button type="primary" @click="interviewBtn(item)" v-if="item.status == 1">
面试邀请
</button>
<button type="primary" @click="acceptBtn(item, 0)" v-if="item.status != 3 && item.status != 4">
录用
</button>
<button type="primary" @click="acceptBtn(item, 2)" v-if="item.status != 3 && item.status != 4">
不录用
</button>
</view>
</view>
<!-- 加载状态 -->
<view v-if="loading" class="loading">加载中...</view>
<view v-if="!loading && detail.list.length >= detail.total" class="no-more">没有更多数据了</view>
</scroll-view>
<!-- 面试邀请弹窗 -->
<uni-popup ref="interviewPopup" type="center" :maskClick="false">
<view class="popup-content job-dialog" :style="getItemBackgroundStyle('zphbm-k.png')">
<view class="title">
<image :src="`${imgBaseUrl}/jobfair/xb.png`" mode="aspectFit"></image>
面试邀请
</view>
<view class="dialog-content">
<view class="detail-item1">
<view class="label">面试时间</view>
<view class="value">
<picker mode="date" fields="day" :value="interviewForm.date"
@change="(e) => (interviewForm.date = e.detail.value)">
<view class="picker">
{{ interviewForm.date || "选择日期" }}
</view>
</picker>
<picker mode="time" :value="interviewForm.time"
@change="(e) => (interviewForm.time = e.detail.value)">
<view class="picker">
{{ interviewForm.time || "选择时间" }}
</view>
</picker>
</view>
</view>
<view class="detail-item1">
<view class="label">面试地点</view>
<view class="value">
<radio-group @change="changeAddress">
<!-- v-model="interviewForm.radio" -->
<radio :value="'1'" style="transform: scale(0.7)" />企业地址
<radio :value="'2'" style="transform: scale(0.7)" />定义地址
</radio-group>
</view>
</view>
<view class="detail-item1" v-if="interviewForm.radio == '2'">
<view class="label">定义地址</view>
<view class="value">
<input v-model="interviewForm.address" placeholder="请输入地址" />
</view>
</view>
</view>
<view class="btn-box">
<button type="primary" @click="submitInterview">提交</button>
<button type="default" @click="closeInterviewPopup">关闭</button>
</view>
</view>
</uni-popup>
<!-- 企业录用反馈弹窗 -->
<uni-popup ref="feedBackPopup" type="center" :maskClick="false">
<view class="popup-content job-dialog" :style="getItemBackgroundStyle('zphbm-k.png')">
<view class="title">
<image :src="`${imgBaseUrl}/jobfair/xb.png`" mode="aspectFit"></image>
企业录用反馈
</view>
<view class="dialog-content">
<textarea :rows="7" placeholder="请输入内容(必填)" v-model="feedBack"></textarea>
</view>
<view class="btn-box">
<button type="primary" @click="submitFeedBack">提交</button>
<button type="default" @click="closeFeedBackPopup">关闭</button>
</view>
</view>
</uni-popup>
</view>
</template>
<script setup>
import config from "@/config.js";
import {
ref,
reactive,
onMounted,
nextTick,
watch,
inject
} from "vue";
import {
onLoad
} from "@dcloudio/uni-app";
import {
useRouter,
useRoute
} from "vue-router";
const {
$api
} = inject("globalFunction");
const imgBaseUrl = config.imgBaseUrl;
const router = useRouter();
const route = useRoute();
const interviewPopup = ref(null);
const feedBackPopup = ref(null);
const loading = ref(false);
const hasMore = ref(true);
const jobFairId = ref("");
const userInfo = ref({});
const isExpanded = ref(false);
const publicUrl = config.LCBaseUrl;
onLoad((option) => {
jobFairId.value = option.jobFairId;
getUser();
});
// 响应式数据
const interviewForm = reactive({
date: "",
time: "",
address: "",
radio: "1",
});
const feedBack = ref("");
const pages = reactive({
pageNum: 1,
pageSize: 10,
});
const detail = reactive({
list: [],
total: 0,
});
const fair = reactive({});
const openGw = reactive({});
const isAccept = ref(true);
function expand() {
isExpanded.value = !isExpanded.value;
}
// 获取状态文本
const getStatusText = (status) => {
switch (status) {
case "1":
return "已投递";
case "2":
return "已邀请面试";
case "3":
return "已录用";
case "4":
return "不录用";
default:
return "未知状态";
}
};
const baseUrl = config.imgBaseUrl;
const getItemBackgroundStyle = (imageName) => ({
backgroundImage: `url(${baseUrl}/jobfair/${imageName})`,
backgroundSize: "100% 100%", // 覆盖整个容器
backgroundPosition: "center", // 居中
backgroundRepeat: "no-repeat",
});
const getDetail = () => {
const data = {
jobFairId: jobFairId.value,
enterpriseId: userInfo.value.info.userId,
};
$api.myRequest("/jobfair/public/jobfair/detail", data).then((resData) => {
// 直接将数据合并到fair对象
Object.assign(fair, resData.data);
console.log(fair, "fair data");
});
};
function changeAddress(e) {
interviewForm.radio = e.detail.value;
}
const getUser = () => {
const raw = uni.getStorageSync("Padmin-Token");
const token = typeof raw === "string" ? raw.trim() : "";
const headers = token ?
{
Authorization: raw.startsWith("Bearer ") ? raw : `Bearer ${token}`,
} :
{};
$api
.myRequest("/system/user/login/user/info", {}, "GET", 10100, headers)
.then((resData) => {
userInfo.value = resData;
getDetail();
getList(false);
});
};
// 获取收到的简历列表
const getList = (isLoadMore = false) => {
if (loading.value) return;
loading.value = true;
try {
$api
.myRequest(
"/jobfair/public/job-fair-person-job/enterprise/my-received-resume", {
enterpriseId: userInfo.value.info.userId,
jobFairId: jobFairId.value,
...pages,
}
)
.then((data) => {
if (data.code === 200) {
if (isLoadMore) {
detail.list = [...detail.list, ...data.data.list];
} else {
detail.list = data.data.list || [];
}
detail.total = data.data.total || 0;
hasMore.value = detail.list.length < detail.total;
}
});
} catch (error) {
console.log(error, "error");
uni.showToast({
title: "获取数据失败",
icon: "none",
});
} finally {
loading.value = false;
}
};
// 面试邀请
const interviewBtn = (item) => {
Object.assign(openGw, item);
interviewPopup.value.open();
};
// 关闭面试弹窗
const closeInterviewPopup = () => {
interviewPopup.value.close();
};
// 关闭反馈弹窗
const closeFeedBackPopup = () => {
feedBackPopup.value.close();
};
// 提交面试邀请
const submitInterview = () => {
if (!interviewForm.date) {
uni.showToast({
title: "请选择面试日期",
icon: "none",
});
return;
}
if (!interviewForm.time) {
uni.showToast({
title: "请选择面试时间",
icon: "none",
});
return;
}
if (interviewForm.radio == "2" && !interviewForm.address) {
uni.showToast({
title: "请输入面试地址",
icon: "none",
});
return;
}
const data = {
jobFairPersonJobId: openGw.jobFairPersonJobId,
status: "2",
interviewTime: `${interviewForm.date} ${interviewForm.time}`,
interviewAddress: interviewForm.radio == "2" ? interviewForm.address : fair.jobFairAddress,
};
update(data, "面试邀请成功");
};
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();
// "YYYY-MM-DD HH:mm[:ss]" -> "YYYY-MM-DDTHH:mm[:ss]"
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";
}
}
// 其余已是 iOS 可解析格式:"YYYY/MM/DD[ HH:mm:ss]"、"YYYY-MM-DD"、ISO 8601 等
const d = new Date(s);
return isNaN(d.getTime()) ? null : d;
}
function parseDateTime(datetimeStr) {
if (!datetimeStr)
return {
time: "",
date: "",
};
const dateObj = toIOSDate(datetimeStr);
if (!dateObj)
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: "时间异常",
color: "#999999",
};
}
// 判断状态0 开始中1 过期2 未开始
let status = 0;
let statusText = "开始中";
let color = "#13C57C"; // 进行中 - 绿色
if (now < startTime) {
status = 2; // 未开始
statusText = "未开始";
color = "#015EEA"; // 未开始 - 蓝色
} else if (now > endTime) {
status = 1; // 已过期
statusText = "已过期";
color = "#999999"; // 已过期 - 灰色
} else {
status = 0; // 进行中
statusText = "进行中";
color = "#13C57C"; // 进行中 - 绿色
}
return {
status, // 0: 进行中1: 已过期2: 未开始
statusText,
color,
};
}
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); // 保留 2 位小数
}
// 更新简历状态
const update = (data, msg) => {
$api
.myRequest(
"/jobfair/public/job-fair-person-job/update",
data,
"POST",
9100, {
"Content-Type": "application/json",
}
)
.then((resData) => {
if (resData.code == 200) {
uni.showToast({
title: msg,
icon: "success",
});
closeInterviewPopup();
closeFeedBackPopup();
// 重新加载列表
pages.pageNum = 1;
getList(false);
} else {
uni.showToast({
title: "操作失败",
icon: "none",
});
}
});
};
// 提交反馈
const submitFeedBack = () => {
if (!feedBack.value) {
uni.showToast({
title: "请输入内容",
icon: "none",
});
return;
}
const data = {
jobFairPersonJobId: openGw.jobFairPersonJobId,
status: isAccept.value ? "3" : "4",
feedback: feedBack.value,
};
update(data, "操作成功");
feedBack.value = "";
};
// 录用/不录用按钮
const acceptBtn = (item, num) => {
isAccept.value = num == 0;
Object.assign(openGw, item);
feedBackPopup.value.open();
};
// 返回
const goBack = () => {
router.go(-1);
};
// 获取活动状态
const getActivityStatus = (startTime, endTime) => {
// 获取当前时间戳(毫秒)
const now = Date.now();
// 将开始时间和结束时间转换为时间戳
const start =
typeof startTime === "string" ? new Date(startTime).getTime() : startTime;
const end =
typeof endTime === "string" ? new Date(endTime).getTime() : endTime;
// 处理无效时间
if (isNaN(start) || isNaN(end)) {
return {
text: "未知状态",
style: {},
};
}
// 判断状态
if (now < start) {
return {
status: 0,
text: "未开始",
style: {},
};
} else if (now > end) {
return {
status: 1,
text: "已结束",
style: {},
};
} else {
return {
status: 2,
text: "进行中",
style: {},
};
}
};
// 滚动到底部加载更多
const onReachBottom = () => {
if (!loading.value && hasMore.value) {
pages.pageNum++;
getList(true);
}
};
</script>
<style lang="scss" scoped>
.jobfair-content {
color: #656565;
margin-bottom: 20rpx;
line-height: 44rpx;
image {
width: 300rpx;
height: 300rpx;
}
}
.jobfair-title {
width: max-content;
color: #404040;
font-weight: 600;
font-size: 28rpx;
margin-bottom: 25rpx;
position: relative;
&::after {
bottom: -8px;
position: absolute;
left: 31%;
content: "";
display: block;
width: 37rpx;
height: 9rpx;
background: linear-gradient(to right, #ffad58 0%, #ff7a5b 100%);
border-radius: 10rpx;
}
}
.job-detail {
width: 100%;
height: 100vh;
overflow-y: auto;
padding: 20rpx;
background: #e9f2ff;
box-sizing: border-box;
.detail-box {
min-height: max-content;
max-height: calc(100vh - 375rpx);
padding-top: 20rpx;
box-sizing: border-box;
overflow-y: auto;
.detail-item {
margin-top: 30rpx;
background: linear-gradient(to bottom, #deecff 0%, #ffffff 100%);
box-shadow: 0 0 10rpx rgba(0, 95, 169, 0.19);
border-radius: 12rpx;
border: 2rpx solid #ffffff;
padding: 30rpx;
}
.name {
font-size: 32rpx;
font-weight: 600;
margin: 18rpx 0;
display: flex;
align-items: center;
justify-content: space-between;
.status {
width: 160rpx;
text-align: center;
margin-left: 24rpx;
font-size: 24rpx;
color: #fff;
background-size: 100% 100%;
font-weight: normal;
padding: 6rpx 0;
}
}
.bottom {
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
.tag {
width: 100%;
display: flex;
align-items: center;
gap: 16rpx;
flex-wrap: wrap;
.tag-item {
padding: 6rpx 30rpx;
border-radius: 6rpx;
background-color: #E5F1FF;
color: #589BFF;
font-size: 28rpx;
&.success {
background-color: #E6F8EB;
color: #21AA5B;
}
}
}
}
.btn {
width: 100%;
display: flex;
align-items: center;
justify-content: flex-end;
button {
margin: 10rpx;
font-size: 24rpx;
}
}
.gw {
display: flex;
align-items: center;
color: #1477f1;
font-weight: 600;
font-size: 30rpx;
}
}
.company-header {
display: flex;
align-items: center;
gap: 20rpx;
margin-bottom: 26rpx;
.company-img {
width: 110rpx;
height: 107rpx;
border-radius: 20rpx;
image {
width: 100%;
height: 100%;
}
}
}
.company-Address {
font-size: 26rpx;
color: #404040;
display: flex;
align-items: center;
gap: 10rpx;
.activity-status {
font-size: 24rpx;
color: #0088ff;
font-weight: 600;
position: relative;
margin-left: 10rpx;
&::before {
content: "";
display: inline-block;
width: 10rpx;
height: 10rpx;
background: #0088ff;
border-radius: 50rpx;
margin-right: 6rpx;
}
}
}
.company-name {
display: flex;
align-items: center;
font-size: 38rpx;
font-weight: bold;
color: #4d4d4d;
position: relative;
margin-bottom: 10rpx;
gap: 10rpx;
.job-type {
padding: 0 20rpx;
color: #fff;
background: #fd9a33;
border-radius: 20rpx;
font-weight: normal;
font-size: 24rpx;
}
}
.loading,
.no-more {
text-align: center;
padding: 20rpx 0;
color: #909399;
font-size: 28rpx;
}
}
// 弹窗样式
.popup-content {
width: 90vw;
height: 70vh;
// max-width: 600rpx;
// background: #fff;
border-radius: 20rpx;
padding: 10rpx 20rpx;
}
.btn-box {
width: 100%;
padding: 15rpx 0 20rpx;
display: flex;
justify-content: center;
align-items: center;
gap: 50rpx;
button {
padding: 0 80rpx;
height: 80rpx;
line-height: 80rpx;
}
}
.job-dialog {
// background: url("../../../assets/images/job/company/module/") no-repeat 100% 100%;
.dialog-content {
padding: 10rpx;
height: 80%;
.detail-item1 {
// display: flex;
// align-items: center;
margin: 30rpx 0;
.label {
width: 200rpx;
font-size: 32rpx;
color: #303133;
margin-bottom: 15rpx;
}
.value {
flex: 1;
display: flex;
gap: 20rpx;
align-items: center;
}
.picker {
width: 275rpx;
border: 1px solid #dcdfe6;
border-radius: 8rpx;
padding: 16rpx;
}
}
}
.title {
font-size: 38rpx;
font-weight: 600;
color: #303133;
padding: 20rpx;
padding-bottom: 0;
display: flex;
align-items: center;
position: sticky;
top: 0;
image {
width: 14rpx;
height: 33rpx;
margin-right: 14rpx;
}
}
textarea {
width: 100%;
height: 300rpx;
border: 1px solid #dcdfe6;
border-radius: 8rpx;
padding: 20rpx;
box-sizing: border-box;
}
input {
width: 100%;
border: 1px solid #dcdfe6;
border-radius: 8rpx;
height: 75rpx;
line-height: 75rpx;
}
}
.card-times {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 24rpx;
.left-date {
font-weight: 600;
font-size: 48rpx;
font-family: "PingFangSC-Medium", "PingFang SC", "Helvetica Neue", Helvetica,
Arial, "Microsoft YaHei", sans-serif;
background: linear-gradient(180deg, #015eea 0%, #00c0fa 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.left-dateDay {
font-weight: 400;
font-size: 24rpx;
color: #333333;
margin-top: 12rpx;
}
.line {
width: 40rpx;
height: 2rpx;
background: #7bb6ff;
margin-top: 7rpx;
}
.time-center {
text-align: center;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
}
.card-times .time-center .center-dateDay {
font-weight: 400;
font-size: 24rpx;
color: #fff;
margin-top: 6rpx;
line-height: 48rpx;
width: max-content;
height: 48rpx;
background: #71b1ff;
border-radius: 8rpx;
padding: 0 30rpx;
}
.card-times .time-left,
.card-times .time-right {
text-align: center;
}
.expand {
display: flex;
flex-wrap: nowrap;
white-space: nowrap;
justify-content: center;
margin-bottom: 12rpx;
font-weight: 400;
font-size: 28rpx;
color: #256bfa;
.expand-img {
width: 40rpx;
height: 40rpx;
}
.expand-img-active {
transform: rotate(180deg);
}
}
.num-box {
width: 100%;
display: flex;
justify-content: center;
align-items: center;
.num-item {
width: 50%;
text-align: center;
.num {
font-size: 40rpx;
font-weight: 600;
background: linear-gradient(184deg, #0BBAFB 0%, #4285EC 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.num-desc {
font-size: 30rpx;
color: #0C458A;
}
}
}
</style>