feat: Add Cross-Domain Job Fair and Alliance City Management
- Implemented AppOutdoorFairController for outdoor job fair public H5 interfaces. - Created CrossCityAllianceController for managing cross-domain alliance cities. - Added CrossCityAlliance domain model and corresponding service and mapper. - Developed CrossDomainJobVO for aggregating job data across job fairs. - Created SQL scripts for initializing the cross-city alliance table and modifying the public job fair schema. - Added migration script for backing up and restoring the HighGo database schema.
This commit is contained in:
@@ -20,12 +20,16 @@ spring:
|
||||
driverClassName: com.highgo.jdbc.Driver
|
||||
druid:
|
||||
# 主库数据源
|
||||
# master:
|
||||
# url: jdbc:highgo://124.243.245.42:5866/shz?useUnicode=true&characterEncoding=utf8¤tSchema=shz&stringtype=unspecified
|
||||
# #username: syssso
|
||||
# username: sysdba
|
||||
# password: ZKR2024@comzkr
|
||||
master:
|
||||
url: jdbc:highgo://39.98.44.136:6023/highgo?useUnicode=true&characterEncoding=utf8¤tSchema=shz&stringtype=unspecified
|
||||
#username: syssso
|
||||
username: sysdba
|
||||
password: Hello@2026
|
||||
|
||||
# 从库数据源
|
||||
slave:
|
||||
# 从数据源开关/默认关闭
|
||||
@@ -86,7 +90,7 @@ spring:
|
||||
# 密码
|
||||
password: ZKR2024@@.com
|
||||
# 连接超时时间
|
||||
timeout: 10s
|
||||
timeout: 100s
|
||||
lettuce:
|
||||
pool:
|
||||
# 连接池中的最小空闲连接
|
||||
|
||||
@@ -0,0 +1,299 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
|
||||
<title>参会企业</title>
|
||||
<style>
|
||||
:root {
|
||||
color: #172033;
|
||||
background: #f4f7fb;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif;
|
||||
}
|
||||
* { box-sizing: border-box; }
|
||||
body { margin: 0; background: #f4f7fb; }
|
||||
.page { min-height: 100vh; padding: 18px 14px 28px; }
|
||||
.hero {
|
||||
padding: 22px 18px;
|
||||
color: #fff;
|
||||
background: linear-gradient(135deg, #1f6feb, #14866d);
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 10px 28px rgba(31, 111, 235, .18);
|
||||
}
|
||||
.hero h1 { margin: 0 0 10px; font-size: 24px; line-height: 1.25; letter-spacing: 0; }
|
||||
.hero p { margin: 4px 0 0; font-size: 14px; line-height: 1.7; opacity: .92; }
|
||||
.section { margin-top: 14px; padding: 16px; background: #fff; border-radius: 8px; box-shadow: 0 8px 22px rgba(23, 32, 51, .06); }
|
||||
.section h2 { margin: 0 0 12px; font-size: 17px; line-height: 1.4; letter-spacing: 0; }
|
||||
.meta { display: grid; gap: 8px; }
|
||||
.meta div { display: flex; gap: 10px; font-size: 14px; line-height: 1.55; }
|
||||
.meta span:first-child { flex: 0 0 72px; color: #667085; }
|
||||
.meta span:last-child { min-width: 0; flex: 1; word-break: break-word; }
|
||||
.desc { margin: 0; color: #344054; font-size: 14px; line-height: 1.75; white-space: pre-wrap; }
|
||||
.jobs { display: grid; gap: 10px; }
|
||||
.job { padding: 12px; border: 1px solid #e5eaf2; border-radius: 8px; background: #fbfcff; }
|
||||
.job h3 { margin: 0 0 8px; font-size: 16px; line-height: 1.35; letter-spacing: 0; }
|
||||
.job p { margin: 3px 0; color: #475467; font-size: 13px; line-height: 1.55; }
|
||||
.actions { display: grid; grid-template-columns: 1fr 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;
|
||||
}
|
||||
.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>
|
||||
<main class="page" id="app">
|
||||
<div class="loading">正在加载...</div>
|
||||
</main>
|
||||
<script>
|
||||
(function () {
|
||||
var app = document.getElementById('app');
|
||||
var params = new URLSearchParams(window.location.search);
|
||||
var deviceCode = params.get('deviceCode');
|
||||
var fairId = params.get('fairId');
|
||||
var companyId = params.get('companyId');
|
||||
var pageFairId = null;
|
||||
|
||||
function text(value) {
|
||||
return value === null || value === undefined || value === '' ? '暂无' : String(value);
|
||||
}
|
||||
|
||||
function escapeHtml(value) {
|
||||
return text(value).replace(/[&<>"']/g, function (ch) {
|
||||
return ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' })[ch];
|
||||
});
|
||||
}
|
||||
|
||||
function apiUrl() {
|
||||
if (deviceCode) {
|
||||
return '/app/outdoor-fair/company-page?deviceCode=' + encodeURIComponent(deviceCode);
|
||||
}
|
||||
return '/app/outdoor-fair/' + encodeURIComponent(fairId) + '/companies/' + encodeURIComponent(companyId) + '/page';
|
||||
}
|
||||
|
||||
function fetchJson(url) {
|
||||
return fetch(url).then(function (res) {
|
||||
return res.json();
|
||||
});
|
||||
}
|
||||
|
||||
function fetchDict(dictType) {
|
||||
return fetchJson('/app/common/dict/' + encodeURIComponent(dictType)).then(function (body) {
|
||||
if (body.code !== 200 || !Array.isArray(body.data)) {
|
||||
return {};
|
||||
}
|
||||
return body.data.reduce(function (map, item) {
|
||||
if (item && item.dictValue !== undefined && item.dictValue !== null) {
|
||||
map[String(item.dictValue)] = item.dictLabel;
|
||||
}
|
||||
return map;
|
||||
}, {});
|
||||
}).catch(function () {
|
||||
return {};
|
||||
});
|
||||
}
|
||||
|
||||
function dictLabel(dictMap, value) {
|
||||
if (value === null || value === undefined || value === '') {
|
||||
return '暂无';
|
||||
}
|
||||
return dictMap[String(value)] || String(value);
|
||||
}
|
||||
|
||||
function render(data, dicts) {
|
||||
var fair = data.fair || {};
|
||||
var company = data.company || {};
|
||||
var booth = data.booth || {};
|
||||
var jobs = data.jobs || [];
|
||||
var educationDict = dicts.education || {};
|
||||
var experienceDict = dicts.experience || {};
|
||||
pageFairId = (fair.id !== null && fair.id !== undefined) ? fair.id : fairId;
|
||||
document.title = text(company.name);
|
||||
app.innerHTML =
|
||||
'<section class="hero">' +
|
||||
'<h1>' + escapeHtml(company.name) + '</h1>' +
|
||||
'<p>' + escapeHtml(fair.title) + '</p>' +
|
||||
'<p>' + escapeHtml(fair.address) + '</p>' +
|
||||
'</section>' +
|
||||
'<div class="actions">' +
|
||||
'<button type="button" class="btn checkin" id="checkinBtn">签到</button>' +
|
||||
'<a class="btn" href="' + escapeHtml(data.fairH5Url) + '">查看招聘会</a>' +
|
||||
'</div>' +
|
||||
'<section class="section">' +
|
||||
'<h2>企业信息</h2>' +
|
||||
'<div class="meta">' +
|
||||
'<div><span>行业</span><span>' + escapeHtml(company.industry) + '</span></div>' +
|
||||
'<div><span>规模</span><span>' + escapeHtml(company.scale) + '</span></div>' +
|
||||
'<div><span>性质</span><span>' + escapeHtml(company.nature) + '</span></div>' +
|
||||
'<div><span>地址</span><span>' + escapeHtml(company.location || company.registeredAddress) + '</span></div>' +
|
||||
'<div><span>联系人</span><span>' + escapeHtml(company.contactPerson) + '</span></div>' +
|
||||
'<div><span>联系电话</span><span>' + escapeHtml(company.contactPersonPhone) + '</span></div>' +
|
||||
'<div><span>展位</span><span>' + escapeHtml(booth.boothNumber) + '</span></div>' +
|
||||
'</div>' +
|
||||
'</section>' +
|
||||
'<section class="section">' +
|
||||
'<h2>企业介绍</h2>' +
|
||||
'<p class="desc">' + escapeHtml(company.description) + '</p>' +
|
||||
'</section>' +
|
||||
'<section class="section">' +
|
||||
'<h2>招聘岗位</h2>' +
|
||||
(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('/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);
|
||||
});
|
||||
}
|
||||
|
||||
function renderJob(job, educationDict, experienceDict) {
|
||||
var salary = job.minSalary || job.maxSalary ? text(job.minSalary) + '-' + text(job.maxSalary) + ' 元' : '薪资面议';
|
||||
return '<article class="job">' +
|
||||
'<h3>' + escapeHtml(job.jobTitle) + '</h3>' +
|
||||
'<p>' + escapeHtml(salary) + ' · ' + escapeHtml(dictLabel(educationDict, job.education)) + ' · ' + escapeHtml(dictLabel(experienceDict, job.experience)) + '</p>' +
|
||||
'<p>招聘人数:' + escapeHtml(job.vacancies) + '</p>' +
|
||||
'<p>工作地点:' + escapeHtml(job.jobAddress || job.jobLocation) + '</p>' +
|
||||
'</article>';
|
||||
}
|
||||
|
||||
if (!deviceCode && (!fairId || !companyId)) {
|
||||
app.innerHTML = '<div class="error">缺少页面参数</div>';
|
||||
return;
|
||||
}
|
||||
Promise.all([
|
||||
fetchJson(apiUrl()),
|
||||
fetchDict('education'),
|
||||
fetchDict('experience')
|
||||
]).then(function (results) {
|
||||
var body = results[0];
|
||||
if (body.code !== 200) {
|
||||
throw new Error(body.msg || '加载失败');
|
||||
}
|
||||
render(body.data || {}, {
|
||||
education: results[1],
|
||||
experience: results[2]
|
||||
});
|
||||
}).catch(function (err) {
|
||||
app.innerHTML = '<div class="error">' + escapeHtml(err.message) + '</div>';
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,208 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
|
||||
<title>户外招聘会</title>
|
||||
<style>
|
||||
:root {
|
||||
color: #172033;
|
||||
background: #f4f7fb;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif;
|
||||
}
|
||||
* { box-sizing: border-box; }
|
||||
body { margin: 0; background: #f4f7fb; }
|
||||
.page { min-height: 100vh; padding: 18px 14px 28px; }
|
||||
.hero {
|
||||
padding: 22px 18px;
|
||||
color: #fff;
|
||||
background: linear-gradient(135deg, #1f6feb, #14866d);
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 10px 28px rgba(31, 111, 235, .18);
|
||||
}
|
||||
.hero h1 { margin: 0 0 10px; font-size: 24px; line-height: 1.25; letter-spacing: 0; }
|
||||
.hero p { margin: 4px 0 0; font-size: 14px; line-height: 1.7; opacity: .92; }
|
||||
.section { margin-top: 14px; padding: 16px; background: #fff; border-radius: 8px; box-shadow: 0 8px 22px rgba(23, 32, 51, .06); }
|
||||
.section h2 { margin: 0 0 12px; font-size: 17px; line-height: 1.4; letter-spacing: 0; }
|
||||
.meta { display: grid; gap: 8px; }
|
||||
.meta div { display: flex; gap: 10px; font-size: 14px; line-height: 1.55; }
|
||||
.meta span:first-child { flex: 0 0 86px; color: #667085; }
|
||||
.meta span:last-child { min-width: 0; flex: 1; word-break: break-word; }
|
||||
.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>
|
||||
<main class="page" id="app">
|
||||
<div class="loading">正在加载...</div>
|
||||
</main>
|
||||
<script>
|
||||
(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);
|
||||
}
|
||||
|
||||
function escapeHtml(value) {
|
||||
return text(value).replace(/[&<>"']/g, function (ch) {
|
||||
return ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' })[ch];
|
||||
});
|
||||
}
|
||||
|
||||
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">' +
|
||||
'<h1>' + escapeHtml(fair.title) + '</h1>' +
|
||||
'<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">' +
|
||||
'<div><span>举办时间</span><span>' + escapeHtml(fair.holdTime) + '</span></div>' +
|
||||
'<div><span>截止时间</span><span>' + escapeHtml(fair.endTime) + '</span></div>' +
|
||||
'<div><span>招聘会类型</span><span>' + escapeHtml(fair.fairType) + '</span></div>' +
|
||||
'<div><span>举办区域</span><span>' + escapeHtml(fair.region) + '</span></div>' +
|
||||
'<div><span>举办场地</span><span>' + escapeHtml(fair.venueName) + '</span></div>' +
|
||||
'<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('/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);
|
||||
});
|
||||
}
|
||||
|
||||
if (!fairId) {
|
||||
app.innerHTML = '<div class="error">缺少招聘会参数</div>';
|
||||
return;
|
||||
}
|
||||
fetch('/app/outdoor-fair/' + encodeURIComponent(fairId)).then(function (res) {
|
||||
return res.json();
|
||||
}).then(function (body) {
|
||||
if (body.code !== 200) {
|
||||
throw new Error(body.msg || '加载失败');
|
||||
}
|
||||
render(body.data || {});
|
||||
}).catch(function (err) {
|
||||
app.innerHTML = '<div class="error">' + escapeHtml(err.message) + '</div>';
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user