Files
jiu/web/features/approval.html
T
wangjia b90a89790f feat(web): 五个营销页面完整还原设计稿
统一 nav(5 个链接含支持、auth-aware 用户下拉菜单)、
docs 页补充面包屑/搜索/版本栏三栏布局、
footer 完整 5 列、所有内链替换为生产绝对路径。

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 00:57:32 +08:00

1745 lines
66 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>审核驱动业务流 · 岩美酒库管理系统</title>
<link rel="stylesheet" href="../assets/colors_and_type.css" />
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet">
<script src="https://unpkg.com/lucide@0.469.0/dist/umd/lucide.min.js"></script>
<style>
* { box-sizing: border-box; }
html, body {
margin: 0; padding: 0;
font-family: var(--font-sans); color: var(--fg-default);
background: var(--bg-surface);
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
font-feature-settings: "tnum" 1;
}
img, svg { display: block; max-width: 100%; }
a { color: inherit; text-decoration: none; }
button { font-family: inherit; cursor: pointer; }
.btn {
display: inline-flex; align-items: center; gap: 8px;
height: 40px; padding: 0 18px;
border-radius: var(--radius-md);
font-size: var(--text-md); font-weight: 500;
border: 1px solid transparent;
transition: background var(--duration-fast) var(--ease-standard);
white-space: nowrap;
}
.btn-primary { background: var(--brand-500); color: #fff; }
.btn-primary:hover { background: var(--brand-600); }
.btn-secondary { background: var(--gray-0); color: var(--brand-900); border-color: var(--border-default); }
.btn-secondary:hover { background: var(--gray-50); border-color: var(--border-strong); }
.btn-ghost { background: transparent; color: var(--fg-default); }
.btn-ghost:hover { background: var(--gray-100); }
.btn-lg { height: 48px; padding: 0 24px; font-size: var(--text-lg); }
.btn .icon { width: 18px; height: 18px; }
.container { max-width: 1280px; margin: 0 auto; padding: 0 32px; }
.section { padding: 96px 0; }
.eyebrow {
font-size: var(--text-xs); font-weight: 600;
letter-spacing: 0.12em; text-transform: uppercase;
color: var(--brand-500); margin: 0 0 12px;
}
/* Top Nav */
.topnav { position: sticky; top: 0; z-index: 50; background: rgba(255,255,255,0.85); backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px); border-bottom: 1px solid var(--border-subtle); }
.topnav-inner { height: 64px; display: flex; align-items: center; justify-content: space-between; max-width: 1440px; margin: 0 auto; padding: 0 32px; }
.topnav-brand { display: flex; align-items: center; gap: 10px; }
.topnav-brand img { height: 32px; }
.topnav-links { display: flex; align-items: center; gap: 4px; font-size: var(--text-md); color: var(--gray-700); }
.topnav-links a { padding: 8px 14px; border-radius: var(--radius-md); transition: background var(--duration-fast) var(--ease-standard); }
.topnav-links a:hover { background: var(--gray-100); color: var(--brand-900); }
.topnav-links a.active { color: var(--brand-500); background: var(--brand-50); }
.topnav-cta { display: flex; gap: 10px; align-items: center; }
/* Nav user dropdown */
.nav-user { position: relative; }
.nav-user-btn {
display: flex; align-items: center; gap: 8px;
height: 36px; padding: 0 12px 0 8px;
border-radius: var(--radius-md);
border: 1px solid var(--border-default);
background: var(--gray-0);
font-size: var(--text-sm); color: var(--gray-800);
cursor: pointer;
transition: background var(--duration-fast) var(--ease-standard);
}
.nav-user-btn:hover { background: var(--gray-50); }
.nav-avatar {
width: 22px; height: 22px; border-radius: 50%;
background: var(--brand-500); color: #fff;
font-size: 11px; font-weight: 700;
display: flex; align-items: center; justify-content: center;
flex-shrink: 0;
}
.nav-user-btn .icon { width: 14px; height: 14px; color: var(--fg-muted); }
.nav-dropdown {
display: none; position: absolute; top: calc(100% + 8px); right: 0;
min-width: 200px;
background: var(--gray-0);
border: 1px solid var(--border-default);
border-radius: var(--radius-lg);
box-shadow: var(--shadow-lg);
padding: 6px;
z-index: 100;
}
.nav-dropdown.open { display: block; }
.nav-dropdown-info {
padding: 8px 10px 10px;
border-bottom: 1px solid var(--border-subtle);
margin-bottom: 6px;
}
.nav-dropdown-name { font-size: var(--text-sm); font-weight: 600; color: var(--brand-900); }
.nav-dropdown-role { font-size: 11px; color: var(--fg-muted); margin-top: 2px; }
.nav-dropdown a, .nav-dropdown button {
display: flex; align-items: center; gap: 8px;
width: 100%; padding: 8px 10px;
border-radius: var(--radius-md);
font-size: var(--text-sm); color: var(--gray-700);
background: none; border: none; text-align: left; cursor: pointer;
transition: background var(--duration-fast) var(--ease-standard);
}
.nav-dropdown a:hover, .nav-dropdown button:hover { background: var(--gray-50); color: var(--brand-900); }
.nav-dropdown .icon { width: 15px; height: 15px; color: var(--fg-muted); }
.nav-dropdown .logout { color: var(--danger-600); }
.nav-dropdown .logout .icon { color: var(--danger-400); }
/* ====================================================================
HERO
==================================================================== */
.feat-hero {
position: relative; overflow: hidden;
padding: 80px 0 72px;
background:
radial-gradient(900px 480px at 10% 30%, rgba(37, 99, 172, 0.06), transparent 60%),
radial-gradient(900px 480px at 90% 90%, rgba(139, 35, 49, 0.04), transparent 60%),
linear-gradient(180deg, #FBFCFD 0%, #FFFFFF 80%);
border-bottom: 1px solid var(--border-subtle);
}
.feat-hero-inner {
display: grid;
grid-template-columns: 1fr 1.2fr;
gap: 64px;
align-items: center;
}
.feat-breadcrumb {
display: flex; align-items: center; gap: 8px;
font-size: var(--text-sm); color: var(--fg-muted);
margin-bottom: 20px;
}
.feat-breadcrumb a { color: var(--fg-muted); }
.feat-breadcrumb a:hover { color: var(--brand-500); }
.feat-breadcrumb .icon { width: 14px; height: 14px; color: var(--gray-300); }
.feat-hero h1 {
font-size: 48px; line-height: 1.15;
letter-spacing: var(--tracking-cn-display);
color: var(--brand-900); margin: 0 0 18px;
font-weight: 700;
}
.feat-hero h1 em {
font-style: normal; color: var(--accent-500);
}
.feat-hero p.lead {
font-size: var(--text-xl); line-height: 1.6;
color: var(--gray-600); margin: 0 0 28px;
}
.feat-hero .actions { display: flex; gap: 12px; margin-bottom: 28px; }
.feat-hero .quick-stats {
display: grid; grid-template-columns: repeat(3, 1fr);
border-top: 1px solid var(--border-subtle);
padding-top: 20px;
}
.feat-hero .quick-stat-label { font-size: 11px; color: var(--fg-subtle); letter-spacing: 0.04em; }
.feat-hero .quick-stat-val { font-size: var(--text-xl); font-weight: 600; color: var(--brand-900); font-variant-numeric: tabular-nums; margin-top: 4px; }
/* ====================================================================
HERO STAMP / DOCUMENT VISUAL
==================================================================== */
.hero-doc-stack {
position: relative;
height: 520px;
}
.hero-doc {
position: absolute;
background: var(--gray-0);
border: 1px solid var(--border-default);
border-radius: var(--radius-lg);
box-shadow: var(--shadow-lg);
padding: 24px;
transform-origin: center;
}
.hero-doc-back-2 {
top: 36px; left: 24px; right: 64px; bottom: 64px;
transform: rotate(-2deg);
opacity: 0.6;
background: var(--gray-50);
}
.hero-doc-back-1 {
top: 18px; left: 12px; right: 80px; bottom: 80px;
transform: rotate(2deg);
opacity: 0.85;
background: var(--gray-25);
}
.hero-doc-main {
top: 0; left: 60px; right: 0; bottom: 60px;
box-shadow: var(--shadow-xl);
}
.hero-doc-head {
display: flex; justify-content: space-between; align-items: flex-start;
padding-bottom: 16px;
border-bottom: 1px solid var(--border-subtle);
margin-bottom: 16px;
}
.hero-doc-id {
font-family: var(--font-mono); font-size: var(--text-md);
color: var(--brand-500); font-weight: 600;
}
.hero-doc-meta { font-size: 11px; color: var(--fg-muted); margin-top: 4px; font-family: var(--font-mono); }
.hero-doc-status {
background: var(--success-50); color: var(--success-700);
font-size: 12px; font-weight: 600;
padding: 4px 12px; border-radius: var(--radius-pill);
display: inline-flex; align-items: center; gap: 4px;
}
.hero-doc-status .icon { width: 14px; height: 14px; }
.hero-doc-row {
display: grid; grid-template-columns: 1fr 60px 80px;
padding: 8px 0;
border-bottom: 1px dashed var(--border-subtle);
font-size: var(--text-sm);
color: var(--gray-700);
font-variant-numeric: tabular-nums;
}
.hero-doc-row:last-of-type { border-bottom: 1px solid var(--border-default); }
.hero-doc-foot {
display: grid; grid-template-columns: 1fr 60px 80px;
padding-top: 10px;
font-weight: 600;
color: var(--brand-900);
font-size: var(--text-md);
}
/* Stamp visual on hero */
.hero-stamp {
position: absolute;
right: -10px; bottom: 20px;
width: 130px; height: 130px;
border-radius: 50%;
border: 3px solid var(--accent-500);
background: rgba(139, 35, 49, 0.04);
transform: rotate(-12deg);
display: grid; place-items: center;
z-index: 10;
box-shadow: 0 6px 18px rgba(139, 35, 49, 0.15);
}
.hero-stamp::before {
content: '';
position: absolute; inset: 8px;
border-radius: 50%;
border: 1px solid var(--accent-500);
}
.hero-stamp svg.ring { position: absolute; inset: 0; width: 100%; height: 100%; }
.hero-stamp-core {
font-family: var(--font-sans);
color: var(--accent-500);
text-align: center;
font-weight: 700;
z-index: 1;
}
.hero-stamp-core .big { font-size: 22px; letter-spacing: 0.06em; }
.hero-stamp-core .sub { font-size: 9px; letter-spacing: 0.16em; margin-top: 2px; font-weight: 600; }
.hero-stamp-core .date { font-family: var(--font-mono); font-size: 10px; margin-top: 4px; color: var(--accent-700); font-weight: 500; }
/* ====================================================================
FEATURE BLOCKS — alternating
==================================================================== */
.feat-block {
padding: 96px 0;
border-bottom: 1px solid var(--border-subtle);
}
.feat-block:nth-child(even) { background: var(--gray-25); }
.feat-block-inner {
display: grid;
grid-template-columns: 5fr 7fr;
gap: 64px;
align-items: center;
}
.feat-block.reverse .feat-block-inner { grid-template-columns: 7fr 5fr; }
.feat-block.reverse .feat-text { order: 2; }
.feat-block.reverse .feat-visual { order: 1; }
.feat-tag {
display: inline-flex; align-items: center; gap: 6px;
background: var(--brand-50);
color: var(--brand-700);
padding: 4px 10px;
border-radius: var(--radius-pill);
font-size: var(--text-xs);
font-weight: 600;
letter-spacing: 0.04em;
margin-bottom: 16px;
}
.feat-tag.danger { background: var(--danger-50); color: var(--danger-700); }
.feat-tag.success { background: var(--success-50); color: var(--success-700); }
.feat-tag .icon { width: 14px; height: 14px; }
.feat-text h2 {
font-size: 36px; font-weight: 600;
line-height: 1.18; letter-spacing: var(--tracking-cn-display);
color: var(--brand-900); margin: 0 0 16px;
}
.feat-text p.lead {
font-size: var(--text-lg); line-height: 1.7;
color: var(--gray-600); margin: 0 0 24px;
}
.feat-bullets { list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; gap: 14px; }
.feat-bullets li {
display: grid; grid-template-columns: 22px 1fr;
gap: 12px;
font-size: var(--text-md); color: var(--gray-700);
line-height: 1.6; align-items: start;
}
.feat-bullets li .icon { width: 18px; height: 18px; color: var(--brand-500); margin-top: 2px; }
.feat-bullets li strong { color: var(--brand-900); font-weight: 600; display: block; margin-bottom: 2px; }
/* ====================================================================
BLOCK 1: STATE MACHINE
==================================================================== */
.state-machine {
background: var(--gray-0);
border: 1px solid var(--border-default);
border-radius: var(--radius-lg);
box-shadow: var(--shadow-md);
padding: 48px 36px 40px;
position: relative;
overflow: hidden;
}
.state-machine-tabs {
position: absolute; top: 16px; left: 16px;
display: flex; gap: 2px;
background: var(--gray-50);
padding: 3px;
border-radius: var(--radius-md);
border: 1px solid var(--border-default);
}
.state-tab {
padding: 4px 12px;
font-size: 11px;
font-weight: 500;
color: var(--fg-muted);
border-radius: var(--radius-sm);
letter-spacing: 0.04em;
}
.state-tab.active {
background: var(--brand-500); color: #fff;
}
.state-machine-meta {
position: absolute; top: 24px; right: 20px;
font-size: 11px; color: var(--fg-subtle);
font-family: var(--font-mono); letter-spacing: 0.04em;
}
/* Diagram */
.sm-diagram {
display: grid;
grid-template-columns: 1fr 50px 1fr 50px 1fr;
gap: 0;
align-items: center;
margin-top: 24px;
}
.sm-node {
display: flex; flex-direction: column;
align-items: center; gap: 8px;
}
.sm-bubble {
padding: 12px 18px;
border-radius: var(--radius-pill);
font-size: var(--text-md);
font-weight: 600;
letter-spacing: 0.04em;
border: 2px solid;
display: inline-flex; align-items: center; gap: 6px;
background: var(--gray-0);
}
.sm-bubble .icon { width: 14px; height: 14px; }
.sm-node.draft .sm-bubble { border-color: var(--gray-300); color: var(--gray-700); background: var(--gray-50); }
.sm-node.pending .sm-bubble { border-color: var(--info-500); color: var(--info-700); background: var(--info-50); }
.sm-node.approved .sm-bubble {
border-color: var(--success-500); color: var(--success-700);
background: var(--success-50);
box-shadow: 0 0 0 4px rgba(46, 139, 87, 0.10);
}
.sm-node.rejected .sm-bubble { border-color: var(--danger-500); color: var(--danger-700); background: var(--danger-50); }
.sm-node small { font-size: 11px; color: var(--fg-muted); font-family: var(--font-mono); }
.sm-arrow {
display: flex; align-items: center; justify-content: center;
color: var(--gray-400);
position: relative;
}
.sm-arrow svg { width: 36px; height: 14px; }
.sm-arrow-label {
position: absolute; top: -22px;
font-size: 10px; color: var(--fg-subtle);
font-family: var(--font-mono); letter-spacing: 0.04em;
white-space: nowrap;
}
.sm-fork {
display: flex; flex-direction: column; gap: 20px;
position: relative;
}
.sm-fork::before {
content: '';
position: absolute;
left: -28px; top: 50%; transform: translateY(-50%);
width: 1px; height: 60%;
background: var(--gray-300);
}
/* applies-to legend */
.sm-applies {
margin-top: 32px;
padding-top: 20px;
border-top: 1px dashed var(--border-default);
display: flex; gap: 10px; align-items: center;
flex-wrap: wrap;
font-size: var(--text-xs);
color: var(--fg-muted);
letter-spacing: 0.02em;
}
.sm-applies-pill {
display: inline-flex; align-items: center; gap: 6px;
padding: 4px 10px;
background: var(--gray-50);
border: 1px solid var(--border-default);
border-radius: var(--radius-md);
font-size: 11px;
color: var(--gray-700);
font-weight: 500;
}
.sm-applies-pill .icon { width: 12px; height: 12px; color: var(--brand-500); }
/* ====================================================================
BLOCK 2: AUTO-LINKING — Before/After
==================================================================== */
.linking-visual {
display: flex; flex-direction: column; gap: 0;
}
.linking-doc {
background: var(--gray-0);
border: 1px solid var(--border-default);
border-radius: var(--radius-lg);
box-shadow: var(--shadow-md);
padding: 20px 24px;
position: relative;
}
.linking-doc-head {
display: flex; justify-content: space-between; align-items: center;
margin-bottom: 12px;
}
.linking-doc-id {
font-family: var(--font-mono); font-size: var(--text-md);
color: var(--brand-700); font-weight: 600;
}
.linking-doc-action {
display: inline-flex; align-items: center; gap: 6px;
padding: 6px 14px;
background: var(--success-500); color: #fff;
border-radius: var(--radius-md);
font-size: 12px; font-weight: 600;
letter-spacing: 0.04em;
}
.linking-doc-action .icon { width: 14px; height: 14px; }
.linking-arrow {
width: 100%;
display: flex; align-items: center; justify-content: center;
padding: 16px 0;
position: relative;
}
.linking-arrow::before, .linking-arrow::after {
content: ''; height: 1px; flex: 1;
background: var(--gray-200);
}
.linking-arrow-label {
margin: 0 14px;
padding: 4px 14px;
background: var(--success-50);
color: var(--success-700);
border-radius: var(--radius-pill);
font-size: var(--text-xs);
font-weight: 600;
letter-spacing: 0.04em;
display: inline-flex; align-items: center; gap: 6px;
}
.linking-arrow-label .icon { width: 13px; height: 13px; }
.linking-effects {
display: grid; grid-template-columns: 1fr 1fr;
gap: 12px;
}
.linking-effect {
background: var(--gray-0);
border: 1px solid var(--border-default);
border-radius: var(--radius-lg);
padding: 20px;
position: relative;
}
.linking-effect-label {
font-size: 11px; color: var(--fg-muted);
font-weight: 600; letter-spacing: 0.06em;
text-transform: uppercase;
margin-bottom: 12px;
display: flex; align-items: center; gap: 6px;
}
.linking-effect-label .icon { width: 14px; height: 14px; color: var(--brand-500); }
.linking-effect-num {
font-size: 28px; font-weight: 700;
color: var(--brand-900);
font-variant-numeric: tabular-nums;
font-family: var(--font-mono);
}
.linking-effect-from {
font-size: 12px; color: var(--fg-subtle);
font-family: var(--font-mono);
margin-top: 4px;
font-variant-numeric: tabular-nums;
}
.linking-effect-from .delta { color: var(--success-700); font-weight: 600; }
/* ====================================================================
BLOCK 3: STOCK VALIDATION — Error visual
==================================================================== */
.validation-card {
background: var(--gray-0);
border: 1px solid var(--border-default);
border-radius: var(--radius-lg);
box-shadow: var(--shadow-md);
overflow: hidden;
}
.validation-head {
padding: 16px 20px;
background: var(--gray-50);
border-bottom: 1px solid var(--border-subtle);
display: flex; justify-content: space-between; align-items: center;
}
.validation-title { font-size: var(--text-sm); font-weight: 600; color: var(--brand-900); }
.validation-meta { font-size: 11px; color: var(--fg-muted); font-family: var(--font-mono); }
.validation-body { padding: 20px; }
.validation-row {
display: grid;
grid-template-columns: 1fr 60px 60px 80px;
gap: 12px;
padding: 10px 0;
border-bottom: 1px solid var(--border-subtle);
font-size: var(--text-sm);
align-items: center;
font-variant-numeric: tabular-nums;
}
.validation-row.head { font-size: 11px; color: var(--fg-muted); font-weight: 500; padding-top: 0; }
.validation-row .qty-req { font-weight: 600; color: var(--gray-900); }
.validation-row .qty-stock { color: var(--gray-700); }
.validation-row .qty-shortage {
background: var(--danger-50);
color: var(--danger-700);
font-weight: 600;
padding: 2px 8px;
border-radius: var(--radius-pill);
font-size: 11px;
text-align: center;
display: inline-block;
}
.validation-row.invalid .qty-req { color: var(--danger-700); }
.error-toast {
margin: 16px 0 0;
padding: 14px 16px;
background: var(--danger-50);
border: 1px solid #F8B4B4;
border-left: 3px solid var(--danger-500);
border-radius: var(--radius-md);
display: grid; grid-template-columns: 22px 1fr;
gap: 12px;
}
.error-toast .icon { width: 18px; height: 18px; color: var(--danger-700); margin-top: 1px; }
.error-toast-title {
font-size: var(--text-sm); font-weight: 600;
color: var(--danger-700);
margin-bottom: 2px;
}
.error-toast-body { font-size: 12px; color: var(--gray-700); line-height: 1.55; }
.api-response {
background: var(--gray-900);
color: #DCE2EB;
font-family: var(--font-mono);
font-size: 12px;
padding: 16px 18px;
border-radius: var(--radius-md);
margin-top: 16px;
line-height: 1.7;
overflow-x: auto;
}
.api-response .key { color: #ADC9EA; }
.api-response .str { color: #C97B86; }
.api-response .num { color: #FFD9A6; }
.api-response .bool { color: #C97B86; font-weight: 500; }
/* ====================================================================
BLOCK 4: AUDIT LOG TIMELINE
==================================================================== */
.audit-timeline {
background: var(--gray-0);
border: 1px solid var(--border-default);
border-radius: var(--radius-lg);
box-shadow: var(--shadow-md);
overflow: hidden;
}
.audit-head {
padding: 14px 18px;
background: var(--gray-50);
border-bottom: 1px solid var(--border-subtle);
display: flex; justify-content: space-between; align-items: center;
}
.audit-doc-id { font-family: var(--font-mono); color: var(--brand-700); font-size: var(--text-sm); font-weight: 600; }
.audit-doc-status {
padding: 3px 10px; border-radius: var(--radius-pill);
background: var(--success-50); color: var(--success-700);
font-size: 11px; font-weight: 600;
}
.audit-events { padding: 4px 18px 18px; }
.audit-event {
display: grid;
grid-template-columns: 32px 1fr;
gap: 14px;
padding: 14px 0;
border-bottom: 1px solid var(--border-subtle);
position: relative;
}
.audit-event:last-child { border-bottom: none; }
.audit-event-marker {
width: 32px; height: 32px;
border-radius: 50%;
display: grid; place-items: center;
position: relative;
z-index: 1;
}
.audit-event-marker .icon { width: 16px; height: 16px; }
.audit-event.create .audit-event-marker { background: var(--gray-100); color: var(--gray-700); }
.audit-event.submit .audit-event-marker { background: var(--info-50); color: var(--info-700); border: 1px solid var(--info-500); }
.audit-event.approve .audit-event-marker { background: var(--success-50); color: var(--success-700); border: 1px solid var(--success-500); }
.audit-event.reject .audit-event-marker { background: var(--danger-50); color: var(--danger-700); border: 1px solid var(--danger-500); }
.audit-event.settle .audit-event-marker { background: var(--brand-50); color: var(--brand-700); border: 1px solid var(--brand-500); }
.audit-event::before {
content: '';
position: absolute;
left: 15px; top: 32px; bottom: -14px;
width: 1px;
background: var(--gray-200);
}
.audit-event:last-child::before { display: none; }
.audit-event-body { padding-top: 5px; }
.audit-event-action {
font-size: var(--text-sm);
font-weight: 600;
color: var(--brand-900);
margin-bottom: 2px;
}
.audit-event-meta {
font-size: 12px; color: var(--fg-muted);
display: flex; gap: 12px;
flex-wrap: wrap;
font-variant-numeric: tabular-nums;
letter-spacing: 0.02em;
}
.audit-event-meta strong { color: var(--gray-800); font-weight: 500; }
.audit-event-meta .time { font-family: var(--font-mono); font-size: 11px; }
.audit-event-comment {
margin-top: 6px;
padding: 8px 12px;
background: var(--gray-25);
border-radius: var(--radius-sm);
font-size: 12px;
color: var(--gray-700);
line-height: 1.55;
font-style: italic;
border-left: 2px solid var(--gray-300);
}
/* ====================================================================
BLOCK 5: REVERSAL / RED-MARK
==================================================================== */
.reversal-pair {
display: grid;
grid-template-columns: 1fr 60px 1fr;
gap: 0;
align-items: center;
}
.reversal-doc {
background: var(--gray-0);
border: 1px solid var(--border-default);
border-radius: var(--radius-lg);
box-shadow: var(--shadow-md);
padding: 18px;
position: relative;
}
.reversal-doc.original .reversal-stamp-mini { color: var(--success-500); border-color: var(--success-500); }
.reversal-doc.reversal {
background: linear-gradient(135deg, #FDFAFA 0%, #FFF 100%);
border-color: #F1D2D7;
}
.reversal-stamp-mini {
position: absolute;
top: 14px; right: 14px;
font-size: 10px;
border: 1.5px solid var(--accent-500);
color: var(--accent-500);
padding: 2px 8px;
border-radius: var(--radius-sm);
font-weight: 700;
letter-spacing: 0.08em;
transform: rotate(-4deg);
}
.reversal-doc-id {
font-family: var(--font-mono); font-size: 13px;
color: var(--brand-700); font-weight: 600;
margin-bottom: 4px;
}
.reversal-doc-meta {
font-size: 11px; color: var(--fg-muted);
font-family: var(--font-mono);
margin-bottom: 12px;
padding-bottom: 12px;
border-bottom: 1px solid var(--border-subtle);
}
.reversal-doc-row {
display: grid; grid-template-columns: 1fr 60px;
font-size: 12px;
padding: 5px 0;
color: var(--gray-700);
font-variant-numeric: tabular-nums;
}
.reversal-doc.reversal .reversal-doc-row { color: var(--accent-700); }
.reversal-doc-foot {
margin-top: 12px; padding-top: 10px;
border-top: 1px solid var(--border-default);
font-size: var(--text-sm); font-weight: 700;
color: var(--brand-900);
display: grid; grid-template-columns: 1fr 70px;
font-variant-numeric: tabular-nums;
}
.reversal-doc.reversal .reversal-doc-foot { color: var(--accent-500); }
.reversal-arrow {
display: flex; flex-direction: column;
align-items: center; gap: 6px;
color: var(--accent-500);
font-size: 10px;
font-family: var(--font-mono);
letter-spacing: 0.06em;
}
.reversal-arrow svg { width: 24px; height: 24px; }
.reversal-arrow-label {
background: var(--accent-50);
color: var(--accent-700);
padding: 3px 10px;
border-radius: var(--radius-pill);
font-size: 10px;
font-weight: 600;
letter-spacing: 0.04em;
}
.reversal-net {
margin-top: 24px;
padding: 16px 20px;
background: var(--brand-900);
color: #fff;
border-radius: var(--radius-md);
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 16px;
}
.reversal-net-item { text-align: center; }
.reversal-net-label {
font-size: 11px; color: #ADC9EA;
letter-spacing: 0.06em; margin-bottom: 4px;
}
.reversal-net-val {
font-size: var(--text-xl); font-weight: 700;
font-variant-numeric: tabular-nums;
font-family: var(--font-mono);
}
/* ====================================================================
BLOCK 6: PERMISSION MATRIX
==================================================================== */
.perm-matrix {
background: var(--gray-0);
border: 1px solid var(--border-default);
border-radius: var(--radius-lg);
overflow: hidden;
box-shadow: var(--shadow-md);
}
.perm-row {
display: grid;
grid-template-columns: 200px repeat(4, 1fr);
align-items: center;
border-bottom: 1px solid var(--border-subtle);
font-size: var(--text-sm);
}
.perm-row.head {
background: var(--brand-900); color: #fff;
font-size: 12px; font-weight: 500;
letter-spacing: 0.04em;
}
.perm-row.head > div { padding: 14px 16px; }
.perm-row.head > div:not(:first-child) { text-align: center; border-left: 1px solid rgba(255,255,255,0.08); }
.perm-row:last-child { border-bottom: none; }
.perm-role {
padding: 16px 20px;
background: var(--gray-25);
font-weight: 600;
color: var(--brand-900);
}
.perm-role small {
display: block; font-weight: 400;
color: var(--fg-muted);
font-size: 11px;
margin-top: 2px;
letter-spacing: 0.02em;
}
.perm-cell {
padding: 16px;
text-align: center;
border-left: 1px solid var(--border-subtle);
}
.perm-cell .icon { width: 18px; height: 18px; display: inline-block; }
.perm-cell.yes .icon { color: var(--success-500); }
.perm-cell.no .icon { color: var(--gray-300); }
.perm-cell.partial { color: var(--warning-500); font-size: 11px; font-weight: 500; }
/* ====================================================================
USE CASES
==================================================================== */
.usecases-grid {
display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px;
margin-top: 48px;
}
.usecase-card {
background: var(--gray-0);
border: 1px solid var(--border-default);
border-radius: var(--radius-lg);
padding: 28px;
}
.usecase-card .icon { width: 36px; height: 36px; color: var(--brand-500); margin-bottom: 16px; }
.usecase-card h4 { margin: 0 0 8px; font-size: var(--text-lg); font-weight: 600; color: var(--brand-900); }
.usecase-card p { margin: 0; font-size: var(--text-sm); color: var(--gray-700); line-height: 1.65; }
.usecase-card .quote {
margin-top: 14px;
font-size: 12px;
color: var(--fg-muted);
font-style: italic;
border-left: 2px solid var(--brand-300);
padding-left: 12px;
}
/* Final CTA */
.cta-strip {
background:
radial-gradient(800px 360px at 20% 30%, rgba(139, 35, 49, 0.20), transparent 60%),
linear-gradient(135deg, #0A1F3B 0%, #15407D 100%);
color: #fff;
border-radius: var(--radius-xl);
padding: 56px 48px;
display: grid;
grid-template-columns: 1.4fr 1fr;
gap: 32px;
align-items: center;
margin: 0 0 96px;
}
.cta-strip h2 { font-size: 32px; font-weight: 600; letter-spacing: var(--tracking-cn-display); color: #fff; margin: 0 0 12px; line-height: 1.25; }
.cta-strip p { font-size: var(--text-md); color: #ADC9EA; margin: 0; line-height: 1.6; }
.cta-strip-actions { display: flex; flex-direction: column; gap: 10px; align-items: flex-end; }
.cta-strip .btn-primary { background: #fff; color: var(--brand-700); }
.cta-strip .btn-primary:hover { background: #DCE2EB; }
.cta-strip .btn-secondary { background: transparent; color: #fff; border-color: rgba(255,255,255,0.30); }
.cta-strip .btn-secondary:hover { background: rgba(255,255,255,0.10); }
/* Footer */
footer { background: var(--gray-25); border-top: 1px solid var(--border-subtle); padding: 64px 0 32px; color: var(--fg-muted); font-size: var(--text-sm); }
.footer-grid { display: grid; grid-template-columns: 1.5fr repeat(4, 1fr); gap: 48px; margin-bottom: 48px; }
.footer-brand img { height: 32px; margin-bottom: 16px; }
.footer-brand p { margin: 0 0 16px; max-width: 320px; line-height: 1.6; color: var(--fg-muted); }
.footer-contact { font-size: var(--text-sm); color: var(--gray-700); line-height: 1.8; }
.footer-col h5 { font-size: var(--text-xs); font-weight: 600; color: var(--brand-900); letter-spacing: 0.06em; margin: 0 0 16px; text-transform: uppercase; }
.footer-col ul { list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; gap: 10px; }
.footer-col li a { color: var(--gray-600); }
.footer-col li a:hover { color: var(--brand-500); }
.footer-bottom { border-top: 1px solid var(--border-subtle); padding-top: 24px; display: flex; justify-content: space-between; font-size: var(--text-xs); color: var(--fg-subtle); }
@media (max-width: 1024px) {
.feat-hero-inner, .feat-block-inner, .feat-block.reverse .feat-block-inner, .cta-strip, .linking-effects, .reversal-pair { grid-template-columns: 1fr; gap: 24px; }
.feat-block.reverse .feat-text, .feat-block.reverse .feat-visual { order: initial; }
.usecases-grid { grid-template-columns: 1fr 1fr; }
.footer-grid { grid-template-columns: 1fr 1fr; }
.feat-hero h1 { font-size: 36px; }
.sm-diagram { grid-template-columns: 1fr; gap: 16px; }
.sm-arrow { transform: rotate(90deg); }
.perm-row { grid-template-columns: 130px repeat(4, 1fr); }
.reversal-arrow { transform: rotate(90deg); padding: 16px 0; }
}
</style>
</head>
<body>
<nav class="topnav">
<div class="topnav-inner">
<a href="/" class="topnav-brand">
<img src="../assets/logo-full.svg" alt="岩美" />
</a>
<div class="topnav-links">
<a href="/#modules" class="active">产品</a>
<a href="/#pricing">价格</a>
<a href="/download.html">下载</a>
<a href="/docs.html">文档</a>
<a href="/#faq">支持</a>
</div>
<div class="topnav-cta" id="nav-cta"></div>
</div>
</nav>
<!-- ============== HERO ============== -->
<section class="feat-hero">
<div class="container feat-hero-inner">
<div>
<div class="feat-breadcrumb">
<a href="/">首页</a>
<i data-lucide="chevron-right" class="icon"></i>
<a href="/#modules">产品功能</a>
<i data-lucide="chevron-right" class="icon"></i>
<span style="color: var(--gray-900); font-weight: 500;">审核驱动业务流</span>
</div>
<p class="eyebrow">功能详情 · 审核驱动业务流</p>
<h1>每一笔库存变动,<br/>都经过一次<em>签字</em></h1>
<p class="lead">
入库、出库、盘点、调拨 —— 统一状态机,统一审批界面。审批不仅是一个按钮,是一笔事务:库存动了,账款也跟着动,全程留痕。
</p>
<div class="actions">
<a href="#" class="btn btn-primary btn-lg">免费试用 30 天</a>
<a href="/docs.html" class="btn btn-secondary btn-lg">
<i data-lucide="book-open" class="icon"></i>查看文档
</a>
</div>
<div class="quick-stats">
<div>
<div class="quick-stat-label">审批延迟</div>
<div class="quick-stat-val">≤ 1s</div>
</div>
<div>
<div class="quick-stat-label">审计字段</div>
<div class="quick-stat-val">14</div>
</div>
<div>
<div class="quick-stat-label">回滚机制</div>
<div class="quick-stat-val">红冲</div>
</div>
</div>
</div>
<div class="hero-doc-stack">
<div class="hero-doc hero-doc-back-2"></div>
<div class="hero-doc hero-doc-back-1"></div>
<div class="hero-doc hero-doc-main">
<div class="hero-doc-head">
<div>
<div class="hero-doc-id">RK20260523000007</div>
<div class="hero-doc-meta">入库审核 · A 仓 · 茅台经销华东</div>
</div>
<div class="hero-doc-status">
<i data-lucide="check" class="icon"></i>已审批
</div>
</div>
<div class="hero-doc-row">
<div>茅台飞天 53° · 500ml×6</div><div>4</div><div>¥8,000</div>
</div>
<div class="hero-doc-row">
<div>五粮液第八代 52°</div><div>20</div><div>¥18,400</div>
</div>
<div class="hero-doc-row">
<div>剑南春水晶剑 52°</div><div>2</div><div>¥4,200</div>
</div>
<div class="hero-doc-row">
<div>拉菲传奇波尔多</div><div>10</div><div>¥17,800</div>
</div>
<div class="hero-doc-foot">
<div>合计</div><div>36</div><div>¥48,400</div>
</div>
<div style="margin-top: 20px; padding-top: 14px; border-top: 1px dashed var(--border-default); font-size: 11px; color: var(--fg-muted); font-family: var(--font-mono); letter-spacing: 0.02em;">
审批人 李建国 · 2026-05-23 14:32:08
</div>
</div>
<!-- Stamp -->
<div class="hero-stamp">
<svg class="ring" viewBox="0 0 130 130">
<defs>
<path id="stampRing" d="M 65, 65 m -50, 0 a 50,50 0 1,1 100,0 a 50,50 0 1,1 -100,0" />
</defs>
<text fontSize="8" fill="#8B2331" fontFamily="'Noto Sans SC', sans-serif" fontWeight="600" letterSpacing="3">
<textPath href="#stampRing" startOffset="2%">岩美 · 审批通过 · APPROVED · 岩美 ·</textPath>
</text>
</svg>
<div class="hero-stamp-core">
<div class="big">通过</div>
<div class="sub">APPROVED</div>
<div class="date">2026.05.23</div>
</div>
</div>
</div>
</div>
</section>
<!-- ============== BLOCK 1: State machine ============== -->
<section class="feat-block">
<div class="container feat-block-inner">
<div class="feat-text">
<div class="feat-tag"><i data-lucide="git-branch" class="icon"></i>统一状态机</div>
<h2>四种状态,<br/>覆盖所有业务单据</h2>
<p class="lead">
入库、出库、盘点、调拨 —— 全部走同一套状态:草稿 → 待审核 → 已审批 / 已拒绝。员工学一次,全员通用。
</p>
<ul class="feat-bullets">
<li>
<i data-lucide="lock" class="icon"></i>
<div>
<strong>提交后即锁定</strong>
从「草稿」进入「待审核」即不可编辑,避免审批人看到的与录入人提交的不一致。
</div>
</li>
<li>
<i data-lucide="zap" class="icon"></i>
<div>
<strong>状态机由后端事务驱动</strong>
状态变更与库存 / 账款变更在同一事务内,要么都成功,要么都回滚。
</div>
</li>
<li>
<i data-lucide="layers" class="icon"></i>
<div>
<strong>一套界面,四种单据复用</strong>
前端审批界面统一,节省培训成本,避免分模块状态差异。
</div>
</li>
</ul>
</div>
<div class="feat-visual">
<div class="state-machine">
<div class="state-machine-tabs">
<div class="state-tab active">入库单</div>
<div class="state-tab">出库单</div>
<div class="state-tab">盘点单</div>
<div class="state-tab">调拨单</div>
</div>
<div class="state-machine-meta">stock_in_orders.status</div>
<div class="sm-diagram">
<div class="sm-node draft">
<div class="sm-bubble">
<i data-lucide="pencil" class="icon"></i>草稿
</div>
<small>draft</small>
</div>
<div class="sm-arrow">
<span class="sm-arrow-label">提交</span>
<svg viewBox="0 0 36 14"><line x1="0" y1="7" x2="30" y2="7" stroke="currentColor" strokeWidth="1.5"/><polyline points="26 3 32 7 26 11" fill="none" stroke="currentColor" strokeWidth="1.5"/></svg>
</div>
<div class="sm-node pending">
<div class="sm-bubble">
<i data-lucide="clock" class="icon"></i>待审核
</div>
<small>pending</small>
</div>
<div class="sm-arrow">
<span class="sm-arrow-label">审批</span>
<svg viewBox="0 0 36 14"><line x1="0" y1="7" x2="30" y2="7" stroke="currentColor" strokeWidth="1.5"/><polyline points="26 3 32 7 26 11" fill="none" stroke="currentColor" strokeWidth="1.5"/></svg>
</div>
<div class="sm-fork">
<div class="sm-node approved">
<div class="sm-bubble">
<i data-lucide="check" class="icon"></i>已审批
</div>
<small>approved</small>
</div>
<div class="sm-node rejected">
<div class="sm-bubble">
<i data-lucide="x" class="icon"></i>已拒绝
</div>
<small>rejected</small>
</div>
</div>
</div>
<div class="sm-applies">
<span>同一状态机适用于:</span>
<span class="sm-applies-pill"><i data-lucide="package-plus" class="icon"></i>入库单</span>
<span class="sm-applies-pill"><i data-lucide="package-minus" class="icon"></i>出库单</span>
<span class="sm-applies-pill"><i data-lucide="clipboard-check" class="icon"></i>盘点单</span>
<span class="sm-applies-pill"><i data-lucide="arrow-right-left" class="icon"></i>调拨单</span>
</div>
</div>
</div>
</div>
</section>
<!-- ============== BLOCK 2: Auto-linking ============== -->
<section class="feat-block reverse">
<div class="container feat-block-inner">
<div class="feat-text">
<div class="feat-tag success"><i data-lucide="zap" class="icon"></i>自动联动</div>
<h2>按一次"通过"<br/>库存与账款都跟着动</h2>
<p class="lead">
审批通过不是一个"标记",而是一组真实的数据库写入。库存增减、应付应收、批次创建 —— 在同一事务内完成。
</p>
<ul class="feat-bullets">
<li>
<i data-lucide="database" class="icon"></i>
<div>
<strong>事务级一致性</strong>
底层用 <code style="font-family: var(--font-mono); font-size: 0.9em; background: var(--gray-100); padding: 1px 5px; border-radius: 3px;">FOR UPDATE</code> 锁住库存行,杜绝并发超卖。
</div>
</li>
<li>
<i data-lucide="link-2" class="icon"></i>
<div>
<strong>账款自动落地</strong>
入库审批 → 自动生成应付;出库审批 → 自动生成应收。挂在对应往来单位名下,可逐月对账。
</div>
</li>
<li>
<i data-lucide="git-merge" class="icon"></i>
<div>
<strong>批次自动建立</strong>
每笔入库审批通过即创建一个新批次,后续出库按 FIFO 自动扣减。
</div>
</li>
</ul>
</div>
<div class="feat-visual">
<div class="linking-visual">
<div class="linking-doc">
<div class="linking-doc-head">
<div>
<div class="linking-doc-id">RK20260523000007</div>
<div style="font-size: 11px; color: var(--fg-muted); margin-top: 2px; font-family: var(--font-mono);">36 瓶 · ¥48,400 · 待审核</div>
</div>
<button class="linking-doc-action">
<i data-lucide="check" class="icon"></i>审批通过
</button>
</div>
</div>
<div class="linking-arrow">
<span class="linking-arrow-label">
<i data-lucide="zap" class="icon"></i>事务内同步执行
</span>
</div>
<div class="linking-effects">
<div class="linking-effect">
<div class="linking-effect-label">
<i data-lucide="package" class="icon"></i>库存
</div>
<div class="linking-effect-num">+36 瓶</div>
<div class="linking-effect-from">A 仓 · 2,780 <span class="delta">→ 2,816</span></div>
</div>
<div class="linking-effect">
<div class="linking-effect-label">
<i data-lucide="wallet" class="icon"></i>应付账款
</div>
<div class="linking-effect-num">+¥48,400</div>
<div class="linking-effect-from">茅台经销华东 · 新增 1 笔</div>
</div>
<div class="linking-effect">
<div class="linking-effect-label">
<i data-lucide="layers" class="icon"></i>批次
</div>
<div class="linking-effect-num">+4 个</div>
<div class="linking-effect-from">L-2026-05-001 ~ L-2026-05-004</div>
</div>
<div class="linking-effect">
<div class="linking-effect-label">
<i data-lucide="scroll-text" class="icon"></i>库存流水
</div>
<div class="linking-effect-num">+4 条</div>
<div class="linking-effect-from">逐 SKU 写入 inventory_logs</div>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- ============== BLOCK 3: Stock validation ============== -->
<section class="feat-block">
<div class="container feat-block-inner">
<div class="feat-text">
<div class="feat-tag danger"><i data-lucide="shield-alert" class="icon"></i>出库库存校验</div>
<h2>库存不足,<br/>审批直接被拦下</h2>
<p class="lead">
出库单审批前,系统按 SKU 实时核对库存。任一行不足,整单审批失败、库存与账款均不变。永远不会超卖。
</p>
<ul class="feat-bullets">
<li>
<i data-lucide="search-check" class="icon"></i>
<div>
<strong>逐行校验</strong>
按出库单每一行的 商品 × 仓库 × 数量 校验,失败明确指出是哪一行。
</div>
</li>
<li>
<i data-lucide="undo-2" class="icon"></i>
<div>
<strong>整单回滚</strong>
校验失败时单据状态回到「待审核」,无需重新录入。
</div>
</li>
<li>
<i data-lucide="settings-2" class="icon"></i>
<div>
<strong>可配置宽松模式</strong>
系统设置中可开启「允许超量出库」(备货场景使用),缺省关闭。
</div>
</li>
</ul>
</div>
<div class="feat-visual">
<div class="validation-card">
<div class="validation-head">
<div class="validation-title">出库单 CK20260523000012 · 审批前校验</div>
<div class="validation-meta">客户 北辰大酒店 · B 仓</div>
</div>
<div class="validation-body">
<div class="validation-row head">
<div>商品</div><div>需出</div><div>库存</div><div>状态</div>
</div>
<div class="validation-row">
<div>茅台飞天 53° · 500ml×6</div>
<div class="qty-req">24</div>
<div class="qty-stock">148</div>
<div style="text-align: right;"><span style="background: var(--success-50); color: var(--success-700); font-size: 11px; padding: 2px 8px; border-radius: var(--radius-pill); font-weight: 500;">充足</span></div>
</div>
<div class="validation-row invalid">
<div>剑南春水晶剑 52°</div>
<div class="qty-req">20</div>
<div class="qty-stock">12</div>
<div style="text-align: right;"><span class="qty-shortage">短缺 8</span></div>
</div>
<div class="validation-row">
<div>拉菲传奇波尔多 · 750ml</div>
<div class="qty-req">6</div>
<div class="qty-stock">64</div>
<div style="text-align: right;"><span style="background: var(--success-50); color: var(--success-700); font-size: 11px; padding: 2px 8px; border-radius: var(--radius-pill); font-weight: 500;">充足</span></div>
</div>
<div class="error-toast">
<i data-lucide="x-circle" class="icon"></i>
<div>
<div class="error-toast-title">审批已拒绝 · 库存不足</div>
<div class="error-toast-body">
第 2 行「剑南春水晶剑」B 仓库存仅 12 瓶,本单需 20 瓶,短缺 8 瓶。请补足库存后重新提交,或修改出库数量。
</div>
</div>
</div>
<div class="api-response">
<span style="color: #888;">// HTTP/1.1 422 Unprocessable Entity</span><br/>
{<br/>
&nbsp;&nbsp;<span class="key">"ok"</span>: <span class="bool">false</span>,<br/>
&nbsp;&nbsp;<span class="key">"error"</span>: <span class="str">"insufficient_inventory"</span>,<br/>
&nbsp;&nbsp;<span class="key">"items"</span>: [<br/>
&nbsp;&nbsp;&nbsp;&nbsp;{ <span class="key">"sku"</span>: <span class="str">"XJ-330-024"</span>, <span class="key">"required"</span>: <span class="num">20</span>, <span class="key">"available"</span>: <span class="num">12</span> }<br/>
&nbsp;&nbsp;]<br/>
}
</div>
</div>
</div>
</div>
</div>
</section>
<!-- ============== BLOCK 4: Audit log ============== -->
<section class="feat-block reverse">
<div class="container feat-block-inner">
<div class="feat-text">
<div class="feat-tag"><i data-lucide="file-clock" class="icon"></i>审计留痕</div>
<h2>每一步操作,<br/>都签着人名、贴着时间</h2>
<p class="lead">
从录入到结清,每一次状态变更都记录经办人、时间、决策、备注。是单据的完整生命周期,也是合规审计的完整证据。
</p>
<ul class="feat-bullets">
<li>
<i data-lucide="user-check" class="icon"></i>
<div>
<strong>谁动的、什么时候动的</strong>
14 个字段:actor / role / device / IP / 时间 / 备注 / 旧值 / 新值 / 原因 …
</div>
</li>
<li>
<i data-lucide="hard-drive" class="icon"></i>
<div>
<strong>永久保留</strong>
审计日志不会被任何角色(含超级管理员)删除或修改。
</div>
</li>
<li>
<i data-lucide="file-down" class="icon"></i>
<div>
<strong>可导出</strong>
按单据 / 时间段 / 经办人 任意组合筛选导出,应对税务、内审、合规检查。
</div>
</li>
</ul>
</div>
<div class="feat-visual">
<div class="audit-timeline">
<div class="audit-head">
<div class="audit-doc-id">RK20260523000007</div>
<div class="audit-doc-status">已审批</div>
</div>
<div class="audit-events">
<div class="audit-event create">
<div class="audit-event-marker"><i data-lucide="plus" class="icon"></i></div>
<div class="audit-event-body">
<div class="audit-event-action">创建草稿</div>
<div class="audit-event-meta">
<span><strong>王芳</strong> · 操作员</span>
<span class="time">2026-05-23 11:24:08</span>
<span>192.168.10.42 · 入库 PC</span>
</div>
</div>
</div>
<div class="audit-event create">
<div class="audit-event-marker"><i data-lucide="pencil" class="icon"></i></div>
<div class="audit-event-body">
<div class="audit-event-action">编辑明细</div>
<div class="audit-event-meta">
<span><strong>王芳</strong></span>
<span class="time">2026-05-23 13:02:51</span>
<span>修改 4 项 · 新增 1 项</span>
</div>
</div>
</div>
<div class="audit-event submit">
<div class="audit-event-marker"><i data-lucide="upload" class="icon"></i></div>
<div class="audit-event-body">
<div class="audit-event-action">提交审核</div>
<div class="audit-event-meta">
<span><strong>王芳</strong></span>
<span class="time">2026-05-23 13:15:22</span>
<span>状态 · 草稿 → 待审核</span>
</div>
</div>
</div>
<div class="audit-event approve">
<div class="audit-event-marker"><i data-lucide="check" class="icon"></i></div>
<div class="audit-event-body">
<div class="audit-event-action">审批通过</div>
<div class="audit-event-meta">
<span><strong>李建国</strong> · 管理员</span>
<span class="time">2026-05-23 14:32:08</span>
<span>库存 +36 · 应付 +¥48,400</span>
</div>
<div class="audit-event-comment">已核对数量与单价,可入库。</div>
</div>
</div>
<div class="audit-event settle">
<div class="audit-event-marker"><i data-lucide="wallet" class="icon"></i></div>
<div class="audit-event-body">
<div class="audit-event-action">应付结清</div>
<div class="audit-event-meta">
<span><strong>张磊</strong> · 财务</span>
<span class="time">2026-05-26 10:08:33</span>
<span>AP20260523000012 · 全额结清</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- ============== BLOCK 5: Reversal ============== -->
<section class="feat-block">
<div class="container feat-block-inner">
<div class="feat-text">
<div class="feat-tag" style="background: var(--accent-50); color: var(--accent-700);"><i data-lucide="undo" class="icon"></i>红冲机制</div>
<h2>审批不可撤销,<br/>但错误可以修正</h2>
<p class="lead">
审批一旦通过,原单据永远是历史的真实记录。如有错误,只能通过红冲(反向调整单)来修正 —— 既保留审计链,又能纠正库存与账款。
</p>
<ul class="feat-bullets">
<li>
<i data-lucide="archive" class="icon"></i>
<div>
<strong>原单不动</strong>
原始单据完整保留,不被覆盖、不被删除,永久可查。
</div>
</li>
<li>
<i data-lucide="rotate-ccw" class="icon"></i>
<div>
<strong>反向调整自动生成</strong>
点击「红冲」一键生成反向单据,数量取反、关联原单据 ID、状态自动进入「待审核」。
</div>
</li>
<li>
<i data-lucide="calculator" class="icon"></i>
<div>
<strong>净库存为零</strong>
红冲单审批通过后,库存与账款回到红冲前的状态,但审计链上多了两笔记录而非清除。
</div>
</li>
</ul>
</div>
<div class="feat-visual">
<div class="reversal-pair">
<div class="reversal-doc original">
<div class="reversal-stamp-mini" style="color: var(--success-500); border-color: var(--success-500);">已审批</div>
<div class="reversal-doc-id">RK20260523000007</div>
<div class="reversal-doc-meta">2026-05-23 · 入库 · 茅台经销华东</div>
<div class="reversal-doc-row"><div>茅台飞天 53°</div><div>+4</div></div>
<div class="reversal-doc-row"><div>五粮液第八代</div><div>+20</div></div>
<div class="reversal-doc-row"><div>剑南春水晶剑</div><div>+2</div></div>
<div class="reversal-doc-row"><div>拉菲传奇</div><div>+10</div></div>
<div class="reversal-doc-foot"><div>合计</div><div>+36 · ¥48,400</div></div>
</div>
<div class="reversal-arrow">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><polyline points="1 4 1 10 7 10"/><path d="M3.51 15a9 9 0 1 0 2.13-9.36L1 10"/></svg>
<span class="reversal-arrow-label">红冲</span>
</div>
<div class="reversal-doc reversal">
<div class="reversal-stamp-mini">红 冲</div>
<div class="reversal-doc-id">RK20260524000001</div>
<div class="reversal-doc-meta">2026-05-24 · 反向调整 · 关联 ...000007</div>
<div class="reversal-doc-row"><div>茅台飞天 53°</div><div>4</div></div>
<div class="reversal-doc-row"><div>五粮液第八代</div><div>20</div></div>
<div class="reversal-doc-row"><div>剑南春水晶剑</div><div>2</div></div>
<div class="reversal-doc-row"><div>拉菲传奇</div><div>10</div></div>
<div class="reversal-doc-foot"><div>合计</div><div>36 · −¥48,400</div></div>
</div>
</div>
<div class="reversal-net">
<div class="reversal-net-item">
<div class="reversal-net-label">原单库存影响</div>
<div class="reversal-net-val">+36 瓶</div>
</div>
<div class="reversal-net-item">
<div class="reversal-net-label">红冲库存影响</div>
<div class="reversal-net-val" style="color: #C97B86;">36 瓶</div>
</div>
<div class="reversal-net-item">
<div class="reversal-net-label">净影响</div>
<div class="reversal-net-val" style="color: var(--success-500);">0</div>
</div>
</div>
</div>
</div>
</section>
<!-- ============== BLOCK 6: Permission matrix ============== -->
<section class="feat-block reverse">
<div class="container feat-block-inner">
<div class="feat-text">
<div class="feat-tag"><i data-lucide="users" class="icon"></i>权限矩阵</div>
<h2>四级角色,<br/>各干各的事</h2>
<p class="lead">
系统内置四个角色,按岗位职责拆分审批权。最小权限原则 —— 不该看的看不到,不该签的签不了。
</p>
<ul class="feat-bullets">
<li>
<i data-lucide="user" class="icon"></i>
<div>
<strong>角色绑定门店</strong>
一个用户只属于一个门店(<code style="font-family: var(--font-mono); font-size: 0.9em; background: var(--gray-100); padding: 1px 5px; border-radius: 3px;">shop_id</code>),跨店访问数据天然隔离。
</div>
</li>
<li>
<i data-lucide="shield" class="icon"></i>
<div>
<strong>角色升降随时生效</strong>
管理员调整角色后,下一次请求即按新权限校验,无需重新登录。
</div>
</li>
<li>
<i data-lucide="eye" class="icon"></i>
<div>
<strong>只读角色可看不可改</strong>
适合财务、税务、内审岗位 —— 看见全部数据,但不能产生任何变更。
</div>
</li>
</ul>
</div>
<div class="feat-visual">
<div class="perm-matrix">
<div class="perm-row head">
<div>角色</div>
<div>查看</div>
<div>录入</div>
<div>审批</div>
<div>设置</div>
</div>
<div class="perm-row">
<div class="perm-role">超级管理员<small>superadmin · 系统最高</small></div>
<div class="perm-cell yes"><i data-lucide="check" class="icon"></i></div>
<div class="perm-cell yes"><i data-lucide="check" class="icon"></i></div>
<div class="perm-cell yes"><i data-lucide="check" class="icon"></i></div>
<div class="perm-cell yes"><i data-lucide="check" class="icon"></i></div>
</div>
<div class="perm-row">
<div class="perm-role">管理员<small>admin · 门店负责人</small></div>
<div class="perm-cell yes"><i data-lucide="check" class="icon"></i></div>
<div class="perm-cell yes"><i data-lucide="check" class="icon"></i></div>
<div class="perm-cell yes"><i data-lucide="check" class="icon"></i></div>
<div class="perm-cell yes"><i data-lucide="check" class="icon"></i></div>
</div>
<div class="perm-row">
<div class="perm-role">操作员<small>operator · 日常录入</small></div>
<div class="perm-cell yes"><i data-lucide="check" class="icon"></i></div>
<div class="perm-cell yes"><i data-lucide="check" class="icon"></i></div>
<div class="perm-cell yes"><i data-lucide="check" class="icon"></i></div>
<div class="perm-cell no"><i data-lucide="minus" class="icon"></i></div>
</div>
<div class="perm-row">
<div class="perm-role">只读<small>readonly · 财务 / 内审</small></div>
<div class="perm-cell yes"><i data-lucide="check" class="icon"></i></div>
<div class="perm-cell no"><i data-lucide="minus" class="icon"></i></div>
<div class="perm-cell no"><i data-lucide="minus" class="icon"></i></div>
<div class="perm-cell no"><i data-lucide="minus" class="icon"></i></div>
</div>
</div>
<div style="margin-top: 14px; font-size: 12px; color: var(--fg-muted); display: flex; gap: 14px; padding: 0 4px;">
<span style="display: flex; align-items: center; gap: 6px;">
<i data-lucide="check" style="width: 12px; height: 12px; color: var(--success-500);"></i>允许
</span>
<span style="display: flex; align-items: center; gap: 6px;">
<i data-lucide="minus" style="width: 12px; height: 12px; color: var(--gray-300);"></i>禁止
</span>
</div>
</div>
</div>
</section>
<!-- ============== Use cases ============== -->
<section class="section" style="background: var(--bg-surface); border-bottom: 1px solid var(--border-subtle);">
<div class="container">
<p class="eyebrow">典型场景</p>
<h2 style="font-size: 36px; font-weight: 600; color: var(--brand-900); margin: 0 0 12px; letter-spacing: var(--tracking-cn-display);">
审核驱动业务流,落到实际操作里是什么样
</h2>
<p style="font-size: var(--text-lg); color: var(--fg-muted); max-width: 640px; margin: 0;">
三个常见门店场景,看审核流如何把日常工作中的失误拦在事前。
</p>
<div class="usecases-grid">
<div class="usecase-card">
<i data-lucide="package-x" class="icon"></i>
<h4>避免超卖发不了货</h4>
<p>大客户来订 24 瓶飞天,操作员录了出库单。审批时系统提示某 SKU 库存只剩 12 瓶 —— 提前发现,先补货后出库。</p>
<div class="quote">"以前是发了货才发现没库存,半夜紧急调货。现在审批就拦下,省事多了。"</div>
</div>
<div class="usecase-card">
<i data-lucide="file-search" class="icon"></i>
<h4>财务来对账,3 秒定位</h4>
<p>财务问"这批拉菲是哪个供应商进的,谁审的",打开单据点击「审计链」,从草稿、提交、审批到结清的每一步都列得清清楚楚。</p>
<div class="quote">"以前要翻三个 Excel 表对照,现在一张图就讲完了。"</div>
</div>
<div class="usecase-card">
<i data-lucide="rotate-ccw" class="icon"></i>
<h4>录错了不慌</h4>
<p>新员工录错入库单一次性多写了 10 瓶,审批通过后才发现。点击「红冲」,反向调整单一键生成,1 分钟修正库存与账款。</p>
<div class="quote">"原始数据保留,红冲也留痕,内审完全不会有问题。"</div>
</div>
</div>
</div>
</section>
<!-- ============== Final CTA ============== -->
<section>
<div class="container" style="padding-top: 80px;">
<div class="cta-strip">
<div>
<h2>每一笔库存变动都有人负责,<br/>就从一次审批开始。</h2>
<p>30 天免费试用,无需绑定支付方式。所有审批与审计数据可随时导出。</p>
</div>
<div class="cta-strip-actions">
<a href="#" class="btn btn-primary btn-lg">
免费开通试用<i data-lucide="arrow-right" class="icon"></i>
</a>
<a href="/features/inventory.html" class="btn btn-secondary btn-lg">
← 上一个功能:库存管理
</a>
</div>
</div>
</div>
</section>
<!-- ============== Footer ============== -->
<footer>
<div class="container">
<div class="footer-grid">
<div class="footer-brand">
<img src="../assets/logo-full.svg" alt="岩美" />
<p>为酒行与酒店设计的库存、审核、财务一体化管理平台。</p>
<div class="footer-contact">
<div>support@yanmei.app</div>
<div>400-880-8888</div>
</div>
</div>
<div class="footer-col">
<h5>产品</h5>
<ul>
<li><a href="/#modules">核心模块</a></li>
<li><a href="/features/inventory.html">库存管理</a></li>
<li><a href="/features/approval.html">审核流</a></li>
<li><a href="/#pricing">价格方案</a></li>
<li><a href="/download.html">下载客户端</a></li>
</ul>
</div>
<div class="footer-col">
<h5>资源</h5>
<ul>
<li><a href="/docs.html">使用手册</a></li>
<li><a href="#">API 文档</a></li>
<li><a href="#">视频教程</a></li>
<li><a href="#">数据导入模板</a></li>
</ul>
</div>
<div class="footer-col">
<h5>客户支持</h5>
<ul>
<li><a href="/#faq">常见问题</a></li>
<li><a href="#">提交工单</a></li>
<li><a href="#">技术支持</a></li>
<li><a href="#">服务等级</a></li>
</ul>
</div>
<div class="footer-col">
<h5>公司</h5>
<ul>
<li><a href="#">关于岩美</a></li>
<li><a href="#">客户案例</a></li>
<li><a href="#">招贤纳士</a></li>
<li><a href="#">联系我们</a></li>
</ul>
</div>
</div>
<div class="footer-bottom">
<div>© 2026 岩美科技 · 保留所有权利</div>
<div>沪 ICP 备 2026000000 号 · 沪公网安备 31010000000000 号</div>
</div>
</div>
</footer>
<script>
function getAuthUser() {
var token = localStorage.getItem('flutter.access_token');
var username = localStorage.getItem('flutter.username');
var realName = localStorage.getItem('flutter.real_name') || '';
var role = localStorage.getItem('flutter.role') || '';
var shopNo = localStorage.getItem('flutter.shop_no') || '';
if (!token || !username) return null;
return { username: username, realName: realName, role: role, shopNo: shopNo };
}
function doLogout() {
['access_token','refresh_token','username','real_name','shop_no','shop_id','role']
.forEach(function(k) { localStorage.removeItem('flutter.' + k); });
location.reload();
}
function toggleUserMenu() {
var el = document.getElementById('userDropdown');
if (el) el.classList.toggle('open');
}
function renderNav() {
var cta = document.getElementById('nav-cta');
if (!cta) return;
var user = getAuthUser();
if (user) {
var initial = (user.realName || user.username).charAt(0).toUpperCase();
var displayName = user.realName || user.username;
var roleLabel = { admin: '管理员', operator: '操作员', readonly: '只读', superadmin: '超管' }[user.role] || user.role;
cta.innerHTML =
'<div class="nav-user">' +
'<button class="nav-user-btn" onclick="toggleUserMenu()">' +
'<div class="nav-avatar">' + initial + '</div>' +
'<span>' + displayName + '</span>' +
'<i data-lucide="chevron-down" class="icon"></i>' +
'</button>' +
'<div class="nav-dropdown" id="userDropdown">' +
'<div class="nav-dropdown-info">' +
'<div class="nav-dropdown-name">' + displayName + '</div>' +
'<div class="nav-dropdown-role">' + roleLabel + (user.shopNo ? ' · ' + user.shopNo : '') + '</div>' +
'</div>' +
'<a href="/app"><i data-lucide="layout-dashboard" class="icon"></i>进入系统</a>' +
'<button class="logout" onclick="doLogout()"><i data-lucide="log-out" class="icon"></i>退出登录</button>' +
'</div>' +
'</div>';
} else {
cta.innerHTML =
'<a href="/app/login" class="btn btn-ghost">登录</a>' +
'<a href="/app/register" class="btn btn-primary">免费试用</a>';
}
if (window.lucide) lucide.createIcons();
}
document.addEventListener('click', function(e) {
var dropdown = document.getElementById('userDropdown');
if (dropdown && dropdown.classList.contains('open')) {
var userEl = dropdown.closest('.nav-user');
if (userEl && !userEl.contains(e.target)) dropdown.classList.remove('open');
}
});
document.addEventListener('DOMContentLoaded', function() {
if (window.lucide) lucide.createIcons();
renderNav();
});
</script>
</body>
</html>