feat: 改为移动端用户一键签到

This commit is contained in:
2026-07-24 10:17:59 +08:00
parent 1758dd9cfa
commit a7e9d911db
14 changed files with 421 additions and 262 deletions

View File

@@ -48,37 +48,8 @@
text-decoration: none;
}
.btn.secondary { color: #1f6feb; background: #eaf2ff; }
.btn.checkin { background: #14866d; }
.empty, .loading, .error { padding: 28px 16px; color: #667085; text-align: center; font-size: 14px; line-height: 1.7; }
.error { color: #b42318; }
/* 签到弹窗 */
.mask { position: fixed; inset: 0; background: rgba(23,32,51,.45); display: none; z-index: 50; }
.mask.show { display: block; }
.dialog {
position: fixed; left: 50%; top: 50%; transform: translate(-50%, -50%);
width: calc(100vw - 56px); max-width: 380px; background: #fff;
border-radius: 10px; padding: 18px 16px; z-index: 51; display: none;
box-shadow: 0 18px 48px rgba(23,32,51,.22);
}
.dialog.show { display: block; }
.dialog h3 { margin: 0 0 4px; font-size: 17px; }
.dialog .sub { margin: 0 0 14px; font-size: 13px; color: #667085; }
.field { margin-bottom: 12px; }
.field label { display: block; margin-bottom: 6px; font-size: 14px; color: #344054; }
.field input {
width: 100%; min-height: 42px; padding: 8px 12px; border: 1px solid #d0d5dd;
border-radius: 8px; font-size: 15px; background: #fff; box-sizing: border-box;
}
.field input:focus { outline: none; border-color: #1f6feb; }
.dialog-actions { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-top: 4px; }
.btn.ghost { color: #475467; background: #f2f4f7; }
.toast {
position: fixed; left: 50%; top: 24px; transform: translateX(-50%);
padding: 10px 16px; border-radius: 8px; font-size: 14px; color: #fff;
background: #14866d; z-index: 60; display: none; max-width: 80vw; text-align: center;
}
.toast.error { background: #b42318; }
.toast.show { display: block; }
</style>
</head>
<body>
@@ -169,7 +140,6 @@
'<p>' + escapeHtml(fair.address) + '</p>' +
'</section>' +
'<div class="actions">' +
'<button type="button" class="btn checkin" id="checkinBtn">签到</button>' +
'<a class="btn" href="' + escapeHtml(publicBase() + '/h5/outdoor-fair/detail.html?fairId=' + pageFairId) + '">查看招聘会</a>' +
'</div>' +
'<section class="section">' +
@@ -193,87 +163,7 @@
(jobs.length ? '<div class="jobs">' + jobs.map(function (job) {
return renderJob(job, educationDict, experienceDict);
}).join('') + '</div>' : '<div class="empty">暂无岗位</div>') +
'</section>' +
'<div class="mask" id="checkinMask"></div>' +
'<div class="dialog" id="checkinDialog">' +
'<h3>签到</h3>' +
'<p class="sub">请填写您的姓名和手机号完成签到</p>' +
'<div class="field">' +
'<label for="checkinName">姓名</label>' +
'<input type="text" id="checkinName" placeholder="请输入姓名" autocomplete="name">' +
'</div>' +
'<div class="field">' +
'<label for="checkinPhone">手机号</label>' +
'<input type="tel" id="checkinPhone" placeholder="请输入手机号" autocomplete="tel" maxlength="11">' +
'</div>' +
'<div class="dialog-actions">' +
'<button type="button" class="btn ghost" id="checkinCancel">取消</button>' +
'<button type="button" class="btn checkin" id="checkinSubmit">提交签到</button>' +
'</div>' +
'</div>' +
'<div class="toast" id="checkinToast"></div>';
bindCheckIn();
}
function showToast(msg, isError) {
var toast = document.getElementById('checkinToast');
if (!toast) return;
toast.textContent = msg;
toast.className = 'toast show' + (isError ? ' error' : '');
setTimeout(function () { toast.className = 'toast' + (isError ? ' error' : ''); }, 2400);
}
function openCheckIn() {
var mask = document.getElementById('checkinMask');
var dialog = document.getElementById('checkinDialog');
if (mask && dialog) {
mask.className = 'mask show';
dialog.className = 'dialog show';
}
}
function closeCheckIn() {
var mask = document.getElementById('checkinMask');
var dialog = document.getElementById('checkinDialog');
if (mask) mask.className = 'mask';
if (dialog) dialog.className = 'dialog';
}
function bindCheckIn() {
var openBtn = document.getElementById('checkinBtn');
var cancelBtn = document.getElementById('checkinCancel');
var mask = document.getElementById('checkinMask');
var submitBtn = document.getElementById('checkinSubmit');
if (openBtn) openBtn.onclick = openCheckIn;
if (cancelBtn) cancelBtn.onclick = closeCheckIn;
if (mask) mask.onclick = closeCheckIn;
if (submitBtn) submitBtn.onclick = submitCheckIn;
}
function submitCheckIn() {
var name = (document.getElementById('checkinName') || {}).value;
var phone = (document.getElementById('checkinPhone') || {}).value;
if (!name || !name.trim()) { showToast('请输入姓名', true); return; }
if (!phone || !/^\d{11}$/.test(phone.trim())) { showToast('请输入正确的11位手机号', true); return; }
if (!pageFairId) { showToast('缺少招聘会信息', true); return; }
var submitBtn = document.getElementById('checkinSubmit');
if (submitBtn) submitBtn.disabled = true;
fetch(apiBase() + '/app/outdoor-fair/check-in', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ fairId: Number(pageFairId), name: name.trim(), phone: phone.trim() })
}).then(function (res) { return res.json(); }).then(function (body) {
if (submitBtn) submitBtn.disabled = false;
if (body && body.code === 200) {
closeCheckIn();
showToast('签到成功');
} else {
showToast((body && body.msg) || '签到失败', true);
}
}).catch(function () {
if (submitBtn) submitBtn.disabled = false;
showToast('网络异常,请重试', true);
});
'</section>';
}
function renderJob(job, educationDict, experienceDict) {

View File

@@ -31,41 +31,11 @@
.photo { width: 100%; margin-top: 14px; border-radius: 8px; object-fit: cover; }
.loading, .error { padding: 28px 16px; color: #667085; text-align: center; font-size: 14px; line-height: 1.7; }
.error { color: #b42318; }
.actions { display: grid; grid-template-columns: 1fr; gap: 10px; margin-top: 14px; }
.btn {
display: flex; align-items: center; justify-content: center;
min-height: 44px; padding: 10px 14px; color: #fff; background: #1f6feb;
border: 0; border-radius: 8px; font-size: 15px; text-decoration: none; width: 100%;
}
.btn.checkin { background: #14866d; }
/* 签到弹窗 */
.mask { position: fixed; inset: 0; background: rgba(23,32,51,.45); display: none; z-index: 50; }
.mask.show { display: block; }
.dialog {
position: fixed; left: 50%; top: 50%; transform: translate(-50%, -50%);
width: calc(100vw - 56px); max-width: 380px; background: #fff;
border-radius: 10px; padding: 18px 16px; z-index: 51; display: none;
box-shadow: 0 18px 48px rgba(23,32,51,.22);
}
.dialog.show { display: block; }
.dialog h3 { margin: 0 0 4px; font-size: 17px; }
.dialog .sub { margin: 0 0 14px; font-size: 13px; color: #667085; }
.field { margin-bottom: 12px; }
.field label { display: block; margin-bottom: 6px; font-size: 14px; color: #344054; }
.field input {
width: 100%; min-height: 42px; padding: 8px 12px; border: 1px solid #d0d5dd;
border-radius: 8px; font-size: 15px; background: #fff; box-sizing: border-box;
}
.field input:focus { outline: none; border-color: #1f6feb; }
.dialog-actions { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-top: 4px; }
.btn.ghost { color: #475467; background: #f2f4f7; }
.toast {
position: fixed; left: 50%; top: 24px; transform: translateX(-50%);
padding: 10px 16px; border-radius: 8px; font-size: 14px; color: #fff;
background: #14866d; z-index: 60; display: none; max-width: 80vw; text-align: center;
}
.toast.error { background: #b42318; }
.toast.show { display: block; }
</style>
</head>
<body>
@@ -76,7 +46,6 @@
(function () {
var app = document.getElementById('app');
var fairId = new URLSearchParams(window.location.search).get('fairId');
var pageFairId = null;
function text(value) {
return value === null || value === undefined || value === '' ? '暂无' : String(value);
@@ -103,7 +72,6 @@
function render(data) {
var fair = data.fair || {};
pageFairId = (fair.id !== null && fair.id !== undefined) ? fair.id : fairId;
document.title = text(fair.title);
app.innerHTML =
'<section class="hero">' +
@@ -111,9 +79,6 @@
'<p>' + escapeHtml(fair.hostUnit) + '</p>' +
'<p>' + escapeHtml(fair.address) + '</p>' +
'</section>' +
'<div class="actions">' +
'<button type="button" class="btn checkin" id="checkinBtn">签到</button>' +
'</div>' +
'<section class="section">' +
'<h2>招聘会信息</h2>' +
'<div class="meta">' +
@@ -125,80 +90,7 @@
'<div><span>展位数量</span><span>' + escapeHtml(fair.boothCount) + '</span></div>' +
'</div>' +
(fair.photoUrl ? '<img class="photo" src="' + escapeHtml(fair.photoUrl) + '" alt="招聘会照片">' : '') +
'</section>' +
'<div class="mask" id="checkinMask"></div>' +
'<div class="dialog" id="checkinDialog">' +
'<h3>签到</h3>' +
'<p class="sub">请填写您的姓名和手机号完成签到</p>' +
'<div class="field">' +
'<label for="checkinName">姓名</label>' +
'<input type="text" id="checkinName" placeholder="请输入姓名" autocomplete="name">' +
'</div>' +
'<div class="field">' +
'<label for="checkinPhone">手机号</label>' +
'<input type="tel" id="checkinPhone" placeholder="请输入手机号" autocomplete="tel" maxlength="11">' +
'</div>' +
'<div class="dialog-actions">' +
'<button type="button" class="btn ghost" id="checkinCancel">取消</button>' +
'<button type="button" class="btn checkin" id="checkinSubmit">提交签到</button>' +
'</div>' +
'</div>' +
'<div class="toast" id="checkinToast"></div>';
bindCheckIn();
}
function showToast(msg, isError) {
var toast = document.getElementById('checkinToast');
if (!toast) return;
toast.textContent = msg;
toast.className = 'toast show' + (isError ? ' error' : '');
setTimeout(function () { toast.className = 'toast' + (isError ? ' error' : ''); }, 2400);
}
function openCheckIn() {
var mask = document.getElementById('checkinMask');
var dialog = document.getElementById('checkinDialog');
if (mask && dialog) { mask.className = 'mask show'; dialog.className = 'dialog show'; }
}
function closeCheckIn() {
var mask = document.getElementById('checkinMask');
var dialog = document.getElementById('checkinDialog');
if (mask) mask.className = 'mask';
if (dialog) dialog.className = 'dialog';
}
function bindCheckIn() {
var openBtn = document.getElementById('checkinBtn');
var cancelBtn = document.getElementById('checkinCancel');
var mask = document.getElementById('checkinMask');
var submitBtn = document.getElementById('checkinSubmit');
if (openBtn) openBtn.onclick = openCheckIn;
if (cancelBtn) cancelBtn.onclick = closeCheckIn;
if (mask) mask.onclick = closeCheckIn;
if (submitBtn) submitBtn.onclick = submitCheckIn;
}
function submitCheckIn() {
var name = (document.getElementById('checkinName') || {}).value;
var phone = (document.getElementById('checkinPhone') || {}).value;
if (!name || !name.trim()) { showToast('请输入姓名', true); return; }
if (!phone || !/^\d{11}$/.test(phone.trim())) { showToast('请输入正确的11位手机号', true); return; }
if (!pageFairId) { showToast('缺少招聘会信息', true); return; }
var submitBtn = document.getElementById('checkinSubmit');
if (submitBtn) submitBtn.disabled = true;
fetch(apiBase() + '/app/outdoor-fair/check-in', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ fairId: Number(pageFairId), name: name.trim(), phone: phone.trim() })
}).then(function (res) { return res.json(); }).then(function (body) {
if (submitBtn) submitBtn.disabled = false;
if (body && body.code === 200) { closeCheckIn(); showToast('签到成功'); }
else { showToast((body && body.msg) || '签到失败', true); }
}).catch(function () {
if (submitBtn) submitBtn.disabled = false;
showToast('网络异常,请重试', true);
});
'</section>';
}
if (!fairId) {