Files
shanxia2.0/docs/demos/duty-schedule-v1-demo.html
T

549 lines
26 KiB
HTML
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.
<!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>