feat(duty): add web-admin calendar page and public H5 grab UI
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1 parent
36fc7f286c
commit
35e09fa09b
9 files changed
+3152
No files matched your search
@@ -0,0 +1,519 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
|
||||
<title>值班抢班 · 公开 H5 Demo</title>
|
||||
<style>
|
||||
:root {
|
||||
--brand: #0a9f6d;
|
||||
--brand-dark: #087f57;
|
||||
--brand-soft: #e9f9f2;
|
||||
--barley: #d6a343;
|
||||
--barley-soft: #fff7e8;
|
||||
--bg: #f5f8f7;
|
||||
--surface: #ffffff;
|
||||
--text: #1f2a26;
|
||||
--muted: #6b7b74;
|
||||
--border: #dcebe4;
|
||||
--danger: #d14343;
|
||||
--warning: #cf8a19;
|
||||
--success: #189b65;
|
||||
--am: #cf8a19;
|
||||
--am-bg: #fff7e8;
|
||||
--pm: #3b82f6;
|
||||
--pm-bg: #eff6ff;
|
||||
--duty: #d14343;
|
||||
--duty-bg: #fef2f2;
|
||||
}
|
||||
* { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", "Helvetica Neue", sans-serif;
|
||||
background: #dfe8e4;
|
||||
color: var(--text);
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.chrome {
|
||||
position: sticky; top: 0; z-index: 40;
|
||||
background: #12241c; color: #d7e8df;
|
||||
padding: 10px 16px;
|
||||
display: flex; flex-wrap: wrap; gap: 8px; align-items: center;
|
||||
}
|
||||
.chrome strong { color: #fff; margin-right: 4px; }
|
||||
.chrome button {
|
||||
border: 1px solid #2a4638; background: #1a3228; color: #d7e8df;
|
||||
border-radius: 8px; padding: 6px 12px; font-size: 12px; cursor: pointer;
|
||||
}
|
||||
.chrome button.active {
|
||||
background: var(--brand); border-color: var(--brand); color: #fff; font-weight: 600;
|
||||
}
|
||||
.chrome .hint { width: 100%; font-size: 11px; color: #8eaa9a; margin-top: 2px; }
|
||||
|
||||
.stage {
|
||||
display: flex; justify-content: center; gap: 28px; padding: 28px 16px 48px;
|
||||
flex-wrap: wrap; align-items: flex-start;
|
||||
}
|
||||
.stage-label {
|
||||
text-align: center; font-size: 12px; color: #4a6358; margin-bottom: 10px; font-weight: 600;
|
||||
}
|
||||
|
||||
.phone {
|
||||
width: 375px;
|
||||
background: var(--bg);
|
||||
border-radius: 32px;
|
||||
border: 10px solid #1a2420;
|
||||
box-shadow: 0 24px 60px rgba(18, 36, 28, 0.28);
|
||||
overflow: hidden;
|
||||
min-height: 760px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.notch {
|
||||
position: absolute; top: 8px; left: 50%; transform: translateX(-50%);
|
||||
width: 110px; height: 24px; background: #1a2420; border-radius: 0 0 14px 14px; z-index: 5;
|
||||
}
|
||||
|
||||
.page { display: none; flex: 1; flex-direction: column; min-height: 740px; }
|
||||
.page.active { display: flex; }
|
||||
|
||||
.header {
|
||||
background: linear-gradient(160deg, #0a9f6d 0%, #087f57 100%);
|
||||
color: #fff;
|
||||
padding: 44px 18px 22px;
|
||||
border-radius: 0 0 22px 22px;
|
||||
position: relative;
|
||||
}
|
||||
.header .eyebrow {
|
||||
font-size: 12px; opacity: 0.88; margin-bottom: 6px;
|
||||
}
|
||||
.header h1 {
|
||||
margin: 0; font-size: 22px; font-weight: 700; letter-spacing: 0.02em;
|
||||
}
|
||||
.header .sub {
|
||||
margin: 8px 0 0; font-size: 13px; line-height: 1.45; opacity: 0.92;
|
||||
}
|
||||
.header .meta {
|
||||
margin-top: 14px;
|
||||
display: flex; gap: 8px; flex-wrap: wrap;
|
||||
}
|
||||
.pill {
|
||||
display: inline-flex; align-items: center; gap: 4px;
|
||||
background: rgba(255,255,255,0.18);
|
||||
border: 1px solid rgba(255,255,255,0.22);
|
||||
border-radius: 999px; padding: 4px 10px; font-size: 11px;
|
||||
}
|
||||
|
||||
.body { padding: 14px 14px 28px; flex: 1; }
|
||||
|
||||
.tip {
|
||||
background: var(--barley-soft);
|
||||
border: 1px solid #f0dfb5;
|
||||
color: #8a6418;
|
||||
border-radius: 12px;
|
||||
padding: 10px 12px;
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.slot {
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 16px;
|
||||
padding: 14px;
|
||||
margin-bottom: 12px;
|
||||
box-shadow: 0 1px 2px rgba(31, 42, 38, 0.04);
|
||||
}
|
||||
.slot.is-full { opacity: 0.92; }
|
||||
.slot.is-closed { opacity: 0.85; }
|
||||
|
||||
.slot-hd {
|
||||
display: flex; align-items: flex-start; justify-content: space-between; gap: 10px;
|
||||
}
|
||||
.slot-title {
|
||||
display: flex; align-items: center; gap: 8px;
|
||||
font-size: 16px; font-weight: 700;
|
||||
}
|
||||
.dot {
|
||||
width: 10px; height: 10px; border-radius: 50%; flex-shrink: 0;
|
||||
}
|
||||
.dot.am { background: var(--am); }
|
||||
.dot.pm { background: var(--pm); }
|
||||
.dot.duty { background: var(--duty); }
|
||||
|
||||
.badge {
|
||||
flex-shrink: 0;
|
||||
font-size: 11px; font-weight: 600;
|
||||
padding: 3px 8px; border-radius: 999px;
|
||||
}
|
||||
.badge.open { background: var(--brand-soft); color: var(--brand-dark); }
|
||||
.badge.full { background: #eef2f0; color: var(--muted); }
|
||||
.badge.closed { background: #fde8e8; color: var(--danger); }
|
||||
|
||||
.cap-row {
|
||||
margin-top: 10px;
|
||||
display: flex; justify-content: space-between; align-items: baseline;
|
||||
font-size: 12px; color: var(--muted);
|
||||
}
|
||||
.cap-row strong { color: var(--text); font-size: 15px; }
|
||||
|
||||
.bar {
|
||||
margin-top: 8px; height: 7px; background: #e8f0ec; border-radius: 999px; overflow: hidden;
|
||||
}
|
||||
.bar > i {
|
||||
display: block; height: 100%; border-radius: 999px; background: var(--brand);
|
||||
}
|
||||
.bar.full > i { background: #9aabb0; }
|
||||
.bar.am > i { background: var(--am); }
|
||||
.bar.pm > i { background: var(--pm); }
|
||||
.bar.duty > i { background: var(--duty); }
|
||||
|
||||
.people {
|
||||
margin-top: 10px;
|
||||
display: flex; flex-wrap: wrap; gap: 6px;
|
||||
}
|
||||
.person {
|
||||
font-size: 12px;
|
||||
padding: 4px 9px;
|
||||
border-radius: 999px;
|
||||
background: #f3f7f5;
|
||||
color: var(--muted);
|
||||
border: 1px solid var(--border);
|
||||
}
|
||||
.person.empty {
|
||||
background: transparent; border-style: dashed; color: #8a9a93;
|
||||
}
|
||||
|
||||
.field { margin-top: 12px; }
|
||||
.field label {
|
||||
display: block; font-size: 12px; color: var(--muted); margin-bottom: 6px;
|
||||
}
|
||||
.field input {
|
||||
width: 100%; height: 44px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 12px;
|
||||
padding: 0 12px;
|
||||
font-size: 15px;
|
||||
background: #fff;
|
||||
outline: none;
|
||||
}
|
||||
.field input:focus { border-color: var(--brand); box-shadow: 0 0 0 3px rgba(10,159,109,0.12); }
|
||||
|
||||
.btn-grab {
|
||||
margin-top: 10px;
|
||||
width: 100%; height: 44px;
|
||||
border: none; border-radius: 12px;
|
||||
background: var(--brand); color: #fff;
|
||||
font-size: 15px; font-weight: 700;
|
||||
cursor: pointer;
|
||||
}
|
||||
.btn-grab:active { background: var(--brand-dark); }
|
||||
.btn-grab:disabled {
|
||||
background: #b7c5bf; color: #fff; cursor: not-allowed;
|
||||
}
|
||||
.btn-grab.secondary {
|
||||
background: #fff; color: var(--brand-dark);
|
||||
border: 1px solid var(--brand);
|
||||
}
|
||||
|
||||
.footer-note {
|
||||
text-align: center; font-size: 11px; color: #8a9a93;
|
||||
padding: 4px 16px 18px; line-height: 1.5;
|
||||
}
|
||||
|
||||
/* Success / closed full-page states */
|
||||
.state-panel {
|
||||
flex: 1; display: flex; flex-direction: column;
|
||||
align-items: center; justify-content: center;
|
||||
padding: 40px 28px; text-align: center;
|
||||
background: var(--bg);
|
||||
}
|
||||
.state-icon {
|
||||
width: 72px; height: 72px; border-radius: 50%;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
font-size: 34px; margin-bottom: 16px;
|
||||
}
|
||||
.state-icon.ok { background: var(--brand-soft); color: var(--brand); }
|
||||
.state-icon.off { background: #eef2f0; color: var(--muted); }
|
||||
.state-panel h2 { margin: 0; font-size: 20px; }
|
||||
.state-panel p { margin: 10px 0 0; color: var(--muted); font-size: 14px; line-height: 1.55; }
|
||||
.state-card {
|
||||
width: 100%; margin-top: 22px; text-align: left;
|
||||
background: #fff; border: 1px solid var(--border);
|
||||
border-radius: 14px; padding: 14px;
|
||||
}
|
||||
.kv { display: flex; justify-content: space-between; padding: 6px 0; font-size: 13px; }
|
||||
.kv span:first-child { color: var(--muted); }
|
||||
.kv span:last-child { font-weight: 600; }
|
||||
|
||||
.toast {
|
||||
position: absolute; left: 50%; bottom: 28px; transform: translateX(-50%);
|
||||
background: rgba(31,42,38,0.92); color: #fff;
|
||||
padding: 10px 14px; border-radius: 10px; font-size: 13px;
|
||||
white-space: nowrap; display: none; z-index: 20;
|
||||
max-width: 90%;
|
||||
}
|
||||
.toast.show { display: block; animation: fadeUp .2s ease; }
|
||||
@keyframes fadeUp {
|
||||
from { opacity: 0; transform: translate(-50%, 8px); }
|
||||
to { opacity: 1; transform: translate(-50%, 0); }
|
||||
}
|
||||
|
||||
@media (max-width: 420px) {
|
||||
.stage { padding: 12px 0 24px; }
|
||||
.phone {
|
||||
width: 100%; border-radius: 0; border: none; min-height: 100vh;
|
||||
box-shadow: none;
|
||||
}
|
||||
.notch { display: none; }
|
||||
.chrome { border-radius: 0; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="chrome">
|
||||
<strong>公开 H5 · 抢班页</strong>
|
||||
<button type="button" class="active" data-page="grab" onclick="showPage('grab', this)">可抢班(主流程)</button>
|
||||
<button type="button" data-page="success" onclick="showPage('success', this)">提交成功</button>
|
||||
<button type="button" data-page="closed" onclick="showPage('closed', this)">链接已关闭</button>
|
||||
<button type="button" data-page="invalid" onclick="showPage('invalid', this)">链接无效</button>
|
||||
<div class="hint">一天一个链接 · 班次独立罗列 · 免登录只填姓名 · 满员不可抢 · 对齐三墙 Token</div>
|
||||
</div>
|
||||
|
||||
<div class="stage">
|
||||
<div>
|
||||
<div class="stage-label">375px 手机预览 · 点击上方切换状态</div>
|
||||
<div class="phone" id="phone">
|
||||
<div class="notch"></div>
|
||||
|
||||
<!-- 主流程:可抢 -->
|
||||
<div class="page active" id="page-grab">
|
||||
<div class="header">
|
||||
<div class="eyebrow">山峡化工 · 值班抢班</div>
|
||||
<h1>8月20日 值班</h1>
|
||||
<p class="sub">无需登录,填写姓名后选择班次提交。名额满了就不能再抢。</p>
|
||||
<div class="meta">
|
||||
<span class="pill">周三</span>
|
||||
<span class="pill">链接有效</span>
|
||||
<span class="pill">剩余 2 个名额</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="body">
|
||||
<div class="tip">提示:同名可能重复,请填写真实姓名;提交后如需改动请联系管理员。</div>
|
||||
|
||||
<!-- 早班 已满 -->
|
||||
<section class="slot is-full">
|
||||
<div class="slot-hd">
|
||||
<div class="slot-title"><span class="dot am"></span>早班</div>
|
||||
<span class="badge full">已满</span>
|
||||
</div>
|
||||
<div class="cap-row">
|
||||
<span>名额</span>
|
||||
<strong>2 / 2</strong>
|
||||
</div>
|
||||
<div class="bar full am"><i style="width:100%"></i></div>
|
||||
<div class="people">
|
||||
<span class="person">张伟</span>
|
||||
<span class="person">陈静</span>
|
||||
</div>
|
||||
<button class="btn-grab" type="button" disabled>名额已满</button>
|
||||
</section>
|
||||
|
||||
<!-- 晚班 可抢 -->
|
||||
<section class="slot" id="slot-pm">
|
||||
<div class="slot-hd">
|
||||
<div class="slot-title"><span class="dot pm"></span>晚班</div>
|
||||
<span class="badge open">可抢</span>
|
||||
</div>
|
||||
<div class="cap-row">
|
||||
<span>名额</span>
|
||||
<strong id="pm-count">1 / 2</strong>
|
||||
</div>
|
||||
<div class="bar pm"><i id="pm-bar" style="width:50%"></i></div>
|
||||
<div class="people" id="pm-people">
|
||||
<span class="person">李娜</span>
|
||||
<span class="person empty">空缺 1</span>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="name-pm">您的姓名</label>
|
||||
<input id="name-pm" type="text" maxlength="20" placeholder="请输入真实姓名" autocomplete="name" />
|
||||
</div>
|
||||
<button class="btn-grab" type="button" onclick="submitGrab('pm')">提交抢班</button>
|
||||
</section>
|
||||
|
||||
<!-- 值班 空 -->
|
||||
<section class="slot" id="slot-duty">
|
||||
<div class="slot-hd">
|
||||
<div class="slot-title"><span class="dot duty"></span>值班</div>
|
||||
<span class="badge open">可抢</span>
|
||||
</div>
|
||||
<div class="cap-row">
|
||||
<span>名额</span>
|
||||
<strong id="duty-count">0 / 1</strong>
|
||||
</div>
|
||||
<div class="bar duty"><i id="duty-bar" style="width:0%"></i></div>
|
||||
<div class="people" id="duty-people">
|
||||
<span class="person empty">暂无人报名</span>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="name-duty">您的姓名</label>
|
||||
<input id="name-duty" type="text" maxlength="20" placeholder="请输入真实姓名" autocomplete="name" />
|
||||
</div>
|
||||
<button class="btn-grab" type="button" onclick="submitGrab('duty')">提交抢班</button>
|
||||
</section>
|
||||
</div>
|
||||
<div class="footer-note">提交即表示确认当日可到岗值班<br />链接由管理员分享 · 请勿转发无关人员</div>
|
||||
</div>
|
||||
|
||||
<!-- 成功页 -->
|
||||
<div class="page" id="page-success">
|
||||
<div class="header" style="padding-bottom:28px">
|
||||
<div class="eyebrow">山峡化工 · 值班抢班</div>
|
||||
<h1>报名成功</h1>
|
||||
<p class="sub">请按时到岗。如需调整请联系管理员。</p>
|
||||
</div>
|
||||
<div class="state-panel" style="justify-content:flex-start;padding-top:28px">
|
||||
<div class="state-icon ok">✓</div>
|
||||
<h2>已抢到晚班</h2>
|
||||
<p>2026年8月20日(周三)</p>
|
||||
<div class="state-card">
|
||||
<div class="kv"><span>日期</span><span>2026-08-20</span></div>
|
||||
<div class="kv"><span>班次</span><span>晚班</span></div>
|
||||
<div class="kv"><span>姓名</span><span>王小明</span></div>
|
||||
<div class="kv"><span>状态</span><span style="color:var(--success)">已报名</span></div>
|
||||
</div>
|
||||
<button class="btn-grab secondary" style="margin-top:18px;max-width:280px" type="button" onclick="showPage('grab')">返回查看当日班次</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 链接关闭 -->
|
||||
<div class="page" id="page-closed">
|
||||
<div class="state-panel">
|
||||
<div class="state-icon off">⊘</div>
|
||||
<h2>报名已关闭</h2>
|
||||
<p>管理员已关闭本链接,暂时无法继续抢班。<br />如需值班请联系管理员另行安排。</p>
|
||||
<div class="state-card">
|
||||
<div class="kv"><span>日期</span><span>2026-08-20</span></div>
|
||||
<div class="kv"><span>状态</span><span style="color:var(--danger)">已关闭</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 链接无效 -->
|
||||
<div class="page" id="page-invalid">
|
||||
<div class="state-panel">
|
||||
<div class="state-icon off">?</div>
|
||||
<h2>链接无效或已失效</h2>
|
||||
<p>请确认是否复制完整,或向管理员重新索取当日值班链接。</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="toast" id="toast"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const slots = {
|
||||
pm: { taken: 1, cap: 2, names: ['李娜'] },
|
||||
duty: { taken: 0, cap: 1, names: [] }
|
||||
};
|
||||
|
||||
function showPage(id, btn) {
|
||||
document.querySelectorAll('.page').forEach(p => p.classList.remove('active'));
|
||||
document.getElementById('page-' + id).classList.add('active');
|
||||
document.querySelectorAll('.chrome button').forEach(b => b.classList.remove('active'));
|
||||
if (btn) btn.classList.add('active');
|
||||
else {
|
||||
const b = document.querySelector('.chrome button[data-page="' + id + '"]');
|
||||
if (b) b.classList.add('active');
|
||||
}
|
||||
}
|
||||
|
||||
function toast(msg) {
|
||||
const t = document.getElementById('toast');
|
||||
t.textContent = msg;
|
||||
t.classList.add('show');
|
||||
clearTimeout(toast._timer);
|
||||
toast._timer = setTimeout(() => t.classList.remove('show'), 1800);
|
||||
}
|
||||
|
||||
function renderSlot(key) {
|
||||
const s = slots[key];
|
||||
const countEl = document.getElementById(key + '-count');
|
||||
const barEl = document.getElementById(key + '-bar');
|
||||
const peopleEl = document.getElementById(key + '-people');
|
||||
const section = document.getElementById('slot-' + key);
|
||||
if (!countEl) return;
|
||||
|
||||
countEl.textContent = s.taken + ' / ' + s.cap;
|
||||
barEl.style.width = Math.round((s.taken / s.cap) * 100) + '%';
|
||||
|
||||
peopleEl.innerHTML = '';
|
||||
s.names.forEach(n => {
|
||||
const span = document.createElement('span');
|
||||
span.className = 'person';
|
||||
span.textContent = n;
|
||||
peopleEl.appendChild(span);
|
||||
});
|
||||
const left = s.cap - s.taken;
|
||||
if (left > 0) {
|
||||
const empty = document.createElement('span');
|
||||
empty.className = 'person empty';
|
||||
empty.textContent = s.taken === 0 ? '暂无人报名' : ('空缺 ' + left);
|
||||
peopleEl.appendChild(empty);
|
||||
}
|
||||
|
||||
const badge = section.querySelector('.badge');
|
||||
const btn = section.querySelector('.btn-grab');
|
||||
const field = section.querySelector('.field');
|
||||
const bar = section.querySelector('.bar');
|
||||
if (s.taken >= s.cap) {
|
||||
badge.className = 'badge full';
|
||||
badge.textContent = '已满';
|
||||
btn.disabled = true;
|
||||
btn.textContent = '名额已满';
|
||||
if (field) field.style.display = 'none';
|
||||
section.classList.add('is-full');
|
||||
bar.classList.add('full');
|
||||
} else {
|
||||
badge.className = 'badge open';
|
||||
badge.textContent = '可抢';
|
||||
btn.disabled = false;
|
||||
btn.textContent = '提交抢班';
|
||||
if (field) field.style.display = '';
|
||||
section.classList.remove('is-full');
|
||||
bar.classList.remove('full');
|
||||
}
|
||||
}
|
||||
|
||||
function submitGrab(key) {
|
||||
const input = document.getElementById('name-' + key);
|
||||
const name = (input.value || '').trim();
|
||||
if (!name) {
|
||||
toast('请先填写姓名');
|
||||
input.focus();
|
||||
return;
|
||||
}
|
||||
const s = slots[key];
|
||||
if (s.taken >= s.cap) {
|
||||
toast('该班次名额已满');
|
||||
renderSlot(key);
|
||||
return;
|
||||
}
|
||||
s.names.push(name);
|
||||
s.taken += 1;
|
||||
input.value = '';
|
||||
renderSlot(key);
|
||||
toast(name + ' 已提交成功');
|
||||
setTimeout(() => showPage('success'), 600);
|
||||
}
|
||||
|
||||
renderSlot('pm');
|
||||
renderSlot('duty');
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,548 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>值班排班 · 方案1 Demo</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet" />
|
||||
<style>
|
||||
:root {
|
||||
--primary: #10b981;
|
||||
--primary-dark: #059669;
|
||||
--primary-bg: #ecfdf5;
|
||||
--text: #1e293b;
|
||||
--text-muted: #64748b;
|
||||
--border: #d9e8e2;
|
||||
--bg-page: #f8fafc;
|
||||
--surface: #ffffff;
|
||||
--shadow: 0 1px 3px rgba(0,0,0,.06);
|
||||
--radius: 10px;
|
||||
--shift-am: #f59e0b;
|
||||
--shift-am-bg: #fffbeb;
|
||||
--shift-pm: #3b82f6;
|
||||
--shift-pm-bg: #eff6ff;
|
||||
--shift-duty: #ef4444;
|
||||
--shift-duty-bg: #fef2f2;
|
||||
}
|
||||
* { box-sizing: border-box; }
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: Inter, -apple-system, "PingFang SC", sans-serif;
|
||||
font-size: 14px;
|
||||
color: var(--text);
|
||||
background: #e8eef2;
|
||||
min-height: 100vh;
|
||||
}
|
||||
.demo-chrome {
|
||||
position: sticky; top: 0; z-index: 50;
|
||||
background: #0f172a; color: #e2e8f0;
|
||||
padding: 10px 20px;
|
||||
display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
|
||||
}
|
||||
.demo-chrome strong { color: #fff; margin-right: 8px; }
|
||||
.demo-chrome button {
|
||||
border: 1px solid #334155; background: #1e293b; color: #e2e8f0;
|
||||
border-radius: 8px; padding: 7px 14px; cursor: pointer; font-size: 13px;
|
||||
}
|
||||
.demo-chrome button.active {
|
||||
background: var(--primary); border-color: var(--primary); color: #fff; font-weight: 600;
|
||||
}
|
||||
.demo-chrome .hint { margin-left: auto; font-size: 12px; color: #94a3b8; }
|
||||
.view { display: none; padding: 20px; }
|
||||
.view.active { display: block; }
|
||||
|
||||
/* ===== Admin shell ===== */
|
||||
.admin-shell {
|
||||
max-width: 1180px; margin: 0 auto;
|
||||
background: var(--bg-page); border-radius: 12px;
|
||||
border: 1px solid var(--border); overflow: hidden;
|
||||
box-shadow: var(--shadow); min-height: 720px;
|
||||
}
|
||||
.admin-top {
|
||||
height: 56px; background: var(--surface);
|
||||
border-bottom: 1px solid var(--border);
|
||||
display: flex; align-items: center; padding: 0 24px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.admin-top span { color: var(--primary); }
|
||||
.admin-body { padding: 24px; }
|
||||
.panel {
|
||||
background: var(--surface); border: 1px solid var(--border);
|
||||
border-radius: var(--radius); padding: 20px 24px;
|
||||
}
|
||||
.panel-top { display: flex; align-items: center; justify-content: space-between; margin-bottom: 16px; gap: 12px; flex-wrap: wrap; }
|
||||
.title-bar-heading {
|
||||
margin: 0; font-size: 16px; font-weight: 600; display: flex; align-items: center; gap: 8px;
|
||||
}
|
||||
.title-bar-heading::before {
|
||||
content: ""; width: 3px; height: 16px; border-radius: 2px; background: var(--primary);
|
||||
}
|
||||
.month-nav { display: flex; align-items: center; gap: 8px; }
|
||||
.month-nav .month { font-weight: 600; min-width: 120px; text-align: center; }
|
||||
.btn {
|
||||
display: inline-flex; align-items: center; gap: 6px;
|
||||
height: 36px; padding: 0 14px; border-radius: 8px;
|
||||
border: 1px solid var(--border); background: #fff; color: var(--text);
|
||||
cursor: pointer; font-size: 13px;
|
||||
}
|
||||
.btn:hover { background: #f8fafc; }
|
||||
.btn-primary { background: var(--primary); border-color: var(--primary); color: #fff; }
|
||||
.btn-primary:hover { background: var(--primary-dark); }
|
||||
.legend { display: flex; gap: 14px; flex-wrap: wrap; align-items: center; color: var(--text-muted); font-size: 12px; }
|
||||
.legend i {
|
||||
display: inline-block; width: 8px; height: 8px; border-radius: 50%; margin-right: 4px; vertical-align: middle;
|
||||
}
|
||||
.cal {
|
||||
width: 100%; border-collapse: separate; border-spacing: 0;
|
||||
table-layout: fixed; border: 1px solid var(--border); border-radius: 8px; overflow: hidden;
|
||||
}
|
||||
.cal th {
|
||||
background: #f8fafc; padding: 10px; font-weight: 500; color: var(--text-muted);
|
||||
border-bottom: 1px solid var(--border); font-size: 12px;
|
||||
}
|
||||
.cal th.wknd { color: #dc2626; }
|
||||
.cal td {
|
||||
vertical-align: top; height: 108px; padding: 6px;
|
||||
border-right: 1px solid var(--border); border-bottom: 1px solid var(--border);
|
||||
cursor: pointer; background: #fff; transition: background .15s;
|
||||
}
|
||||
.cal tr td:last-child { border-right: none; }
|
||||
.cal tr:last-child td { border-bottom: none; }
|
||||
.cal td:hover { background: #f0fdf4; }
|
||||
.cal td.muted { background: #fafafa; color: #94a3b8; }
|
||||
.cal td.muted:hover { background: #f1f5f9; }
|
||||
.cal td.today .day-num {
|
||||
background: var(--primary); color: #fff; border-radius: 50%;
|
||||
width: 22px; height: 22px; display: inline-flex; align-items: center; justify-content: center;
|
||||
}
|
||||
.day-num { font-size: 12px; font-weight: 600; margin-bottom: 4px; }
|
||||
.chip {
|
||||
display: block; font-size: 11px; line-height: 1.3; padding: 2px 6px;
|
||||
border-radius: 4px; margin-bottom: 3px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
||||
}
|
||||
.chip.am { background: var(--shift-am-bg); color: #b45309; }
|
||||
.chip.pm { background: var(--shift-pm-bg); color: #1d4ed8; }
|
||||
.chip.duty { background: var(--shift-duty-bg); color: #b91c1c; }
|
||||
.chip.empty { background: #f1f5f9; color: var(--text-muted); border: 1px dashed #cbd5e1; }
|
||||
.chip.more { background: transparent; color: var(--text-muted); padding-left: 2px; }
|
||||
|
||||
/* Dialog */
|
||||
.mask {
|
||||
position: fixed; inset: 0; background: rgba(15,23,42,.45);
|
||||
display: none; align-items: center; justify-content: center; z-index: 100;
|
||||
backdrop-filter: none;
|
||||
}
|
||||
.mask.show { display: flex; }
|
||||
.dlg {
|
||||
width: 560px; max-width: calc(100vw - 32px); max-height: calc(100vh - 100px);
|
||||
background: #fff; border-radius: 12px; box-shadow: 0 20px 40px rgba(0,0,0,.15);
|
||||
display: flex; flex-direction: column; overflow: hidden;
|
||||
}
|
||||
.dlg-hd {
|
||||
padding: 16px 20px; border-bottom: 1px solid var(--border);
|
||||
display: flex; justify-content: space-between; align-items: center; font-weight: 600;
|
||||
}
|
||||
.dlg-bd { padding: 16px 20px; overflow: auto; }
|
||||
.dlg-ft {
|
||||
padding: 12px 20px; border-top: 1px solid var(--border);
|
||||
display: flex; justify-content: flex-end; gap: 8px;
|
||||
}
|
||||
.form-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px 16px; }
|
||||
.field label { display: block; font-size: 12px; color: var(--text-muted); margin-bottom: 4px; }
|
||||
.field input, .field select {
|
||||
width: 100%; height: 36px; border: 1px solid var(--border); border-radius: 8px;
|
||||
padding: 0 10px; font-size: 14px;
|
||||
}
|
||||
.slot-card {
|
||||
border: 1px solid var(--border); border-radius: 8px; padding: 12px; margin-bottom: 10px;
|
||||
background: #fafafa;
|
||||
}
|
||||
.slot-card h4 { margin: 0 0 8px; font-size: 13px; display: flex; align-items: center; gap: 8px; }
|
||||
.person-tags { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
|
||||
.tag {
|
||||
display: inline-flex; align-items: center; gap: 4px;
|
||||
padding: 2px 8px; border-radius: 999px; font-size: 12px;
|
||||
background: var(--primary-bg); color: var(--primary-dark);
|
||||
}
|
||||
.tag button {
|
||||
border: none; background: transparent; cursor: pointer; color: var(--text-muted); padding: 0; line-height: 1;
|
||||
}
|
||||
.link-box {
|
||||
margin-top: 12px; padding: 10px 12px; background: var(--primary-bg);
|
||||
border: 1px solid #a7f3d0; border-radius: 8px; font-size: 12px;
|
||||
display: flex; gap: 8px; align-items: center; flex-wrap: wrap;
|
||||
}
|
||||
.link-box code { flex: 1; word-break: break-all; color: var(--primary-dark); }
|
||||
|
||||
/* ===== H5 phone frame ===== */
|
||||
.phone-wrap { display: flex; justify-content: center; padding: 12px; }
|
||||
.phone {
|
||||
width: 375px; min-height: 720px; background: #f5f7f6;
|
||||
border-radius: 28px; border: 10px solid #1e293b;
|
||||
box-shadow: 0 20px 50px rgba(0,0,0,.2); overflow: hidden;
|
||||
display: flex; flex-direction: column;
|
||||
}
|
||||
.phone-status {
|
||||
height: 44px; background: linear-gradient(135deg, #0a9f6d, #087f57);
|
||||
color: #fff; display: flex; align-items: flex-end; justify-content: center;
|
||||
padding-bottom: 8px; font-size: 11px; opacity: .9;
|
||||
}
|
||||
.h5-header {
|
||||
background: linear-gradient(135deg, #0a9f6d, #087f57);
|
||||
color: #fff; padding: 8px 16px 20px; border-radius: 0 0 20px 20px;
|
||||
}
|
||||
.h5-header h1 { margin: 0; font-size: 18px; font-weight: 600; }
|
||||
.h5-header p { margin: 6px 0 0; font-size: 13px; opacity: .9; }
|
||||
.h5-body { padding: 16px; flex: 1; overflow: auto; }
|
||||
.h5-card {
|
||||
background: #fff; border-radius: 14px; padding: 14px;
|
||||
margin-bottom: 12px; box-shadow: 0 1px 3px rgba(0,0,0,.04);
|
||||
border: 1px solid #e5ebe8;
|
||||
}
|
||||
.h5-card .row { display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px; }
|
||||
.h5-card .name { font-weight: 600; font-size: 15px; }
|
||||
.badge {
|
||||
font-size: 11px; padding: 2px 8px; border-radius: 999px; font-weight: 500;
|
||||
}
|
||||
.badge.open { background: #e9f9f2; color: #087f57; }
|
||||
.badge.full { background: #f1f5f9; color: #64748b; }
|
||||
.progress {
|
||||
height: 6px; background: #eef2f0; border-radius: 999px; overflow: hidden; margin: 8px 0 10px;
|
||||
}
|
||||
.progress > span { display: block; height: 100%; background: #0a9f6d; border-radius: 999px; }
|
||||
.h5-card input {
|
||||
width: 100%; height: 40px; border: 1px solid #d7e0dc; border-radius: 10px;
|
||||
padding: 0 12px; margin-bottom: 8px; font-size: 14px;
|
||||
}
|
||||
.h5-card .grab {
|
||||
width: 100%; height: 40px; border: none; border-radius: 10px;
|
||||
background: #0a9f6d; color: #fff; font-weight: 600; font-size: 14px; cursor: pointer;
|
||||
}
|
||||
.h5-card .grab:disabled { background: #94a3b8; cursor: not-allowed; }
|
||||
.h5-card .people { font-size: 12px; color: #64748b; margin-top: 6px; }
|
||||
.toast {
|
||||
position: fixed; left: 50%; bottom: 40px; transform: translateX(-50%);
|
||||
background: #0f172a; color: #fff; padding: 10px 16px; border-radius: 8px;
|
||||
font-size: 13px; display: none; z-index: 200;
|
||||
}
|
||||
.toast.show { display: block; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="demo-chrome">
|
||||
<strong>值班 · 方案1 Demo</strong>
|
||||
<button type="button" class="active" data-view="calendar" onclick="showView('calendar', this)">① 管理端 · 月历</button>
|
||||
<button type="button" data-view="day" onclick="showView('day', this)">② 管理端 · 配置某日</button>
|
||||
<button type="button" data-view="h5" onclick="showView('h5', this)">③ 员工 · 公开 H5 抢班</button>
|
||||
<span class="hint">Fresh Mint 管理端 · 三墙绿 H5 · 仅示意交互</span>
|
||||
</div>
|
||||
|
||||
<!-- View 1: Calendar -->
|
||||
<div id="view-calendar" class="view active">
|
||||
<div class="admin-shell">
|
||||
<div class="admin-top">山峡化工 · <span>值班排班</span></div>
|
||||
<div class="admin-body">
|
||||
<div class="panel">
|
||||
<div class="panel-top">
|
||||
<h2 class="title-bar-heading">值班排班</h2>
|
||||
<div class="month-nav">
|
||||
<button class="btn" type="button">‹</button>
|
||||
<span class="month">2026年 8月</span>
|
||||
<button class="btn" type="button">›</button>
|
||||
<button class="btn" type="button">今天</button>
|
||||
</div>
|
||||
<div class="legend">
|
||||
<span><i style="background:var(--shift-am)"></i>早班</span>
|
||||
<span><i style="background:var(--shift-pm)"></i>晚班</span>
|
||||
<span><i style="background:var(--shift-duty)"></i>值班</span>
|
||||
<span>点击日期可配置 / 复制链接</span>
|
||||
</div>
|
||||
</div>
|
||||
<table class="cal">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="wknd">日</th><th>一</th><th>二</th><th>三</th><th>四</th><th>五</th><th class="wknd">六</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="muted"><div class="day-num">26</div></td>
|
||||
<td class="muted"><div class="day-num">27</div></td>
|
||||
<td class="muted"><div class="day-num">28</div></td>
|
||||
<td class="muted"><div class="day-num">29</div></td>
|
||||
<td class="muted"><div class="day-num">30</div></td>
|
||||
<td class="muted"><div class="day-num">31</div></td>
|
||||
<td onclick="openDayDlg()"><div class="day-num" style="color:#dc2626">1</div>
|
||||
<span class="chip am">早班 张伟</span>
|
||||
<span class="chip pm">晚班 李娜</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td onclick="openDayDlg()"><div class="day-num" style="color:#dc2626">2</div>
|
||||
<span class="chip am">早班 王磊</span>
|
||||
<span class="chip duty">值班 刘洋</span>
|
||||
<span class="chip more">+2</span>
|
||||
</td>
|
||||
<td onclick="openDayDlg()"><div class="day-num">3</div>
|
||||
<span class="chip am">早班 陈静</span>
|
||||
<span class="chip pm">晚班 赵鑫</span>
|
||||
</td>
|
||||
<td onclick="openDayDlg()"><div class="day-num">4</div>
|
||||
<span class="chip empty">未排班 · 点击配置</span>
|
||||
</td>
|
||||
<td onclick="openDayDlg()"><div class="day-num">5</div>
|
||||
<span class="chip am">早班 张伟</span>
|
||||
<span class="chip pm">晚班 李娜</span>
|
||||
<span class="chip duty">值班 王磊</span>
|
||||
</td>
|
||||
<td class="today" onclick="openDayDlg()"><div class="day-num">6</div>
|
||||
<span class="chip am">早班 刘洋 · 陈静</span>
|
||||
<span class="chip pm">晚班 1/2</span>
|
||||
<span class="chip empty">可抢 · 有链接</span>
|
||||
</td>
|
||||
<td onclick="openDayDlg()"><div class="day-num">7</div>
|
||||
<span class="chip empty">未排班 · 点击配置</span>
|
||||
</td>
|
||||
<td onclick="openDayDlg()"><div class="day-num" style="color:#dc2626">8</div>
|
||||
<span class="chip empty">未排班 · 点击配置</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td onclick="openDayDlg()"><div class="day-num" style="color:#dc2626">9</div><span class="chip empty">未排班</span></td>
|
||||
<td onclick="openDayDlg()"><div class="day-num">10</div><span class="chip empty">未排班</span></td>
|
||||
<td onclick="openDayDlg()"><div class="day-num">11</div>
|
||||
<span class="chip am">早班 张伟</span>
|
||||
<span class="chip pm">晚班 满</span>
|
||||
</td>
|
||||
<td onclick="openDayDlg()"><div class="day-num">12</div><span class="chip empty">未排班</span></td>
|
||||
<td onclick="openDayDlg()"><div class="day-num">13</div><span class="chip empty">未排班</span></td>
|
||||
<td onclick="openDayDlg()"><div class="day-num">14</div>
|
||||
<span class="chip duty">值班 赵鑫</span>
|
||||
</td>
|
||||
<td onclick="openDayDlg()"><div class="day-num" style="color:#dc2626">15</div><span class="chip empty">未排班</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td onclick="openDayDlg()"><div class="day-num" style="color:#dc2626">16</div><span class="chip empty">未排班</span></td>
|
||||
<td onclick="openDayDlg()"><div class="day-num">17</div><span class="chip empty">未排班</span></td>
|
||||
<td onclick="openDayDlg()"><div class="day-num">18</div><span class="chip empty">未排班</span></td>
|
||||
<td onclick="openDayDlg()"><div class="day-num">19</div><span class="chip empty">未排班</span></td>
|
||||
<td onclick="openDayDlg()"><div class="day-num">20</div>
|
||||
<span class="chip am">早班 2/2</span>
|
||||
<span class="chip pm">晚班 1/2</span>
|
||||
<span class="chip duty">值班 0/1</span>
|
||||
</td>
|
||||
<td onclick="openDayDlg()"><div class="day-num">21</div><span class="chip empty">未排班</span></td>
|
||||
<td onclick="openDayDlg()"><div class="day-num" style="color:#dc2626">22</div><span class="chip empty">未排班</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td onclick="openDayDlg()"><div class="day-num" style="color:#dc2626">23</div><span class="chip empty">未排班</span></td>
|
||||
<td onclick="openDayDlg()"><div class="day-num">24</div><span class="chip empty">未排班</span></td>
|
||||
<td onclick="openDayDlg()"><div class="day-num">25</div><span class="chip empty">未排班</span></td>
|
||||
<td onclick="openDayDlg()"><div class="day-num">26</div><span class="chip empty">未排班</span></td>
|
||||
<td onclick="openDayDlg()"><div class="day-num">27</div><span class="chip empty">未排班</span></td>
|
||||
<td onclick="openDayDlg()"><div class="day-num">28</div><span class="chip empty">未排班</span></td>
|
||||
<td onclick="openDayDlg()"><div class="day-num" style="color:#dc2626">29</div><span class="chip empty">未排班</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td onclick="openDayDlg()"><div class="day-num" style="color:#dc2626">30</div><span class="chip empty">未排班</span></td>
|
||||
<td onclick="openDayDlg()"><div class="day-num">31</div><span class="chip empty">未排班</span></td>
|
||||
<td class="muted"><div class="day-num">1</div></td>
|
||||
<td class="muted"><div class="day-num">2</div></td>
|
||||
<td class="muted"><div class="day-num">3</div></td>
|
||||
<td class="muted"><div class="day-num">4</div></td>
|
||||
<td class="muted"><div class="day-num">5</div></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- View 2: Day config (standalone page look) -->
|
||||
<div id="view-day" class="view">
|
||||
<div class="admin-shell" style="max-width:640px">
|
||||
<div class="admin-top">配置值班日 · <span>2026-08-20</span></div>
|
||||
<div class="admin-body">
|
||||
<div class="panel">
|
||||
<div class="panel-top">
|
||||
<h2 class="title-bar-heading">2026年8月20日</h2>
|
||||
<button class="btn" type="button" onclick="toast('链接已关闭(示意)')">关闭链接</button>
|
||||
</div>
|
||||
|
||||
<div class="slot-card">
|
||||
<h4><i style="width:8px;height:8px;border-radius:50%;background:var(--shift-am);display:inline-block"></i> 早班 · 名额 2</h4>
|
||||
<div class="form-grid">
|
||||
<div class="field"><label>名额</label><input type="number" value="2" min="1" /></div>
|
||||
<div class="field"><label>直接安排(系统用户)</label>
|
||||
<select><option>选择员工…</option><option>张伟</option><option>李娜</option><option>王磊</option></select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="person-tags">
|
||||
<span class="tag">张伟(指定) <button type="button">×</button></span>
|
||||
<span class="tag">陈静(抢班) <button type="button">×</button></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="slot-card">
|
||||
<h4><i style="width:8px;height:8px;border-radius:50%;background:var(--shift-pm);display:inline-block"></i> 晚班 · 名额 2</h4>
|
||||
<div class="form-grid">
|
||||
<div class="field"><label>名额</label><input type="number" value="2" min="1" /></div>
|
||||
<div class="field"><label>直接安排(系统用户)</label>
|
||||
<select><option>选择员工…</option><option>李娜</option><option>赵鑫</option></select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="person-tags">
|
||||
<span class="tag">李娜(抢班) <button type="button">×</button></span>
|
||||
<span class="tag" style="background:#f1f5f9;color:#64748b">空缺 1</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="slot-card">
|
||||
<h4><i style="width:8px;height:8px;border-radius:50%;background:var(--shift-duty);display:inline-block"></i> 值班 · 名额 1</h4>
|
||||
<div class="form-grid">
|
||||
<div class="field"><label>名额</label><input type="number" value="1" min="1" /></div>
|
||||
<div class="field"><label>直接安排(系统用户)</label>
|
||||
<select><option>选择员工…</option><option>王磊</option></select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="person-tags">
|
||||
<span class="tag" style="background:#f1f5f9;color:#64748b">空缺 1 · 可抢</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="link-box">
|
||||
<span>公开链接(一天一条)</span>
|
||||
<code>https://example.com/h5/duty/d8f3a2c1</code>
|
||||
<button class="btn btn-primary" type="button" onclick="toast('已复制链接(示意)')">复制链接</button>
|
||||
</div>
|
||||
|
||||
<div style="margin-top:16px;display:flex;justify-content:flex-end;gap:8px">
|
||||
<button class="btn" type="button" onclick="showView('calendar')">返回日历</button>
|
||||
<button class="btn btn-primary" type="button" onclick="toast('已保存(示意)')">保存</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- View 3: H5 -->
|
||||
<div id="view-h5" class="view">
|
||||
<div class="phone-wrap">
|
||||
<div class="phone">
|
||||
<div class="phone-status">值班抢班</div>
|
||||
<div class="h5-header">
|
||||
<h1>8月20日 值班</h1>
|
||||
<p>免登录 · 填写姓名即可抢班 · 满员不可再抢</p>
|
||||
</div>
|
||||
<div class="h5-body">
|
||||
<div class="h5-card" data-slot="am">
|
||||
<div class="row">
|
||||
<span class="name">早班</span>
|
||||
<span class="badge full">已满 2/2</span>
|
||||
</div>
|
||||
<div class="progress"><span style="width:100%;background:#94a3b8"></span></div>
|
||||
<div class="people">已报名:张伟、陈静</div>
|
||||
<button class="grab" type="button" disabled>名额已满</button>
|
||||
</div>
|
||||
|
||||
<div class="h5-card" data-slot="pm">
|
||||
<div class="row">
|
||||
<span class="name">晚班</span>
|
||||
<span class="badge open">可抢 1/2</span>
|
||||
</div>
|
||||
<div class="progress"><span style="width:50%"></span></div>
|
||||
<div class="people">已报名:李娜</div>
|
||||
<input id="name-pm" type="text" placeholder="请输入姓名" />
|
||||
<button class="grab" type="button" onclick="grab('pm')">提交抢班</button>
|
||||
</div>
|
||||
|
||||
<div class="h5-card" data-slot="duty">
|
||||
<div class="row">
|
||||
<span class="name">值班</span>
|
||||
<span class="badge open">可抢 0/1</span>
|
||||
</div>
|
||||
<div class="progress"><span style="width:0%"></span></div>
|
||||
<div class="people">暂无人报名</div>
|
||||
<input id="name-duty" type="text" placeholder="请输入姓名" />
|
||||
<button class="grab" type="button" onclick="grab('duty')">提交抢班</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Overlay dialog when clicking calendar day -->
|
||||
<div class="mask" id="dayMask" onclick="if(event.target===this)closeDayDlg()">
|
||||
<div class="dlg">
|
||||
<div class="dlg-hd">
|
||||
<span>配置 · 2026-08-06</span>
|
||||
<button class="btn" type="button" onclick="closeDayDlg()">关闭</button>
|
||||
</div>
|
||||
<div class="dlg-bd">
|
||||
<p style="margin:0 0 12px;color:var(--text-muted);font-size:13px">为当天配置班次名额,保存后生成公开链接。可直接从系统用户安排,也可留给员工抢班。</p>
|
||||
<div class="slot-card">
|
||||
<h4>早班</h4>
|
||||
<div class="form-grid">
|
||||
<div class="field"><label>名额</label><input type="number" value="2" /></div>
|
||||
<div class="field"><label>直接安排</label><select><option>选择员工…</option><option>张伟</option></select></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="slot-card">
|
||||
<h4>晚班</h4>
|
||||
<div class="form-grid">
|
||||
<div class="field"><label>名额</label><input type="number" value="2" /></div>
|
||||
<div class="field"><label>直接安排</label><select><option>选择员工…</option><option>李娜</option></select></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="slot-card">
|
||||
<h4>值班</h4>
|
||||
<div class="form-grid">
|
||||
<div class="field"><label>名额</label><input type="number" value="1" /></div>
|
||||
<div class="field"><label>直接安排</label><select><option>选择员工…</option><option>王磊</option></select></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="link-box">
|
||||
<code>https://example.com/h5/duty/a1b2c3d4</code>
|
||||
<button class="btn btn-primary" type="button" onclick="toast('已复制(示意)')">复制</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="dlg-ft">
|
||||
<button class="btn" type="button" onclick="closeDayDlg()">取消</button>
|
||||
<button class="btn btn-primary" type="button" onclick="toast('已保存并生成链接(示意)');closeDayDlg()">保存</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="toast" id="toast"></div>
|
||||
|
||||
<script>
|
||||
function showView(id, btn) {
|
||||
document.querySelectorAll('.view').forEach(v => v.classList.remove('active'));
|
||||
document.getElementById('view-' + id).classList.add('active');
|
||||
document.querySelectorAll('.demo-chrome button').forEach(b => b.classList.remove('active'));
|
||||
if (btn) btn.classList.add('active');
|
||||
else {
|
||||
const b = document.querySelector('.demo-chrome button[data-view="' + id + '"]');
|
||||
if (b) b.classList.add('active');
|
||||
}
|
||||
closeDayDlg();
|
||||
}
|
||||
function openDayDlg() { document.getElementById('dayMask').classList.add('show'); }
|
||||
function closeDayDlg() { document.getElementById('dayMask').classList.remove('show'); }
|
||||
function toast(msg) {
|
||||
const t = document.getElementById('toast');
|
||||
t.textContent = msg;
|
||||
t.classList.add('show');
|
||||
setTimeout(() => t.classList.remove('show'), 1800);
|
||||
}
|
||||
function grab(slot) {
|
||||
const input = document.getElementById('name-' + slot);
|
||||
const name = (input && input.value || '').trim();
|
||||
if (!name) { toast('请先填写姓名'); return; }
|
||||
toast(name + ' 已提交「' + (slot === 'pm' ? '晚班' : '值班') + '」(示意)');
|
||||
if (input) input.value = '';
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in new issue
Block a user