Files
ks-app-employment-service/packageB/jobFair/detailCom.vue
2025-11-05 17:21:01 +08:00

1029 lines
23 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view class="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,
code: userInfo.value.info.entCreditCode,
};
$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>