@@ -0,0 +1,543 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN" data-page-node-id="kKD2zGHaUO9U8u7IkrNWoA">
|
||||
<head data-page-node-id="Dla59qTcmtkSQPQHpk2rXm">
|
||||
<meta charset="UTF-8" data-page-node-id="gYv6D6nA0dbg3IvkENsNxP">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" data-page-node-id="CXN5L7TMr7y3USos7buZPt">
|
||||
<title>教师请假 · 我要请假(美化版)</title>
|
||||
<style>
|
||||
:root {
|
||||
--primary: #2f6bff;
|
||||
--primary-dark: #1e54d6;
|
||||
--primary-light: #eef3ff;
|
||||
--bg: #f4f6fa;
|
||||
--card: #ffffff;
|
||||
--text-1: #1c2333;
|
||||
--text-2: #5a6478;
|
||||
--text-3: #9aa3b5;
|
||||
--border: #e6eaf2;
|
||||
--danger: #e5484d;
|
||||
--success: #18a058;
|
||||
--radius: 12px;
|
||||
--shadow: 0 1px 3px rgba(28, 35, 51, .06), 0 8px 24px rgba(28, 35, 51, .05);
|
||||
}
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body {
|
||||
font-family: "PingFang SC", "Microsoft YaHei", "Segoe UI", system-ui, sans-serif;
|
||||
background: var(--bg);
|
||||
color: var(--text-1);
|
||||
font-size: 14px;
|
||||
display: flex;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
/* ---------- 侧边栏 ---------- */
|
||||
.sidebar {
|
||||
width: 220px;
|
||||
background: #ffffff;
|
||||
border-right: 1px solid var(--border);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
height: 100vh;
|
||||
}
|
||||
.brand {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 20px 20px 18px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.brand-logo {
|
||||
width: 32px; height: 32px;
|
||||
border-radius: 9px;
|
||||
background: var(--primary);
|
||||
color: #fff;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
font-size: 15px;
|
||||
box-shadow: 0 4px 10px rgba(47, 107, 255, .35);
|
||||
}
|
||||
.nav { flex: 1; padding: 6px 12px; overflow-y: auto; }
|
||||
.nav a {
|
||||
display: flex; align-items: center; gap: 10px;
|
||||
padding: 10px 12px;
|
||||
margin-bottom: 2px;
|
||||
border-radius: 8px;
|
||||
color: var(--text-2);
|
||||
text-decoration: none;
|
||||
transition: all .15s;
|
||||
}
|
||||
.nav a:hover { background: #f4f6fb; color: var(--text-1); }
|
||||
.nav a.active {
|
||||
background: var(--primary-light);
|
||||
color: var(--primary);
|
||||
font-weight: 600;
|
||||
}
|
||||
.nav a svg { width: 16px; height: 16px; flex-shrink: 0; }
|
||||
.nav-section {
|
||||
font-size: 12px;
|
||||
color: var(--text-3);
|
||||
padding: 14px 12px 6px;
|
||||
}
|
||||
|
||||
/* ---------- 主区域 ---------- */
|
||||
.main { flex: 1; min-width: 0; display: flex; flex-direction: column; }
|
||||
.topbar {
|
||||
height: 56px;
|
||||
background: #ffffff;
|
||||
border-bottom: 1px solid var(--border);
|
||||
display: flex; align-items: center; justify-content: space-between;
|
||||
padding: 0 24px;
|
||||
position: sticky; top: 0; z-index: 10;
|
||||
}
|
||||
.breadcrumb { display: flex; align-items: center; gap: 8px; color: var(--text-3); font-size: 13px; }
|
||||
.breadcrumb .current { color: var(--text-1); font-weight: 600; }
|
||||
.user-chip { display: flex; align-items: center; gap: 10px; }
|
||||
.avatar {
|
||||
width: 30px; height: 30px; border-radius: 50%;
|
||||
background: linear-gradient(135deg, #5b8cff, #2f6bff);
|
||||
color: #fff; font-size: 12px;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
}
|
||||
|
||||
.content { max-width: 860px; width: 100%; margin: 0 auto; padding: 24px 24px 120px; }
|
||||
|
||||
/* ---------- 页头卡片 ---------- */
|
||||
.page-head {
|
||||
display: flex; align-items: center; justify-content: space-between;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.page-head h1 { font-size: 20px; font-weight: 600; }
|
||||
.page-head .sub { color: var(--text-3); font-size: 13px; margin-top: 4px; }
|
||||
|
||||
.duration-banner {
|
||||
display: flex; align-items: center; gap: 10px;
|
||||
background: var(--primary-light);
|
||||
border: 1px solid #d8e3ff;
|
||||
color: var(--primary-dark);
|
||||
border-radius: 10px;
|
||||
padding: 10px 14px;
|
||||
font-size: 13px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.duration-banner svg { width: 16px; height: 16px; flex-shrink: 0; }
|
||||
|
||||
/* ---------- 表单卡片 ---------- */
|
||||
.card {
|
||||
background: var(--card);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
box-shadow: var(--shadow);
|
||||
margin-bottom: 16px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.card-head {
|
||||
display: flex; align-items: center; gap: 10px;
|
||||
padding: 16px 24px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
background: #fafbfe;
|
||||
}
|
||||
.card-head .bar { width: 4px; height: 16px; border-radius: 2px; background: var(--primary); }
|
||||
.card-head h2 { font-size: 15px; font-weight: 600; }
|
||||
.card-head .hint { margin-left: auto; font-size: 12px; color: var(--text-3); }
|
||||
.card-body { padding: 24px; }
|
||||
|
||||
.form-row { display: grid; grid-template-columns: 112px 1fr; gap: 16px; margin-bottom: 22px; align-items: start; }
|
||||
.form-row:last-child { margin-bottom: 0; }
|
||||
.form-row > label { padding-top: 9px; font-size: 14px; color: var(--text-2); text-align: right; }
|
||||
.req::before { content: "*"; color: var(--danger); margin-right: 2px; }
|
||||
|
||||
.field-control { min-width: 0; }
|
||||
|
||||
/* 输入控件 */
|
||||
.input, .select, .textarea {
|
||||
width: 100%;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
padding: 9px 12px;
|
||||
font-size: 14px;
|
||||
font-family: inherit;
|
||||
color: var(--text-1);
|
||||
background: #fff;
|
||||
transition: border-color .15s, box-shadow .15s;
|
||||
outline: none;
|
||||
}
|
||||
.input::placeholder, .textarea::placeholder { color: var(--text-3); }
|
||||
.input:hover, .select:hover, .textarea:hover { border-color: #c3cede; }
|
||||
.input:focus, .select:focus, .textarea:focus {
|
||||
border-color: var(--primary);
|
||||
box-shadow: 0 0 0 3px rgba(47, 107, 255, .12);
|
||||
}
|
||||
.select {
|
||||
appearance: none;
|
||||
background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%239aa3b5' stroke-width='2.5'><path d='M6 9l6 6 6-6'/></svg>");
|
||||
background-repeat: no-repeat;
|
||||
background-position: right 12px center;
|
||||
padding-right: 34px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.textarea { resize: vertical; min-height: 108px; line-height: 1.6; }
|
||||
.char-count { text-align: right; font-size: 12px; color: var(--text-3); margin-top: 6px; }
|
||||
.help-text { font-size: 12px; color: var(--text-3); margin-top: 6px; }
|
||||
|
||||
/* 分段选择器(替代原生单选) */
|
||||
.segmented {
|
||||
display: inline-flex;
|
||||
background: #f1f3f9;
|
||||
border-radius: 8px;
|
||||
padding: 3px;
|
||||
gap: 2px;
|
||||
}
|
||||
.segmented button {
|
||||
border: none; background: transparent;
|
||||
padding: 7px 18px;
|
||||
border-radius: 6px;
|
||||
font-size: 13px; font-family: inherit;
|
||||
color: var(--text-2);
|
||||
cursor: pointer;
|
||||
transition: all .15s;
|
||||
}
|
||||
.segmented button.on {
|
||||
background: #fff;
|
||||
color: var(--primary);
|
||||
font-weight: 600;
|
||||
box-shadow: 0 1px 4px rgba(28, 35, 51, .12);
|
||||
}
|
||||
.segmented.mini button { padding: 6px 14px; }
|
||||
|
||||
.inline-group { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
|
||||
.date-wrap { position: relative; width: 170px; }
|
||||
.date-wrap .input { padding-right: 34px; }
|
||||
.date-wrap svg {
|
||||
position: absolute; right: 12px; top: 50%; transform: translateY(-50%);
|
||||
width: 14px; height: 14px; color: var(--text-3); pointer-events: none;
|
||||
}
|
||||
.arrow { color: var(--text-3); font-size: 13px; }
|
||||
|
||||
.auto-length {
|
||||
display: inline-flex; align-items: center; gap: 8px;
|
||||
background: #f0faf4;
|
||||
border: 1px solid #cdeeda;
|
||||
color: var(--success);
|
||||
border-radius: 8px;
|
||||
padding: 8px 14px;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.auto-length .big { font-size: 16px; }
|
||||
|
||||
/* 上传区 */
|
||||
.upload {
|
||||
display: flex; align-items: center; gap: 16px;
|
||||
border: 1.5px dashed #c9d4ea;
|
||||
border-radius: 10px;
|
||||
padding: 18px 20px;
|
||||
cursor: pointer;
|
||||
transition: all .15s;
|
||||
background: #fafbff;
|
||||
}
|
||||
.upload:hover { border-color: var(--primary); background: var(--primary-light); }
|
||||
.upload-icon {
|
||||
width: 44px; height: 44px; border-radius: 10px;
|
||||
background: var(--primary-light);
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.upload-icon svg { width: 20px; height: 20px; color: var(--primary); }
|
||||
.upload .t { font-size: 14px; color: var(--text-1); font-weight: 500; }
|
||||
.upload .t em { color: var(--primary); font-style: normal; }
|
||||
.upload .d { font-size: 12px; color: var(--text-3); margin-top: 3px; }
|
||||
|
||||
/* 抄送人 chips */
|
||||
.cc-row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
|
||||
.cc-chip {
|
||||
display: inline-flex; align-items: center; gap: 7px;
|
||||
background: #f4f6fb;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 999px;
|
||||
padding: 5px 12px 5px 5px;
|
||||
font-size: 13px;
|
||||
}
|
||||
.cc-chip .av {
|
||||
width: 24px; height: 24px; border-radius: 50%;
|
||||
background: linear-gradient(135deg, #7fa4ff, #4f7dff);
|
||||
color: #fff; font-size: 11px;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
}
|
||||
.cc-chip .x { color: var(--text-3); cursor: pointer; font-size: 12px; }
|
||||
.cc-chip .x:hover { color: var(--danger); }
|
||||
.cc-add {
|
||||
display: inline-flex; align-items: center; gap: 6px;
|
||||
border: 1px dashed #c9d4ea;
|
||||
background: #fff;
|
||||
border-radius: 999px;
|
||||
padding: 6px 14px;
|
||||
font-size: 13px;
|
||||
color: var(--text-2);
|
||||
cursor: pointer;
|
||||
transition: all .15s;
|
||||
}
|
||||
.cc-add:hover { color: var(--primary); border-color: var(--primary); }
|
||||
|
||||
/* 底部操作栏 */
|
||||
.action-bar {
|
||||
position: fixed;
|
||||
bottom: 0; left: 220px; right: 0;
|
||||
background: rgba(255, 255, 255, .92);
|
||||
backdrop-filter: blur(8px);
|
||||
border-top: 1px solid var(--border);
|
||||
padding: 14px 24px;
|
||||
display: flex; justify-content: center; gap: 12px;
|
||||
z-index: 20;
|
||||
}
|
||||
.btn {
|
||||
border: none; border-radius: 8px;
|
||||
padding: 10px 28px;
|
||||
font-size: 14px; font-family: inherit;
|
||||
cursor: pointer;
|
||||
transition: all .15s;
|
||||
}
|
||||
.btn-primary {
|
||||
background: var(--primary); color: #fff;
|
||||
box-shadow: 0 4px 12px rgba(47, 107, 255, .3);
|
||||
}
|
||||
.btn-primary:hover { background: var(--primary-dark); transform: translateY(-1px); }
|
||||
.btn-ghost { background: #fff; color: var(--text-2); border: 1px solid var(--border); }
|
||||
.btn-ghost:hover { color: var(--primary); border-color: var(--primary); background: var(--primary-light); }
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.sidebar { display: none; }
|
||||
.action-bar { left: 0; }
|
||||
.form-row { grid-template-columns: 1fr; gap: 8px; }
|
||||
.form-row > label { text-align: left; padding-top: 0; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body data-page-node-id="pEuvYe0F7z7IRACLMAJH5X">
|
||||
|
||||
<!-- 侧边栏 -->
|
||||
<aside class="sidebar" data-page-node-id="KxKa3n00u4ulOQOXzW3uMQ">
|
||||
<div class="brand" data-page-node-id="VzcmXzjTNBqyPqyVjfC6om">
|
||||
<div class="brand-logo" data-page-node-id="1X5pxZgYm6LMoLl2c9FYCM">假</div>
|
||||
教师请假
|
||||
</div>
|
||||
<nav class="nav" data-page-node-id="cjnkCzt9ERGD5HUBPowDyR">
|
||||
<a class="active" href="#" data-page-node-id="Va1SYAGt6sOdSJegvz726T">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" data-page-node-id="ulsRZBFqExsBrMsKiUidpD"><path d="M3 12l9-8 9 8" data-page-node-id="oInS7JOZVNoB70QHqOU8au"/><path d="M5 10v10h14V10" data-page-node-id="s85v5eHLxfIQrCdLE4igW3"/></svg>
|
||||
我要请假
|
||||
</a>
|
||||
<a href="#" data-page-node-id="dbHc0a7VHf4n6GtA1azGbs">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" data-page-node-id="IdUSndES0wv3tZuFkhONUv"><rect x="3" y="4" width="18" height="17" rx="2" data-page-node-id="UdHIDsgJuiujc9qVX7s8Fb"/><path d="M3 9h18M8 2v4M16 2v4" data-page-node-id="a0wpFLvqAIDQOWFzuCuvmt"/></svg>
|
||||
我的请假
|
||||
</a>
|
||||
<a href="#" data-page-node-id="71xEYRC8LRGtHeQY6NDQOz">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" data-page-node-id="KoB6xEYlMHWF9EriepZg1T"><path d="M9 11l3 3 8-8" data-page-node-id="sAuu92W4pSErD1OxtLQZcI"/><path d="M20 12v6a2 2 0 01-2 2H6a2 2 0 01-2-2V6a2 2 0 012-2h9" data-page-node-id="810bvLwnB2UtwXIRCZaKbM"/></svg>
|
||||
审批列表
|
||||
</a>
|
||||
<a href="#" data-page-node-id="tTu2zRCjdncO67183BOFN3">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" data-page-node-id="7xaYewKgpOUXPraaDOw09S"><path d="M4 4h16v12H7l-3 3z" data-page-node-id="mCQ6ySlxaiBlbkkeMcL51m"/></svg>
|
||||
抄送列表
|
||||
</a>
|
||||
<a href="#" data-page-node-id="Gs6GSHth07CrYFLJ5o60Ih">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" data-page-node-id="rnvZzs2XUuaYGWqAmdC5bP"><circle cx="12" cy="12" r="9" data-page-node-id="OBeF2bYsozNJur04Bw7G3H"/><path d="M12 7v5l3 3" data-page-node-id="H0dRAUZKl3qm0DN10cNIJZ"/></svg>
|
||||
全部请假
|
||||
</a>
|
||||
<div class="nav-section" data-page-node-id="JCwkmbaLM5TweGEcJUs6W8">系统管理</div>
|
||||
<a href="#" data-page-node-id="hSXfr0wCRB8DkbJ73RKWrw">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" data-page-node-id="Cum3smV6ZijGAg79gVMDKD"><circle cx="6" cy="6" r="2.5" data-page-node-id="z14t9hJa8FAt2wvPQXgYBm"/><circle cx="6" cy="18" r="2.5" data-page-node-id="1igtdCB3rheNVDrLPK0mtn"/><circle cx="18" cy="12" r="2.5" data-page-node-id="41A0s7EkwjAmkIfKsmB5qt"/><path d="M8 7l8 4M8 17l8-4" data-page-node-id="b90DRkfc6OQr6Ng55Y6Lyd"/></svg>
|
||||
流程管理
|
||||
</a>
|
||||
<a href="#" data-page-node-id="rGTpqu5DeYdCvYXgQ81MWW">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" data-page-node-id="CBSqMxi7TuCnpE8st3XH6B"><circle cx="12" cy="12" r="3" data-page-node-id="TdGoOsqI7u14xZ79HaN5aD"/><path d="M19 12a7 7 0 00-.2-1.6l2-1.5-2-3.4-2.3.9a7 7 0 00-2.8-1.6L13 2h-4l-.7 2.8a7 7 0 00-2.8 1.6l-2.3-.9-2 3.4 2 1.5A7 7 0 005 12c0 .5.1 1.1.2 1.6l-2 1.5 2 3.4 2.3-.9a7 7 0 002.8 1.6L11 22h4l.7-2.8a7 7 0 002.8-1.6l2.3.9 2-3.4-2-1.5c.1-.5.2-1.1.2-1.6z" data-page-node-id="SqgFM1A88nYkkJtU4AuEuB"/></svg>
|
||||
请假设置
|
||||
</a>
|
||||
<a href="#" data-page-node-id="s91aGwCnjwTsqj2A7s54xN">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" data-page-node-id="b5cA0cUXWODHQkrTIV4ZIe"><path d="M4 20V10M10 20V4M16 20v-7M22 20H2" data-page-node-id="LoYAVhjFTXPibADPv31BD8"/></svg>
|
||||
时长日志
|
||||
</a>
|
||||
<a href="#" data-page-node-id="yhzjmHJncnQrOPvpdD3BiF">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" data-page-node-id="awX52Tr5qvDCXDbD8KELcN"><path d="M21 12a9 9 0 11-9-9" data-page-node-id="nSDfCui4uPQiDsUk0gUbQU"/><path d="M21 3l-9 9" data-page-node-id="F0gLfIp8UkNL5hVPdDZfqw"/></svg>
|
||||
统计分析
|
||||
</a>
|
||||
</nav>
|
||||
</aside>
|
||||
|
||||
<!-- 主区域 -->
|
||||
<div class="main" data-page-node-id="sMV0VdZBGSodsH6qwlDCf1">
|
||||
<header class="topbar" data-page-node-id="EUigeyFAoCR9CEkqV85MCL">
|
||||
<div class="breadcrumb" data-page-node-id="ZAKRQ3iCsCyuE8oHQfnSve">
|
||||
<span data-page-node-id="Ayu9ndzrCH6xHNgDH5i8q3">首页</span><span data-page-node-id="LmTzuZOBp5w5HOVLhA0krW">/</span><span data-page-node-id="E8NJ91gnT4qxt3c7LVx1QV">教师请假</span><span data-page-node-id="kz4Aszy3nhe8WzQrBFedxF">/</span><span class="current" data-page-node-id="gIzqk9uFZT0LJZcqSO4QKT">我要请假</span>
|
||||
</div>
|
||||
<div class="user-chip" data-page-node-id="siJRD2l0dxOjNQkzOLqbbJ">
|
||||
<div class="avatar" data-page-node-id="QEct8lAiU3XQ4bBNXHuJhw">李</div>
|
||||
<span style="font-size:13px;color:var(--text-2)" data-page-node-id="xSsq81vrQKNcHfvbzWwKcl">李晶</span>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="content" data-page-node-id="b3qFT1d5MbfLz1MWBoQHWb">
|
||||
<div class="page-head" data-page-node-id="EYQoa9nU7Y6CnSpBTIqUBm">
|
||||
<div data-page-node-id="afVHkaWg2h9ECBzpDkzhhU">
|
||||
<h1 data-page-node-id="ixJbTpAJuugIv7If5HmJmc">请假申请</h1>
|
||||
<div class="sub" data-page-node-id="jihbsdZhsWChtVO6krHgte">提交后将进入审批流程,审批结果会通过消息通知您</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="duration-banner" data-page-node-id="NuMwuvJBQQSC1kwUhEJOei">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" data-page-node-id="gHR7TvdetFVkT1EPRHLVlt"><circle cx="12" cy="12" r="9" data-page-node-id="yD6ZRVO91u1UMflIj2cS78"/><path d="M12 8v4M12 16h.01" data-page-node-id="3FiynVNRF9hbQHn0tAkDPu"/></svg>
|
||||
时间说明:上午(08:00–12:00)– 下午(13:00–17:00),固定 3 小时工作制,按半天为单位计费时长
|
||||
</div>
|
||||
|
||||
<form onsubmit="return false" data-page-node-id="lM5rDi8YBXazoQmj2xRVYJ">
|
||||
|
||||
<!-- 请假信息 -->
|
||||
<section class="card" data-page-node-id="ldXTzuvrIHdwdYLOmKrvXw">
|
||||
<div class="card-head" data-page-node-id="jfBksYP1p6jlOHovaVLKnC">
|
||||
<span class="bar" data-page-node-id="Ae8GrrHH2KB1zFXrlkLDmF"></span>
|
||||
<h2 data-page-node-id="mTOLZasDNFB8cOC3UEnkHB">请假信息</h2>
|
||||
<span class="hint" data-page-node-id="u15G14hbxqxPA23NONBMPr">带 * 为必填项</span>
|
||||
</div>
|
||||
<div class="card-body" data-page-node-id="uB0cUq8E7NcEwA0KbcVgfS">
|
||||
|
||||
<div class="form-row" data-page-node-id="XuguJUjkgUkjSJJ0P9uEeP">
|
||||
<label class="req" data-page-node-id="34aLY6Q8cxk0ahXoQVBaGJ">请假类型</label>
|
||||
<div class="field-control" data-page-node-id="CKLpKzQSySrTZ5zYviP6AF">
|
||||
<div class="segmented" data-page-node-id="c5sryFi9RHrfgtKuCM6BtV">
|
||||
<button type="button" class="on" data-page-node-id="qvFXFmXWkCYQ8xqIR8Yf2z">事假</button>
|
||||
<button type="button" data-page-node-id="MRr8xFyPnvbpHoYUMM7649">病假</button>
|
||||
<button type="button" data-page-node-id="Y25dmgoQGO7fXtB6YddSfE">婚假</button>
|
||||
<button type="button" data-page-node-id="Zwqv2c06roJgSS6s1FDYoS">产假</button>
|
||||
<button type="button" data-page-node-id="WTteUcAAA4AqS5vqCcgQ9P">其他</button>
|
||||
</div>
|
||||
<div class="help-text" data-page-node-id="MRptvR6jjGk9D1arZ40esi">不同类型可能需要附加不同的证明材料</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-row" data-page-node-id="hN63ZRXjqMJQpD3xXOouYi">
|
||||
<label class="req" data-page-node-id="R143QpoTjkGIzkpR2AzqJ2">起止时间</label>
|
||||
<div class="field-control" data-page-node-id="Xi0VxxTy0Bv2XGNZ0FDbTz">
|
||||
<div class="inline-group" data-page-node-id="R5moEYc0CgMHEqoTulXFwY">
|
||||
<div class="segmented mini" data-page-node-id="jjyPX3fb7jGvasHchRoWy4">
|
||||
<button type="button" class="on" data-page-node-id="gKpIGCohbkQXDFyFOU4MdS">半天</button>
|
||||
<button type="button" data-page-node-id="0rtjA9ABbaYiUUHbCPivlR">半天以上</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="inline-group" style="margin-top:12px" data-page-node-id="S07l3479yHAQekvgpAEGSf">
|
||||
<div class="date-wrap" data-page-node-id="XpWVG57eBS6I6XqF4F0ZLb">
|
||||
<input class="input" type="text" placeholder="开始日期" value="2026-10-12" data-page-node-id="IYSLOFh1EM0Mqf8MT5UQQT">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" data-page-node-id="4fz42ERqratTWNZM6LWrhI"><rect x="3" y="4" width="18" height="17" rx="2" data-page-node-id="iaPtKo2tHABJgKW7T3dFTz"/><path d="M3 9h18M8 2v4M16 2v4" data-page-node-id="bBMfb0djLcpOZasbPdXIut"/></svg>
|
||||
</div>
|
||||
<span class="arrow" data-page-node-id="DxL3MF49N3ExzXbEQutH9d">至</span>
|
||||
<div class="date-wrap" data-page-node-id="2KRaxUjqirMcWuMqxQRd0k">
|
||||
<input class="input" type="text" placeholder="结束日期" value="2026-10-12" data-page-node-id="7jBUHBHAZwVIaA9DMFFRoY">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" data-page-node-id="Akv8tEVe2B6KGI2rCquErM"><rect x="3" y="4" width="18" height="17" rx="2" data-page-node-id="X35kk0y5sR2A5Tnpf5HfC1"/><path d="M3 9h18M8 2v4M16 2v4" data-page-node-id="3gGsKjfVBpvHPyNlkQt426"/></svg>
|
||||
</div>
|
||||
<div class="segmented mini" data-page-node-id="99sq4CQ74B4HmE4aExVt9S">
|
||||
<button type="button" class="on" data-page-node-id="ZaJXegt7BQVfQzBxYsR4wT">上午</button>
|
||||
<button type="button" data-page-node-id="QZWxPZhCmnEJMxl5cotFKV">下午</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-row" data-page-node-id="R5Gvc1SQ83ujS4rLZ9AHf3">
|
||||
<label data-page-node-id="9XdzSMTiqlRl3rD9cPbFta">时长(天)</label>
|
||||
<div class="field-control" data-page-node-id="GAFjTSwPQuEmC9RWeDqrPM">
|
||||
<div class="auto-length" data-page-node-id="lEoDmiF9jaYSI2WvAW2D0G">
|
||||
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" data-page-node-id="ZXnqn8CedYbenu9o8NAu3F"><path d="M20 6L9 17l-5-5" data-page-node-id="EVwPtbhwgQ2qC8EOb7abtL"/></svg>
|
||||
<span class="big" id="len" data-page-node-id="vgwQ3unWYE8McDZebzxBQS">0.5</span> 天(按选择自动计算)
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-row" data-page-node-id="C1PM1qOPV2z6oAbsd5JuuR">
|
||||
<label data-page-node-id="1dBrOAqidP5ZFKz6FxhMUM">课务安排</label>
|
||||
<div class="field-control" data-page-node-id="8DCGf8LVPImLJDHB9nZDcM">
|
||||
<div class="segmented" data-page-node-id="NxGgrvEpNrDliW5u7tUQJ4">
|
||||
<button type="button" class="on" data-page-node-id="Wjc0C3bX6c1VH5ZTAjnaLf">无课务安排</button>
|
||||
<button type="button" data-page-node-id="IqNGMXfXHzor4DcfdLLn1W">自行安排</button>
|
||||
<button type="button" data-page-node-id="aiNkqxmQHLjSR2SrYdfHtb">教务安排</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-row" data-page-node-id="k1M4sxumnqLNMCjc1Kq49J">
|
||||
<label class="req" data-page-node-id="wS0SJtUbLrykrIAPDjhfz5">请假事由</label>
|
||||
<div class="field-control" data-page-node-id="o8ZA7twmpBVwnlNZ8EGrlk">
|
||||
<textarea class="textarea" maxlength="50" placeholder="请输入请假事由,如:因家中急事需请假半天处理…" oninput="document.getElementById('cnt').textContent=this.value.length+'/50'"></textarea>
|
||||
<div class="char-count" id="cnt" data-page-node-id="P9RJV4Wh8j1H7nJxrYhbFX">0/50</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 证明材料 -->
|
||||
<section class="card" data-page-node-id="0nBpDIAQYCPPsKK3V6Dcdf">
|
||||
<div class="card-head" data-page-node-id="OGbHxmvAXt5qc7792De287">
|
||||
<span class="bar" data-page-node-id="h7OOjcknF1mUCC7zh2tUJB"></span>
|
||||
<h2 data-page-node-id="l5cbaC8S3XpGjKOfVXtT2o">证明材料</h2>
|
||||
<span class="hint" data-page-node-id="rYBk78Ey7v1IZ8Zw6oJPlD">选填</span>
|
||||
</div>
|
||||
<div class="card-body" data-page-node-id="YRZq0roOiODObFtB7EFfyQ">
|
||||
|
||||
<div class="form-row" data-page-node-id="lPh9hGa5dUJJceCXbcFOsc">
|
||||
<label data-page-node-id="ADJZDgRPUTBOiFUc9khFDy">图片上传</label>
|
||||
<div class="field-control" data-page-node-id="ovpkFVnIsdCZWUcBN21EuO">
|
||||
<label class="upload" data-page-node-id="sHylpiEVUllqifYR5Rjnux">
|
||||
<div class="upload-icon" data-page-node-id="9jA1234YfjzKAqGtEXdfxv">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" data-page-node-id="jpo7HN1U8dyb10eTMZXWmJ"><path d="M12 16V4M7 9l5-5 5 5" data-page-node-id="jCYvVZ9bqQ3eWhd0JGCn6P"/><path d="M4 15v3a2 2 0 002 2h12a2 2 0 002-2v-3" data-page-node-id="mGAuau1yyJJ8rPVkHsc80i"/></svg>
|
||||
</div>
|
||||
<div data-page-node-id="pOYMzudSilp8anfhdX6NoT">
|
||||
<div class="t" data-page-node-id="7270wGAv0zvuovLNixYv0F">点击或拖拽图片到此处<em data-page-node-id="4SBpaJ9F1SQmgcF4h1TXMQ">上传</em></div>
|
||||
<div class="d" data-page-node-id="VWiqsopOKlryVzUEyNDV1N">支持 .jpg / .bmp / .png 格式,单张不超过 20MB,可多选</div>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-row" data-page-node-id="g2rqW2Pg1mV7kxU5pjhc6A">
|
||||
<label data-page-node-id="HaMeoFJEFIxuExlSEd5vdt">抄送人</label>
|
||||
<div class="field-control" data-page-node-id="hPrQi3dsNAoaIbHYH12r7W">
|
||||
<div class="cc-row" data-page-node-id="JYP4lZiwZHPvAxOSGfyPyT">
|
||||
<span class="cc-chip" data-page-node-id="7Dm8zeNpe3f9cqzSHWmhgc"><span class="av" data-page-node-id="7mtBNgDqn01CMyOOYdqySn">王</span>王主任<span class="x" data-page-node-id="46ZNU30heTGESUTHBXl8VR">✕</span></span>
|
||||
<span class="cc-chip" data-page-node-id="pGp1TbwLQQBtkMezEdPxIL"><span class="av" style="background:linear-gradient(135deg,#8fd3a8,#4caf7d)" data-page-node-id="Jo33PdZrLUHNLRd8Xcx491">张</span>张老师<span class="x" data-page-node-id="Nfqm6fh8IBpnqh8YxdSvi5">✕</span></span>
|
||||
<button type="button" class="cc-add" data-page-node-id="kJjoQnQQqIGSW9RnjMssnA">+ 选择抄送人</button>
|
||||
</div>
|
||||
<div class="help-text" data-page-node-id="3I1len510FzvXASsKgVwL5">审批通过后,请假单将自动抄送给以上人员</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
</form>
|
||||
</main>
|
||||
|
||||
<!-- 底部操作栏 -->
|
||||
<div class="action-bar" data-page-node-id="sHkY1QqGjh30YMgwDkGxyx">
|
||||
<button class="btn btn-primary" type="submit" data-page-node-id="rEzE7N1bIe6Y4NKyeBKi6g">提交申请</button>
|
||||
<button class="btn btn-ghost" type="button" data-page-node-id="fwFH5H3nWV0Q04QtHFZJTp">返回</button>
|
||||
<button class="btn btn-ghost" type="button" data-page-node-id="ICHmTkrxmEPVAguA6wmbUo">重置</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// 分段选择器通用交互
|
||||
document.querySelectorAll('.segmented').forEach(function(group){
|
||||
group.addEventListener('click', function(e){
|
||||
var btn = e.target.closest('button');
|
||||
if (!btn) return;
|
||||
group.querySelectorAll('button').forEach(function(b){ b.classList.remove('on'); });
|
||||
btn.classList.add('on');
|
||||
// 半天 / 半天以上 联动结束日期与时长
|
||||
if (btn.textContent === '半天') {
|
||||
document.querySelectorAll('.date-wrap .input')[1].value = document.querySelectorAll('.date-wrap .input')[0].value;
|
||||
document.getElementById('len').textContent = '0.5';
|
||||
} else if (btn.textContent === '半天以上') {
|
||||
document.getElementById('len').textContent = '1.0';
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,189 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN" data-page-node-id="refVnJgepgGX7ooSSw1oNl">
|
||||
<head data-page-node-id="CVAXxq86IWt6duxnFeTLA0">
|
||||
<meta charset="UTF-8" data-page-node-id="79IZgCK1mSNOJYhwo4Fqxs">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" data-page-node-id="xfrrrb0wcshQP1YGHHoPEF">
|
||||
<title>查询条件区(.search-wrap)视觉规范</title>
|
||||
<style>
|
||||
:root{
|
||||
--brand:#577afb; --brand-hover:#3f63e8; --brand-bg:#eef3ff; --brand-ring:rgba(87,122,251,.12);
|
||||
--text-main:#1c2333; --text-sub:#5b6478; --text-hint:#99a2b5;
|
||||
--border:#e6ebf4; --border-hover:#c8d4ee; --panel-border:#e3eaf6; --divider:#ecedf3;
|
||||
--page:#f5f7fa; --radius:8px; --radius-panel:10px;
|
||||
}
|
||||
*{box-sizing:border-box}
|
||||
body{margin:0;padding:32px 28px 72px;background:var(--page);color:var(--text-main);
|
||||
font:14px/1.65 "Microsoft YaHei","PingFang SC",-apple-system,Arial,sans-serif}
|
||||
.wrap{max-width:1000px;margin:0 auto}
|
||||
h1{font-size:22px;font-weight:600;margin:0 0 6px}
|
||||
h2{font-size:16px;font-weight:600;margin:36px 0 14px;padding-left:11px;position:relative}
|
||||
h2::before{content:"";position:absolute;left:0;top:4px;width:3px;height:16px;border-radius:2px;background:var(--brand)}
|
||||
h3{font-size:14px;font-weight:600;margin:22px 0 10px;color:var(--text-sub)}
|
||||
p.lede{margin:0 0 4px;color:var(--text-sub)}
|
||||
p.note{color:var(--text-sub);font-size:13px;margin:8px 0}
|
||||
code{font-family:Consolas,"Courier New",monospace;font-size:12.5px;background:#eef1f7;
|
||||
color:#31405c;padding:1px 5px;border-radius:4px}
|
||||
.card{background:#fff;border:1px solid var(--panel-border);border-radius:12px;padding:20px 22px;
|
||||
box-shadow:0 2px 8px rgba(23,35,61,.05)}
|
||||
.stage{background:#fff;border:1px solid var(--divider);border-radius:12px;padding:20px;margin-top:12px}
|
||||
|
||||
/* ---------- 真实组件复刻 ---------- */
|
||||
.sw{display:flex;align-items:center;gap:12px;background:#fff;border:1px solid var(--panel-border);
|
||||
border-radius:var(--radius-panel);height:56px;padding:10px 16px;box-shadow:0 2px 8px rgba(23,35,61,.05)}
|
||||
.sw.form-only{gap:12px}
|
||||
.sw .grp{display:flex;align-items:center;gap:12px;flex:1;min-width:0}
|
||||
.ctl{height:32px;border:1px solid var(--border);border-radius:var(--radius);background:#fff;
|
||||
display:flex;align-items:center;padding:0 10px;font-size:13px;color:var(--text-hint);
|
||||
transition:border-color .18s,box-shadow .18s;white-space:nowrap}
|
||||
.ctl.sel{width:150px;justify-content:space-between;padding-right:8px}
|
||||
.dates{display:flex;align-items:center;gap:8px;flex:none}
|
||||
.ctl.dt{width:140px;justify-content:space-between;padding-right:6px}
|
||||
.tilde{color:var(--text-hint);font-size:13px;line-height:1;flex:none;user-select:none}
|
||||
.ctl.kw{width:240px;padding-right:0;overflow:hidden}
|
||||
.ctl.kw .ph{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis}
|
||||
.ctl.kw .mag{width:34px;text-align:center;color:var(--text-hint);font-size:16px;transition:color .18s;flex:none}
|
||||
.ctl.hover{border-color:var(--border-hover)}
|
||||
.ctl.focus{border-color:var(--brand);box-shadow:0 0 0 3px var(--brand-ring);color:var(--text-main)}
|
||||
.ctl .val{color:var(--text-main)}
|
||||
.caret{width:0;height:0;border-left:4px solid transparent;border-right:4px solid transparent;
|
||||
border-top:5px solid #b9c1d1;margin-left:8px;flex:none}
|
||||
.btn{height:32px;padding:0 14px;border:1px solid var(--border);border-radius:var(--radius);background:#fff;
|
||||
color:var(--text-sub);font-size:13px;display:inline-flex;align-items:center;gap:5px;cursor:pointer;transition:all .18s}
|
||||
.btn.hover{border-color:#bccbf5;background:var(--brand-bg);color:var(--brand)}
|
||||
.btn.primary{background:var(--brand);border-color:var(--brand);color:#fff}
|
||||
.reset{margin-left:auto}
|
||||
.tip{font-size:13px;color:var(--text-hint);white-space:nowrap}
|
||||
.cal{color:var(--text-hint);font-size:14px}
|
||||
.hintline{margin:8px 0 0;font-size:12.5px;color:var(--text-hint)}
|
||||
|
||||
table{border-collapse:collapse;width:100%;margin-top:12px;font-size:13px;background:#fff;
|
||||
border:1px solid var(--panel-border);border-radius:10px;overflow:hidden}
|
||||
th,td{padding:9px 12px;text-align:left;border-bottom:1px solid var(--divider);vertical-align:top}
|
||||
th{background:#f7f9fc;font-weight:600;color:var(--text-sub);font-size:12.5px}
|
||||
tr:last-child td{border-bottom:none}
|
||||
td.k{white-space:nowrap}
|
||||
.swatch{display:inline-block;width:22px;height:14px;border-radius:3px;border:1px solid rgba(0,0,0,.08);
|
||||
vertical-align:-2px;margin-right:6px}
|
||||
ul{margin:8px 0;padding-left:20px;color:var(--text-sub);font-size:13px}
|
||||
ul li{margin:5px 0}
|
||||
.ok::before{content:"✓ ";color:#18a058;font-weight:700}
|
||||
.no::before{content:"✕ ";color:#e5484d;font-weight:700}
|
||||
.grid2{display:grid;grid-template-columns:1fr 1fr;gap:14px;margin-top:12px}
|
||||
@media (max-width:760px){.grid2{grid-template-columns:1fr}}
|
||||
</style>
|
||||
</head>
|
||||
<body data-page-node-id="DbU6KA9kcHZ5A6UxgKvMfI">
|
||||
<div class="wrap" data-page-node-id="rW64T9TP4LiTFfDjrquu1W">
|
||||
|
||||
<h1 data-page-node-id="XBvI0zZupskdTOnaDMGbrU">查询条件区 <code data-page-node-id="GPkR2fhCaNFbYKyyBT3IgV">.search-wrap</code> 视觉规范</h1>
|
||||
<p class="lede" data-page-node-id="hONYABIgTfq5bSVWxG6tEc">请假模块 6 个列表页统一筛选条 · 全局面板 · 纯 CSS 实现(<code data-page-node-id="cDJHHzzpxTv1CxLDRNBMH9">layout.css</code>)</p>
|
||||
<p class="note" data-page-node-id="ZMLNaHf0zr4syQVPove17Z">适用范围:我的请假 / 全部请假 / 抄送列表 / 审批列表 / 时长日志 / 统计分析</p>
|
||||
|
||||
<h2 data-page-node-id="IPlypUJTJtHiuL0BSfBMwf">一、容器(面板)</h2>
|
||||
<p class="note" data-page-node-id="1EbMzEPnq7o6Fk67TWD9Lo">原先是一条通栏灰带(<code data-page-node-id="Yn76yVNJxLYwTVHI3LfS9C">#f7f7f7</code>、无圆角、<code data-page-node-id="bkxIcOD4CaGvpjQZQdHLLE">52px</code>),与白色页面板对比生硬、边界模糊。改为<b data-page-node-id="DIfqVVtuXcLQSYD69QsEU2">白底卡片</b>:靠细边框分层,靠极淡投影浮起。高度 <code data-page-node-id="4AXM8wEzUIBHl6aE1D21OV">56px</code> 与 <code data-page-node-id="vnbcG1k0YR5p6oy2TbV2PC">padding 10px</code> 是为了让 32px 控件上下各留 10px 余量,同时消化 1px 边框与聚焦光圈,不顶破容器。</p>
|
||||
|
||||
<div class="stage" data-page-node-id="uPGFVFQyeBY4mjVX8ag1eJ">
|
||||
<div class="sw" data-page-node-id="7uhW2SibiLEn5DmZnGXosA">
|
||||
<div class="grp" data-page-node-id="vzF3ugJNZRHwrP8phMkFrZ">
|
||||
<div class="dates" data-page-node-id="datesPair1">
|
||||
<div class="ctl dt" data-page-node-id="dtStart"><span class="ph" data-page-node-id="dtStartPh">开始时间</span><span class="cal" data-page-node-id="dtStartCal">▦</span></div>
|
||||
<span class="tilde" data-page-node-id="dtTilde">至</span>
|
||||
<div class="ctl dt" data-page-node-id="dtEnd"><span class="ph" data-page-node-id="dtEndPh">结束时间</span><span class="cal" data-page-node-id="dtEndCal">▦</span></div>
|
||||
</div>
|
||||
<div class="ctl sel" data-page-node-id="lQtAxbP9AOugipVbdv3vNF"><span data-page-node-id="fKHg0mqzTN0DK9TayNC2pB">请假类型</span><i class="caret" data-page-node-id="9h7WgGW5aqLb8IbBh99NEH"></i></div>
|
||||
</div>
|
||||
<button class="btn" data-page-node-id="cUwL5UCpHwSPcKwN8urCGf">↻ 重置</button>
|
||||
<div class="ctl kw" data-page-node-id="UGXtduSh70kS1eWwRMcsr9"><span class="ph" data-page-node-id="9rIBncREbH2Qd7l4uqKeSZ">搜索申请人 / 事由</span><span class="mag" data-page-node-id="Iy8Cbt71L0a0P8IM8MQhX9">⌕</span></div>
|
||||
</div>
|
||||
<p class="hintline" data-page-node-id="uxMlxiBdRxgqtLqHoCtGjn">默认态:起止两个日期框等宽等距、同一基线,中间以弱化的「至」连接;右侧「重置 + 搜索」固定靠右</p>
|
||||
</div>
|
||||
|
||||
<table data-page-node-id="tG3RfjQHTjxdVkIDxtLxOU">
|
||||
<thead data-page-node-id="fpuTHBgahdVVQX7ZBGzfB5"><tr data-page-node-id="ascah5lsrAHODWkDiOWFqA"><th data-page-node-id="DlcWpzxi06FzRSzOVzFn3f">属性</th><th data-page-node-id="hdYdehWo113jtXi72Y9Fju">取值</th><th data-page-node-id="HGKv0IYBDZcop9a861xxum">说明</th></tr></thead>
|
||||
<tbody data-page-node-id="h2KaDn1y1VIBiC7UGCo1J2">
|
||||
<tr data-page-node-id="QoeUBBda9u7X0u5GOZ0EmA"><td class="k" data-page-node-id="wEKMQDoufZI6TdbKKOjiO8">background</td><td class="k" data-page-node-id="KUbufawueFV0Gw3WLXROMg"><span class="swatch" style="background:#fff" data-page-node-id="iSFbVJroPdgy8qEjzCPtWd"></span><code data-page-node-id="s3I3p54StlNATFY7sZ0zco">#fff</code></td><td data-page-node-id="CCAY5U4t1xu63XivNSvSdZ">与页面底板同色,靠边框与投影分层</td></tr>
|
||||
<tr data-page-node-id="7IJlrVTuA4RF87nb8LEFNz"><td class="k" data-page-node-id="b51sfORV9f0LSEI7x9l5WF">border</td><td class="k" data-page-node-id="FyoC9KkCq9QDrZ2Iql7t3s"><span class="swatch" style="background:#e3eaf6" data-page-node-id="aTaWC50R3fj6YUWGueV5gQ"></span><code data-page-node-id="gny0RckX6yXzlKPaWOfUwA">1px solid #e3eaf6</code></td><td data-page-node-id="fWTRVSSV8pe1Cverwgoan1">比表格分隔线 <code data-page-node-id="NAzRLSTz96anmLZnRcIipH">#ecedf3</code> 略深偏冷,保证纯白底上仍看得见</td></tr>
|
||||
<tr data-page-node-id="GREKnCOi6eUEBOyHFCyud8"><td class="k" data-page-node-id="htDrVwze4Hc6D7z8a4hto6">border-radius</td><td class="k" data-page-node-id="iSBFyfD7Q9JG4FrYdnIU2k"><code data-page-node-id="Fp9T3x2su9EVAaYeCdYyaq">10px</code></td><td data-page-node-id="ja0OWDCMjH5crX0vH6vtVv">面板用 10,内部控件用 8,形成层次差</td></tr>
|
||||
<tr data-page-node-id="qP0jFXthyXTMeXCSl9Y2Bk"><td class="k" data-page-node-id="Oag8DX4Ri8hcesumZOD0zv">height</td><td class="k" data-page-node-id="w9Gq4DbHD2bTHg4JZnTTcR"><code data-page-node-id="1hI0axPpv0cHo2800tsxSW">56px</code>(含 <code data-page-node-id="VUMFCbZDSYyq8Isi5VxQpm">box-sizing:border-box</code>)</td><td data-page-node-id="C167BYbQGLDmzOLTyMRZ4n">10 + 32 + 10 + 边框,与改造前 <code data-page-node-id="qRsVpTyCB4uIBliM0QUrmW">52px</code> 竖向占位接近,不影响表格高度预算</td></tr>
|
||||
<tr data-page-node-id="OmFqTMtu2J2HBS2JPVY7YB"><td class="k" data-page-node-id="pzRWQ8CJqXXfoFPG7wmMDH">box-shadow</td><td class="k" data-page-node-id="WLhJMrOWOwm3ut20oySaiF"><code data-page-node-id="62N46BysqNdTCHByrCdL5W">0 2px 8px rgba(23,35,61,.05)</code></td><td data-page-node-id="dENDS9SjxxB9BiggU3SmEJ">极淡,只做「浮起」暗示,不做视觉噪音</td></tr>
|
||||
<tr data-page-node-id="QRbdfJ4rBDglARB8zfsJlX"><td class="k" data-page-node-id="2Rh3v7SM3oIGPrdeuCkmIF">layout</td><td class="k" data-page-node-id="sr0aAhzjNQXPitBcHeYFQ5"><code data-page-node-id="sIy1r0ScmNYUEnMKNQi4jN">display:flex; align-items:center</code></td><td data-page-node-id="kCVWFEsoXraH9EtVVUoK23">弃用 <code data-page-node-id="ni7xh9bVcC3AePrK23YUsZ">.fl/.fr</code> 浮动:条件项增减(抄送页无日期、统计页无搜索框)都不会挤在一起</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h2 data-page-node-id="tN7BpuYOc4k9gKCWYUyXCd">二、控件</h2>
|
||||
<p class="note" data-page-node-id="VgPvVEPO3zcbTiwZaDFIOx">输入框、下拉、起止日期统一 <b data-page-node-id="CTfmtDSgg7hku0s0AjBvv3">32px 高 / 8px 圆角</b>,边框比面板边框略深一档,让控件在面板内可辨。同一对起止日期之间只允许「宽度、高度、边框、圆角、字号、内边距」逐项相同。</p>
|
||||
|
||||
<div class="stage" data-page-node-id="vqsuvAaSEDm70q4kp9gb2D">
|
||||
<h3 data-page-node-id="we8dUQQAaK2RsCoU7Cim2X">交互状态</h3>
|
||||
<div class="grid2" data-page-node-id="BHfPWJtg7YAT8ZyHcVCwsx">
|
||||
<div data-page-node-id="kY3AnQtm20foUDMPBziuZX">
|
||||
<div class="ctl" data-page-node-id="xDIqRQlyUqx16F0piMqjVY">默认态 · 占位文字 #99a2b5</div>
|
||||
<p class="hintline" data-page-node-id="t0iRCmRAhXWOsQFGWOqJ35">border <code data-page-node-id="gwII8Ms08EVkb7CD4zEzbz">#e6ebf4</code></p>
|
||||
</div>
|
||||
<div data-page-node-id="gm7VJ6nfqat1awqIuFzoJ4">
|
||||
<div class="ctl hover" data-page-node-id="BpSsuitTxy2TK69AzUIRLS">悬停态 · 边框加深提示可交互</div>
|
||||
<p class="hintline" data-page-node-id="7FXiJo28SywvPbg1kbDUT8">border <code data-page-node-id="obJ4kh5DULF2IardAXJFwV">#c8d4ee</code></p>
|
||||
</div>
|
||||
<div data-page-node-id="oPPYXCPMVQRXqzXd5ZADMV">
|
||||
<div class="ctl focus" data-page-node-id="LkBYO3E7n9Z41ERoLV0ABv"><span class="val" data-page-node-id="t7JlI9R1GzXdJTnP1tAJfk">聚焦态 · 品牌色描边</span></div>
|
||||
<p class="hintline" data-page-node-id="bNAKsF3axHXEvYT1ucOSSy">border <code data-page-node-id="0v8vhsPHswGqS1QEE54qtf">#577afb</code> + <code data-page-node-id="tpDrSi5v4wAs2831bXuuNj">0 0 0 3px rgba(87,122,251,.12)</code></p>
|
||||
</div>
|
||||
<div data-page-node-id="d4tU7pcrUA5WmICoPIG9xB">
|
||||
<div class="ctl kw" data-page-node-id="RoWvDBPMdj7oGoEiy0Rpg4"><span class="val" data-page-node-id="shQC4K4lKzdJUBaYMuyNE9">已输入内容</span><span class="mag" style="color:var(--brand)" data-page-node-id="VCHBO1FZhxRma8pj0P44iG">⌕</span><span style="width:22px;text-align:center;color:#b9c1d1" data-page-node-id="Bc6B6IEbM0qHcFNNeRLCI4">✕</span></div>
|
||||
<p class="hintline" data-page-node-id="US54C6xCYvIKDykPdispN1">悬停整框时放大镜转品牌蓝;清空键位于其左侧,两个图标不叠在同一槽位</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<table data-page-node-id="fdYpbbuKVh1HSHLWCJMPr1">
|
||||
<thead data-page-node-id="iX0QXUEgc713psvXTByFtq"><tr data-page-node-id="DcybrruoNoIrdIcLVfhxwr"><th data-page-node-id="4ojzKLjg0qDtFitCBTp5UA">控件</th><th data-page-node-id="NXsaQxFChbhCpOCvxZr8P2">宽 / 高</th><th data-page-node-id="1pEdK1Nh92gepb6Lg9JsGc">关键样式</th></tr></thead>
|
||||
<tbody data-page-node-id="xtDxu6E8ONtrdSogSeFQjo">
|
||||
<tr data-page-node-id="ckuGGk80I979kHjubl5vus"><td class="k" data-page-node-id="zXN5Co1t865AF9XbULrljr">起止日期对 <code data-page-node-id="jurMf0m45zZBAE81EIafRB">.search-wrap_dates</code></td><td class="k" data-page-node-id="aeFd8JgEeYlRkIVjVrzpm7">140 × 32(各)</td><td data-page-node-id="a1niDhil4ZLNI8ruDbxSfD"><code data-page-node-id="CS4A681EeynXQQwcTxvZQO">type="date" transfer</code>,两个独立输入框;容器为 <code data-page-node-id="dts1">display:flex; gap:8px</code>,保证等宽、等高、同一基线;中间 <code data-page-node-id="dts2">.search-wrap_tilde</code>(「至」)仅作语义连接</td></tr>
|
||||
<tr data-page-node-id="dzzVQtgO9rYRNiunYSOIKK"><td class="k" data-page-node-id="jX4yLwQ81B699vGsQWBNnw">类型下拉 <code data-page-node-id="D4lFDrXSZRw21JGjTctHNP">.search-wrap_sel</code></td><td class="k" data-page-node-id="lkDd5DOzy1XRE5W8dHom1j">150 × 32</td><td data-page-node-id="g4wFN52eDCnodEMeKlpjdh">选中值与占位文字左右内边距 10px,行高 30px 视觉居中</td></tr>
|
||||
<tr data-page-node-id="GRiNJGWHQYEFtkPte4ZwsX"><td class="k" data-page-node-id="v7jagjj1HIBM5oEdJQt1tV">关键词 <code data-page-node-id="oQmswz3zQvSq4ChR6jZYH0">.search-wrap_kw</code></td><td class="k" data-page-node-id="43LP4mEOBzrduWOonFW9N3">240 × 32</td><td data-page-node-id="Wr0beTVBEo4KwR9WMkMwtB"><code data-page-node-id="vjZ7l6MNXxOziZarU8ZTAW">search</code> 输入框;右内边距 40px 给放大镜让位</td></tr>
|
||||
<tr data-page-node-id="SESuTlrknBIdUIONWrZ3DZ"><td class="k" data-page-node-id="YIkZYaVc3LA23nTIaxHImK">重置 <code data-page-node-id="GCjPasKYyawgDKOHnpPhE0">.search-wrap_reset</code></td><td class="k" data-page-node-id="FsgMCqu15fX04EIPShBCSG">自动 × 32</td><td data-page-node-id="VQe5JJ3AgmFJUA9Eflv3uI">幽灵按钮:白底 + 灰字,hover 才染品牌色,避免与查询按钮抢注意力</td></tr>
|
||||
<tr data-page-node-id="i8u7uGTax2ULeF6LApAVYn"><td class="k" data-page-node-id="ADzFaW4ZOMcxHayHVE7DFB">查询(统计页)</td><td class="k" data-page-node-id="XOIQoUHhFHnGkfisxEE43P">自动 × 32</td><td data-page-node-id="nEXjPdOMT3JmYCCCdfUICc">沿用 <code data-page-node-id="9CgJHDFmFpezJCb4bBT6vU">type="primary"</code> 品牌蓝实心,全页仅此一处主按钮</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h2 data-page-node-id="PZSeLthAli8jssX38NAkjs">三、色板(与「我要请假」页同源)</h2>
|
||||
<p class="note" data-page-node-id="GlOD7qtfB0GikmTLGJrryW">不引入第二套品牌色。所有取值复用 ApplyLeave 已有 token,保证跨页一致。</p>
|
||||
|
||||
<table data-page-node-id="Omhc5xvDwCdeeHzD6tVH2E">
|
||||
<thead data-page-node-id="jYpqQDFNYzK1ZXHt8OXpG2"><tr data-page-node-id="SABlBVHK15IiJnvsp9FE5H"><th data-page-node-id="BsMHcO7vs5TyGdXZ7xHU55">用途</th><th data-page-node-id="yzUej8Ww4KLDKzn3RWmnKf">色值</th><th data-page-node-id="esUpjczOtzl3HEswAt27dD">Token</th></tr></thead>
|
||||
<tbody data-page-node-id="BZpsl85mrd5HhjAhXBHCjm">
|
||||
<tr data-page-node-id="jFplq0ZreBVbnWnFdWpvzf"><td class="k" data-page-node-id="xgPjcs6bYWfhx6v2WjkjUG">品牌主色</td><td class="k" data-page-node-id="LvdOiq7YOE5hBd5eK9Hnzq"><span class="swatch" style="background:#577afb" data-page-node-id="LywILAEbYRdhMwICQrTpsX"></span><code data-page-node-id="kpzFkB1qPzHercu3VEWuAk">#577afb</code></td><td class="k" data-page-node-id="KIPRGBT7YBgFkzONgDXQgk">$primary</td></tr>
|
||||
<tr data-page-node-id="uZlgMOcPZLYXR8NvBtwDlW"><td class="k" data-page-node-id="ZbBeBCoS7hyopCRi04iVJQ">浅主色底</td><td class="k" data-page-node-id="WJ9nV2PeAoOTfcyxjhqEPt"><span class="swatch" style="background:#eef3ff" data-page-node-id="8tXIvaOZZZ6sjQ2316Zl47"></span><code data-page-node-id="TvGOsZ5PqXHXvuaQ3EigUQ">#eef3ff</code></td><td class="k" data-page-node-id="sFK3vjbPxLpv5YGwm09sDx">$primary-bg</td></tr>
|
||||
<tr data-page-node-id="LagiXmsxYXua7IYHQhKdhF"><td class="k" data-page-node-id="alRRC7PePsJVUporRHh0bD">主文字</td><td class="k" data-page-node-id="XhGbNVau1JGxZYv6L54Fdm"><span class="swatch" style="background:#1c2333" data-page-node-id="KmlBLptF06Z6hOgkUgWPMe"></span><code data-page-node-id="Kzu8PjhN8lnpjB1mymxMDB">#1c2333</code></td><td class="k" data-page-node-id="BeyOno2BPhOwluzGRjCZQJ">$text-main</td></tr>
|
||||
<tr data-page-node-id="iLyAGeZgMUirhZxUZcikNA"><td class="k" data-page-node-id="o6ff0IWdvIigtnKTqjdVIN">次文字</td><td class="k" data-page-node-id="FeXK1nHGTnCkTAl2kteTe3"><span class="swatch" style="background:#5b6478" data-page-node-id="2gtM0rIGhtBhfBqpoKJKm7"></span><code data-page-node-id="xUmfBbsDANtMQctONHON3Y">#5b6478</code></td><td class="k" data-page-node-id="FeDI9cpFZaQYkUUqTLLomf">$text-sub</td></tr>
|
||||
<tr data-page-node-id="XaN85L6cGGg7zVkwaO4B2o"><td class="k" data-page-node-id="0sZyt0rgBvsZXG19nKP17T">提示 / 占位</td><td class="k" data-page-node-id="PBcV0Bi2hySCqD1y4mHzhi"><span class="swatch" style="background:#99a2b5" data-page-node-id="HBz1olJw1h53kuE9d7HKnM"></span><code data-page-node-id="kyuPJAtngFX8ZDupmIrcD7">#99a2b5</code></td><td class="k" data-page-node-id="UnXH0vZRNU4iZnlHyrGKH8">$text-hint</td></tr>
|
||||
<tr data-page-node-id="18te0UZwqUBEH7eXzTx3xv"><td class="k" data-page-node-id="uF086wMjQTZCNFWQIhoJDf">控件边框</td><td class="k" data-page-node-id="fKR3N6FI1kQoOoFiPzL7HY"><span class="swatch" style="background:#e6ebf4" data-page-node-id="jGtNVoNvPjsUM9nNpNOhFR"></span><code data-page-node-id="894kOUWEF92GRE9DVyKmNn">#e6ebf4</code></td><td class="k" data-page-node-id="Hs2ErCvJcsL8vbPQZ2TjPo">$border</td></tr>
|
||||
<tr data-page-node-id="ayQAFnpkLws9R0BTN7GB0D"><td class="k" data-page-node-id="Of2ra8ljr5dXMaHz5rurM3">面板边框</td><td class="k" data-page-node-id="pzwXEGGT2gJ4tkdl7FF6Hp"><span class="swatch" style="background:#e3eaf6" data-page-node-id="KdhHhXEIYK0ZShgBkd1C7R"></span><code data-page-node-id="NLB2kuodccqIjGgEnYYNhe">#e3eaf6</code></td><td class="k" data-page-node-id="n4rHgUEXYxIsKuL7bKo7I9">—</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h2 data-page-node-id="KvlGBmLwf3q6MKa3IxTiEY">四、无障碍与可用性</h2>
|
||||
<ul data-page-node-id="JFMuZFMsHXI1WtNHzPlPTf">
|
||||
<li class="ok" data-page-node-id="lBDmUg2zds2YtDCnuYvaOp">所有可交互控件带 <code data-page-node-id="5mPwrHQ3DBqwkwjFs3S7bh">:focus-visible</code> 外描边(<code data-page-node-id="fAFrcsfRsClGzSggj7XHKQ">2px #577afb</code>),键盘 Tab 可完整走通「日期 → 类型 → 重置 → 搜索」。</li>
|
||||
<li class="ok" data-page-node-id="IA8EGii0NGaioFWIN7DaTZ">聚焦态用「描边 + 3px 柔光圈」双重提示,不依赖单一颜色差异。</li>
|
||||
<li class="ok" data-page-node-id="ooZ4ilRHSDV3DhqtBdFa7u">控件高度 32px;表单页输入框沿用 32px,跨页命中区域一致。</li>
|
||||
<li class="ok" data-page-node-id="vSLBkXGIukoX5jGz2FC3i9">占位文字统一 <code data-page-node-id="5Q53VBBac6wWZAHtB9nEQg">#99a2b5</code>,与其上白底对比度约 2.9:1 —— 仅用于占位(WCAG 对占位文字不作 4.5:1 强制),实际输入值走 <code data-page-node-id="zF0Z2UdHvPOgCMWttFVcyU">#1c2333</code>(≈15:1)。</li>
|
||||
<li class="ok" data-page-node-id="JF61H7vs5WXIIaAFfALeiF">「重置」清空起止时间 + 关键词 + 状态页签并回到第 1 页,行为可预期。</li>
|
||||
<li class="ok" data-page-node-id="dtsAcc1">起止两个日期框:宽度差 < 0.5px、高度差 < 0.5px、顶边与垂直中心线偏差 < 0.5px,且边框/圆角/字号/内边距逐项一致 —— 已纳入自动化断言。</li>
|
||||
<li class="ok" data-page-node-id="dtsAcc2">两个日期框均为可编辑态,点击输入框本体即弹出月历(无需只点右侧图标),两框交互完全一致。</li>
|
||||
</ul>
|
||||
|
||||
<h2 data-page-node-id="3HJ08rehnOnSEbse9GKrUn">五、实现要点(易错处)</h2>
|
||||
<ul data-page-node-id="4PuBZIUS3mLGEmE2ANvjSK">
|
||||
<li class="no" data-page-node-id="Gx72dYzZGUw9HBWWEKLh7u">iView 的 <code data-page-node-id="azAUwrONE05RIp1Qvwn9qG">search</code> 输入框放大镜类名是 <code data-page-node-id="JGBezSukH1uowmPKnIEOqa">.ivu-input-search-icon</code>(绝对定位),<b data-page-node-id="2UAo766Cul5Mwslo9QMEHP">没有</b> <code data-page-node-id="l7iVcajhxV6dJuM1cAHkjN">.ivu-input-suffix</code> 包裹层;按 suffix 写样式会全部失效。</li>
|
||||
<li class="no" data-page-node-id="oSaQCdSI1q2xfqFwZ5FLWV">日期面板的根类名是 <code data-page-node-id="HLufqTlSLAPXWTol7qaDik">.ivu-picker-panel-body-wrapper</code>,不是 <code data-page-node-id="I9EGDshkHcX3svaXdAbksO">.ivu-picker-panel</code>;且 <code data-page-node-id="dtTrans">transfer</code> 会把所有 picker 的面板都挂到 body,统计可见面板前必须先按 <code data-page-node-id="dtOffPar">offsetParent !== null</code> 过滤。</li>
|
||||
<li class="no" data-page-node-id="9pZxdEQrVEz39xi1B4XwT7"><code data-page-node-id="Prz1tPH0UovAfl1AUmllKB">:editable="false"</code> 会让内部 input 变 readonly,<b data-page-node-id="RbnqMod3IXv6WHZEFKYNFF">点输入框本体会失效</b>(只有日历图标可用)。因此起止日期框保持默认可编辑,保证「点框即弹出」的一致体验。</li>
|
||||
<li class="ok" data-page-node-id="DkPAGzfOgk64BZJY0gwzfX">全部新规则以 <code data-page-node-id="w4qcyGyTSfTJYDQIMcb3xH">.search-wrap</code> 为前缀 → 作用域隔离;实测仅 6 个列表页使用该容器,其它页面零影响。</li>
|
||||
<li class="no" data-page-node-id="dtSrvNote">业务 dev 端口(如 8081)的 vite 对<b data-page-node-id="dtSrvNote2">任何浏览器导航</b>都返回 <code data-page-node-id="dtSrvNote3">index.html</code>(curl 用 <code data-page-node-id="dtSrvNote4">Accept: */*</code> 才能拿到真实文件),自定义预览页在浏览器里会直接启动真实 SPA 并被 CAS 跳去 SSO。故「渲染验收」在独立预览服务上做,「该端口代码是否已生效」用直接抓取模块源码确认。</li>
|
||||
<li class="no" data-page-node-id="9pZxdEQrVEz39xi1B4XwT7"><code data-page-node-id="Prz1tPH0UovAfl1AUmllKB">:editable="false"</code> 会让内部 input 变 readonly,<b data-page-node-id="RbnqMod3IXv6WHZEFKYNFF">点输入框本体会失效</b>(只有日历图标可用)。因此起止日期框保持默认可编辑,保证「点框即弹出」的一致体验。</li>
|
||||
<li class="ok" data-page-node-id="DkPAGzfOgk64BZJY0gwzfX">全部新规则以 <code data-page-node-id="w4qcyGyTSfTJYDQIMcb3xH">.search-wrap</code> 为前缀 → 作用域隔离;实测仅 6 个列表页使用该容器,其它页面零影响。</li>
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,154 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN" data-page-node-id="c8jJRN3qgiN5kfAt3vgZYD">
|
||||
<head data-page-node-id="WMNABuuAvIt65s6192UA4K">
|
||||
<meta charset="UTF-8" data-page-node-id="Yk6AGtVnERulInfIlfo8Ck" />
|
||||
<title>左侧导航栏视觉升级 · 效果预览</title>
|
||||
<style>
|
||||
/* ===== 复制自 src/assets/styles/layout.css 的真实规则 ===== */
|
||||
*{font-family:"微软雅黑","Microsoft YaHei","Helvetica Neue",Helvetica,"PingFang SC","Hiragino Sans GB",Arial,sans-serif}
|
||||
body{margin:0;background:#fff}
|
||||
|
||||
.left-menu{
|
||||
position:fixed;background:#fff;color:#272829;width:216px;left:0;top:0;bottom:0px;
|
||||
display:flex;flex-direction:column;z-index:9;overflow:auto;
|
||||
border-right:1px solid #EFF1F5;
|
||||
padding:10px 0 20px;
|
||||
box-sizing:border-box;
|
||||
}
|
||||
.left-menu .menu-top-box,.left-menu .clear-box{border-right:none;height:0;flex:none}
|
||||
.left-menu ul{list-style:none;margin:0;padding:0}
|
||||
|
||||
.left-menu li{
|
||||
height:44px;margin:2px 12px;position:relative;border:none;border-radius:8px;
|
||||
background:transparent;transition:background-color .2s ease,color .2s ease;
|
||||
}
|
||||
.left-menu li a{
|
||||
display:flex;align-items:center;height:44px;width:100%;padding:0 12px;line-height:1;
|
||||
box-sizing:border-box;border:none;color:#4b5568;font-size:14px;letter-spacing:.2px;
|
||||
outline:none;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;transition:none;
|
||||
cursor:pointer;user-select:none;
|
||||
}
|
||||
.left-menu li a .menu-text{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
||||
.left-menu li a .menu-icon{font-size:18px;line-height:1;margin-right:10px;flex:none;color:#98a2b3;transition:color .2s ease}
|
||||
.left-menu li:hover{background:#f5f7fb}
|
||||
.left-menu li:hover a{color:#1f2a44}
|
||||
.left-menu li:hover a .menu-icon{color:#577afb}
|
||||
|
||||
.left-menu li.active{background:#eff4ff;border:none}
|
||||
.left-menu li.active::before{
|
||||
content:'';position:absolute;left:0;top:50%;transform:translateY(-50%);
|
||||
width:3px;height:18px;border-radius:0 3px 3px 0;background:#577afb;
|
||||
}
|
||||
.left-menu li.active a,.left-menu li.active a:hover{color:#3a63e0;font-weight:600}
|
||||
.left-menu li.active a .menu-icon{color:#3a63e0}
|
||||
|
||||
.left-menu li.menu-group{margin-top:10px}
|
||||
.left-menu li.menu-group > a{color:#667085;font-weight:600}
|
||||
.left-menu li.menu-group > a .menu-icon{font-size:17px}
|
||||
|
||||
.child-item-wrap{transition:all .3s ease-in-out;overflow:hidden}
|
||||
.left-menu li.child-item{height:38px;margin:2px 12px}
|
||||
.left-menu li.child-item a{margin-left:0;padding-left:14px;color:#6b7280;font-size:13px}
|
||||
.left-menu li.child-item a .child-dot{
|
||||
position:relative;width:5px;height:5px;margin-right:10px;flex:none;border-radius:50%;
|
||||
background:#d0d5dd;transition:background-color .2s ease,transform .2s ease;
|
||||
}
|
||||
.left-menu li.child-item:hover a .child-dot{background:#9db4f7;transform:scale(1.2)}
|
||||
.left-menu li.child-item.active a{color:#3a63e0;font-weight:600}
|
||||
.left-menu li.child-item.active a .child-dot{background:#577afb;box-shadow:0 0 0 3px rgba(87,122,251,.18)}
|
||||
|
||||
.left-menu::-webkit-scrollbar{width:6px}
|
||||
.left-menu::-webkit-scrollbar-thumb{background:#e2e6ef;border-radius:3px}
|
||||
.left-menu::-webkit-scrollbar-track{background:transparent}
|
||||
|
||||
/* 占位图标(无 iconfont 字体文件时用方块示意真实 18px 图形) */
|
||||
.ph{flex:none;width:18px;height:18px;margin-right:10px;border-radius:5px;
|
||||
background:#dfe4ee;display:flex;align-items:center;justify-content:center}
|
||||
.ph svg{width:12px;height:12px;stroke:#98a2b3;fill:none;stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round}
|
||||
.left-menu li:hover .ph svg,.left-menu li.active .ph svg{stroke:#577afb}
|
||||
.left-menu li.menu-group .ph{width:17px;height:17px}
|
||||
.menu-arrow{
|
||||
position:absolute;right:14px;top:50%;width:7px;height:7px;
|
||||
border-right:1.5px solid currentColor;border-bottom:1.5px solid currentColor;
|
||||
background:none;transform-origin:center;transition:transform .25s ease;margin-top:-5px;opacity:.55;
|
||||
}
|
||||
.menu-arrow.arrow-up{transform:rotate(225deg)}
|
||||
.menu-arrow.arrow-down{transform:rotate(45deg)}
|
||||
|
||||
.stage{margin-left:216px;padding:28px 32px}
|
||||
.stage h2{font-size:15px;color:#17233d;margin:0 0 6px}
|
||||
.stage p{font-size:13px;color:#808695;margin:0;line-height:1.9}
|
||||
.tag{display:inline-block;background:#f2f5ff;color:#3a63e0;font-size:12px;
|
||||
padding:3px 9px;border-radius:11px;margin:0 6px 6px 0}
|
||||
</style>
|
||||
</head>
|
||||
<body data-page-node-id="3h7DE4QC674Nnv72KJMbX0">
|
||||
|
||||
<div class="left-menu" data-page-node-id="vabeCjHmuEHr2IaWKMTE6S">
|
||||
<ul data-page-node-id="aTKRGUyuILFdYCwefWUzoi">
|
||||
<li class="menu-item" data-page-node-id="pR24sUgCtatpckTYmXe3we"><a href="javascript:;" data-page-node-id="tgXpBVqM3EATjswoGGHS7H">
|
||||
<i class="ph" data-page-node-id="tAKj2fjiNrwddnGqCxArvQ"><svg viewBox="0 0 24 24" data-page-node-id="jxz7O7CtBup4o32mfQEEiy"><path d="M4 4h16v16H4z" data-page-node-id="uGb3wfRZSxUM2pbmg2OEiZ"/><path d="M8 9h8M8 13h5" data-page-node-id="DJaWHf3WkzWRR67pOrByur"/></svg></i>
|
||||
<span class="menu-text" data-page-node-id="WXmF7gJBXMBQ1N3joLEvvO">我要请假</span></a></li>
|
||||
|
||||
<li class="menu-item" data-page-node-id="oXDXp7oyU7UPkudPnM4PM7"><a href="javascript:;" data-page-node-id="88dBWBfxk83WmuzU9NhydP">
|
||||
<i class="ph" data-page-node-id="eoMgHd82AW7JktRn4JwfdX"><svg viewBox="0 0 24 24" data-page-node-id="A1pcU27FJzu1AbUsqzIqVf"><rect x="4" y="4" width="16" height="16" rx="2" data-page-node-id="VFPTbMHfjLRJAQOZ7n241p"/><path d="M8 9h8M8 13h8M8 17h5" data-page-node-id="wzmPhFWvYKXC6IUcWeuRO8"/></svg></i>
|
||||
<span class="menu-text" data-page-node-id="a3Z2s2BpAL4LvBpJhKOjmT">我的请假</span></a></li>
|
||||
|
||||
<li class="menu-item" data-page-node-id="sIvEaACTM1PMS0H9JbWF5m"><a href="javascript:;" data-page-node-id="2h1QUpErVCz6qhjz8Nyoux">
|
||||
<i class="ph" data-page-node-id="OgOJGh7zrPBZgG9tsmZOW6"><svg viewBox="0 0 24 24" data-page-node-id="ezV60F6HRKYpYmke4FEZq2"><path d="M9 11l3 3 8-8" data-page-node-id="SNP8F4GeGQSIY2kNGdRyHn"/><path d="M20 12v7H4V5h11" data-page-node-id="dq9mg7vdSQgcOXNceWutCB"/></svg></i>
|
||||
<span class="menu-text" data-page-node-id="gumzYNtFnRuxWTwuV0bBFh">审批列表</span></a></li>
|
||||
|
||||
<li class="menu-item" data-page-node-id="PJGxm3sQlT5h2UdzgRA7Vw"><a href="javascript:;" data-page-node-id="F9YkIlNR3Qp4rMJZTuvIXc">
|
||||
<i class="ph" data-page-node-id="mnBQDL5W34ZEyRRiRNqJOV"><svg viewBox="0 0 24 24" data-page-node-id="ZoE3ViWjgzdRR8T3zTJ2af"><path d="M3 11l18-8-8 18-2-8z" data-page-node-id="oiSGCEHHQBql0vGsKrj42A"/></svg></i>
|
||||
<span class="menu-text" data-page-node-id="y0COJXTTgACj9foNSo38di">抄送列表</span></a></li>
|
||||
|
||||
<li class="menu-item" data-page-node-id="phsM2T0lQ3y8wW6msFYHnU"><a href="javascript:;" data-page-node-id="s0aPHxAKM91LdbE4Un0nsS">
|
||||
<i class="ph" data-page-node-id="JUYuXRqZXdeS1mcym3lpzh"><svg viewBox="0 0 24 24" data-page-node-id="gTYhHKmOcYRsUbfN9utE3C"><rect x="3" y="4" width="18" height="16" rx="2" data-page-node-id="mNnoNfYbDK8nNzoWytGRCw"/><path d="M3 9h18M8 13h8" data-page-node-id="8lPJSF8kH0RkWzOZzQi6iK"/></svg></i>
|
||||
<span class="menu-text" data-page-node-id="KppZsMkJhdN5dWPWO29B0p">全部请假</span></a></li>
|
||||
|
||||
<li class="menu-item" data-page-node-id="IEO5ewQPGUBEUVlLDH9vXl"><a href="javascript:;" data-page-node-id="M92YdKNLMUYwSLCj2f2rQb">
|
||||
<i class="ph" data-page-node-id="9CYJ7bW7QdVAgimyElAZMm"><svg viewBox="0 0 24 24" data-page-node-id="VscXa3xrz5TUYXp0wCBQ3d"><path d="M4 20l16-16" data-page-node-id="MnVc30edhOZoEiHoUvKHFF"/><circle cx="8" cy="8" r="3" data-page-node-id="DJwnbswxrv1OSVXnG2jcKY"/><path d="M14 14l4 4" data-page-node-id="jYqP7D6B7mblk6qMJ60h9i"/></svg></i>
|
||||
<span class="menu-text" data-page-node-id="eLZVIN95qaofw6MSBqDaa7">指派代课</span></a></li>
|
||||
|
||||
<li class="menu-group" data-page-node-id="V4si42iDLHug7ceQLQgEXC">
|
||||
<a href="javascript:;" data-page-node-id="ivZ7JBNQGaLqorfPkh9pwY">
|
||||
<i class="ph" data-page-node-id="iy6lh8HGmHmEJtZwosHjBt"><svg viewBox="0 0 24 24" data-page-node-id="5dDzVscj2TCMGXHrSDL6NV"><rect x="3" y="3" width="7" height="7" rx="1" data-page-node-id="npPqa6NxdIbfo7T40hByZ4"/><rect x="14" y="3" width="7" height="7" rx="1" data-page-node-id="hJv7jqMLxtFiXi1sL5WAeu"/><rect x="3" y="14" width="7" height="7" rx="1" data-page-node-id="injDikerKDZNRMEFc4jWDO"/><path d="M17.5 14v7M14 17.5h7" data-page-node-id="Kwe7pIYmWMI9bAf19SDWlL"/></svg></i>
|
||||
<span class="menu-text" data-page-node-id="25sokshNMmWhyWw8z8jR7i">流程管理</span>
|
||||
<i class="menu-arrow arrow-down" data-page-node-id="nrb1S6BJHKnjDiFD4Iq1qm"></i>
|
||||
</a>
|
||||
</li>
|
||||
<div class="child-item-wrap" style="max-height:none" data-page-node-id="jfzvi3DtJtGhTUrll4vwF3">
|
||||
<li class="child-item" data-page-node-id="0PTYj1y32gzIAEdfitLJkK"><a href="javascript:;" data-page-node-id="wQgGo2UjhG6xLhhHZ9PrUn"><i class="child-dot" data-page-node-id="wYtfFYu9fCvpWAh3i0tk8y"></i><span class="menu-text" data-page-node-id="3aYROZTDN0J8fwxPF30LoG">流程配置</span></a></li>
|
||||
<li class="child-item active" data-page-node-id="9ERi4TshqkBopLMjhOAA9W"><a href="javascript:;" data-page-node-id="AOLoECfunwtLmP3FOE2FjK"><i class="child-dot" data-page-node-id="u0KnAqW981QLBkOJosnHB8"></i><span class="menu-text" data-page-node-id="reRq6ZLvNdhZdjaQ5WjYyW">审批节点</span></a></li>
|
||||
<li class="child-item" data-page-node-id="4Ecn91IMUtiCrmka8QsGHe"><a href="javascript:;" data-page-node-id="RUD5TjLsDxuShMn19XQG50"><i class="child-dot" data-page-node-id="1g5xWpTHXOFdg85cllnN21"></i><span class="menu-text" data-page-node-id="VKd9Ch47JAHmLsJSbIVRSv">代理人设置</span></a></li>
|
||||
</div>
|
||||
|
||||
<li class="menu-item active" data-page-node-id="KtQC3tV7nO2Kx2Jsqr2wQQ"><a href="javascript:;" data-page-node-id="GpE9yU3keYBWFwBOQrxKca">
|
||||
<i class="ph" data-page-node-id="B4QP1pM6mUY8seklb9vkI8"><svg viewBox="0 0 24 24" data-page-node-id="9LDP9MlN4xsKtlJ3X5SSmH"><circle cx="12" cy="12" r="9" data-page-node-id="lcBfpbb9V1Nkg1AGUsoGa8"/><path d="M12 7v5l3 2" data-page-node-id="0iGRTaaf9ot1pCATgHoMjS"/></svg></i>
|
||||
<span class="menu-text" data-page-node-id="QkBL0DhBIlTzULvISU2PES">请假设置</span></a></li>
|
||||
|
||||
<li class="menu-item" data-page-node-id="cEQEBjCOQFrl7EuBTkYTKF"><a href="javascript:;" data-page-node-id="41CEH2TvHevASbeXtC6r5J">
|
||||
<i class="ph" data-page-node-id="w5o5sGUrMTchDYDE0KfFDC"><svg viewBox="0 0 24 24" data-page-node-id="4MZS8wqRTnOmEUjfPl2Oao"><rect x="3" y="4" width="18" height="17" rx="2" data-page-node-id="3rFDH9IV09tfkquR63MERi"/><path d="M8 2v4M16 2v4M3 9h18" data-page-node-id="XcRzuCbuHl5P6kZ5wwIg8o"/></svg></i>
|
||||
<span class="menu-text" data-page-node-id="kqQev70pCcovhIvosXodiI">时长日志</span></a></li>
|
||||
|
||||
<li class="menu-item" data-page-node-id="EfJ26ZtRvVERXkvYY8rfdZ"><a href="javascript:;" data-page-node-id="TZvSBwSVjLmMqJOqSA0jfD">
|
||||
<i class="ph" data-page-node-id="5hKf0Rhfvl4MhFxkcipyYo"><svg viewBox="0 0 24 24" data-page-node-id="pkrRH2F6yvoS5j7rGSfhl9"><path d="M4 20V10M10 20V4M16 20v-7M22 20H2" data-page-node-id="c8lYmIueRvwMcNjKYKEt7T"/></svg></i>
|
||||
<span class="menu-text" data-page-node-id="BxJzeNPXAmGEiymbZ58zi8">统计分析</span></a></li>
|
||||
</ul>
|
||||
<div class="clear-box" data-page-node-id="jtANDCzzW5qwaFn5cWfHC0"></div>
|
||||
</div>
|
||||
|
||||
<div class="stage" data-page-node-id="uhFczKbBxjardMiksRvArY">
|
||||
<h2 data-page-node-id="CjgddVGOLTFAwLRgqSi0rk">主内容区(216px 偏移)</h2>
|
||||
<p data-page-node-id="CY3hnDE5BHZ4lBhC7UDL02">
|
||||
<span class="tag" data-page-node-id="iTjNhLrL0yQJ4eZWinfFFE">216px 侧栏</span><span class="tag" data-page-node-id="KCobNeHbQht7wOmHGEJKzh">44px 行高</span><span class="tag" data-page-node-id="Z0VUB03L72jPcYx3H6Cnm9">8px 圆角</span>
|
||||
<span class="tag" data-page-node-id="KtZgxywfpuBBocYHFkRQFI">12px 侧边留白</span><span class="tag" data-page-node-id="GCCOb5yw34rKBF5U5w1H44">胶囊选中态</span><span class="tag" data-page-node-id="hM2VNDxm7XXYesXxQtiXAK">左侧指示条</span>
|
||||
<span class="tag" data-page-node-id="NeMaYeSBIJMcHWp8TcIYqV">图标随状态变色</span><span class="tag" data-page-node-id="NphJ4aZFHCEajbIQGA0Zyw">子级圆点层级</span>
|
||||
</p>
|
||||
<p style="margin-top:14px;color:#98a2b3" data-page-node-id="hCu6JMbkjWLPQAI8lt7fcB">
|
||||
鼠标悬停任一菜单项可查看 hover 态(浅灰底 #f5f7fb + 图标转品牌蓝)。
|
||||
</p>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,180 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>图片上传样式预览</title>
|
||||
<style>
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
body {
|
||||
font-family: -apple-system, "PingFang SC", "Microsoft YaHei", sans-serif;
|
||||
background: #f5f7fa;
|
||||
padding: 32px 24px 56px;
|
||||
color: #1c2333;
|
||||
}
|
||||
.wrap { max-width: 1080px; margin: 0 auto; }
|
||||
h2 { font-size: 16px; font-weight: 600; margin-bottom: 6px; }
|
||||
.desc { font-size: 13px; color: #8b93a7; margin-bottom: 20px; }
|
||||
.card {
|
||||
background: #fff; border-radius: 10px; padding: 24px 28px;
|
||||
margin-bottom: 28px; box-shadow: 0 1px 3px rgba(28,35,51,.06);
|
||||
}
|
||||
.label { font-size: 14px; color: #5b6478; margin-bottom: 12px; }
|
||||
|
||||
/* ===== 与 AttachmentUpload.vue imageOnly 模式样式保持一致 ===== */
|
||||
$primary: #577afb;
|
||||
.img-drag-upload { display: block; width: 100%; }
|
||||
.img-drag-upload .ivu-upload-drag {
|
||||
display: block; width: 100%; padding: 26px 28px;
|
||||
background: #fbfcfe; border: 1px dashed #e6ebf4; border-radius: 8px;
|
||||
transition: border-color .2s ease, background .2s ease; cursor: pointer;
|
||||
}
|
||||
.img-drag-upload .ivu-upload-drag:hover { background: #f8faff; border: 1px dashed rgba(87,122,251,.5); }
|
||||
.img-drag-upload .ivu-upload-dragOver { background: #eef3ff; border: 1px dashed #577afb; }
|
||||
.drag-inner { display: flex; align-items: center; gap: 16px; user-select: none; }
|
||||
.drag-icon {
|
||||
flex-shrink: 0; display: flex; align-items: center; justify-content: center;
|
||||
width: 56px; height: 56px; border-radius: 8px; background: #eef3ff; color: #577afb;
|
||||
}
|
||||
.drag-icon svg { width: 26px; height: 26px; }
|
||||
.drag-text { min-width: 0; }
|
||||
.drag-title { font-size: 15px; line-height: 22px; color: #5b6478; }
|
||||
.drag-link { color: #577afb; }
|
||||
.drag-sub { margin-top: 4px; font-size: 13px; line-height: 20px; color: #99a2b5; }
|
||||
|
||||
/* ===== 缩略图网格(上传成功后) ===== */
|
||||
.img-preview-list { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 12px; }
|
||||
.img-preview-item {
|
||||
width: 108px; border: 1px solid #e6ebf4; border-radius: 6px;
|
||||
overflow: hidden; background: #fff; transition: box-shadow .2s;
|
||||
}
|
||||
.img-preview-item:hover { box-shadow: 0 2px 10px rgba(87,122,251,.18); border-color: #577afb; }
|
||||
.img-thumb-box { position: relative; width: 100%; height: 80px; cursor: pointer; background: #f6f8fc; }
|
||||
.img-thumb { width: 100%; height: 80px; object-fit: cover; display: block; }
|
||||
.img-mask {
|
||||
position: absolute; inset: 0; display: flex; flex-direction: column;
|
||||
align-items: center; justify-content: center; gap: 2px; color: #fff;
|
||||
font-size: 12px; background: rgba(28,35,51,.45); opacity: 0; transition: opacity .2s;
|
||||
}
|
||||
.img-preview-item:hover .img-mask { opacity: 1; }
|
||||
.img-meta { display: flex; align-items: center; padding: 4px 6px; border-top: 1px solid #f0f3f9; }
|
||||
.img-name {
|
||||
flex: 1; font-size: 12px; color: #5b6478; overflow: hidden;
|
||||
text-overflow: ellipsis; white-space: nowrap;
|
||||
}
|
||||
.img-del { color: #c5c8ce; cursor: pointer; font-size: 18px; line-height: 1; }
|
||||
.img-del:hover { color: #e5484d; }
|
||||
|
||||
.tag {
|
||||
display: inline-block; font-size: 12px; padding: 2px 8px; border-radius: 4px;
|
||||
margin-bottom: 12px; background: #eef3ff; color: #577afb;
|
||||
}
|
||||
.thumb-holder { width: 108px; height: 80px; background: linear-gradient(135deg,#c7d7ff,#9fb6f7); }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="wrap">
|
||||
<h2>图片上传样式预览</h2>
|
||||
<p class="desc">对应组件:src/components/AttachmentUpload.vue(imageOnly 模式)— 应用页 src/views/leave/ApplyLeave.vue</p>
|
||||
|
||||
<!-- 状态1:默认 -->
|
||||
<div class="card">
|
||||
<span class="tag">默认态</span>
|
||||
<div class="label">图片</div>
|
||||
<div class="img-drag-upload">
|
||||
<div class="ivu-upload-drag">
|
||||
<div class="drag-inner">
|
||||
<div class="drag-icon">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M20 17.58A5 5 0 0 0 18 8h-1.26A8 8 0 1 0 4 16.25"/><path d="M16 16l-4-4-4 4"/><path d="M12 12v9"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="drag-text">
|
||||
<div class="drag-title">点击或拖拽图片到此处<span class="drag-link">上传</span></div>
|
||||
<div class="drag-sub">支持 .jpg / .bmp / .png 格式,单张不超过 20MB,可多选</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 状态2:hover -->
|
||||
<div class="card">
|
||||
<span class="tag">悬停态</span>
|
||||
<div class="label">图片</div>
|
||||
<div class="img-drag-upload">
|
||||
<div class="ivu-upload-drag" style="background:#f8faff; border:1px dashed rgba(87,122,251,.5);">
|
||||
<div class="drag-inner">
|
||||
<div class="drag-icon">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M20 17.58A5 5 0 0 0 18 8h-1.26A8 8 0 1 0 4 16.25"/><path d="M16 16l-4-4-4 4"/><path d="M12 12v9"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="drag-text">
|
||||
<div class="drag-title">点击或拖拽图片到此处<span class="drag-link">上传</span></div>
|
||||
<div class="drag-sub">支持 .jpg / .bmp / .png 格式,单张不超过 20MB,可多选</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 状态3:拖拽经过 -->
|
||||
<div class="card">
|
||||
<span class="tag">拖拽经过态(dragOver)</span>
|
||||
<div class="label">图片</div>
|
||||
<div class="img-drag-upload">
|
||||
<div class="ivu-upload-drag" style="background:#eef3ff; border:1px dashed #577afb;">
|
||||
<div class="drag-inner">
|
||||
<div class="drag-icon">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M20 17.58A5 5 0 0 0 18 8h-1.26A8 8 0 1 0 4 16.25"/><path d="M16 16l-4-4-4 4"/><path d="M12 12v9"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="drag-text">
|
||||
<div class="drag-title">点击或拖拽图片到此处<span class="drag-link">上传</span></div>
|
||||
<div class="drag-sub">松开鼠标即可上传</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 状态4:已上传 -->
|
||||
<div class="card">
|
||||
<span class="tag">已上传态(缩略图 + 点击放大)</span>
|
||||
<div class="label">图片</div>
|
||||
<div class="img-drag-upload">
|
||||
<div class="ivu-upload-drag">
|
||||
<div class="drag-inner">
|
||||
<div class="drag-icon">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M20 17.58A5 5 0 0 0 18 8h-1.26A8 8 0 1 0 4 16.25"/><path d="M16 16l-4-4-4 4"/><path d="M12 12v9"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="drag-text">
|
||||
<div class="drag-title">点击或拖拽图片到此处<span class="drag-link">上传</span></div>
|
||||
<div class="drag-sub">支持 .jpg / .bmp / .png 格式,单张不超过 20MB,可多选</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="img-preview-list">
|
||||
<div class="img-preview-item">
|
||||
<div class="img-thumb-box">
|
||||
<div class="img-thumb thumb-holder"></div>
|
||||
<div class="img-mask"><span>查看大图</span></div>
|
||||
</div>
|
||||
<div class="img-meta"><span class="img-name">病历单.jpg</span><span class="img-del">✕</span></div>
|
||||
</div>
|
||||
<div class="img-preview-item">
|
||||
<div class="img-thumb-box">
|
||||
<div class="img-thumb" style="background:linear-gradient(135deg,#d5e8d0,#9ec9a1);"></div>
|
||||
<div class="img-mask"><span>查看大图</span></div>
|
||||
</div>
|
||||
<div class="img-meta"><span class="img-name">证明材料_01.png</span><span class="img-del">✕</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,349 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN" data-page-node-id="GiF1pjpTSsFZcToUsJOSy3">
|
||||
<head data-page-node-id="nOsb0mCEhfByiN5FJCR7C8">
|
||||
<meta charset="UTF-8" data-page-node-id="gNADgqtE6orZYZd4BDzNbc" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" data-page-node-id="KVNGXCPSRCxEOcgHtQ1mjy" />
|
||||
<title>教师时长 · 页面设计预览</title>
|
||||
<style>
|
||||
* { box-sizing: border-box; }
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 24px;
|
||||
background: #f2f4f8;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", "Microsoft YaHei", sans-serif;
|
||||
color: #17233d;
|
||||
font-size: 14px;
|
||||
}
|
||||
.wrap { max-width: 1440px; margin: 0 auto; }
|
||||
.crumb { color: #808695; font-size: 13px; margin-bottom: 10px; }
|
||||
.crumb b { color: #17233d; }
|
||||
|
||||
.card { background: #fff; border-radius: 6px; box-shadow: 0 1px 4px rgba(0,21,41,.08); }
|
||||
|
||||
/* tabs */
|
||||
.tabs { display: flex; gap: 34px; padding: 0 20px; border-bottom: 1px solid #eef0f5; background: #fff;
|
||||
border-radius: 6px 6px 0 0; }
|
||||
.tab { padding: 12px 2px; color: #515a6e; position: relative; font-size: 14px; }
|
||||
.tab.on { color: #577afb; font-weight: 600; }
|
||||
.tab.on::after { content: ""; position: absolute; left: 0; right: 0; bottom: -1px; height: 2px;
|
||||
background: #577afb; border-radius: 2px; }
|
||||
|
||||
.body { padding: 16px 20px 20px; }
|
||||
|
||||
/* managed */
|
||||
.managed {
|
||||
padding: 14px 16px;
|
||||
background: linear-gradient(135deg,#f7f9ff 0%,#fbfcff 100%);
|
||||
border: 1px solid #e4ebff; border-radius: 8px;
|
||||
}
|
||||
.managed-head { display: flex; align-items: center; justify-content: space-between; gap: 16px; margin-bottom: 12px; }
|
||||
.mhl { display: flex; align-items: flex-start; gap: 10px; }
|
||||
.mh-icon { color: #577afb; font-size: 19px; line-height: 22px; }
|
||||
.managed-title { font-size: 14px; font-weight: 600; display: flex; align-items: center; gap: 8px; }
|
||||
.tag { display: inline-block; padding: 1px 7px; border-radius: 3px; font-size: 11px; font-weight: 400; line-height: 17px; }
|
||||
.tag.success { background: #f0f9eb; color: #19be6b; border: 1px solid #e1f3d8; }
|
||||
.managed-tip { margin-top: 3px; color: #808695; font-size: 12px; line-height: 18px; }
|
||||
.btn {
|
||||
display: inline-flex; align-items: center; gap: 5px; height: 32px; padding: 0 15px;
|
||||
border-radius: 4px; border: 1px solid transparent; font-size: 14px; cursor: pointer; background: #fff;
|
||||
}
|
||||
.btn.ghost { color: #577afb; border-color: #b4c4fb; background: #f7f9ff; }
|
||||
.btn.primary { background: #577afb; color: #fff; }
|
||||
.btn.plain { border-color: #dcdee2; color: #515a6e; }
|
||||
.btn.sm { height: 28px; padding: 0 12px; font-size: 13px; }
|
||||
.btn:disabled { opacity: .5; cursor: not-allowed; }
|
||||
|
||||
.managed-types { padding: 10px 12px; background: #fff; border: 1px solid #eef1f7; border-radius: 6px; }
|
||||
.cbx { display: inline-flex; align-items: center; gap: 6px; padding: 4px 8px; border-radius: 4px; font-size: 13px;
|
||||
color: #515a6e; margin-right: 8px; }
|
||||
.cbx:hover { background: #f0f4ff; }
|
||||
.box { width: 14px; height: 14px; border: 1px solid #c5c8ce; border-radius: 3px; background: #fff;
|
||||
display: inline-flex; align-items: center; justify-content: center; color: #fff; font-size: 10px; }
|
||||
.cbx.on .box { background: #577afb; border-color: #577afb; }
|
||||
.cbx.on { color: #577afb; font-weight: 600; }
|
||||
|
||||
/* stats */
|
||||
.stat-row { display: grid; grid-template-columns: repeat(4,1fr); gap: 12px; margin: 14px 0; }
|
||||
.stat-card { display: flex; align-items: center; gap: 12px; padding: 14px 16px;
|
||||
background: #fff; border: 1px solid #eef1f5; border-radius: 8px; transition: .2s; }
|
||||
.stat-card:hover { border-color: #c9d8ef; box-shadow: 0 4px 14px rgba(87,122,251,.12); transform: translateY(-1px); }
|
||||
.stat-card.warn { background: #fffbfb; border-color: #ffe0e0; }
|
||||
.sc-icon { flex: none; width: 40px; height: 40px; border-radius: 10px; display: flex;
|
||||
align-items: center; justify-content: center; font-size: 20px; }
|
||||
.ic-blue { background:#eef2ff; color:#577afb } .ic-green { background:#f0f9eb; color:#19be6b }
|
||||
.ic-purple { background:#f5f0ff; color:#7b61ff } .ic-red { background:#fff0f0; color:#ed4014 }
|
||||
.sc-num { font-size: 21px; font-weight: 700; line-height: 26px; letter-spacing: -.4px; }
|
||||
.sc-num i { margin-left: 3px; font-size: 12px; font-weight: 400; font-style: normal; color: #a0a6b0; }
|
||||
.sc-label { font-size: 12px; color: #808695; }
|
||||
|
||||
/* layout */
|
||||
.cols { display: flex; gap: 14px; align-items: flex-start; }
|
||||
.tree-panel { flex: none; width: 216px; padding: 12px 10px; background: #fff;
|
||||
border: 1px solid #eef1f5; border-radius: 8px; }
|
||||
.side-title { display: flex; align-items: center; gap: 6px; margin-bottom: 8px; padding: 0 6px 8px;
|
||||
border-bottom: 1px solid #f2f4f8; font-size: 13px; font-weight: 600; }
|
||||
.side-title i { color:#577afb; font-style: normal; font-size: 15px; }
|
||||
.side-title em { margin-left: auto; font-size: 11px; font-weight: 400; font-style: normal; color: #c5c8ce; }
|
||||
.tree { max-height: 470px; overflow: auto; }
|
||||
.tnode { display: flex; align-items: center; height: 32px; padding: 0 6px 0 22px; border-radius: 4px;
|
||||
font-size: 13px; color: #3c4b5f; }
|
||||
.tnode.sub { padding-left: 38px; }
|
||||
.tnode.sel { background: #eef2ff; }
|
||||
.tnode.sel .nm { color: #577afb; font-weight: 600; }
|
||||
.tnode .nm { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.tnode .cnt { flex: none; margin-left: 6px; min-width: 20px; padding: 0 5px; border-radius: 9px;
|
||||
background: #f2f4f8; color: #a0a6b0; font-size: 11px; line-height: 16px; text-align: center; }
|
||||
.tnode.sel .cnt { background:#dfe7ff; color:#577afb; }
|
||||
.caret { color: #808695; font-size: 10px; width: 14px; }
|
||||
|
||||
/* table */
|
||||
.table-panel { flex: 1; min-width: 0; padding: 12px 14px 0; background: #fff;
|
||||
border: 1px solid #eef1f5; border-radius: 8px; }
|
||||
.toolbar { display: flex; align-items: center; justify-content: space-between; margin-bottom: 10px; }
|
||||
.tt-left { display: flex; align-items: center; gap: 12px; }
|
||||
.search { display: flex; align-items: center; gap: 6px; width: 230px; height: 32px; padding: 0 10px;
|
||||
border: 1px solid #dcdee2; border-radius: 4px; color: #c5c8ce; font-size: 13px; }
|
||||
.search input { flex: 1; border: none; outline: none; font-size: 13px; color: #17233d; background: transparent; }
|
||||
.tt-count { color: #808695; font-size: 12px; } .tt-count b { color:#577afb; font-size:13px; margin:0 2px; }
|
||||
.tt-right { display: flex; gap: 8px; }
|
||||
|
||||
table { width: 100%; border-collapse: collapse; font-size: 13px; }
|
||||
thead th { background: #f8f8f9; color: #17233d; font-weight: 600; text-align: left; padding: 10px 12px;
|
||||
border-bottom: 1px solid #e8eaec; white-space: nowrap; }
|
||||
tbody td { padding: 9px 12px; border-bottom: 1px solid #e8eaec; vertical-align: middle; }
|
||||
tbody tr:hover td { background: #f7f9fc; }
|
||||
tr.edited td { background: #fffaf0; } tr.edited:hover td { background: #fff5e1; }
|
||||
.c-idx { color:#808695; width:52px; text-align:center }
|
||||
.c-act { width:70px; text-align:center }
|
||||
.c-mid { text-align:center }
|
||||
|
||||
.u-cell { display: flex; align-items: center; gap: 8px; }
|
||||
.avatar { flex: none; width: 26px; height: 26px; border-radius: 50%; background: #eef2ff; color: #577afb;
|
||||
font-size: 12px; font-weight: 600; display: flex; align-items: center; justify-content: center; }
|
||||
.u-name { display: flex; flex-direction: column; line-height: 16px; min-width: 0; }
|
||||
.u-main { color: #17233d; font-size: 13px; }
|
||||
.u-rem { color: #a0a6b0; font-size: 11px; }
|
||||
|
||||
.h-cell { line-height: 17px; }
|
||||
.h-num { font-size: 15px; font-weight: 700; } .h-num i { margin-left:3px; font-size:11px; font-weight:400;
|
||||
font-style: normal; opacity:.75; }
|
||||
.h-day { font-size: 11px; opacity: .7; }
|
||||
.ok .h-num { color:#19a465 } .warn .h-num { color:#e6a23c } .danger .h-num { color:#ed4014 }
|
||||
|
||||
.edit-cell { display: flex; align-items: center; justify-content: center; gap: 6px; }
|
||||
.num { width: 108px; height: 30px; border: 1px solid #dfe6fb; border-radius: 4px; background: #f7f9ff;
|
||||
display: flex; align-items: center; justify-content: flex-end; padding: 0 8px; color:#c5c8ce; font-size:13px; }
|
||||
.num.filled { color: #577afb; font-weight: 600; justify-content: center; }
|
||||
.qbtn { width: 26px; height: 24px; border: 1px solid #dcdee2; border-radius: 4px; background: #fff;
|
||||
color: #515a6e; font-size: 12px; cursor: pointer; }
|
||||
.qbtn:hover { border-color:#577afb; color:#577afb; background:#f5f8ff }
|
||||
.qbtn.d:hover { border-color:#ed4014; color:#ed4014; background:#fff5f5 }
|
||||
.rem-input { width: 100%; min-width: 130px; height: 30px; border: 1px solid #dcdee2; border-radius: 4px;
|
||||
padding: 0 9px; font-size: 13px; color: #17233d; outline: none; }
|
||||
.rem-input::placeholder { color:#c5c8ce; }
|
||||
|
||||
.pf-cell { display: inline-flex; align-items: baseline; gap: 5px; }
|
||||
.pf-num { font-size: 14px; font-weight: 700; color: #17233d; }
|
||||
.pf-delta { padding: 1px 5px; border-radius: 3px; font-size: 11px; }
|
||||
.pf-delta.up { color:#19a465; background:#f0f9eb } .pf-delta.down { color:#ed4014; background:#fff0f0 }
|
||||
.pf-empty { color: #dcdee2; }
|
||||
.lk { color:#808695; font-size:12px; cursor:pointer } .lk:hover { color:#ed4014 }
|
||||
|
||||
.footer { position: sticky; bottom: 0; display: flex; align-items: center; justify-content: space-between;
|
||||
margin-top: 12px; padding: 12px 4px; border-top: 1px solid #f2f4f8; font-size: 13px; color:#515a6e; }
|
||||
.footer.active { border-top-color: #ffe0b0; }
|
||||
.foot-info { display: flex; align-items: center; gap: 10px; }
|
||||
.foot-info b { margin: 0 3px; font-size: 15px; }
|
||||
.fi-sep { width:1px; height:12px; background:#e8eaec; }
|
||||
.hl-blue{color:#577afb} .hl-orange{color:#e6a23c} .hl-green{color:#19a465} .hl-red{color:#ed4014}
|
||||
.foot-btns { display: flex; gap: 8px; }
|
||||
|
||||
.note { margin-top: 18px; padding: 14px 16px; background: #fff; border: 1px solid #eef1f5;
|
||||
border-radius: 8px; font-size: 13px; color: #515a6e; line-height: 22px; }
|
||||
.note h4 { margin: 0 0 8px; font-size: 14px; color: #17233d; }
|
||||
.note li { margin-bottom: 4px; }
|
||||
</style>
|
||||
</head>
|
||||
<body data-page-node-id="Yg8oh4VZpbLXhqPjtBLC0R">
|
||||
<div class="wrap" data-page-node-id="smRaZ4eOz02yCstEXVKMlM">
|
||||
<div class="crumb" data-page-node-id="CnDBvTn1W11CUoZ1xvSidl">请假管理 /<b data-page-node-id="HSjP0AD1rcFqOdHVPqmGYl"> 请假设置</b></div>
|
||||
|
||||
<div class="card" data-page-node-id="2gwjetzwzWI75VOduiULNa">
|
||||
<div class="tabs" data-page-node-id="q0DaNpn3b7Z26I7vjLMxIr">
|
||||
<div class="tab" data-page-node-id="dAZK4ykcWhUQRtp1UVBj83">作息时间</div>
|
||||
<div class="tab" data-page-node-id="1FCNiSHQ4BnD32U5xi5Wj4">请假类型</div>
|
||||
<div class="tab on" data-page-node-id="gLQw6QTHCBwJUAd6FS8qHu">教师时长</div>
|
||||
<div class="tab" data-page-node-id="CIYZqHRXQj8kBJqCoE4yDc">节次</div>
|
||||
<div class="tab" data-page-node-id="W7TYACclLIpEcwCGFLcBbD">其他设置</div>
|
||||
</div>
|
||||
|
||||
<div class="body" data-page-node-id="kSrP8PXRQtAE8GdhKtCFNo">
|
||||
<!-- ① 管控类型 -->
|
||||
<div class="managed" data-page-node-id="GdToG8fhRGmLhrcecVOSbD">
|
||||
<div class="managed-head" data-page-node-id="kTMy9FhiUGAFGFyOs0nsTP">
|
||||
<div class="mhl" data-page-node-id="kmFNoziZtumVBG7L7S6mRJ">
|
||||
<div class="mh-icon" data-page-node-id="FSOtYnPlGC1jj3RFQR7z2e">■</div>
|
||||
<div data-page-node-id="SSVHbzQIch3OUHqbZZDoUy">
|
||||
<div class="managed-title" data-page-node-id="eo1IZdUU3sYf56WJNHG5RR">
|
||||
需扣减时长的请假类型
|
||||
<span class="tag success" data-page-node-id="JN2mBcJWvdqV8qNrjzaYLE">已启用 3 项</span>
|
||||
</div>
|
||||
<div class="managed-tip" data-page-node-id="qMJDwwURpWaLbN0wEqa1mr">勾选后,该类型请假审批通过将自动扣减教师时长;取消勾选即停止扣减。变更即时保存。</div>
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn ghost" data-page-node-id="TzmAHdZe9hiVokzlevPbdm">⇩ 批量导入</button>
|
||||
</div>
|
||||
<div class="managed-types" data-page-node-id="otTvoH7txoxhG4BnaK7Dki">
|
||||
<span class="cbx on" data-page-node-id="2raoIDbKt4QirU0pZNetVc"><span class="box" data-page-node-id="fmdhFqBf4LUJnUqm6iH3mZ">✓</span>事假</span>
|
||||
<span class="cbx on" data-page-node-id="GhyiRhbPZAWU4tfRAUthyl"><span class="box" data-page-node-id="xNicqTyQ6wWWEzCd9DPx9p">✓</span>病假</span>
|
||||
<span class="cbx on" data-page-node-id="08EAErdsyuJ2dEPxklNdCS"><span class="box" data-page-node-id="ZtPmmPxRRqaIAzemlquHB2">✓</span>调休</span>
|
||||
<span class="cbx" data-page-node-id="xHnWEGsHdJuRFdcETNzBuC"><span class="box" data-page-node-id="UhN0egzCQGlhV6jWZtTMjp"></span>婚假</span>
|
||||
<span class="cbx" data-page-node-id="6OIHjmkOOyCvjklqgGHWAL"><span class="box" data-page-node-id="YT4FsGGmD7eM5cveIzyZNp"></span>产假</span>
|
||||
<span class="cbx" data-page-node-id="piudnGIUpAwSpn4kXAMoEQ"><span class="box" data-page-node-id="2IpxCxG7ppqrBs83CcE1Uh"></span>丧假</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ② 统计 -->
|
||||
<div class="stat-row" data-page-node-id="XnwOMIbSJgXDBGRV89JYIz">
|
||||
<div class="stat-card" data-page-node-id="vixBBo6pYXDlAiFEWdnJYw">
|
||||
<div class="sc-icon ic-blue" data-page-node-id="YyfAmgGtRXfPGdcMcyUkqq">☆</div>
|
||||
<div data-page-node-id="rPBdyyX7uWSXHsIiJgB7qs"><div class="sc-num" data-page-node-id="rSwvKT4l0XyDgg9Qd4IJSU">48<i data-page-node-id="AM5AdwhqzaIzezMDuSRi8H">人</i></div><div class="sc-label" data-page-node-id="8R1jm13gN3NDartp6AOjw7">当前范围教师</div></div>
|
||||
</div>
|
||||
<div class="stat-card" data-page-node-id="zMkhLVlrONaWkqfCswlJbR">
|
||||
<div class="sc-icon ic-green" data-page-node-id="zGTkgLPS0WGabr9ui8CTRv">⏱</div>
|
||||
<div data-page-node-id="Y4ZZvqH2n0g2cTRDgDmrxT"><div class="sc-num" data-page-node-id="JHMkZq2XqblNOV3evbzpKG">612.5<i data-page-node-id="Hz5nJ0aMb0VPiJ8bquOvzq">小时</i></div><div class="sc-label" data-page-node-id="ESdpYrUNT4S05Cs0HRXLLx">剩余时长合计</div></div>
|
||||
</div>
|
||||
<div class="stat-card" data-page-node-id="Ro8a8GmcUVwZhtWr8zqjis">
|
||||
<div class="sc-icon ic-purple" data-page-node-id="5JCiVemLVfCm1YKszehFCm">◫</div>
|
||||
<div data-page-node-id="Eq263XTX07CWRcK3CjbDyH"><div class="sc-num" data-page-node-id="8BPF17J3CMuC2Ur7RjfpWd">12.76<i data-page-node-id="InITh8jpiZcSKHWTfDNXZ6">小时</i></div><div class="sc-label" data-page-node-id="xJBcC2bqvec0CS6dVhJDur">人均可用时长</div></div>
|
||||
</div>
|
||||
<div class="stat-card warn" data-page-node-id="waq2xPo4cCqZlJ6D5GPWY0">
|
||||
<div class="sc-icon ic-red" data-page-node-id="fe8vdeyGIHlyFvTAGrnEsr">⚠</div>
|
||||
<div data-page-node-id="ajZmbWRBJZ2nA9mVZN1qDa"><div class="sc-num" data-page-node-id="1EVAH1R3Em6orc0VuwpOGC">3<i data-page-node-id="aL0cHGFZtbEpKfiOmgOkPX">人</i></div><div class="sc-label" data-page-node-id="6anrkZFeKH5ouJQFfUQ2Wr">时长已用尽</div></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ③ 双栏 -->
|
||||
<div class="cols" data-page-node-id="IvNnGaSrboYS4EzhPUF5Fj">
|
||||
<div class="tree-panel" data-page-node-id="pxta51iElsYMpdpXi7PyS2">
|
||||
<div class="side-title" data-page-node-id="RFu5O3vFrFHXXopyF3oE8i"><i data-page-node-id="MdLYZeIWsJnO12A4iku6J2">◫</i><span data-page-node-id="BQjpGd8byJKPFHLC6N82Bc">教师分组</span><em data-page-node-id="m6M8LvkBoaU4QYw0Vsp0FE">点击筛选</em></div>
|
||||
<div class="tree" data-page-node-id="Xy0m69vz2FG0HcjUZB366W">
|
||||
<div class="tnode sel" data-page-node-id="BF9LwKxAYfWOLxzSk1PVMs"><span class="caret" data-page-node-id="Yo0wAiWBPvBIfkpddycDrK">▼</span><span class="nm" data-page-node-id="0AEkXURFJQFlJGgTne4Nw2">全部教师</span><span class="cnt" data-page-node-id="xz3IIVnVa8IxVZLqLl3ePp">48</span></div>
|
||||
<div class="tnode sub" data-page-node-id="9ht2SH9CUnigy4D303LP5I"><span class="caret" data-page-node-id="Gr0sfv46hUZSuAcEulcFhO">▶</span><span class="nm" data-page-node-id="rVhfXSGFO8OJOYipR4AqHu">语文组</span><span class="cnt" data-page-node-id="uYNYzxjHkHW6rypdKxl4Iz">12</span></div>
|
||||
<div class="tnode sub" data-page-node-id="2RcW28uJKsRegMBhhv6s2h"><span class="caret" data-page-node-id="RfOWSdKgQhVzMRwvF3aCew">▶</span><span class="nm" data-page-node-id="4Er9Gka2cLzBZf1HZJ1UE9">数学组</span><span class="cnt" data-page-node-id="cAFG5ehEbcdzBmD7R0FBEE">10</span></div>
|
||||
<div class="tnode sub" data-page-node-id="NLdjFPvGcqTDufVrXXbnNC"><span class="caret" data-page-node-id="JSxvn56HxB8ebsWuYabgDV">▶</span><span class="nm" data-page-node-id="GCPDHD3U9mC9mYgoAfqNHV">英语组</span><span class="cnt" data-page-node-id="XsDnGcyWbgoqdAWAOcC9ho">9</span></div>
|
||||
<div class="tnode sub" data-page-node-id="E14QvMFEfcM7N1OfMaJS84"><span class="caret" data-page-node-id="637VKqgnnYJ0hVY6EvuTFE">▶</span><span class="nm" data-page-node-id="dXB32dRcQW8XqccdenFImo">综合组</span><span class="cnt" data-page-node-id="tsw9dDb43zGNFFA6A9u8nj">17</span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="table-panel" data-page-node-id="YdIfYn3jc4DeSIiBDhmZ5N">
|
||||
<div class="toolbar" data-page-node-id="KtbNrcNwSdwJ7ppqsP7gw9">
|
||||
<div class="tt-left" data-page-node-id="YEaanxALXsfHIZFx3tQ72g">
|
||||
<div class="search" data-page-node-id="jvPnVtWdMpEmCDtqxyFBPF"><span data-page-node-id="O6D1yTafHWwzhoQTFfwprL">⚲</span><input placeholder="搜索教师姓名 / 备注" data-page-node-id="xJwEk7zRoaIem4TUhypVq7" /></div>
|
||||
<span class="tt-count" data-page-node-id="iLCii0Qp2nbEOTm3kLuDCH">共 <b data-page-node-id="SXjN4j1QQwcXq8zFfMmA2B">48</b> 位教师</span>
|
||||
</div>
|
||||
<div class="tt-right" data-page-node-id="VDQ9xzeyye0J4F7GRkKM8g">
|
||||
<button class="btn plain sm" data-page-node-id="h88RDcKeQ9xfQjG4BedCCB">↻ 刷新</button>
|
||||
<button class="btn plain sm" data-page-node-id="dtBsngp8IgMktay3x86YKS">↶ 清空改动</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<table data-page-node-id="gBZy1PsAfjjOsaHklGS5tE">
|
||||
<thead data-page-node-id="V0L4JqaDgFBMRaCzuwEX79">
|
||||
<tr data-page-node-id="2GnK0D7NAQgHL2dlZPmsF4">
|
||||
<th class="c-idx" data-page-node-id="DMrVIj68vWgacpVdkcVOUe">序号</th>
|
||||
<th style="width:150px" data-page-node-id="A6RoMDtzT9YGgBpczbVCBx">教师</th>
|
||||
<th style="width:130px" data-page-node-id="NGcTLSLbGC8aAPTfLyGw4s">剩余时长</th>
|
||||
<th style="width:200px" class="c-mid" data-page-node-id="smUVAd6RjPCGLF2konjIYu">本次新加(小时)</th>
|
||||
<th style="width:120px" class="c-mid" data-page-node-id="0uk3VBwed4YHphKgiHFHwP">调整后</th>
|
||||
<th data-page-node-id="UKVXCq64ipyptCmRIYcZ1I">情况备注</th>
|
||||
<th class="c-act" data-page-node-id="FkRSLNsGdHMIIoHpwXFmC4">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody data-page-node-id="weMZ2dPaaJ6iVZULJub8cQ">
|
||||
<tr class="edited" data-page-node-id="n4zUHTBKF8V6KKCgEYTEeu">
|
||||
<td class="c-idx" data-page-node-id="IOYGxau8x3j2I2pueqStNX">1</td>
|
||||
<td data-page-node-id="yjW3NTQzowbwcxRwVTQhaU"><div class="u-cell" data-page-node-id="MXFp6i2Vy88UkN8OtD7TKU"><span class="avatar" data-page-node-id="HafGklgRUXzjASBTRbEUKO">王</span><span class="u-name" data-page-node-id="7ly9nuQqN9O3DAATZRX6GP"><span class="u-main" data-page-node-id="2TTCywrdRNJGoNFw9RSI8Z">王雅静</span><span class="u-rem" data-page-node-id="Tvy9Bt2NRg1ayNmMexd9lG">语文组 · 教研组长</span></span></div></td>
|
||||
<td data-page-node-id="agY1GW42c4N4raG4X9XYla"><div class="h-cell lv-ok" data-page-node-id="9E6XJXCCgm2KzQh131aap1"><span class="h-num" data-page-node-id="3tsBwWeXQh8RoyGgGEjR0T">24<i data-page-node-id="svdWIbd34Hg9zGieMzlGo5">小时</i></span><div class="h-day" data-page-node-id="XaoSSy39I5l92FsOSYp63h">≈ 3 天</div></div></td>
|
||||
<td class="c-mid" data-page-node-id="dDEYRbcHIZW36tv4FA6bde"><div class="edit-cell" data-page-node-id="WaTqQmt9F1o51lf9v6djr1"><div class="num filled" data-page-node-id="6af1HJFan01l0zHEsHbFFI">4</div>
|
||||
<button class="qbtn" data-page-node-id="9ewCYHMnHxwrXOXdoiEFeD">+1</button><button class="qbtn" data-page-node-id="lNgiYxODTzFdV5EMW3pDZA">+4</button><button class="qbtn d" data-page-node-id="0wr8x8SCF393GBUPeQPhw5">-1</button></div></td>
|
||||
<td class="c-mid" data-page-node-id="8HTmTRLufxFZoe7BRHC7HQ"><div class="pf-cell" data-page-node-id="XYCeldaBD0j7L7cDMfA1Uk"><span class="pf-num" data-page-node-id="sA1bC0HcGYLH6RDh7EZd38">28</span><span class="pf-delta up" data-page-node-id="eeEKS9fvOtDLCJRHlVv7jd">+4</span></div></td>
|
||||
<td data-page-node-id="CiOJv3L94HpMfpevyWm9fC"><input class="rem-input" value="周末补课补时" data-page-node-id="4zpswltDOIzhw9J2oGzkJp" /></td>
|
||||
<td class="c-act" data-page-node-id="W76HNv8oGn1DxCiIiT6GNx"><span class="lk" data-page-node-id="hOF4sRM9ioGcZmRN7oFv13">撤销</span></td>
|
||||
</tr>
|
||||
<tr data-page-node-id="lcQaRTXtIIOZAglJwr39De">
|
||||
<td class="c-idx" data-page-node-id="zeWCHKMSJFqopt6MqZYRhl">2</td>
|
||||
<td data-page-node-id="240iFFZK23rRBuFieN9pd6"><div class="u-cell" data-page-node-id="YTH0tBLrGBao2xe7B4rwRm"><span class="avatar" data-page-node-id="TSPBFT4eQpf9m9wzQJXVrq">李</span><span class="u-name" data-page-node-id="EmRoDctTCR4teOeNy6vBGO"><span class="u-main" data-page-node-id="n8lAjx6tQnD3njLs69p6xz">李国栋</span><span class="u-rem" data-page-node-id="7L3vYWSlCG5Z5LsDflcBfL">数学组</span></span></div></td>
|
||||
<td data-page-node-id="smjJuTvFDDKCtzI44Vak21"><div class="h-cell lv-ok" data-page-node-id="0GzBhR2Z8o9Ww1hoxdPJWp"><span class="h-num" data-page-node-id="3sWlYSi5FxuSOVBPtq6BGt">16<i data-page-node-id="cXBoPcjVA49eEcDCSrseg8">小时</i></span><div class="h-day" data-page-node-id="7uhHwhjTrGi62kT6HAOvtJ">≈ 2 天</div></div></td>
|
||||
<td class="c-mid" data-page-node-id="yEfKPm4fDkvNs82QBNX5v5"><div class="edit-cell" data-page-node-id="kvtIGGfLaha6I4viKvDGmh"><div class="num" data-page-node-id="EtGniqOwFl5Fcmd1N8GhxO">可填负数</div>
|
||||
<button class="qbtn" data-page-node-id="EAfFeHFCyYAe7x5cj8CBps">+1</button><button class="qbtn" data-page-node-id="U0EJwBzXsv007Vz5pXVDwW">+4</button><button class="qbtn d" data-page-node-id="C7Mb0d3QC47mPpcQ53aMIg">-1</button></div></td>
|
||||
<td class="c-mid" data-page-node-id="aVwepCNnSIM4Fu2Qm4RPFt"><span class="pf-empty" data-page-node-id="AQi93C376ZPDCKUi6Z7X58">—</span></td>
|
||||
<td data-page-node-id="eovnFpWevUcNJTVFbZKOBn"><input class="rem-input" placeholder="如:补课补时" data-page-node-id="T2KYZCfs61ow7KCyGuF8sd" /></td>
|
||||
<td class="c-act" data-page-node-id="YH0pXgqc5y0gpxXcj7RT9g"><span class="pf-empty" data-page-node-id="HuZWNFpNHuAZGNGLqO1pPZ">—</span></td>
|
||||
</tr>
|
||||
<tr data-page-node-id="8PrKIvk80rV2US4WjyyA3w">
|
||||
<td class="c-idx" data-page-node-id="sOFf9zYy594uUZzrnJpdn6">3</td>
|
||||
<td data-page-node-id="NssfV9fjCGuJf7QLEpMEIc"><div class="u-cell" data-page-node-id="73fvXHiwv6i2CeJKsS0skO"><span class="avatar" data-page-node-id="vrHN4L37LafVY3jxyN3b1f">张</span><span class="u-name" data-page-node-id="9doL7oGNSr9LcHJs7QQvVO"><span class="u-main" data-page-node-id="SRDgRi5YkDCGSl1mOfA27h">张明远</span><span class="u-rem" data-page-node-id="7vld9Fc3z4u6jHZWoQLNZK">英语组</span></span></div></td>
|
||||
<td data-page-node-id="jVJdSx17tXmeCGUVk3AOFX"><div class="h-cell lv-warn" data-page-node-id="fAjevOuMLWJSHwBJdxEAnY"><span class="h-num" data-page-node-id="XR33xGPOlTREGZfBKL66XR">6<i data-page-node-id="Cn690wifz000tKfHoohJ7X">小时</i></span><div class="h-day" data-page-node-id="4qXNRN7z1In4LLqSmvBoLb">≈ 0.75 天</div></div></td>
|
||||
<td class="c-mid" data-page-node-id="DE1HAH4Dv7Y27ZFMkS0fiY"><div class="edit-cell" data-page-node-id="QsQjUCaGaIAdfB9cpEKlSN"><div class="num filled" data-page-node-id="i8LjkqfljhcQmrZUjTeBvo">-6</div>
|
||||
<button class="qbtn" data-page-node-id="oy6BYrQwbqACzeXjkwG71z">+1</button><button class="qbtn" data-page-node-id="SHNsIIbaW09Fk7K8wDEH8h">+4</button><button class="qbtn d" data-page-node-id="pKDmO0f4QfUUThho7Vdv1F">-1</button></div></td>
|
||||
<td class="c-mid" data-page-node-id="0AF1AtzMPcAYcwl7Fgmi9j"><div class="pf-cell" data-page-node-id="SPnL9VsE0FKnItIGunUVgK"><span class="pf-num" data-page-node-id="OaMnfKdifqidVCgLySvGSl">0</span><span class="pf-delta down" data-page-node-id="ZjI016mxLv8Wlzp6WQsArt">-6</span></div></td>
|
||||
<td data-page-node-id="gSPXFIBGqlNP3PCyiFJmLS"><input class="rem-input" placeholder="如:事假扣减" data-page-node-id="dpEO43c6SDI8fgOhlKLB9q" /></td>
|
||||
<td class="c-act" data-page-node-id="rH4035FGPQqYP5ibGjCHhp"><span class="lk" data-page-node-id="F2RiqFdT56NHm3omYJcHa7">撤销</span></td>
|
||||
</tr>
|
||||
<tr data-page-node-id="lPS6KNy5IAeuqto5v5dcFA">
|
||||
<td class="c-idx" data-page-node-id="2xrk0GsDTKKZPWGdyZIzMn">4</td>
|
||||
<td data-page-node-id="9tekiXvEGIYxAxOAdD2I84"><div class="u-cell" data-page-node-id="HqZkK48QG50ZHe0VRsrtNp"><span class="avatar" data-page-node-id="8eMeC3i7kz53mI9DPhPXeu">陈</span><span class="u-name" data-page-node-id="hgdxwDGaAguIrCoEdeaQNd"><span class="u-main" data-page-node-id="oNlPt4b1PRoTfVe8aJaKBD">陈思彤</span><span class="u-rem" data-page-node-id="P0NPwpkHD4xDpqTJ6g4hgd">综合组</span></span></div></td>
|
||||
<td data-page-node-id="FWfuMCbjzZDUirWPsPhwF9"><div class="h-cell lv-danger" data-page-node-id="v1UZMeYTnvOEBaApaTbvUV"><span class="h-num" data-page-node-id="YGn70DTNEv4O1OETby3vB9">0<i data-page-node-id="dHfbNsUnlYAE9UubqX1z5u">小时</i></span><div class="h-day" data-page-node-id="dLSCdYHQzP48wDj47ErRfO">需及时补充</div></div></td>
|
||||
<td class="c-mid" data-page-node-id="wdSUThppNFrNH4rA9Q0jW9"><div class="edit-cell" data-page-node-id="wwYqFb78MEdU0khxSitQM0"><div class="num" data-page-node-id="SqVhjUY6rDrfMnv7uLBnkp">可填负数</div>
|
||||
<button class="qbtn" data-page-node-id="GLPCkhgIEGqcF1HTRsiegm">+1</button><button class="qbtn" data-page-node-id="zS5VrDWPk5WrolJlNI3Grv">+4</button><button class="qbtn d" data-page-node-id="nm9iiCwzGXvbQgt5HTfTy5">-1</button></div></td>
|
||||
<td class="c-mid" data-page-node-id="RDmjE12Xua8lwabZlKUPIt"><span class="pf-empty" data-page-node-id="tQRcpLE4LGakMF6Wc6JkgL">—</span></td>
|
||||
<td data-page-node-id="e4WpknJiXuSjdE6q7CBJNu"><input class="rem-input" placeholder="如:补课补时" data-page-node-id="PpE6F43exgUjCeBWpwXx2w" /></td>
|
||||
<td class="c-act" data-page-node-id="BGL37oPVVQZcZOQiGwGarZ"><span class="pf-empty" data-page-node-id="i1PK5LWZDi4Gjbv3AEohHw">—</span></td>
|
||||
</tr>
|
||||
<tr data-page-node-id="4uNpYUUqjqA6faCWm5zhit">
|
||||
<td class="c-idx" data-page-node-id="tqMI0VVviTnRG3mGbY7EAQ">5</td>
|
||||
<td data-page-node-id="aG3WwudhmJPHEPwG1xpvoQ"><div class="u-cell" data-page-node-id="TYpgIuHpslchHfGJWhzt6B"><span class="avatar" data-page-node-id="hGNKIZhWjz6OHhlC3ujWL1">周</span><span class="u-name" data-page-node-id="jLbQwXBAO2JqBeFQsLIYKb"><span class="u-main" data-page-node-id="3evVB0aAx2MbwARlRz7p1i">周建华</span><span class="u-rem" data-page-node-id="QZJD5y8R22LduL0r0s5IwH">语文组</span></span></div></td>
|
||||
<td data-page-node-id="3WdZC6sF6Q3OnnKPdIMTz0"><div class="h-cell lv-ok" data-page-node-id="MtrF3rVjbIaQ85OXaYvAaU"><span class="h-num" data-page-node-id="fGArtWAWICS6GtVRtEsMMa">40<i data-page-node-id="2fAQOsbmBr17ZpvgWLLwbM">小时</i></span><div class="h-day" data-page-node-id="suU8zJcxzDlp3Ui115nFp4">≈ 5 天</div></div></td>
|
||||
<td class="c-mid" data-page-node-id="r03YnFQTFN2ihDesRQHE1W"><div class="edit-cell" data-page-node-id="0kDAzw4ULgyGjSCbG7uXu5"><div class="num" data-page-node-id="Xzcr9CFrW5iAocnoTvtQZL">可填负数</div>
|
||||
<button class="qbtn" data-page-node-id="e2ECXvyU5T1FgFXQER6gtu">+1</button><button class="qbtn" data-page-node-id="VfwpFExThsm9d8IIDczcxC">+4</button><button class="qbtn d" data-page-node-id="ZyxPi4zqokE6JBO2krMUn1">-1</button></div></td>
|
||||
<td class="c-mid" data-page-node-id="kRbBOJgb7wFiyLOMlBcSxE"><span class="pf-empty" data-page-node-id="BuXWUKVHI6BFAEJwESEtqd">—</span></td>
|
||||
<td data-page-node-id="BF6Lcx42WikoiZAETvewVt"><input class="rem-input" placeholder="如:补课补时" data-page-node-id="27h1h6428GqEihJm5qUNHU" /></td>
|
||||
<td class="c-act" data-page-node-id="2Vjiqs3tMujdPksBodU8oq"><span class="pf-empty" data-page-node-id="Ut00QTvaziprxKziTnv8i6">—</span></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="footer active" data-page-node-id="auQWEPsZlBcDk90ZPBapAu">
|
||||
<div class="foot-info" data-page-node-id="fWOscVlC6r3wB9VRAeA74N">
|
||||
<span data-page-node-id="TzcUOvMKxs6WUXOHzdZ0zr">范围剩余合计<b class="hl-blue" data-page-node-id="MwwoP0w02y8oQFLt8cAQ6V">612.5</b>小时</span>
|
||||
<span class="fi-sep" data-page-node-id="azQIoyW96y6VANNHPrxqJ9"></span>
|
||||
<span data-page-node-id="zSOHxsgROLQztbvST7IufM">待提交<b class="hl-orange" data-page-node-id="zkE4HfBMkzY1PVn1fwVDEM">2</b>人,净增减<b class="hl-green" data-page-node-id="6uuXUU1jnYqiUoR3QbsMlq">-2</b>小时</span>
|
||||
</div>
|
||||
<div class="foot-btns" data-page-node-id="FV7MtMkxjEOPC97PUFnHt6">
|
||||
<button class="btn plain" data-page-node-id="3D7nbKIHCY1l7D8I8w3wI8">取消</button>
|
||||
<button class="btn primary" data-page-node-id="9PSeF3mhggVLnX8TJyWGHG">✓ 提交本次调整</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="note" data-page-node-id="DPJWMQB0PixGQW2FqHSWpL">
|
||||
<h4 data-page-node-id="YBeKgcD9lyzRI7ITc9GbA4">本次设计要点</h4>
|
||||
<ul data-page-node-id="RFzrvh2UZJ4hOnvZdFkex4">
|
||||
<li data-page-node-id="W86NOru5YmX64EwwbacgAm"><b data-page-node-id="l4aCUx6e5fz0snTgNpLMcM">顶部概览四卡</b>:教师人数 / 剩余合计 / 人均时长 / 时长用尽人数,随左侧分组与搜索实时联动,异常数据(用尽)自动红底提示。</li>
|
||||
<li data-page-node-id="zrRdlecBGJXndHc7MZrElr"><b data-page-node-id="OyuwEIBZBQoJfcrSaIKd86">时长水位分级</b>:≥8 小时绿色、<8 小时橙色、≤0 红色,并附「≈ X 天」折算(按 8 小时/天),比单一数字更容易判断余额。</li>
|
||||
<li data-page-node-id="r7DmnwSDagBf9wX9lRbOBD"><b data-page-node-id="BEb2BG4xwqmmDtLjuJBrYw">快捷加减按钮</b>:每行 +1 / +4 / -1 三个小按钮,免去输入数字;扣减后若会变负会即时拦截提示。</li>
|
||||
<li data-page-node-id="vdBvpkv6P8NoZGDha472or"><b data-page-node-id="LGATWlAKsYGa8pbpUjziHT">调整后预览列</b>:填写即预览「调整后时长 + 增减徽标」(绿加红减),提交前不必刷新核对。</li>
|
||||
<li data-page-node-id="BAyJ8l5ZHAqpzj8GDc7B0o"><b data-page-node-id="tj04sVhMxLPbGm2vpTJrER">编辑痕迹可视化</b>:已编辑行淡黄高亮 + 底部粘性条显示「待提交 N 人 / 净增减」,未改动时提交按钮禁用,杜绝误提交。</li>
|
||||
<li data-page-node-id="MsvP4QweVKqokyRGetyqpN"><b data-page-node-id="6hbH4D1PSBExseDuS0UjpI">教师头像 + 备注</b>:姓名旁以首字头像区分,原备注降级为次要信息,不再被表格覆盖。</li>
|
||||
<li data-page-node-id="QREACCSASfesiqJdFsxOo9"><b data-page-node-id="nA0pk9cGvdDIT3U9FZPDl3">分组树增强</b>:新增「全部教师」虚拟根节点、每组显示人数徽标,与统计卡片口径一致。</li>
|
||||
<li data-page-node-id="ZeW3hwy4xFB8jzIcHEXkCH"><b data-page-node-id="5dTH3ouJmocn9XGJGPAslB">撤销 / 清空改动</b>:单行可撤销,顶部可一键清空全部未提交编辑,减少误操作。</li>
|
||||
<li data-page-node-id="rQwC7Mm3JnZMIvhXeFBzEE"><b data-page-node-id="dgYPaNW2WhsZm4lyPyivKc">导入入口收敛</b>:「批量导入」并入管控类型区标题栏,与 Excel 导入能力语义相关联。</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,542 @@
|
||||
# 教师请假(WorkLeave)模块改造计划 v1
|
||||
|
||||
> 目标:把当前「宿舍通知(DormNotice)」模板工程整体改造为**教师请假**子应用。
|
||||
> 业务闭环:**教师提交请假申请 → 平台流程审核器(流程中心)流转审批 → 回调推进单据状态 → 扣减教师时长**。
|
||||
> 本文件是执行依据:按 P0~P7 逐阶段落地,每阶段附验收标准。
|
||||
|
||||
- 当前后端:`E:\YHXT\java\WorkLeave`(现为 com.yunhai.dormnotice,Spring Boot 2.7 + JPA + 基础平台 yh-spring-boot-starter)
|
||||
- 当前前端:`E:\YHXT\view\WorkLeave`(现为 dorm-notice,Vue3 + Vite + view-ui-plus + alova)
|
||||
- 历史前端(Vue2,**只参考页面/菜单节点逻辑**):`H:\工作区\MYFile\备份\请假前端`
|
||||
- 历史后端(C#/EF,参考实体字段与业务规则):`H:\工作区\MYFile\备份\请假申请2.2`
|
||||
- 流程对接手册:`E:\xwechat_files\wxid_psk2hommdhtq22_310d\msg\file\2026-09\子应用流程与消息对接手册-20260929.md`
|
||||
- 平台参考实现:`E:\YHXT\java\UserclassService`(UserContext / SysOperationLogAspect)、`E:\YHXT\java\HomeLeave`(同平台请假姊妹工程,风格参照)
|
||||
|
||||
---
|
||||
|
||||
## 1. 参考资产盘点(已勘察结论)
|
||||
|
||||
### 1.1 历史左侧菜单节点(请假前端 router,modId 100~190)
|
||||
|
||||
| modId | 页面 | 历史路径 | 新方案 |
|
||||
|---|---|---|---|
|
||||
| 100 | 我要请假(申请表单) | `/leave/` | 保留 → `/apply` |
|
||||
| 110 | 我的请假 | `/leave/myleave` | 保留 → `/myleave` |
|
||||
| 111 | 请假编辑(子页) | `/leave/edit` | 合并进 `/apply?serid=`(隐藏路由) |
|
||||
| 120 | 审核列表(待我审核) | `/leave/shenhe` | 保留 → `/audit`(改用流程中心待办) |
|
||||
| 130 | 抄送列表 | `/leave/leave_send` | 保留 → `/cc` |
|
||||
| 140 | 全部请假(管理端) | `/leave/totallist` | 保留 → `/all` |
|
||||
| 150 | 指派代课 | `/leave/assignment` | **P7 可选**(依赖排课/课务数据,v1 暂缓) |
|
||||
| 160 | 流程管理(固定流程配置) | `/leave/liucheng` | **删除**:新平台由「流程中心-流程编排」承担(管理员配置) |
|
||||
| 170 | 请假设置(6 个 tab) | `/leave/leave_timeSetting` | 保留 → `/setting`(作息/类型/教师时长/节次/其他) |
|
||||
| 180 | 时长日志 | `/leave/leave_amendant` | 保留 → `/timelog` |
|
||||
| 190 | 统计分析 | `/leave/tongji` | 保留 → `/stats`(本地实现,旧版跳外部站点不沿用) |
|
||||
|
||||
### 1.2 历史状态机(C# 口径,新方案做了流程化收敛)
|
||||
|
||||
历史:`0 待审核 / 1 通过 / 2 未通过 / 3 撤销(通过) / 4 撤销待审 / 5 通过(撤销被拒) / 6 修改待审`,
|
||||
并配套自建流程引擎(`Common_flowinfo/flowstep/freeflowinfo/flowtranctInfo/Groupuser`)实现固定流程与自由流程。
|
||||
|
||||
新方案:**审核流转全部交给平台流程审核器**,本地只保留单据状态;4/5/6 三个依赖自建引擎的状态取消:
|
||||
|
||||
| 新状态 | 枚举 | 进入事件 |
|
||||
|---|---|---|
|
||||
| 0 | APPROVING 审批中 | 提交(flow.submitted / flow.task.created / flow.task.handled) |
|
||||
| 1 | PASSED 已通过 | flow.completed(**扣减时长**) |
|
||||
| 2 | REJECTED 已拒绝 | flow.rejected |
|
||||
| 3 | CANCELED 已销假 | 已通过后教师发起销假(本模块动作,**返还时长**) |
|
||||
| 4 | WITHDRAWN 已撤回 | flow.withdrawn(提交人撤回,需无任务被办理) |
|
||||
|
||||
### 1.3 历史核心业务规则(重写要点,详见 §6)
|
||||
|
||||
- 请假类型:字典(历史 `Common_Dictionary typeName='请假类别'`),学校开关 + 排序 → 新方案 `sys_dict(dict_type='leaveType')`。
|
||||
- 时长(调休余额)池:类型进入「时长管控清单」时校验余额;在途占用 = 审批中单据时长之和;通过扣减、销假返还;加减流水留痕。
|
||||
- 时间/时长计算:按学校作息(上午 F1-F2、下午 A1-A2)折算小时,同日去午休、跨天按整天 8h;小时制(`IsCalculateWay=true`)为唯一口径,按天模式取消。
|
||||
- 冲突校验:同一申请人、状态 ∈ {0,1} 的时间区间不得重叠(历史含 5,新方案无该状态)。
|
||||
- 编辑:仅「审批中」「已拒绝」可编辑并重新提交(重新发起流程,bizId 追加尝试序号);已通过只能销假,不能改。
|
||||
- 详情快照:终态时把回调事件 data(含审批意见)序列化落库,详情页免二次查询。
|
||||
- 抄送:流程中心 NOTIFY 节点自动发消息;本模块落 `leave_cc` 留痕表供「抄送列表」渲染。
|
||||
- 删除:物理删除(历史口径),仅限审批中/已拒绝/已撤回,且仅本人。
|
||||
|
||||
### 1.4 历史缺陷清单(重写时修正,不复制)
|
||||
|
||||
1. 半天换算 `JsAPm` 判成 `KsAPm`(C# 源码 bug)→ 新方案按天模式整体取消,不涉及。
|
||||
2. 拒绝时返还时长传错类型 → 新方案拒绝不返还(未扣过),无此分支。
|
||||
3. 附件绑定不落库 → 新方案仓储显式 save。
|
||||
4. 时长池新建记录 `null + x = null` → 新方案 `default 0`。
|
||||
5. 时间段校验用字符串拼 SQL → 新方案参数化 JPA + LocalDateTime。
|
||||
6. 列表时间过滤口径不一(重叠 vs 包含)→ 统一为**重叠**语义。
|
||||
|
||||
---
|
||||
|
||||
## 2. 关键决策
|
||||
|
||||
| # | 决策 | 理由 |
|
||||
|---|---|---|
|
||||
| D1 | 后端包名 `com.yunhai.workleave`,artifactId/finalName=`workleave`,应用名`WorkLeave` | 需求 1;与旧模板彻底切割 |
|
||||
| D2 | 删除宿舍业务(Activity/ActivityRead/ActivitySignup 及全部 Model/Repository/Service/Controller),**保留** `sys_operation_log`(含切面)、`sys_dict`、`up_file`、contacts(自定义组)、base/dict/file/log 平台代理控制器 | 需求 2(删实体保留日志表);保留件是请假的通用底座 |
|
||||
| D3 | 业务人员字段**只存平台数字 ID**(userId),姓名/头像/类型名做冗余展示字段;`BaseEntity.create_by/update_by` 改存**操作人 userId**,不存账号 | 需求 3(不保存师生账号) |
|
||||
| D4 | 审核流转对接**平台流程审核器**(yh-flow-callback-starter + YschFlowOpenApi),bizType=`leave` 全局唯一 | 需求 7 对接手册;替代历史自建流程引擎 |
|
||||
| D5 | 申请发起 `bizId = {leaveId}-{attempt}`(attempt 为第几次提交,从 1 起);回调按 `-` 前缀解析 leaveId | 流程中心按 bizType+bizId 幂等,拒绝后重新提交必须换 bizId |
|
||||
| D6 | 消息跳转 `JumpTarget.page("detail").param("leaveId", id)`,前端提供 `/detail?leaveId=` 落地页 | 手册 §3.1 红线(query 形态、页面名两端一致) |
|
||||
| D7 | 时长计算做成工具类前后端同规则(前端实时算、后端复核),小时制保留 2 位小数 | 需求 3 业务逻辑;历史算法在前端算、后端只校验,重写为双端一致 |
|
||||
| D8 | 前端保留现有技术栈与壳层(Vue3+Vite+view-ui-plus+alova),仅替换业务域与菜单契约 | 与当前前端工程一致,降低风险;"只参考页面逻辑"不搬 Vue2 代码 |
|
||||
| D9 | 节点 160 流程管理删除;审批意见/流程图/进度时间线用流程中心能力(progress/diagramUrl) | 平台已提供,避免重复建设 |
|
||||
|
||||
---
|
||||
|
||||
## 3. 前置依赖(需平台管理员配合,先登记后联调)
|
||||
|
||||
| # | 事项 | 说明 | 状态 |
|
||||
|---|---|---|---|
|
||||
| A1 | projman 建应用 | 部署名称 `leave`(= app_code,逐字一致;公共组件勾 Redis) | ☐ 待确认 |
|
||||
| A2 | UserBase 注册应用 | app_code=`leave`、app_key/app_secret、pc_url=`http://<域名>/leave/front/index.html#/` | ☐ 待确认 |
|
||||
| A3 | 流程中心编排 | 注册流程记 flowGuid(建议 `leave-apply-v1`;如需销假审批再加 `leave-cancel-v1`) | ☐ 待确认 |
|
||||
| A4 | 白名单登记 | flow-center `trusted-apps` 加 `appKey:secret`;UserMsg/UserBase 密钥链 | ☐ 待确认 |
|
||||
| A5 | 菜单树配置 | 基础平台按 §4.3 的 modId/path 配置 9 个节点(对齐 GetUserMod) | ☐ 待确认 |
|
||||
| A6 | Nacos 配置 | `workleave.yaml`(app-key/secret/flow-guid/open.base-host),本地用 application.yml 兜底 | ☐ 待确认 |
|
||||
|
||||
> 本地开发不需要 A4;`open-host` 指向 `http://open.yunhaixingtong.com`(hosts 加 `192.168.10.160 open.yunhaixingtong.com`)可直连真实服务(手册 §5.0)。
|
||||
|
||||
---
|
||||
|
||||
## 4. 目标架构
|
||||
|
||||
### 4.1 后端目录结构(com.yunhai.workleave)
|
||||
|
||||
```
|
||||
src/main/java/com/yunhai/workleave/
|
||||
├── WorkLeaveApplication.java
|
||||
├── aspect/ ControllerUserContextAspect(Order1)· SysOperationLogAspect(Order3)
|
||||
├── common/ listener/BaseAuditListener · validation/ValidationGroups
|
||||
├── config/ Config(API_PATH) · JacksonConfig · SwaggerConfig
|
||||
├── entity/ BaseEntity · SysDict · UpFile · SysOperationLog
|
||||
│ LeaveApply · LeaveSetting · LeaveTimeSet · LeaveUserTime · LeaveUserTimeRecord
|
||||
│ LeaveSection · LeaveCc · (P7) LeaveCourse · LeaveCourseExchange · LeaveApplyOld
|
||||
├── enums/ LeaveStatusEnum · LeaveActionEnum · UserType
|
||||
├── flow/ LeaveFlowService(apply/withdraw/todo/approve/reject/progress/diagram)
|
||||
│ LeaveFlowCallbackHandler(事件状态机 + 终态守卫 + 快照 + 扣减/返还 + 抄送留痕)
|
||||
├── model/ BasePageQuery/BasePageResult · bsch(SchTeaList/ClassTreeNodeVo) · file(UpFileVo/Result)
|
||||
│ └── leave/ LeaveVo · LeaveDetailVo · LeaveQuery · LeaveRequest · LeaveCancelRequest
|
||||
│ LeaveAuditRequest · LeaveProgressVo · LeaveStatsVo · TimeSetVo/TimeSetRequest
|
||||
│ LeaveSettingVo · UserTimeVo · UserTimeRecordVo · UserTimeAdjustRequest · SectionVo ...
|
||||
├── repository/ LeaveApplyRepository · LeaveSettingRepository · LeaveTimeSetRepository
|
||||
│ LeaveUserTimeRepository · LeaveUserTimeRecordRepository · LeaveSectionRepository
|
||||
│ LeaveCcRepository · SysDictRepository · UpFileRepository · SysOperationLogRepository
|
||||
├── service/ I/DictService · I/FileService · I/ContactsService · I/UserCenterService · I/ClassService
|
||||
│ I/LeaveApplyService · I/LeaveSettingService · I/LeaveUserTimeService
|
||||
│ I/LeaveStatsService · I/SysOperationLogService
|
||||
│ └── impl/ 同名实现 + LeaveTimeCalculator(时长算法)
|
||||
├── controller/api/ BaseApiController · DictController · FileController · LogController · ContactsController
|
||||
│ LeaveController(申请域)· LeaveAuditController(待办/审核/进度)
|
||||
│ LeaveSettingController(设置域)· LeaveTimeController(时长域)· LeaveStatsController
|
||||
└── utils/ UserContext · PageUtils · GzipUtil · IpUtils · LeaveTimeText
|
||||
```
|
||||
|
||||
### 4.2 数据表(`sql/workleave_schema.sql`;ddl-auto=update 兜底建表,脚本供人工核对)
|
||||
|
||||
| 表 | 说明 | 关键字段 |
|
||||
|---|---|---|
|
||||
| `leave_apply` | 请假申请主表 | id, sch_id, user_id, user_name, user_photo, leave_type(字典key), leave_type_name, start_time, end_time, total_hours, reason, file_ids, cc_user_ids, status, biz_id, instance_id, instance_version, flow_guid, flow_attempt, snapshot(终态JSON), apply_time + BaseEntity 审计列 |
|
||||
| `leave_setting` | 学校设置(一校一行) | sch_id, reason_must, show_explain, enable_course, allow_exchange, time_managed_types(CSV 字典key), time_limit_enabled |
|
||||
| `leave_time_set` | 作息时间(一校一行) | sch_id, forenoon1, forenoon2, afternoon1, afternoon2, interval_minutes(默认 08:00/12:00/13:00/17:00/10) |
|
||||
| `leave_user_time` | 教师时长池 | sch_id, user_id, user_name, usable_hours, remark;唯一键 (sch_id,user_id) |
|
||||
| `leave_user_time_record` | 时长流水 | sch_id, user_id, user_name, leave_id, leave_type, edit_hours(±), old_hours, new_hours, describe, operator_id, create_time |
|
||||
| `leave_section` | 节次表(P7 课务用,设置页可维护) | sch_id, name, rank, begin_time, end_time |
|
||||
| `leave_cc` | 抄送留痕 | sch_id, leave_id, cc_user_id, cc_user_name, instance_id, create_time |
|
||||
| `sys_dict` | 字典(保留):新增 `leaveType` | (sch_id, dict_type='leaveType', dict_key, dict_value, sort_order, enable_flag) |
|
||||
| `up_file` / `sys_operation_log` | 附件 / 审计日志(保留) | 现状不动 |
|
||||
| `flow_callback_event` / `flow_callback_reg` | 回调 starter 自动建表 | 不手工维护 |
|
||||
|
||||
### 4.3 前端路由与 modId 契约(对齐平台菜单树)
|
||||
|
||||
| modId | path | 页面 | 说明 |
|
||||
|---|---|---|---|
|
||||
| 100 | `/apply` | 我要请假 | 新建/编辑(`?serid=`),含时长试算、附件、抄送人 |
|
||||
| 110 | `/myleave` | 我的请假 | 本人列表:查看/编辑/销假/撤回/删除 |
|
||||
| 111 | `/detail` | 请假详情 | `?leaveId=`,**流程消息跳转落地页**;meta.parentId=110(菜单不单列) |
|
||||
| 120 | `/audit` | 审核列表 | tab:待我审核(流程待办)/ 我已审核;同意/拒绝/流程图 |
|
||||
| 130 | `/cc` | 抄送列表 | leave_cc 渲染 |
|
||||
| 140 | `/all` | 全部请假 | 管理端全量 + 导出 CSV |
|
||||
| 170 | `/setting` | 请假设置 | tab:作息时间/请假类型/教师时长/节次/其他设置 |
|
||||
| 180 | `/timelog` | 时长日志 | 时长流水 + 合计(新加/剩余),支持加减时长 |
|
||||
| 190 | `/stats` | 统计分析 | 数量/时长按类型与月份、明细 |
|
||||
| (150) | `/assignment` | 指派代课 | **P7 可选** |
|
||||
| — | `/` → `/apply` 重定向 | — | |
|
||||
|
||||
> 平台菜单若分配了不同 modId:优先按 `path` 解析(menuRoute.ts 已具备),仅需把上表 modId 改为实际值即可。
|
||||
|
||||
### 4.4 前端文件结构(E:\YHXT\view\WorkLeave)
|
||||
|
||||
```
|
||||
src/
|
||||
├── api/module/leave.ts # 全部接口封装(沿用 get/postForm/postJSON 约定)
|
||||
├── types/leave.ts # 类型契约(枚举、VO/DTO、请求入参)
|
||||
├── router/routes.ts # 上表路由 + meta.modId/parentId
|
||||
├── utils/leaveTime.ts # 时长计算(与后端同规则)
|
||||
├── views/leave/
|
||||
│ ├── ApplyLeave.vue # 我要请假(新建/编辑)
|
||||
│ ├── MyLeave.vue # 我的请假
|
||||
│ ├── LeaveDetail.vue # 详情(信息+进度时间线+附件+抄送人)
|
||||
│ ├── LeaveAudit.vue # 审核列表
|
||||
│ ├── LeaveCc.vue # 抄送列表
|
||||
│ ├── AllLeave.vue # 全部请假
|
||||
│ ├── LeaveSetting.vue # 请假设置(多 tab)
|
||||
│ ├── TimeLog.vue # 时长日志
|
||||
│ └── LeaveStats.vue # 统计分析
|
||||
└── (保留)components/、utils/、assets/ 等基础设施;删除 views/dorm/*
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 接口契约(前后端对齐)
|
||||
|
||||
统一前缀:`/{platform}/api`(前端 `serviceHost` + `/api/...`;平台值 api/manage/mobiapi)。响应由平台 `ApiResponseAdvice` 包装 `{code,msg,data}`,alova 解包 `data`。
|
||||
|
||||
### 5.1 申请域 `leave/`
|
||||
|
||||
| 方法 | 接口 | 入参 | 出参 |
|
||||
|---|---|---|---|
|
||||
| GET | `/leave/List` | scope(user=我的/all=全部), status, keyword, startTime, endTime, pageIndex, pageSize | `{content:[LeaveVo], total, ...}` |
|
||||
| GET | `/leave/Detail` | leaveId | LeaveDetailVo(含进度、抄送、附件、快照、当前用户能否审核) |
|
||||
| POST | `/leave/Create` | LeaveRequest(JSON) | leaveId(内部:落库→发起流程→回写 bizId/instanceId) |
|
||||
| POST | `/leave/Edit` | LeaveRequest + id(JSON) | void(仅 0/2/4 状态;重新发起流程 attempt+1) |
|
||||
| POST | `/leave/Delete` | ids(JSON 数组) | void |
|
||||
| POST | `/leave/Withdraw` | id | void(调流程中心 withdraw) |
|
||||
| POST | `/leave/Cancel` | id + reason(JSON) | void(销假:状态 1 → 3,返还时长,落流水) |
|
||||
| GET | `/leave/Progress` | leaveId | `{steps:[...], tasks:[...]}`(流程中心 progress 包装) |
|
||||
| GET | `/leave/DiagramUrl` | leaveId | `{url}`(流程图 10 分钟 token) |
|
||||
| GET | `/leave/MyTime` | leaveType | `{usable, inTransit, remaining}`(表单页余额提示) |
|
||||
|
||||
### 5.2 审核域 `leave/`
|
||||
|
||||
| 方法 | 接口 | 入参 | 出参 |
|
||||
|---|---|---|---|
|
||||
| GET | `/leave/TodoList` | pageIndex, pageSize, keyword | `{content:[TodoVo], total}`(todoTasks(bizType=leave) + 本地单号补全) |
|
||||
| GET | `/leave/AuditedList` | pageIndex, pageSize, keyword | 已办:本地按 instance 与审核记录反查 |
|
||||
| POST | `/leave/Audit` | taskId, action(approve/reject), opinion(JSON) | void(approveTask/rejectTask;状态由回调推进) |
|
||||
|
||||
### 5.3 抄送 / 统计 / 设置 / 时长
|
||||
|
||||
| 方法 | 接口 | 说明 |
|
||||
|---|---|---|
|
||||
| GET | `/leave/CcList` | 抄送给我的列表(leave_cc 关联 leave_apply) |
|
||||
| GET | `/leave/Statis` | dateStart/dateEnd → 总数量、总时长、按类型分布、按月趋势、明细 Top |
|
||||
| GET | `/leave/setting/GetTimeSet` / `SetTimeSet` | 学校作息 |
|
||||
| GET | `/leave/setting/GetOption` / `SetOption` | 其他设置(含时长管控类型) |
|
||||
| GET | `/leave/setting/SectionList` | 节次列表(P7 预览,设置页可维护) |
|
||||
| POST | `/leave/setting/SectionSave` / `SectionDelete` / `SectionSort` | 节次维护 |
|
||||
| GET | `/leave/time/UserTimeList` | 教师时长列表(按组/关键字;可用 `base/GetSchoolAll` 组树过滤) |
|
||||
| POST | `/leave/time/Adjust` | 加减时长(单人或批量,JSON;写流水) |
|
||||
| GET | `/leave/time/Records` | 时长流水(rqType 0今日/1本周/2本月/3累计 + 关键字 + 分页) |
|
||||
| GET | `/leave/time/Sum` | 合计:新加总时长、剩余总时长 |
|
||||
| GET/POST | `/api/dict/*`(复用) | type=leaveType 的字典增删改查排序(请假类型 tab) |
|
||||
|
||||
### 5.4 平台代理接口(保留,改包名)
|
||||
|
||||
`base/GetUserInfo`、`base/GetUserMod`、`base/GetSchoolAll`(选人树)、`base/SetRole`、`base/FileUpload`、`base/Logout`、
|
||||
`dict/*`、`file/*`、`contacts/*`(自定义组)、`log/*`(审计日志查询)。
|
||||
|
||||
---
|
||||
|
||||
## 6. 业务规则明细
|
||||
|
||||
### 6.1 时长计算(前后端同规则,工具类 `LeaveTimeCalculator` / `leaveTime.ts`)
|
||||
|
||||
输入:起止时间(yyyy-MM-dd HH:mm)、作息(F1/F2/A1/A2)。输出:小时(四舍五入 2 位)。
|
||||
|
||||
- 同日:start `clamp` 到工作时段(早于 F1→F1;F1~F2 之间保持;F2~A1 之间→A1;晚于 A2→A2;A1~A2 保持);
|
||||
end 同理(F2~A1 之间→F2);`minutes = end - start`;若 `start<=F2 && end>=A1` 再减午休 `(A1-F2)`。
|
||||
- 跨天:首日 = `A2 - start`(start 落在 F1~F2 时再减午休);末日 = `end - F1`(end 落在 A1~A2 时再减午休);
|
||||
中间整天按 `8h/天`(= `A2-A1 + F2-F1`);合计取小时。
|
||||
- 校验:结束必须晚于开始;单条时长 > 0;跨天视为按天折算(不再区分上下午字段)。
|
||||
|
||||
### 6.2 申请与校验(Create/Edit)
|
||||
|
||||
1. 类型必须为启用的 leaveType 字典项;事由在 `reason_must=1` 时必填且 ≤50 字。
|
||||
2. 时间冲突:同人、`status in (0,1)`、`(start_time < 已有.end_time) && (已有.start_time < end_time)` → 报"与已有请假时间冲突"。
|
||||
3. 时长管控:类型 ∈ `time_managed_types` 时,`在途(0状态且管控类型) + 本次 <= usable`(无记录按 0),否则报"可申请 X 小时,时间不足"。
|
||||
4. `total_hours` 后端按 §6.1 复核(与前端不一致以服务端为准落库)。
|
||||
5. 提交成功 → 组装 `FlowApplyBody`:bizType=`leave`、bizId=`{id}-{attempt}`、bizTitle=`{姓名}的{类型}申请`、bizJump=page("detail").param("leaveId",id)、bizSnapshot=摘要(类型/起止/时长/事由)、submitterId/Name、formData(类型、时长、天数)。
|
||||
6. 发起成功回写 `biz_id/instance_id/flow_guid/status=0`;`duplicated=true` 时视为幂等成功(不新建实例)。
|
||||
|
||||
### 6.3 回调状态机(LeaveFlowCallbackHandler)
|
||||
|
||||
| 事件 | 处理 |
|
||||
|---|---|
|
||||
| `flow.submitted` / `flow.task.created` / `flow.task.handled` | status→0(审批中);**终态不回退**:已 1/2/3/4 则忽略 |
|
||||
| `flow.completed` | status→1;扣时长(管控类型才执行)+ 流水"扣除";落终态快照 |
|
||||
| `flow.rejected` | status→2;不扣不返还;落终态快照 |
|
||||
| `flow.withdrawn` | status→4(仅当时为 0 时) |
|
||||
| `flow.cc.sent` | 写 `leave_cc` 留痕(幂等:同 leaveId+ccUserId+instanceId 不重复落) |
|
||||
|
||||
- 乱序守卫:事件带 `instanceVersion`,小于单据已记录版本的事件忽略(终态快照之外)。
|
||||
- 事务:回调方法内异常 → 整体回滚 + 流程中心重推;确定性脏消息(bizId 解析失败等)记录 warn 后正常 return,不抛异常。
|
||||
- 注册:首次发起前 `callbackRegistrar.ensureRegistered(schId, flowGuid)`(结果落 flow_callback_reg 镜像表)。
|
||||
|
||||
### 6.4 销假(Cancel)与撤回(Withdraw)
|
||||
|
||||
- 销假:仅 `status=1`;置 3;管控类型返还时长 + 流水"销假返还";记 `cancel_time/reason`(快照字段)。已通过 50 天时效沿用(可配置为 50 天常量)。
|
||||
- 撤回:仅 `status=0`;调 `YschFlowOpenApi.withdraw(schId, instanceId, submitterId)`;等回调置 4(不本地直改,避免与流程中心不一致)。
|
||||
|
||||
### 6.5 时长池管理
|
||||
|
||||
- 列表:`leave_user_time` LEFT JOIN 教师名单(`base/GetSchoolAll` 同源,按组过滤)。
|
||||
- 调整:`edit_hours` 可正可负,`now = old + edit`(不低于 0),写流水(describe=备注或"新加"),operator 取当前用户。
|
||||
- 流水统计:`Sum` 返回 `新加总=Σ(±)`、`剩余总=Σ usable`(按过滤区间口径)。
|
||||
|
||||
---
|
||||
|
||||
## 7. 流程审核器对接要点(照手册执行)
|
||||
|
||||
1. **pom**:`yh-flow-callback-spring-boot-starter` + `spring-boot-starter-data-redis`(验签密钥链必需,optional 不传递)。
|
||||
2. **配置**:
|
||||
```yaml
|
||||
yh:
|
||||
app-auth: { enabled: true, app-key: "1000xx", app-secret: "******" }
|
||||
flow-callback:
|
||||
enabled: true
|
||||
biz-types: [leave]
|
||||
open-host: ${yh.open.base-host} # 未配 callback-url 时用于拼回调地址
|
||||
management.health.redis.enabled: false
|
||||
leave.flow.guid: leave-apply-v1 # 编排所得
|
||||
```
|
||||
回调地址默认 `/oapi/flow/callback`(starter 自证 context-path 生成);`yh.sso.url-pattern=/api/*` 不含 oapi,无需放行改造。
|
||||
3. **发起**:`YschFlowOpenApi.apply(schId, body, FlowApplyResult.class)`。
|
||||
4. **回调**:实现 `FlowCallbackHandler#onEvent`(§6.3);`schId` 从事件取,不信任本地上下文。
|
||||
5. **待办/审核**:`todoTasks(schId, assigneeId, "leave", page, size)`、`approveTask(schId, taskId, operatorId, operatorName, opinion[, edgeName])`、`rejectTask(...)`。
|
||||
6. **进度/流程图**:`progress(null, "leave", bizId, schId, FlowProgress.class)`、`diagramUrl(instanceId, flowGuid, "track", schId, FlowDiagramUrl.class)`。
|
||||
7. **自检**(手册 §5.1):本地单测(mock 回调事件 + apply 出参断言)→ 160 环境全链路 9 项验收。
|
||||
|
||||
---
|
||||
|
||||
## 8. 分阶段任务与验收
|
||||
|
||||
### P0 骨架重整
|
||||
- [x] 后端:包 `com.yunhai.dormnotice` → `com.yunhai.workleave`(目录迁移 + 全量 import 替换);`DormNoticeApplication` → `WorkLeaveApplication`;pom `artifactId/name/finalName` → `workleave`;`bootstrap.yml spring.application.name` → `workleave`;日志/库名对齐
|
||||
- [x] 后端:删除宿舍业务(ActivityController、Activity 系实体/模型/仓储/服务);保留 log/dict/file/base/contacts 底座
|
||||
- [x] 前端:`package.json` name、`vite.config` 标题、`index.html`、README、App.vue appName → 教师请假
|
||||
- **验收**:后端 `mvn -q compile` 通过;前端构建通过;全局搜索无 `dormnotice` 残留
|
||||
|
||||
### P1 实体与数据层
|
||||
- [x] `BaseEntity` 审计人改存 userId(`create_by/update_by`)
|
||||
- [x] 新实体 + Repository(LeaveApply/LeaveSetting/LeaveTimeSet/LeaveUserTime/LeaveUserTimeRecord/LeaveSection/LeaveCc/LeaveAuditTrace)
|
||||
- [x] `sql/workleave_schema.sql`(建表 + leaveType 字典种子:事假/病假/调休/公假/婚假/产假)
|
||||
- **验收**:编译通过;启动自动建表成功;`leaveType` 字典种子可查
|
||||
|
||||
### P2 设置域 + 时长域
|
||||
- [x] `LeaveSettingController/Service`:作息时间、其他设置、节次
|
||||
- [x] `LeaveTimeController/Service`:时长列表/调整/流水/合计;`LeaveTimeCalculator`
|
||||
- **验收**:单测覆盖时长算法(同日/跨天/边界 clamp/午休)—— 16 例全绿
|
||||
|
||||
### P3 申请域 + 流程对接
|
||||
- [x] `LeaveController/Service`:Create/Edit/Delete/Withdraw/Cancel/List/Detail/MyTime/Statis/TodoList/AuditedList/Audit
|
||||
- [x] `flow/LeaveFlowService` + `flow/LeaveFlowCallbackHandler`(§6.3)
|
||||
- [x] pom/application.yml 流程依赖与配置(§7)
|
||||
- **验收**:编译通过;回调单测(mock FlowCallbackEvent 覆盖 5 类事件 + 终态不回退 + 乱序门 + 抄送幂等)—— 12 例全绿
|
||||
|
||||
### P4 前端壳层与路由
|
||||
- [x] `api/module/leave.ts`、`types/leave.ts`、`router/routes.ts`(§4.3)、`menuRoute.ts` 别名更新
|
||||
- [x] 删除 `views/dorm/*`、`api/module/dorm.ts`、`types/dorm.ts` 及依赖其的组件
|
||||
- **验收**:`npm run build` 通过;菜单解析器对新节点 path 命中
|
||||
|
||||
### P5 前端页面
|
||||
- [x] ApplyLeave:表单 + 时长实时试算 + 余额提示 + 附件 + 抄送人 + 提交/重新提交
|
||||
- [x] MyLeave / LeaveDetail / LeaveAudit / LeaveCc / AllLeave
|
||||
- [x] LeaveSetting(5 个 tab)/ TimeLog / LeaveStats
|
||||
- **验收**:构建通过(联调需本地后端 + DB 环境,见 P6 说明)
|
||||
|
||||
### P6 联调与验收
|
||||
- [x] 后端 `mvn -o compile` / 单测全绿;前端 `npm run build`
|
||||
- [ ] 160 环境按手册 §5.1 九项清单验收(含消息卡片跳转 `/detail?leaveId=`)—— **需管理员提供 A1~A6 前置项**
|
||||
- **验收**:清单全过;`proj34.sys_message` 最新行 `jump_path=/detail?leaveId=xxx`、`jump_app=leave`
|
||||
|
||||
### P6.5 本地冒烟测试(2026-10-09 新增)
|
||||
|
||||
> 目标:在不依赖平台管理员前置项(A1~A6)的前提下,对已交付代码做**本地全链路冒烟**,
|
||||
> 验证后端服务、数据库、核心业务接口、前端构建与页面渲染的可运行性。
|
||||
> 160 全链路(真实 SSO 登录 + 流程审批闭环)仍归 P6,等管理员补齐前置项。
|
||||
|
||||
**冒烟辅助件(开发专用,生产零影响)**:
|
||||
- 新增 `config/DevMockUserFilter`:仅当 `smoke.auth.enabled=true` 时注册,向 `BoaUserContext`
|
||||
注入可配置的模拟用户(`smoke.auth.user-id/user-name/sch-id/user-type`),默认关闭。
|
||||
- 本地启动时用 `--yh.sso.url-pattern=/__smoke_off__/*` 关闭 CAS 拦截,使 `/api/*` 可直连
|
||||
(模拟用户由上述过滤器提供,业务代码无需感知)。
|
||||
|
||||
**冒烟清单(S1~S6)**:
|
||||
|
||||
| # | 项 | 内容 | 通过标准 | 结果 |
|
||||
|---|---|---|---|---|
|
||||
| S1 | 构建 | `mvn -o test`(含 28 例单测) | BUILD SUCCESS,0 失败 | ✅ 28 例全绿(修复后复跑仍全绿) |
|
||||
| S2 | 启动 | smoke 模式 `java -jar` 起 8105 | 日志无 ERROR,`/actuator/health` 200 | ✅ |
|
||||
| S3 | 数据 | DB `work_leave` 13 张表 + leaveType 种子 | 表齐全,LT001~LT006 可查 | ✅ 13 表 + 6 种子 |
|
||||
| S4 | 设置域 API | GetTimeSet/SetTimeSet、GetOption/SetOption、dict 全链路 | 读默认值→写入→回读一致 | ✅(修复 2 个 bug 后通过,见 §10) |
|
||||
| S5 | 申请/时长域 API | List 空态、time/Adjust+Sum+Records、MyTime、Statis、Create(流程中心链路) | 只读接口正常;Adjust 后 Sum/Records 联动;Create 失败时**事务回滚无孤儿单**(flowGuid 占位期预期),若流程中心可用则全链路通过 | ✅ Adjust +8h→Sum/Records/MyTime 联动一致;Create 报"流程失败 404"且回滚(leave_apply=0);操作日志记录失败尝试 |
|
||||
| S6 | 前端 | `npm run build` + dev server 浏览器访问核心页面 | 构建通过;页面壳层渲染、控制台无阻断性报错 | ✅ 9 页面全渲染;申请表单可交互(类型下拉 6 项/时长试算 4h/事由 50 字计数/提交按钮联动);提交失败 toast 正确透传后端错误;"我的请假"空态表格/统计卡正常;设置页 5 tab 正常 |
|
||||
|
||||
**执行结论随做随记入 §10。**
|
||||
|
||||
### P7 可选扩展(v1 后)
|
||||
- [ ] 指派代课/调课(课务安排表 + 教务安排人 + 通知);节次/班级/科目数据源接入
|
||||
- [ ] 修改待审流程(快照表 `leave_apply_old` + 二次流程)
|
||||
- [ ] 发起销假审批(第二 flowGuid `leave-cancel-v1`)
|
||||
- [ ] 教师时长 Excel 导入导出;统计图表增强
|
||||
|
||||
### P8 对齐旧版生产行为(2026-10-09,参考 oa.bestsch.com/leave 实地勘察)
|
||||
|
||||
> 背景:登录旧版生产系统(lwj 账号)实地核对,发现原计划 v1 裁剪掉的两个行为在生产实际在用,
|
||||
> 且旧版"其他设置"里有**请假计算(按小时/按天)**开关(该学校当前为按天)。本阶段把这两块补齐。
|
||||
> 旧版算法结论:后端永远存**小时**(leave_totaltime),"按天"只是输入/显示模式(显示=小时/8),
|
||||
> 与新架构小时制天然兼容,改造聚焦在输入侧与配置面。
|
||||
|
||||
**后端**:
|
||||
- `leave_setting.calc_way`(1 按小时[默认] / 0 按天,历史 IsCalculateWay 同义)
|
||||
- `leave_time_set.min_minutes`(最少请假时间,分钟,0 不限制;历史"学校时间设置"项)
|
||||
- `leave_apply.course_arrange`(0 无课务安排 / 1 自行安排 / 2 教务安排;不传默认 0)
|
||||
- Create/Edit 校验:时长分钟 < min_minutes → 拒绝"请假时长不能少于 X 分钟"(边界=值本身放行)
|
||||
- VO:TimeSetVo+minMinutes、LeaveSettingVo+calcWay、LeaveRequest/LeaveVo/LeaveDetailVo+courseArrange(+Name)
|
||||
|
||||
**前端**:
|
||||
- ApplyLeave:calcWay=0 时表单切换为「半天/半天以上 + 日期 + 上午/下午」输入(按作息组合起止
|
||||
datetime 后复用既有小时试算),时长主显**天**(=小时/8,同旧版);calcWay=1 保持 datetime 输入。
|
||||
新增「时间说明」行(showExplain=1 时):`上午(F1-F2)-下午(A1-A2),固定X小时工作制`。
|
||||
新增「课务安排」三选一(enableCourse=1 时,默认无课务安排)。
|
||||
- LeaveSetting:作息 tab + 「最少请假时间(分钟)」;其他设置 tab 置顶 + 「请假计算:按小时/按天」
|
||||
radio;课务安排/允许换课去掉 P7 标注(课务字段已落地输入侧,代课执行仍属 P7)。
|
||||
- LeaveDetail:显示课务安排。
|
||||
|
||||
**验证(2026-10-09)**:28 例单测全绿;smoke API 实测 calcWay/minMinutes 读写回显一致、
|
||||
20 分钟申请被拒(30 下限)、4h 半天口径(08:00-12:00)正确、courseArrange 落库字段就绪(流程 404
|
||||
回滚期无孤儿单);浏览器(真实账号李晶 + 8108 IDEA 实例 + devtools 热载):时间说明行、按小时表单、
|
||||
左侧 9 菜单节点渲染正常。
|
||||
|
||||
**端口变更**:application.yml `server.port` 8105 → **8108**(IDEA debug 实例),vite 代理同步改 8108。
|
||||
用户另起 dev server 于 **8079**(与 8078 同源,均为 vite --mode dev)。
|
||||
|
||||
**学校 3(李晶,对齐旧版生产)已切按天**:`calc_way=0`、`enable_course=1`(2026-10-09 经 DB 设置)。
|
||||
8079 实测:半天=0.5 天(合 4 小时)、半天以上 10-12 上午~10-14 下午=3 天(合 24 小时),与旧版算法一致。
|
||||
|
||||
**按小时模式对齐旧版(2026-10-09 二次勘察旧系统按小时形态后)**:
|
||||
起止时间改为旧版同款四控件一行(开始日期+开始时间 至 结束日期+结束时间),TimePicker 分钟按
|
||||
`最短请假间隔` 步进(`:steps=[1,intervalMinutes,1]`,对应旧版分钟过滤);时长标签「时长(小时)」
|
||||
纯小时显示(去掉天副注)。8079 实测 10-12 08:00~12:00 = 4 小时。验证后学校 3 已切回按天。
|
||||
|
||||
**附件 → 图片上传(2026-10-09,对齐旧版)**:`AttachmentUpload` 组件加 `imageOnly` 模式
|
||||
(仅 jpg/jpeg/bmp/png、单张 ≤20MB、accept 过滤、错误文案"只能上传 jpg/bmp/png 格式的图片!");
|
||||
申请页 FormItem「附件」→「图片」,按钮「上传图片」+ 提示
|
||||
「(上传图片格式:*.jpg、*.bmp、*.png 图片大小不要超过20MB)」逐字对齐旧版;
|
||||
详情页「附件」→「图片」,图片文件渲染 60×60 缩略图(点击预览直链打开)。
|
||||
后端 FileUpload 保持通用(格式限制在前端,P6 联调时可评估是否服务端加固)。
|
||||
|
||||
**申请页样式对齐旧版(2026-10-09 四轮,末轮按旧版真实 CSS 值精调)**:① label 统一加全角「:」
|
||||
(CSS `::after`,不逐个改);② `label-width` 100px、标签色 #272829(旧版实测值);
|
||||
③ **表单铺满内容区**(取消窄卡片约束,旧版控件实测宽 ~946px);④ 控件宽度按旧版实测:
|
||||
类型下拉/事由文本框**满宽**、日期 190px(旧版 193px)、时间 130px、时长 180px(旧版 180px);
|
||||
⑤ 行距 20px(旧版实测);⑥ 时长由蓝色文字改为**禁用 Input**(占位「自动获取时长」);
|
||||
⑦ 按天模式「起止时间」合并为一行:半天/半天以上 + 开始日期 + 上午/下午(半天以上再接
|
||||
「至」+ 结束日期 + 上午/下午);按小时模式同为四控件一行;⑧ 事由占位「请输入请假事由……」(旧版原文);
|
||||
⑨ 图片上传按钮改旧版同款方形图标按钮;⑩ 抄送人未选时显示灰色提示
|
||||
「请选择抄送人,审批通过后通知抄送人」;⑪ 时间说明文字色 #515a6e。
|
||||
8079 浏览器复核:满宽下拉/事由、100px 冒号标签、180px 时长框、一行起止时间、方形图片按钮均与旧版一致。
|
||||
|
||||
### P9 流程一致性与安全加固(2026-10-09 现状审计新增)
|
||||
|
||||
> 审计范围:以《子应用流程与消息对接手册-20260929》§2.3、§2.4、§5.1 为准,复核
|
||||
> `LeaveFlowService`、`LeaveFlowCallbackHandler`、申请身份边界及现有单测。P0~P8 已交付功能不重做。
|
||||
|
||||
**审计发现**:
|
||||
|
||||
1. 流程发起返回 `null` 或缺少 `instanceId` 时,当前代码仍可能提交为“审批中”,形成无真实流程实例的孤儿单。
|
||||
2. 拒绝/撤回后重新提交会复用同一请假主键;旧实例迟到事件若按主键兜底定位,可能误推进新 attempt。
|
||||
3. `LeaveRequest.userId` 来自客户端请求体,普通教师可构造该字段替其他用户发起申请;申请人必须只信任登录态。
|
||||
4. 手册 §5.1 要求 apply 请求契约断言;现有 12 例流程测试只覆盖回调状态机,没有覆盖
|
||||
`bizType/bizId/bizJump/bizSnapshot/formData/submitter`。
|
||||
|
||||
**实施任务**:
|
||||
|
||||
- [x] P9.1 发起结果强校验:`FlowApplyResult` 及 `instanceId` 必须非空,否则抛业务异常并由事务回滚。
|
||||
- [x] P9.2 回调实例隔离:事件 `bizId/instanceId` 必须与当前 attempt 一致;旧实例迟到事件正常忽略,不触发重推。
|
||||
- [x] P9.3 回调终态幂等:重复终态事件不重复产生副作用,冲突终态事件不得覆盖已落终态。
|
||||
- [x] P9.4 申请身份收口:Create 始终使用登录态 `currentUserId/userName`,删除前后端请求契约中的 `userId`。
|
||||
- [x] P9.5 补齐流程发起请求构造测试、空实例回滚边界测试、旧 attempt/终态冲突回调测试。
|
||||
- [x] P9.6 验证:后端 `mvn test` 34 例全绿;前端 `npm run typecheck:leave` 通过;`npm run build` 通过。
|
||||
全工程 `vue-tsc --noEmit` 已执行,另发现 74 个模板历史错误(知识树/年级班级/Vant 等未接入请假路由的遗留件),
|
||||
与本次请假业务文件无关,登记为 R7,不混入本阶段改造。
|
||||
|
||||
**验收标准**:
|
||||
|
||||
- 流程发起无实例 ID 时接口失败且事务不留下申请记录;正常结果完整回写 `bizId/instanceId`。
|
||||
- `100-1` 的迟到回调不能修改当前 `bizId=100-2` 的单据;重复 `completed` 只扣减一次。
|
||||
- 请求体即使伪造 `userId` 也不进入后端 DTO,申请人只取登录态。
|
||||
- 单测显式断言跳转为 `/detail?leaveId=xxx`、`bizType=leave`,以及快照/表单/提交人字段。
|
||||
|
||||
---
|
||||
|
||||
## 9. 风险与未决项
|
||||
|
||||
| # | 风险/未决 | 处置 |
|
||||
|---|---|---|
|
||||
| R1 | app_key/app_secret、flowGuid、菜单 modId 实际值未定(依赖管理员) | 全部走配置项;本地先以占位值 + mock 单测开发;P6 前与管理员确认 |
|
||||
| R2 | 回调在本地收不到(flow-center 不能推回本机) | 回调逻辑以单测 mock 覆盖;真链路 160 验收 |
|
||||
| R3 | Redis 不可达导致启动/健康检查异常 | `management.health.redis.enabled=false`;starter 只在回调验签用 Redis,本地联调可用 docker 起一个 |
|
||||
| R4 | 历史「按天/半天」模式与「课务安排」不影响 v1 核心闭环 | 已在决策中裁剪,P7 按需恢复 |
|
||||
| R5 | 教师名单来源(bschclass `GetSchteaList`)不可达时选人/时长列表降级 | 沿用现有 ClassService 缓存 + 空列表降级策略 |
|
||||
| R6 | 时长算法双端实现漂移 | 同一份规则单测用例(P2 后端 + P4 前端各一份对照) |
|
||||
| R7 | 前端模板遗留 74 个全量类型错误(知识树/年级班级/移动端 Vant 等未使用模块) | 新增 `tsconfig.leave.json` + `npm run typecheck:leave` 作为请假模块严格门禁;遗留模板另立清理任务,不影响当前构建 |
|
||||
|
||||
---
|
||||
|
||||
## 10. 执行记录(随实施更新)
|
||||
|
||||
| 日期 | 阶段 | 说明 |
|
||||
|---|---|---|
|
||||
| 2026-10-08 | 计划 | 本文档创建,勘察完成 |
|
||||
| 2026-10-08 | P0 完成 | 后端包名 `com.yunhai.workleave`、宿舍业务全删、工程标识更名 workleave;前端标识更名教师请假 |
|
||||
| 2026-10-08 | P1 完成 | 8 个业务实体 + 仓储 + 建库脚本(含 leaveType 字典种子);审计人改存 userId |
|
||||
| 2026-10-08 | P2 完成 | 设置域(作息/其他/节次)+ 时长域(列表/调整/流水/合计)+ LeaveTimeCalculator;16 例单测通过 |
|
||||
| 2026-10-08 | P3 完成 | 申请域(申请/编辑/删除/撤回/销假/待办/审核/统计)+ 流程门面 + 回调状态机;12 例单测通过(共 28 例) |
|
||||
| 2026-10-08 | P4/P5 完成 | 前端 types/api/routes/壳层重建;9 个业务页面落地;宿舍遗留全删 |
|
||||
| 2026-10-08 | P6 完成 | 后端 `mvn package` 出包 `target/workleave.jar`(105MB fat jar);28 例单测 + 前端 `npm run build` + `vue-tsc` 新增文件全绿;**启动验证**:`java -jar` 起 8105,Hibernate 自动建 13 张表(leave_apply/leave_setting/leave_time_set/leave_user_time/leave_user_time_record/leave_section/leave_cc/leave_audit_trace + 底座表),`/actuator/health` 返回 200,`leaveType` 字典种子可查(LT001~LT006) |
|
||||
| 2026-10-08 | 待办 | 160 环境全链路验收依赖 §3 前置项 A1~A6(app_key/flowGuid/菜单 modId 由管理员分配);`/api/*` 请求按平台 CAS 约定 302 跳 SSO 登录(浏览器带票据后正常,与改造前行为一致) |
|
||||
| 2026-10-09 | P6.5 冒烟 | S1~S6 全部执行完毕(结果见 §8 P6.5 表)。**新增冒烟辅助件** `config/DevMockUserFilter`(`smoke.auth.enabled=true` + `--yh.sso.url-pattern=/__smoke_off__/*` 启动即可本地免 CAS 冒烟)。**修复 3 个 bug**:① `DictService.normalizeType` 大小写比对错误(`"leaveType".equals("leavetype")` 恒 false)导致字典写接口全挂 → 改为忽略大小写比对;② `LeaveSettingService.saveOption` 显式 null 无法清空 `timeManagedTypes` → 改为无条件写入(null 视为清空);③ **`LeaveRequest.userId` 多余的 `@NotNull` 与前端契约冲突**(前端从不传 userId、后端本就从 UserContext 取)导致前端 Create/Edit 100% 被 Bean Validation 拦截报"不能为null" → 删除该注解。**前端修复**:ApplyLeave/LeaveSetting 共 6 处裸 `required` FormItem 补 `:model`/`prop`/`rules`,消除常驻 "undefined is required" 错误文案。修复后 28 例单测复跑全绿、前端重建通过、浏览器复验通过(提交失败 toast 正确透传"发起审批流程失败:OpenAPI 404",DB 无孤儿单)。流程中心 404 为 flowGuid 占位预期(待 A3) |
|
||||
| 2026-10-09 | P8 完成 | 对照旧版生产系统(oa.bestsch.com/leave,lwj 登录勘察)补齐:按天模式(calc_way + 半天/半天以上表单 + 天显示)、最少请假时间(min_minutes + 校验)、课务安排三选一(course_arrange)、申请页时间说明行;详见 §8 P8。真实账号(李晶)浏览器全链路验证通过;application.yml 端口改 8108(IDEA 实例),vite 代理同步 |
|
||||
| 2026-10-09 | P8 微调 | ① 移除申请页顶部「我要请假/编辑请假申请」标题(页面更贴近旧版留白);② **时长开放编辑**:申请页时长输入框由禁用改为可编辑(默认自动试算值、后缀带单位,起止时间变化后恢复自动值),提交携带手工值;后端 `resolveHours`:请求 totalHours>0 时以手工值为准(原逻辑一律服务端重算覆盖),最少请假时间/余额校验按最终落库值执行;后端编译 + 前端构建通过(后端需重启生效) |
|
||||
| 2026-10-09 | P8 教师时长设置对齐旧版 | 按旧版 oa.bestsch.com/leave/leave_timeSetting「教师时长设置」tab 实地截图 + 历史源码(请假前端 TeaTimeImport.vue、C# leave_System.cs ImportQJSchUserTime/ExportQJSchUserTime)重做:① 管控类型勾选迁入教师时长 tab(「请选择需要使用时长的请假类型」勾选即存,timeLimitEnabled 随勾选联动;其他设置中原两项移除);② 左侧教师组树过滤(base/GetSchoolAll 组节点);③ 表格逐行「新加时长(小时)InputNumber + 情况备注」,底部组内合计剩余时长 + 统一提交(复用 Adjust 批量接口),替代原逐人弹窗;④ 新增 Excel 导入:GET leave/time/ImportTemplate(easyexcel 出模板:部门/教师名称/新加时长(小时)/备注,教师按组预填)+ POST leave/time/Import(表头校验、按部门+姓名定位教师、任一行错整体不入账并返回行级错误明细,语义同旧版 C#);前端 LeaveSetting.vue 重构该 tab + 导入弹窗;mvn test 28 例全绿 + 前端 build 通过(后端需重启) |
|
||||
| 2026-10-09 | P9 完成 | 流程发起空实例强制失败回滚;回调增加当前 attempt/instance 隔离、重复终态幂等与冲突终态保护;Create 申请人只取登录态并移除前后端 `LeaveRequest.userId`;新增 6 例流程契约/边界测试(总计 34 例全绿),`npm run typecheck:leave` 与生产构建通过。全工程类型检查另检出 74 个未使用模板历史错误,见 R7。 |
|
||||
|
||||
### 交付物清单
|
||||
|
||||
**后端**(`E:\YHXT\java\WorkLeave`)
|
||||
- `docs/教师请假模块-改造计划-v1.md`(本文件)
|
||||
- `src/main/resources/sql/workleave_schema.sql`(建表 + 字典种子)
|
||||
- `src/main/java/com/yunhai/workleave/`(8 实体 + 8 仓储 + 4 控制器 + 5 服务 + 2 门面/回调)
|
||||
- `src/test/java/com/yunhai/workleave/`(28 例单测)
|
||||
- `target/workleave.jar`(可执行 fat jar)
|
||||
|
||||
**前端**(`E:\YHXT\view\WorkLeave`)
|
||||
- `src/types/leave.ts`、`src/api/module/leave.ts`、`src/router/routes.ts`
|
||||
- `src/utils/leaveTime.ts`(与后端同规则的时长试算)
|
||||
- `src/views/leave/*.vue`(9 个页面)
|
||||
- `dist/`(构建产物,vite build 直出)
|
||||
|
||||
### 启动与联调说明
|
||||
|
||||
```bash
|
||||
# 后端(本地库 work_leave,root/123123,见 application.yml)
|
||||
cd E:\YHXT\java\WorkLeave
|
||||
mvn -o -DskipTests package
|
||||
java -jar target/workleave.jar # 首启自动建表;8108 端口
|
||||
|
||||
# 前端
|
||||
cd E:\YHXT\view\WorkLeave
|
||||
npm install && npm run dev # 8078,/api 代理到 8108
|
||||
npm run typecheck:leave # 请假模块严格类型检查
|
||||
```
|
||||
|
||||
> 本地未起 Redis / 未配 flow-callback 时 `yh.flow-callback.enabled=false`,
|
||||
> 申请提交会因流程中心不可达而整体回滚(设计如此:不留"不回流"的孤儿单)。
|
||||
> 需要本地跑通提交链路时,按手册 §5.0 配 `yh.open.base-host` 指向 open 子域名并联通 160。
|
||||
|
||||
```bash
|
||||
# 本地冒烟模式(免 CAS + 模拟用户 1001/冒烟测试教师/schId=1,见 P6.5)
|
||||
java -jar target/workleave.jar --smoke.auth.enabled=true --yh.sso.url-pattern=/__smoke_off__/*
|
||||
```
|
||||
@@ -0,0 +1,140 @@
|
||||
# 流程管理页面 · 开发说明
|
||||
|
||||
> 依据《子应用流程与消息对接手册 v1.0》(2026-09-29)实现。
|
||||
> 日期:2026-10-09
|
||||
|
||||
## 一、做了什么
|
||||
|
||||
在「请假设置」(路由 `/setting`,菜单 modId=170)中新增第 6 个 Tab **流程管理**,提供:
|
||||
|
||||
1. **流程列表** —— 实时拉取流程中心「流程编排」里已注册的流程(名称 / flowGuid / 版本 / 节点摘要)。
|
||||
2. **流程绑定** —— 选中流程后绑定到本校,支持两级作用域;绑定即触发回调登记。
|
||||
|
||||
之所以做成设置页的 Tab 而非新路由:左侧菜单来自平台 `GetUserMod`,新增 modId 需平台侧配菜单才会出现;挂在已有的「请假设置」下可立即访问。后续若要在菜单单列,只需新增 `/flow` 路由(meta.modId 取新值)+ 平台配一条菜单,页面逻辑可直接复用。
|
||||
|
||||
## 二、手册要点落地对照
|
||||
|
||||
| 手册条款 | 实现 |
|
||||
|---|---|
|
||||
| §1.3 流程中心编排注册流程,记下 flowGuid;多套流程 = 多个 flowGuid | 流程列表来自 `YschFlowOpenApi.listFlows`;绑定表按 scope+scopeKey 存多条 |
|
||||
| §3.2 bizType 恒等于 app_code,不可点分档 | 页面顶部明示 `bizType = leave`;发起时 `FlowApplyBody.bizType` 仍取 `LeaveFlowService.BIZ_TYPE` |
|
||||
| §2.4 首次发起前完成回调登记 | `bind()` 保存绑定后立刻调用 `flowService.ensureRegistered(schId, flowGuid)` |
|
||||
| §2.4 登记失败不要阻断业务 | 登记异常只 warn,绑定照常落库 |
|
||||
| §6.4 别用异常过滤脏消息 | 流程中心不可达时不抛错,降级为 `centerReachable=false`,页面只展示已绑定关系 |
|
||||
|
||||
## 三、后端改动
|
||||
|
||||
### 新增文件
|
||||
|
||||
| 文件 | 作用 |
|
||||
|---|---|
|
||||
| `entity/LeaveFlowBind.java` | 流程绑定实体,表 `leave_flow_bind` |
|
||||
| `repository/LeaveFlowBindRepository.java` | 按 (sch_id, bind_scope, scope_key) 查询 |
|
||||
| `model/leave/FlowItemVo.java` | 流程列表项 + 绑定标记(是否默认 / 绑定到哪些类型) |
|
||||
| `model/leave/FlowBindVo.java` | 已绑定关系行 |
|
||||
| `model/leave/FlowManageVo.java` | 页面聚合视图(列表 + 绑定 + 请假类型 + 可达性) |
|
||||
| `model/leave/LeaveTypeOptionVo.java` | 请假类型选项 |
|
||||
| `model/leave/FlowBindRequest.java` | 绑定入参 |
|
||||
| `service/ILeaveFlowBindService.java`<br>`service/impl/LeaveFlowBindService.java` | 列表 / 绑定 / 解绑 / 发起时解析 flowGuid |
|
||||
| `controller/api/LeaveFlowController.java` | `/api/leave/flow/List`、`/Bind`、`/Unbind` |
|
||||
|
||||
### 修改文件
|
||||
|
||||
- `flow/ILeaveFlowService.java` + `flow/LeaveFlowService.java`:新增 `listFlows(schId)`,封装 `YschFlowOpenApi.listFlows(schId, FlowSummary.class)`。
|
||||
- `service/impl/LeaveApplyService.java`:`startFlow()` 的 flowGuid 由「配置默认值」改为「绑定解析」:
|
||||
|
||||
```
|
||||
单据已定(重提沿用) → 按请假类型绑定 → 学校默认绑定 → 配置 leave.flow.guid
|
||||
```
|
||||
|
||||
- `resources/sql/workleave_schema.sql`:补充 `leave_flow_bind` 建表脚本(线上以 `ddl-auto=update` 自动建表,该脚本用于人工建库/核对)。
|
||||
|
||||
### 数据表
|
||||
|
||||
```sql
|
||||
leave_flow_bind(
|
||||
sch_id, bind_scope(0=全校默认 / 1=按请假类型), scope_key,
|
||||
flow_guid, flow_name, flow_version, node_summary, ...
|
||||
)
|
||||
```
|
||||
|
||||
`scope=0` 时 `scope_key` 固定为 `-`。未建物理唯一索引(避免不同环境 `ddl-auto=update` 追加约束失败),唯一性由服务层 upsert 保证。
|
||||
|
||||
## 四、前端改动
|
||||
|
||||
- `types/leave.ts`:`FlowBindScope` / `FlowItemVo` / `FlowBindVo` / `LeaveTypeOptionVo` / `FlowManageVo` / `FlowBindRequest`。
|
||||
- `api/module/leave.ts`:新增 `leaveFlowApi`(`List` / `Bind` / `Unbind`),导出 `leaveFlowApi`。
|
||||
- `views/leave/LeaveSetting.vue`:新增「流程管理」Tab,含
|
||||
- 全校默认流程卡片(流程名 / 版本 / flowGuid / 节点摘要 + 更换 / 解除)
|
||||
- 流程列表表格(设为默认、按类型绑定;名称列以徽标展示绑定的请假类型名称,超过 2 个折叠为 +N、悬停看全量)
|
||||
- 绑定弹窗(范围单选 + 请假类型多选下拉 + 流程下拉 + 节点摘要预览;按类型绑定时预选该流程已绑类型,提交为替换语义——未勾选的原绑定类型自动解除)
|
||||
- 流程中心不可达时的告警条
|
||||
- 样式沿用页面既有 CSS 变量(`--set-*`),并补了 `prefers-color-scheme: dark` 深色覆写
|
||||
|
||||
> 2026-10-10 调整:移除独立的「按请假类型指定」表格卡片(类型绑定统一从流程列表「按类型绑定」入口操作);
|
||||
> 绑定弹窗请假类型由单选改为多选(后端 `FlowBindRequest` 新增 `scopeKeys` 列表,一次把多个类型绑到同一流程);
|
||||
> 流程列表名称列直接展示绑定的类型名称(原先只显示「N 个类型」计数)。
|
||||
>
|
||||
> 2026-10-10 二次调整:列表去掉「流程标识(flowGuid)」列,绑定类型从名称列徽标拆为独立
|
||||
> 「绑定的请假类型」列(超 2 个折叠 +N、悬停看全量,未绑定显示灰字);页面不再裸露 flowGuid——
|
||||
> 默认流程卡片、绑定弹窗预览、默认流程空态(回落值改为显示流程名,中心查不到才显示配置值)同步收敛,
|
||||
> 顶部提示删去 flowGuid 说明语句。
|
||||
|
||||
## 五、验收情况
|
||||
|
||||
- 后端 `mvn -o compile`(JDK8)通过。
|
||||
- 前端 `vue-tsc --noEmit -p tsconfig.leave.json` 通过;`vite build --mode base` 通过(154 modules transformed)。
|
||||
- **未做真链路联调**:本地不出站(手册 §5.0),流程中心不可达时页面走降级分支。需在 160 环境用真实 flowGuid 验证「列表拉取 → 绑定 → 发起 → 回调」。
|
||||
|
||||
## 六、遗留事项
|
||||
|
||||
1. 本地 `yh.flow-callback.enabled=false`,回调登记会跳过并 warn——属预期,上环境后由公共配置覆盖打开。
|
||||
2. 前端仓库根目录残留一个构建校验产物目录 `E:\YHXT\view\WorkLeave\dist-verify`(沙箱回收站不可用未能删除),可手动删除。
|
||||
|
||||
## 七、条件路由判断开放接口 `/oapi/flow/dept-judge`(2026-10-10)
|
||||
|
||||
流程中心 CONDITION 节点(`routerKey=work-leave.dept-judge`,路由注册表 path=`/oapi/flow/dept-judge`,
|
||||
serviceKey=work-leave)激活时**同步**回调本端点,按请假时长选出口:**小于3天 / 大于等于3天**。
|
||||
|
||||
已发布流程 `d2237a5b…`(v4)/ `c3e9c6ee…`(v7)「请假申请(小于3天或大于等于3天)」的两条出边
|
||||
name 即出口契约:`小于3天`、`大于等于3天`。
|
||||
|
||||
**契约**(对齐 noticeservice `OapiFlowRouteController` 参考实现,收在 `/oapi/*` 下由 AppAuthFilter
|
||||
验 flow-center 签名):
|
||||
|
||||
- 入参:POST JSON `{schId, instanceId, instanceVersion, bizType, bizId, nodeId, nodeName, routerKey, outlets, variables}`
|
||||
- `bizId` 形态 `{leaveId}-{attempt}`,取首段定位 `leave_apply`;
|
||||
- `outlets`:流程定义的出口名集合;`variables`:发起时透传的 formData(含 `totalHours`/`days`)。
|
||||
- 出参:`{"outlet": "小于3天" | "大于等于3天"}`。
|
||||
- 时长口径:**3天 = 24小时**(一天按作息 8 小时折算,同 `LeaveFlowService#buildFormData` 的
|
||||
`days=totalHours/8`);恰好 24h 归「大于等于3天」。
|
||||
- 免查库:`variables.totalHours` 存在则直接用(与发起口径同源),缺失回落按 bizId 查表。
|
||||
- 契约纪律:计算出的 outlet 不在 `outlets` 集合内 → 抛业务错让 flow-center 挂起,**绝不默认放行**;
|
||||
单据不存在 / 无时长同样挂起。
|
||||
|
||||
**鉴权**:`yh.app-auth.server.enforce-patterns: [/oapi/*]`(本次新增,对齐 noticeservice 生产姿势)——
|
||||
`/oapi/**` 无签名一律 401,有签名走中央密钥链校验;`application-local.yml` 已用永不匹配 pattern
|
||||
`[/__local_no_match__]` 关闭(不能直接置空:base 的 `enforce-patterns[0]` 索引键仍会使条件命中,
|
||||
而空 pattern 列表会让 FilterRegistration 启动报错),本地无签名联调不受影响。
|
||||
|
||||
**代码**:`controller/oapi/FlowJudgeController.java`(端点)+ `FlowJudgeControllerTest`(9 项单测:
|
||||
出口两分支 / 恰好3天边界 / variables 免查库与回落查表 / bizId 带重提后缀解析 / 出口不命中与缺时长挂起)。
|
||||
|
||||
**响应包装**:端点裸返回 `{"outlet":...}`,由 yh-api-std `ApiResponseAdvice` 统一包成
|
||||
`{code:0, msg, data:{outlet}}` —— notice 参考实现(生产在用)同受此包装,flow-center 读 `data.outlet`,行为一致。
|
||||
|
||||
**病假绑定与真链路测试(2026-10-10)**:
|
||||
|
||||
- 病假 = LT002(全局字典,schId=0),已在「流程管理」把 LT001/LT002/LT003/LT004/LT005 五类绑到
|
||||
c3e9c6ee(请假申请(小于3天或大于等于3天) v7,启用中)。v7 结构:CONDITION →「小于3天」审批人
|
||||
10007 /「大于等于3天」审批人 10008(lijing),两分支汇合后抄送 10008 → END。
|
||||
- 本地直连契约测试(模拟 flow-center 请求体)全过:16h→`小于3天`;24h 整→`大于等于3天`;
|
||||
variables 缺 totalHours→回落查表;出口不命中→业务错挂起(`code=2` + 明确 message)。
|
||||
- ⚠️ **平台侧前置缺口:流程中心的判断调用走开放网关 `{gateway}{basePath}{path}`
|
||||
(=`open.yunhaixingtong.com/work-leave/oapi/flow/dept-judge`),网关当前无 `/work-leave` 路由
|
||||
(404;对照 `/notice` 路由存在、401=已到达 notice 服务)⇒ 网关出不了 160,调用永远到不了本机,
|
||||
CONDITION 实例按设计**挂起在 u1c**。需平台部署 work-leave / 注册网关路由后真链路才能闭环。
|
||||
- 挂起实例可正常**撤回**(flow.withdrawn 正常回调,单据回已撤回)——是卡单的恢复路径。
|
||||
- 遗留:病假测试单 id=18(10-19 一天)在绑定生效前提交,走了兜底流程 d2237a5b 并卡在 u1c,
|
||||
可在「我的请假」撤回后重提(重提将按绑定走 c3e9c6ee)。
|
||||
|
||||
Reference in New Issue
Block a user