From d1260e8258d3a4d6687e17fed392dda3a5af6969 Mon Sep 17 00:00:00 2001 From: wangjia <809946525@qq.com> Date: Sun, 7 Jun 2026 22:05:35 +0800 Subject: [PATCH] =?UTF-8?q?refactor(web):=20features=20=E9=A1=B5=E6=94=B9?= =?UTF-8?q?=E4=B8=BA=20njk=20=E6=A8=A1=E6=9D=BF=EF=BC=8C=E6=8E=A5=E5=85=A5?= =?UTF-8?q?=20base.njk=20=E7=BB=9F=E4=B8=80=E5=AF=BC=E8=88=AA=E9=A1=B5?= =?UTF-8?q?=E8=84=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - inventory.html / approval.html → .njk,使用 layout: base.njk - 内联 CSS 提取为独立文件 assets/features-{name}.css(pageExtraCss 加载) - 移除各自硬编码的 nav/footer/script,改由 nav.njk、footer.njk、nav.js 统一提供 - permalink 保持 /features/*.html,现有链接无需修改 - .eleventy.js 移除 features/ passthrough Co-Authored-By: Claude Sonnet 4.6 --- web/.eleventy.js | 2 +- web/assets/features-approval.css | 915 ++++++++++++++++ web/assets/features-inventory.css | 657 +++++++++++ web/features/approval.html | 1701 ----------------------------- web/features/approval.njk | 615 +++++++++++ web/features/inventory.html | 1480 ------------------------- web/features/inventory.njk | 652 +++++++++++ 7 files changed, 2840 insertions(+), 3182 deletions(-) create mode 100644 web/assets/features-approval.css create mode 100644 web/assets/features-inventory.css delete mode 100644 web/features/approval.html create mode 100644 web/features/approval.njk delete mode 100644 web/features/inventory.html create mode 100644 web/features/inventory.njk diff --git a/web/.eleventy.js b/web/.eleventy.js index 9e7f194..4dbb12b 100644 --- a/web/.eleventy.js +++ b/web/.eleventy.js @@ -4,7 +4,7 @@ module.exports = function(cfg) { // scan.html 面向消费者,独立维护,直接复制不经模板 cfg.addPassthroughCopy("scan.html"); - cfg.addPassthroughCopy("features"); + // features/ 已改为 .njk 模板,不再需要 passthrough return { dir: { diff --git a/web/assets/features-approval.css b/web/assets/features-approval.css new file mode 100644 index 0000000..b37da45 --- /dev/null +++ b/web/assets/features-approval.css @@ -0,0 +1,915 @@ +* { 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); +} + +/* ==================================================================== + REVERSAL STYLES (unused, kept for possible future use) + ==================================================================== */ +.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; } +} +/* Hamburger */ +.nav-hamburger { display: none; flex-direction: column; justify-content: center; gap: 5px; width: 40px; height: 40px; padding: 8px; background: none; border: none; cursor: pointer; border-radius: 8px; } +.nav-hamburger span { display: block; height: 2px; width: 22px; background: var(--gray-700); border-radius: 2px; } +.nav-mobile-menu { display: none; flex-direction: column; background: var(--gray-0); border-top: 1px solid var(--border-subtle); padding: 8px 0 16px; } +.nav-mobile-menu.open { display: flex; } +.nav-mobile-menu a { padding: 12px 24px; font-size: var(--text-md); color: var(--gray-700); font-weight: 500; } +.nav-mobile-menu a:hover { background: var(--gray-50); color: var(--brand-900); } +.nav-mobile-cta { display: flex; flex-direction: column; gap: 8px; padding: 12px 24px 0; border-top: 1px solid var(--border-subtle); margin-top: 8px; } +@media (max-width: 768px) { + .nav-hamburger { display: flex; } + .topnav-links, .topnav-cta { display: none; } + .feat-hero-inner { grid-template-columns: 1fr; } +} +@media (max-width: 600px) { + .topnav-inner { padding: 0 16px; } + .feat-hero h1 { font-size: 28px; } + .footer-grid { grid-template-columns: 1fr; gap: 32px; } + .footer-bottom { flex-direction: column; gap: 8px; text-align: center; } + .usecases-grid { grid-template-columns: 1fr; } + .perm-row { overflow-x: auto; } + .container { padding: 0 16px; } +} + diff --git a/web/assets/features-inventory.css b/web/assets/features-inventory.css new file mode 100644 index 0000000..eb15564 --- /dev/null +++ b/web/assets/features-inventory.css @@ -0,0 +1,657 @@ +* { 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(1000px 480px at 88% 8%, rgba(37, 99, 172, 0.08), 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.15fr; + gap: 56px; + 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 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); + gap: 0; 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 composite: stacked cards */ +.composite { position: relative; height: 540px; } +.composite-card { + position: absolute; + background: var(--gray-0); + border: 1px solid var(--border-default); + border-radius: var(--radius-lg); + box-shadow: var(--shadow-md); + overflow: hidden; +} +.composite-main { top: 0; left: 0; right: 60px; bottom: 60px; box-shadow: var(--shadow-xl); } +.composite-secondary { right: 0; top: 70px; width: 240px; padding: 16px; box-shadow: var(--shadow-lg); } +.composite-tertiary { left: 80px; bottom: 0; right: 0; padding: 14px; box-shadow: var(--shadow-md); } + +.mc-head { + padding: 12px 16px; + background: var(--gray-50); + border-bottom: 1px solid var(--border-subtle); + display: flex; justify-content: space-between; align-items: center; +} +.mc-title { font-size: 13px; font-weight: 600; color: var(--brand-900); } +.mc-tabs { display: flex; gap: 4px; } +.mc-tab { padding: 3px 10px; font-size: 11px; color: var(--fg-muted); border-radius: var(--radius-sm); } +.mc-tab.active { background: var(--gray-0); color: var(--brand-700); border: 1px solid var(--brand-200); } + +.mc-stats { display: grid; grid-template-columns: repeat(3, 1fr); padding: 14px 16px; gap: 10px; border-bottom: 1px solid var(--border-subtle); background: var(--gray-25); } +.mc-stat-label { font-size: 10px; color: var(--fg-subtle); margin-bottom: 2px; } +.mc-stat-val { font-size: 16px; font-weight: 600; color: var(--brand-900); font-variant-numeric: tabular-nums; } +.mc-stat-val .delta { font-size: 10px; color: var(--success-500); margin-left: 4px; font-weight: 500; } + +.mc-table { font-size: 11px; } +.mc-thead, .mc-trow { + display: grid; + grid-template-columns: 88px 1fr 50px 60px 60px 70px; + padding: 8px 16px; + gap: 8px; + align-items: center; + font-variant-numeric: tabular-nums; +} +.mc-thead { background: var(--gray-50); font-size: 10px; font-weight: 500; color: var(--fg-muted); border-bottom: 1px solid var(--border-subtle); } +.mc-trow { border-bottom: 1px solid var(--border-subtle); color: var(--gray-800); } +.mc-trow:last-child { border-bottom: none; } +.mc-trow .sku { font-family: var(--font-mono); color: var(--brand-500); font-size: 10px; } +.mc-pill { display: inline-block; padding: 1px 7px; border-radius: var(--radius-pill); font-size: 9px; font-weight: 500; } +.mc-pill.ok { background: var(--success-50); color: var(--success-700); } +.mc-pill.warn { background: var(--warning-50); color: var(--warning-700); } +.mc-pill.low { background: var(--danger-50); color: var(--danger-700); } + +/* Alert card secondary */ +.alert-card-title { font-size: 11px; color: var(--fg-muted); margin-bottom: 8px; letter-spacing: 0.04em; } +.alert-row { + display: flex; justify-content: space-between; align-items: center; + padding: 8px 10px; + background: var(--warning-50); + border-left: 2px solid var(--warning-500); + border-radius: 3px; + margin-bottom: 6px; + font-size: 12px; +} +.alert-row.danger { background: var(--danger-50); border-color: var(--danger-500); } +.alert-row .name { color: var(--gray-900); font-weight: 500; flex: 1; } +.alert-row .qty { color: var(--gray-700); font-variant-numeric: tabular-nums; font-family: var(--font-mono); font-size: 11px; } + +/* Batch card (tertiary) */ +.batch-title { font-size: 11px; color: var(--fg-muted); margin-bottom: 6px; letter-spacing: 0.04em; display: flex; align-items: center; gap: 6px; } +.batch-title .icon { width: 12px; height: 12px; color: var(--brand-500); } +.batch-row { + display: grid; + grid-template-columns: 90px 1fr 60px 50px; + font-size: 11px; + padding: 6px 0; + border-bottom: 1px dashed var(--border-subtle); + align-items: center; + font-variant-numeric: tabular-nums; +} +.batch-row:last-child { border-bottom: none; } +.batch-row .lot { font-family: var(--font-mono); color: var(--brand-600); font-size: 10px; } +.batch-row .meta { color: var(--fg-muted); font-size: 10px; } + +/* ==================================================================== + FEATURE BLOCKS + ==================================================================== */ +.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 .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; } + +/* Visual primitives */ +.visual-card { + background: var(--gray-0); + border: 1px solid var(--border-default); + border-radius: var(--radius-lg); + box-shadow: var(--shadow-md); + overflow: hidden; +} +.visual-card-head { + padding: 14px 18px; + background: var(--gray-50); + border-bottom: 1px solid var(--border-subtle); + display: flex; justify-content: space-between; align-items: center; +} +.visual-card-title { font-size: var(--text-sm); font-weight: 600; color: var(--brand-900); } +.visual-card-meta { font-size: 11px; color: var(--fg-muted); } + +/* Inventory table visual */ +.inv-table { font-size: var(--text-sm); } +.inv-table-row { + display: grid; + grid-template-columns: 110px 1fr 70px 80px 80px 110px; + padding: 12px 18px; + gap: 12px; + border-bottom: 1px solid var(--border-subtle); + align-items: center; + font-variant-numeric: tabular-nums; +} +.inv-table-row.head { + background: var(--gray-50); + font-size: 11px; font-weight: 500; + color: var(--fg-muted); + letter-spacing: 0.02em; + padding: 10px 18px; +} +.inv-table-row:last-child { border-bottom: none; } +.inv-table-row:hover:not(.head) { background: var(--gray-25); } +.inv-table-row .sku { font-family: var(--font-mono); color: var(--brand-500); font-size: 12px; } +.inv-table-row .name { color: var(--gray-900); font-weight: 500; } +.inv-table-row .name small { display: block; color: var(--fg-muted); font-weight: 400; font-size: 11px; margin-top: 1px; } + +/* Batch tracking visual — journey */ +.batch-journey { + background: var(--gray-0); + border: 1px solid var(--border-default); + border-radius: var(--radius-lg); + box-shadow: var(--shadow-md); + padding: 28px; +} +.batch-journey-head { + display: flex; justify-content: space-between; align-items: flex-start; + margin-bottom: 24px; + padding-bottom: 18px; + border-bottom: 1px solid var(--border-subtle); +} +.bj-meta-main { font-size: var(--text-sm); } +.bj-lot { font-family: var(--font-mono); font-size: var(--text-xl); font-weight: 600; color: var(--brand-700); margin-bottom: 4px; } +.bj-product { font-size: var(--text-md); color: var(--gray-900); font-weight: 500; } +.bj-product small { color: var(--fg-muted); font-weight: 400; margin-left: 8px; } +.bj-totals { text-align: right; } +.bj-total-row { font-size: var(--text-xs); color: var(--fg-muted); display: flex; gap: 8px; justify-content: flex-end; margin-bottom: 4px; } +.bj-total-row strong { color: var(--gray-900); font-weight: 600; font-variant-numeric: tabular-nums; } + +.bj-progress { + background: var(--gray-100); + height: 8px; + border-radius: var(--radius-pill); + margin-bottom: 24px; + position: relative; + overflow: hidden; +} +.bj-progress-bar { + height: 100%; + background: linear-gradient(90deg, var(--brand-500), var(--brand-400)); + width: 67%; + border-radius: var(--radius-pill); +} +.bj-progress-labels { display: flex; justify-content: space-between; margin-top: -16px; } +.bj-progress-labels span { font-size: 11px; color: var(--fg-muted); font-variant-numeric: tabular-nums; } + +.bj-timeline { display: flex; flex-direction: column; gap: 0; } +.bj-event { + display: grid; + grid-template-columns: 16px 110px 1fr 80px 90px; + gap: 16px; + padding: 12px 0; + align-items: center; + border-bottom: 1px solid var(--border-subtle); + position: relative; +} +.bj-event:last-child { border-bottom: none; } +.bj-event-dot { + width: 10px; height: 10px; + border-radius: 50%; + background: var(--gray-300); + margin: 0 auto; +} +.bj-event.stockin .bj-event-dot { background: var(--brand-500); box-shadow: 0 0 0 4px var(--brand-50); } +.bj-event.stockout .bj-event-dot { background: var(--accent-500); } +.bj-event.adjust .bj-event-dot { background: var(--warning-500); } +.bj-event-date { font-size: 11px; color: var(--fg-muted); font-family: var(--font-mono); font-variant-numeric: tabular-nums; } +.bj-event-desc { font-size: var(--text-sm); color: var(--gray-800); } +.bj-event-desc small { display: block; color: var(--fg-muted); font-size: 11px; margin-top: 2px; } +.bj-event-qty { font-size: var(--text-md); font-weight: 600; font-variant-numeric: tabular-nums; text-align: right; } +.bj-event.stockin .bj-event-qty { color: var(--success-700); } +.bj-event.stockout .bj-event-qty { color: var(--accent-500); } +.bj-event-doc { font-size: 11px; color: var(--brand-500); font-family: var(--font-mono); text-align: right; } + +/* Stocktake comparison */ +.stocktake-table { + background: var(--gray-0); + border: 1px solid var(--border-default); + border-radius: var(--radius-lg); + overflow: hidden; + box-shadow: var(--shadow-md); +} +.st-summary { + display: grid; grid-template-columns: repeat(4, 1fr); + padding: 18px 20px; + background: var(--brand-900); + color: #fff; + gap: 16px; +} +.st-summary-item .label { font-size: 11px; color: #ADC9EA; margin-bottom: 4px; letter-spacing: 0.04em; } +.st-summary-item .val { font-size: var(--text-xl); font-weight: 600; font-variant-numeric: tabular-nums; } +.st-summary-item .val.neg { color: var(--danger-500); } + +.st-row { + display: grid; + grid-template-columns: 1fr 70px 70px 80px 90px; + padding: 12px 20px; + gap: 12px; + border-bottom: 1px solid var(--border-subtle); + font-size: var(--text-sm); + align-items: center; + font-variant-numeric: tabular-nums; +} +.st-row:last-child { border-bottom: none; } +.st-row.head { background: var(--gray-50); font-size: 11px; color: var(--fg-muted); font-weight: 500; } +.st-row.head > div { text-align: right; } +.st-row.head > div:first-child { text-align: left; } +.st-row .diff-pill { + display: inline-block; + padding: 2px 8px; + border-radius: var(--radius-pill); + font-size: 11px; + font-weight: 600; +} +.st-row .diff-pill.zero { background: var(--gray-100); color: var(--gray-700); } +.st-row .diff-pill.neg { background: var(--danger-50); color: var(--danger-700); } +.st-row .diff-pill.pos { background: var(--success-50); color: var(--success-700); } +.st-row > div:not(:first-child) { text-align: right; } + +/* Log timeline */ +.log-card { + background: var(--gray-0); + border: 1px solid var(--border-default); + border-radius: var(--radius-lg); + box-shadow: var(--shadow-md); + overflow: hidden; +} +.log-toolbar { + display: flex; gap: 8px; padding: 12px 18px; + background: var(--gray-50); + border-bottom: 1px solid var(--border-subtle); + align-items: center; + font-size: var(--text-xs); + color: var(--fg-muted); +} +.log-filter-pill { + display: inline-flex; align-items: center; gap: 4px; + background: var(--gray-0); + border: 1px solid var(--border-default); + border-radius: var(--radius-pill); + padding: 3px 10px; + font-size: 11px; + color: var(--gray-700); +} +.log-filter-pill.active { background: var(--brand-50); border-color: var(--brand-300); color: var(--brand-700); } +.log-filter-pill .icon { width: 12px; height: 12px; } +.log-row { + display: grid; + grid-template-columns: 100px 60px 1fr 60px 80px; + padding: 12px 18px; + gap: 14px; + border-bottom: 1px solid var(--border-subtle); + font-size: var(--text-sm); + align-items: center; + font-variant-numeric: tabular-nums; +} +.log-row:last-child { border-bottom: none; } +.log-time { font-family: var(--font-mono); font-size: 11px; color: var(--fg-muted); } +.log-type-pill { + display: inline-block; + padding: 1px 8px; + border-radius: var(--radius-pill); + font-size: 10px; + font-weight: 500; +} +.log-type-pill.in { background: var(--success-50); color: var(--success-700); } +.log-type-pill.out { background: var(--accent-50); color: var(--accent-700); } +.log-type-pill.check { background: var(--info-50); color: var(--info-700); } +.log-desc { color: var(--gray-800); font-size: 13px; } +.log-desc small { color: var(--fg-muted); display: block; font-size: 11px; margin-top: 1px; } +.log-qty { text-align: right; font-weight: 600; } +.log-qty.in { color: var(--success-700); } +.log-qty.out { color: var(--accent-500); } +.log-op { text-align: right; font-size: 12px; color: var(--gray-700); } + +/* Alert config */ +.alert-config-card { + background: var(--gray-0); + border: 1px solid var(--border-default); + border-radius: var(--radius-lg); + box-shadow: var(--shadow-md); + padding: 24px; +} +.alert-config-card h4 { + margin: 0 0 4px; + font-size: var(--text-md); + font-weight: 600; + color: var(--brand-900); +} +.alert-config-card p.sub { margin: 0 0 20px; font-size: var(--text-sm); color: var(--fg-muted); } + +.threshold-bar { + position: relative; + height: 56px; + margin: 28px 0 12px; +} +.threshold-track { + position: absolute; top: 24px; left: 0; right: 0; + height: 8px; + background: linear-gradient(90deg, var(--danger-100, #FBC9C9) 0%, var(--danger-100, #FBC9C9) 18%, var(--warning-50) 18%, var(--warning-50) 42%, var(--success-50) 42%, var(--success-50) 100%); + border-radius: var(--radius-pill); +} +.threshold-marker { + position: absolute; top: 18px; + width: 20px; height: 20px; + border-radius: 50%; + background: var(--gray-0); + border: 3px solid var(--brand-500); + transform: translateX(-50%); + box-shadow: 0 2px 6px rgba(20,24,33,0.10); +} +.threshold-tick { + position: absolute; + top: 38px; + transform: translateX(-50%); + font-size: 10px; + color: var(--fg-muted); + font-family: var(--font-mono); + font-variant-numeric: tabular-nums; + white-space: nowrap; +} +.threshold-label { + position: absolute; + bottom: 8px; + transform: translateX(-50%); + font-size: 10px; + color: var(--fg-muted); + letter-spacing: 0.04em; + white-space: nowrap; +} +.alert-rules { display: flex; flex-direction: column; gap: 8px; margin-top: 16px; } +.alert-rule { + display: flex; justify-content: space-between; align-items: center; + padding: 10px 14px; + background: var(--gray-25); + border-radius: var(--radius-md); + font-size: 13px; + color: var(--gray-700); +} +.alert-rule strong { color: var(--brand-900); font-weight: 600; } +.alert-rule .switch { + width: 32px; height: 18px; + background: var(--brand-500); + border-radius: var(--radius-pill); + position: relative; +} +.alert-rule .switch::after { + content: ''; + position: absolute; + top: 2px; right: 2px; + width: 14px; height: 14px; + background: #fff; + border-radius: 50%; +} +.alert-rule .switch.off { background: var(--gray-300); } +.alert-rule .switch.off::after { right: auto; left: 2px; } + +/* 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; } + +/* Final CTA */ +.cta-strip { + background: 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 { grid-template-columns: 1fr; gap: 32px; } + .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; } + .composite { height: 480px; } +} +/* Hamburger */ +.nav-hamburger { display: none; flex-direction: column; justify-content: center; gap: 5px; width: 40px; height: 40px; padding: 8px; background: none; border: none; cursor: pointer; border-radius: 8px; } +.nav-hamburger span { display: block; height: 2px; width: 22px; background: var(--gray-700); border-radius: 2px; } +.nav-mobile-menu { display: none; flex-direction: column; background: var(--gray-0); border-top: 1px solid var(--border-subtle); padding: 8px 0 16px; } +.nav-mobile-menu.open { display: flex; } +.nav-mobile-menu a { padding: 12px 24px; font-size: var(--text-md); color: var(--gray-700); font-weight: 500; } +.nav-mobile-menu a:hover { background: var(--gray-50); color: var(--brand-900); } +.nav-mobile-cta { display: flex; flex-direction: column; gap: 8px; padding: 12px 24px 0; border-top: 1px solid var(--border-subtle); margin-top: 8px; } +@media (max-width: 768px) { + .nav-hamburger { display: flex; } + .topnav-links, .topnav-cta { display: none; } + .composite { display: none; } +} +@media (max-width: 600px) { + .topnav-inner { padding: 0 16px; } + .feat-hero h1 { font-size: 28px; } + .footer-grid { grid-template-columns: 1fr; gap: 32px; } + .footer-bottom { flex-direction: column; gap: 8px; text-align: center; } + .usecases-grid { grid-template-columns: 1fr; } + .container { padding: 0 16px; } +} + diff --git a/web/features/approval.html b/web/features/approval.html deleted file mode 100644 index 7eaab90..0000000 --- a/web/features/approval.html +++ /dev/null @@ -1,1701 +0,0 @@ - - - - - -审核驱动业务流 · 岩美酒库管理系统 - - - - - - - - - - - - -
-
-
-
- 首页 - - 产品功能 - - 审核驱动业务流 -
-

功能详情 · 审核驱动业务流

-

每一笔库存变动,
都经过一次签字

-

- 入库、出库、盘点 —— 统一状态机,统一审批界面。审批不仅是一个按钮,是一笔事务:库存动了,账款也跟着动,全程留痕。 -

- -
-
-
单据类型
-
3 种
-
-
-
操作留痕
-
全程
-
-
-
审批联动
-
库存 + 账款
-
-
-
- -
-
-
- -
-
-
-
RK20260523000007
-
入库审核 · A 仓 · 茅台经销华东
-
-
- 已审批 -
-
- -
-
茅台飞天 53° · 500ml×6
4
¥8,000
-
-
-
五粮液第八代 52°
20
¥18,400
-
-
-
剑南春水晶剑 52°
2
¥4,200
-
-
-
拉菲传奇波尔多
10
¥17,800
-
- -
-
合计
36
¥48,400
-
- -
- 审批人 李建国 · 2026-05-23 14:32:08 -
-
- - -
- - - - - - 岩美 · 审批通过 · APPROVED · 岩美 · - - -
-
通过
-
APPROVED
-
2026.05.23
-
-
-
-
-
- - -
-
-
-
统一状态机
-

四种状态,
覆盖所有业务单据

-

- 入库、出库、盘点 —— 全部走同一套状态:草稿 → 待审核 → 已审批 / 已拒绝。员工学一次,全员通用。 -

-
    -
  • - -
    - 提交后即锁定 - 从「草稿」进入「待审核」即不可编辑,避免审批人看到的与录入人提交的不一致。 -
    -
  • -
  • - -
    - 状态机由后端事务驱动 - 状态变更与库存 / 账款变更在同一事务内,要么都成功,要么都回滚。 -
    -
  • -
  • - -
    - 一套界面,三种单据复用 - 前端审批界面统一,节省培训成本,避免分模块状态差异。 -
    -
  • -
-
- -
-
-
-
入库单
-
出库单
-
盘点单
-
-
stock_in_orders.status
- -
-
-
- 草稿 -
- draft -
- -
- 提交 - -
- -
-
- 待审核 -
- pending -
- -
- 审批 - -
- -
-
-
- 已审批 -
- approved -
-
-
- 已拒绝 -
- rejected -
-
-
- -
- 同一状态机适用于: - 入库单 - 出库单 - 盘点单 -
-
-
-
-
- - -
-
-
-
自动联动
-

按一次"通过",
库存与账款都跟着动

-

- 审批通过不是一个"标记",而是一组真实的数据库写入。库存增减、应付应收、批次创建 —— 在同一事务内完成。 -

-
    -
  • - -
    - 事务级一致性 - 底层用 FOR UPDATE 锁住库存行,杜绝并发超卖。 -
    -
  • -
  • - -
    - 账款自动落地 - 入库审批 → 自动生成应付;出库审批 → 自动生成应收。挂在对应往来单位名下,可逐月对账。 -
    -
  • -
  • - -
    - 批次自动建立 - 每笔入库审批通过即创建一个新批次,后续出库按 FIFO 自动扣减。 -
    -
  • -
-
- -
-
-
-
-
-
RK20260523000007
-
36 瓶 · ¥48,400 · 待审核
-
- -
-
- -
- - 事务内同步执行 - -
- -
-
-
- 库存 -
-
+36 瓶
-
A 仓 · 2,780 → 2,816
-
-
-
- 应付账款 -
-
+¥48,400
-
茅台经销华东 · 新增 1 笔
-
-
-
- 批次 -
-
+4 个
-
L-2026-05-001 ~ L-2026-05-004
-
-
-
- 库存流水 -
-
+4 条
-
逐 SKU 写入 inventory_logs
-
-
-
-
-
-
- - -
-
-
-
出库库存校验
-

库存不足,
审批直接被拦下

-

- 出库单审批前,系统按 SKU 实时核对库存。任一行不足,整单审批失败、库存与账款均不变。永远不会超卖。 -

-
    -
  • - -
    - 逐行校验 - 按出库单每一行的 商品 × 仓库 × 数量 校验,失败明确指出是哪一行。 -
    -
  • -
  • - -
    - 整单回滚 - 校验失败时单据状态回到「待审核」,无需重新录入。 -
    -
  • -
-
- -
-
-
-
出库单 CK20260523000012 · 审批前校验
-
客户 北辰大酒店 · B 仓
-
-
-
-
商品
需出
库存
状态
-
-
-
茅台飞天 53° · 500ml×6
-
24
-
148
-
充足
-
-
-
剑南春水晶剑 52°
-
20
-
12
-
短缺 8
-
-
-
拉菲传奇波尔多 · 750ml
-
6
-
64
-
充足
-
- -
- -
-
审批已拒绝 · 库存不足
-
- 第 2 行「剑南春水晶剑」B 仓库存仅 12 瓶,本单需 20 瓶,短缺 8 瓶。请补足库存后重新提交,或修改出库数量。 -
-
-
- -
- // HTTP/1.1 422 Unprocessable Entity
-{
-  "ok": false,
-  "error": "insufficient_inventory",
-  "items": [
-    { "sku": "XJ-330-024", "required": 20, "available": 12 }
-  ]
-} -
-
-
-
-
-
- - -
-
-
-
审计留痕
-

每一步操作,
都签着人名、贴着时间

-

- 从录入到结清,每一次状态变更都记录经办人、时间、决策、备注。是单据的完整生命周期,也是合规审计的完整证据。 -

-
    -
  • - -
    - 谁动的、什么时候动的 - 14 个字段:actor / role / device / IP / 时间 / 备注 / 旧值 / 新值 / 原因 … -
    -
  • -
  • - -
    - 永久保留 - 审计日志不会被任何角色(含超级管理员)删除或修改。 -
    -
  • -
  • - -
    - 可导出 - 按单据 / 时间段 / 经办人 任意组合筛选导出,应对税务、内审、合规检查。 -
    -
  • -
-
- -
-
-
-
RK20260523000007
-
已审批
-
-
-
-
-
-
创建草稿
-
- 王芳 · 操作员 - 2026-05-23 11:24:08 - 192.168.10.42 · 入库 PC -
-
-
- -
-
-
-
编辑明细
-
- 王芳 - 2026-05-23 13:02:51 - 修改 4 项 · 新增 1 项 -
-
-
- -
-
-
-
提交审核
-
- 王芳 - 2026-05-23 13:15:22 - 状态 · 草稿 → 待审核 -
-
-
- -
-
-
-
审批通过
-
- 李建国 · 管理员 - 2026-05-23 14:32:08 - 库存 +36 · 应付 +¥48,400 -
-
已核对数量与单价,可入库。
-
-
- -
-
-
-
应付结清
-
- 张磊 · 财务 - 2026-05-26 10:08:33 - AP20260523000012 · 全额结清 -
-
-
-
-
-
-
-
- - -
-
-
-
权限矩阵
-

四级角色,
各干各的事

-

- 系统内置四个角色,按岗位职责拆分审批权。最小权限原则 —— 不该看的看不到,不该签的签不了。 -

-
    -
  • - -
    - 角色绑定门店 - 一个用户只属于一个门店(shop_id),跨店访问数据天然隔离。 -
    -
  • -
  • - -
    - 角色升降随时生效 - 管理员调整角色后,下一次请求即按新权限校验,无需重新登录。 -
    -
  • -
  • - -
    - 只读角色可看不可改 - 适合财务、税务、内审岗位 —— 看见全部数据,但不能产生任何变更。 -
    -
  • -
-
- -
-
-
-
角色
-
查看
-
录入
-
审批
-
设置
-
-
-
超级管理员superadmin · 系统最高
-
-
-
-
-
-
-
管理员admin · 门店负责人
-
-
-
-
-
-
-
操作员operator · 日常录入
-
-
-
-
-
-
-
只读readonly · 财务 / 内审
-
-
-
-
-
-
- -
- - 允许 - - - 禁止 - -
-
-
-
- - -
-
-

典型场景

-

- 审核驱动业务流,落到实际操作里是什么样 -

-

- 三个常见门店场景,看审核流如何把日常工作中的失误拦在事前。 -

- -
-
- -

避免超卖发不了货

-

大客户来订 24 瓶飞天,操作员录了出库单。审批时系统提示某 SKU 库存只剩 12 瓶 —— 提前发现,先补货后出库。

-
"以前是发了货才发现没库存,半夜紧急调货。现在审批就拦下,省事多了。"
-
-
- -

财务来对账,3 秒定位

-

财务问"这批拉菲是哪个供应商进的,谁审的",打开单据点击「审计链」,从草稿、提交、审批到结清的每一步都列得清清楚楚。

-
"以前要翻三个 Excel 表对照,现在一张图就讲完了。"
-
-
- -

录错了不慌

-

审批通过的单据不可撤销,但可补录一张方向相反的修正单提交审核,两笔记录都完整保留在审计链中,库存与账款同步修正。

-
"原始数据保留,修正也留痕,内审完全不会有问题。"
-
-
-
-
- - -
-
-
-
-

每一笔库存变动都有人负责,
就从一次审批开始。

-

30 天免费试用,无需绑定支付方式。所有审批与审计数据可随时导出。

-
- -
-
-
- - - - - - - - diff --git a/web/features/approval.njk b/web/features/approval.njk new file mode 100644 index 0000000..1f01e8f --- /dev/null +++ b/web/features/approval.njk @@ -0,0 +1,615 @@ +--- +layout: base.njk +title: 审核驱动业务流 +description: 岩美酒库管理系统审核流功能:入库/出库/盘点单据全程审核,状态机驱动,多角色权限控制。 +permalink: /features/approval.html +pageExtraCss: /assets/features-approval.css +--- + + +
+
+
+
+ 首页 + + 产品功能 + + 审核驱动业务流 +
+

功能详情 · 审核驱动业务流

+

每一笔库存变动,
都经过一次签字

+

+ 入库、出库、盘点 —— 统一状态机,统一审批界面。审批不仅是一个按钮,是一笔事务:库存动了,账款也跟着动,全程留痕。 +

+ +
+
+
单据类型
+
3 种
+
+
+
操作留痕
+
全程
+
+
+
审批联动
+
库存 + 账款
+
+
+
+ +
+
+
+ +
+
+
+
RK20260523000007
+
入库审核 · A 仓 · 茅台经销华东
+
+
+ 已审批 +
+
+ +
+
茅台飞天 53° · 500ml×6
4
¥8,000
+
+
+
五粮液第八代 52°
20
¥18,400
+
+
+
剑南春水晶剑 52°
2
¥4,200
+
+
+
拉菲传奇波尔多
10
¥17,800
+
+ +
+
合计
36
¥48,400
+
+ +
+ 审批人 李建国 · 2026-05-23 14:32:08 +
+
+ + +
+ + + + + + 岩美 · 审批通过 · APPROVED · 岩美 · + + +
+
通过
+
APPROVED
+
2026.05.23
+
+
+
+
+
+ + +
+
+
+
统一状态机
+

四种状态,
覆盖所有业务单据

+

+ 入库、出库、盘点 —— 全部走同一套状态:草稿 → 待审核 → 已审批 / 已拒绝。员工学一次,全员通用。 +

+
    +
  • + +
    + 提交后即锁定 + 从「草稿」进入「待审核」即不可编辑,避免审批人看到的与录入人提交的不一致。 +
    +
  • +
  • + +
    + 状态机由后端事务驱动 + 状态变更与库存 / 账款变更在同一事务内,要么都成功,要么都回滚。 +
    +
  • +
  • + +
    + 一套界面,三种单据复用 + 前端审批界面统一,节省培训成本,避免分模块状态差异。 +
    +
  • +
+
+ +
+
+
+
入库单
+
出库单
+
盘点单
+
+
stock_in_orders.status
+ +
+
+
+ 草稿 +
+ draft +
+ +
+ 提交 + +
+ +
+
+ 待审核 +
+ pending +
+ +
+ 审批 + +
+ +
+
+
+ 已审批 +
+ approved +
+
+
+ 已拒绝 +
+ rejected +
+
+
+ +
+ 同一状态机适用于: + 入库单 + 出库单 + 盘点单 +
+
+
+
+
+ + +
+
+
+
自动联动
+

按一次"通过",
库存与账款都跟着动

+

+ 审批通过不是一个"标记",而是一组真实的数据库写入。库存增减、应付应收、批次创建 —— 在同一事务内完成。 +

+
    +
  • + +
    + 事务级一致性 + 底层用 FOR UPDATE 锁住库存行,杜绝并发超卖。 +
    +
  • +
  • + +
    + 账款自动落地 + 入库审批 → 自动生成应付;出库审批 → 自动生成应收。挂在对应往来单位名下,可逐月对账。 +
    +
  • +
  • + +
    + 批次自动建立 + 每笔入库审批通过即创建一个新批次,后续出库按 FIFO 自动扣减。 +
    +
  • +
+
+ +
+
+
+
+
+
RK20260523000007
+
36 瓶 · ¥48,400 · 待审核
+
+ +
+
+ +
+ + 事务内同步执行 + +
+ +
+
+
+ 库存 +
+
+36 瓶
+
A 仓 · 2,780 → 2,816
+
+
+
+ 应付账款 +
+
+¥48,400
+
茅台经销华东 · 新增 1 笔
+
+
+
+ 批次 +
+
+4 个
+
L-2026-05-001 ~ L-2026-05-004
+
+
+
+ 库存流水 +
+
+4 条
+
逐 SKU 写入 inventory_logs
+
+
+
+
+
+
+ + +
+
+
+
出库库存校验
+

库存不足,
审批直接被拦下

+

+ 出库单审批前,系统按 SKU 实时核对库存。任一行不足,整单审批失败、库存与账款均不变。永远不会超卖。 +

+
    +
  • + +
    + 逐行校验 + 按出库单每一行的 商品 × 仓库 × 数量 校验,失败明确指出是哪一行。 +
    +
  • +
  • + +
    + 整单回滚 + 校验失败时单据状态回到「待审核」,无需重新录入。 +
    +
  • +
+
+ +
+
+
+
出库单 CK20260523000012 · 审批前校验
+
客户 北辰大酒店 · B 仓
+
+
+
+
商品
需出
库存
状态
+
+
+
茅台飞天 53° · 500ml×6
+
24
+
148
+
充足
+
+
+
剑南春水晶剑 52°
+
20
+
12
+
短缺 8
+
+
+
拉菲传奇波尔多 · 750ml
+
6
+
64
+
充足
+
+ +
+ +
+
审批已拒绝 · 库存不足
+
+ 第 2 行「剑南春水晶剑」B 仓库存仅 12 瓶,本单需 20 瓶,短缺 8 瓶。请补足库存后重新提交,或修改出库数量。 +
+
+
+ +
+ // HTTP/1.1 422 Unprocessable Entity
+{
+  "ok": false,
+  "error": "insufficient_inventory",
+  "items": [
+    { "sku": "XJ-330-024", "required": 20, "available": 12 }
+  ]
+} +
+
+
+
+
+
+ + +
+
+
+
审计留痕
+

每一步操作,
都签着人名、贴着时间

+

+ 从录入到结清,每一次状态变更都记录经办人、时间、决策、备注。是单据的完整生命周期,也是合规审计的完整证据。 +

+
    +
  • + +
    + 谁动的、什么时候动的 + 14 个字段:actor / role / device / IP / 时间 / 备注 / 旧值 / 新值 / 原因 … +
    +
  • +
  • + +
    + 永久保留 + 审计日志不会被任何角色(含超级管理员)删除或修改。 +
    +
  • +
  • + +
    + 可导出 + 按单据 / 时间段 / 经办人 任意组合筛选导出,应对税务、内审、合规检查。 +
    +
  • +
+
+ +
+
+
+
RK20260523000007
+
已审批
+
+
+
+
+
+
创建草稿
+
+ 王芳 · 操作员 + 2026-05-23 11:24:08 + 192.168.10.42 · 入库 PC +
+
+
+ +
+
+
+
编辑明细
+
+ 王芳 + 2026-05-23 13:02:51 + 修改 4 项 · 新增 1 项 +
+
+
+ +
+
+
+
提交审核
+
+ 王芳 + 2026-05-23 13:15:22 + 状态 · 草稿 → 待审核 +
+
+
+ +
+
+
+
审批通过
+
+ 李建国 · 管理员 + 2026-05-23 14:32:08 + 库存 +36 · 应付 +¥48,400 +
+
已核对数量与单价,可入库。
+
+
+ +
+
+
+
应付结清
+
+ 张磊 · 财务 + 2026-05-26 10:08:33 + AP20260523000012 · 全额结清 +
+
+
+
+
+
+
+
+ + +
+
+
+
权限矩阵
+

四级角色,
各干各的事

+

+ 系统内置四个角色,按岗位职责拆分审批权。最小权限原则 —— 不该看的看不到,不该签的签不了。 +

+
    +
  • + +
    + 角色绑定门店 + 一个用户只属于一个门店(shop_id),跨店访问数据天然隔离。 +
    +
  • +
  • + +
    + 角色升降随时生效 + 管理员调整角色后,下一次请求即按新权限校验,无需重新登录。 +
    +
  • +
  • + +
    + 只读角色可看不可改 + 适合财务、税务、内审岗位 —— 看见全部数据,但不能产生任何变更。 +
    +
  • +
+
+ +
+
+
+
角色
+
查看
+
录入
+
审批
+
设置
+
+
+
超级管理员superadmin · 系统最高
+
+
+
+
+
+
+
管理员admin · 门店负责人
+
+
+
+
+
+
+
操作员operator · 日常录入
+
+
+
+
+
+
+
只读readonly · 财务 / 内审
+
+
+
+
+
+
+ +
+ + 允许 + + + 禁止 + +
+
+
+
+ + +
+
+

典型场景

+

+ 审核驱动业务流,落到实际操作里是什么样 +

+

+ 三个常见门店场景,看审核流如何把日常工作中的失误拦在事前。 +

+ +
+
+ +

避免超卖发不了货

+

大客户来订 24 瓶飞天,操作员录了出库单。审批时系统提示某 SKU 库存只剩 12 瓶 —— 提前发现,先补货后出库。

+
"以前是发了货才发现没库存,半夜紧急调货。现在审批就拦下,省事多了。"
+
+
+ +

财务来对账,3 秒定位

+

财务问"这批拉菲是哪个供应商进的,谁审的",打开单据点击「审计链」,从草稿、提交、审批到结清的每一步都列得清清楚楚。

+
"以前要翻三个 Excel 表对照,现在一张图就讲完了。"
+
+
+ +

录错了不慌

+

审批通过的单据不可撤销,但可补录一张方向相反的修正单提交审核,两笔记录都完整保留在审计链中,库存与账款同步修正。

+
"原始数据保留,修正也留痕,内审完全不会有问题。"
+
+
+
+
+ + +
+
+
+
+

每一笔库存变动都有人负责,
就从一次审批开始。

+

30 天免费试用,无需绑定支付方式。所有审批与审计数据可随时导出。

+
+ +
+
+
+ diff --git a/web/features/inventory.html b/web/features/inventory.html deleted file mode 100644 index 189c6c3..0000000 --- a/web/features/inventory.html +++ /dev/null @@ -1,1480 +0,0 @@ - - - - - -库存管理 · 岩美酒库管理系统 - - - - - - - - - - - - -
-
-
-
- 首页 - - 产品功能 - - 库存管理 -
-

功能详情 · 库存管理

-

实时库存,
跨多仓追溯每一瓶酒。

-

- 从入库到售出,每一次库存变动都有记录、可追溯、可回放。多仓库统一管理、批次级追踪、账实差异自动核算 — 不止是库存表。 -

- -
-
-
SKU 上限
-
无限
-
-
-
仓库支持
-
多仓库
-
-
-
流水保留
-
永久
-
-
-
- -
- -
-
-
库存列表 · A 仓
-
-
列表
-
流水
-
盘点
-
-
-
-
-
库存总量
-
2,816↑ 4.2%
-
-
-
库存金额
-
¥486K↑ 2.8%
-
-
-
预警 SKU
-
7
-
-
-
-
-
SKU
商品
仓库
库存
批次
状态
-
-
-
WT-501-006
-
茅台飞天 53°
-
A 仓
-
148
-
L-2026-04
-
充足
-
-
-
XJ-468-001
-
五粮液第八代
-
A 仓
-
92
-
L-2026-03
-
充足
-
-
-
XJ-330-024
-
剑南春水晶剑
-
B 仓
-
12
-
L-2026-02
-
偏低
-
-
-
PJ-750-012
-
拉菲传奇波尔多
-
A 仓
-
64
-
L-2026-05
-
充足
-
-
-
PJ-700-008
-
轩尼诗 VSOP
-
B 仓
-
3
-
L-2026-01
-
告急
-
-
-
- - -
-
- - 低库存商品 -
-
-
轩尼诗 VSOP
-
仅 3 瓶
-
-
-
剑南春水晶剑
-
12 瓶
-
-
-
人头马 X.O
-
8 瓶
-
- -
- - -
-
- - 批次追踪 · L-2026-05 -
-
-
入库 04-28
-
初始 50 瓶
-
A 仓
-
+50
-
-
-
出库 05-12
-
客户 · 春和宴
-
8 瓶
-
-8
-
-
-
出库 05-19
-
客户 · 北辰大酒店
-
12 瓶
-
-12
-
-
-
-
-
- - -
-
-
-
实时库存查询
-

不止是库存表,是一个可筛选的工作面板

-

- 库存数据自动跟随入库、出库、盘点变动 — 每一次写入都是一次事务,不存在「待同步」状态。 -

-
    -
  • - -
    - 多维筛选与列控制 - 按仓库、商品系列、库存状态、批次任意组合筛选,列可隐藏或重排。 -
    -
  • -
  • - -
    - 模糊搜索与拼音匹配 - 支持商品名、SKU、拼音首字母混合搜索,与你输入"飞天"同等结果。 -
    -
  • -
  • - -
    - 行内编辑备注 - 点击「备注」列即可弹出编辑框,不必进入详情页改字段。 -
    -
  • -
  • - -
    - 一键导出 Excel - 筛选后的视图与数据一同导出,可直接发给会计、税务。 -
    -
  • -
-
- -
-
-
-
库存列表 · 全部仓库
-
共 2,816 瓶 · ¥486,290
-
-
-
-
SKU
商品名称 / 规格
仓库
库存
金额
状态
-
-
-
WT-501-006
-
茅台飞天 53°500ml×6 · L-2026-04
-
A 仓
-
148
-
¥298,400
-
充足
-
-
-
XJ-468-001
-
五粮液第八代 52°500ml · L-2026-03
-
A 仓
-
92
-
¥92,920
-
充足
-
-
-
XJ-330-024
-
剑南春水晶剑 52°500ml×6 · L-2026-02
-
B 仓
-
12
-
¥4,560
-
偏低
-
-
-
PJ-750-012
-
拉菲传奇波尔多750ml · L-2026-05
-
A 仓
-
64
-
¥18,560
-
充足
-
-
-
PJ-700-008
-
轩尼诗 VSOP700ml · L-2026-01
-
B 仓
-
3
-
¥1,770
-
告急
-
-
-
-
-
-
- - -
-
-
-
批次追踪
-

每一批货的命运,
都可以回放给你看

-

- 每张审批通过的入库单都会自动形成一个独立批次,可追踪其后续被谁出库、剩余多少、还在哪个仓库。 -

-
    -
  • - -
    - 批次自动生成 - 入库审批通过即建立批次,无需手动维护。 -
    -
  • -
  • - -
    - 逐瓶溯源 - 出库时按"先进先出"自动从最早批次扣减,留下完整的批次→客户对应关系。 -
    -
  • -
  • - -
    - 标签二维码 - 每个批次都可打印含二维码的标签,扫码即查批次详情。 -
    -
  • -
-
- -
-
-
-
-
L-2026-05
-
茅台飞天 53°500ml×6 · A 仓
-
-
-
初始入库 50 瓶
-
累计出库 20 瓶
-
剩余 30 瓶
-
-
-
-
-
-
- 已出库 60% - 40% 库存 -
- -
-
-
-
2026-04-28
-
入库 · 供应商 茅台经销华东经办人:李建国
-
+50
-
RK0007
-
-
-
-
2026-05-12
-
出库 · 客户 春和宴酒楼经办人:王芳
-
−8
-
CK0034
-
-
-
-
2026-05-19
-
出库 · 客户 北辰大酒店经办人:王芳
-
−12
-
CK0041
-
-
-
-
— 当前 —
-
剩余 30 瓶预计 6 月底前售完
-
30
-
-
-
-
-
-
-
- - -
-
-
-
全仓盘点
-

账面 vs 实际,差异一目了然

-

- 逐 SKU 录入实盘数量,系统自动核算账实差异、损耗金额,并一键转为盘亏调整单。 -

-
    -
  • - -
    - 跨多仓库联动 - 一次盘点可同步多仓库,避免分仓时数据不一致。 -
    -
  • -
  • - -
    - 差异自动核算 - 实盘填好后立即显示账面、实际、差异数量与金额。 -
    -
  • -
  • - -
    - 历史盘点对比 - 可对比上一次盘点结果,识别长期偏差最大的 SKU。 -
    -
  • -
-
- -
-
-
-
-
盘点商品
-
128
-
-
-
账面合计
-
2,816
-
-
-
实际合计
-
2,808
-
-
-
损耗金额
-
−¥6,840
-
-
-
-
商品
账面
实际
差异
损耗
-
-
-
茅台飞天 53° · 500ml×6
-
148
-
148
-
0
-
-
-
-
五粮液第八代 52° · 500ml
-
92
-
90
-
−2
-
−¥2,020
-
-
-
剑南春水晶剑 52° · 500ml×6
-
12
-
12
-
0
-
-
-
-
拉菲传奇波尔多 · 750ml
-
64
-
60
-
−4
-
−¥1,160
-
-
-
轩尼诗 VSOP · 700ml
-
3
-
3
-
0
-
-
-
-
人头马 X.O · 700ml
-
40
-
38
-
−2
-
−¥3,660
-
-
-
-
-
- - -
-
-
-
库存流水审计
-

谁动了我的库存?
每一次都查得清楚

-

- 所有库存变动都会自动写入流水表,包括入库、出库、盘点调整,附带时间戳与经办人。 -

-
    -
  • - -
    - 按类型 / 时间 / SKU 任意筛选 - 排查异常时可秒级定位。 -
    -
  • -
  • - -
    - 关联原单据 - 每条流水都关联到对应入库单 / 出库单 / 盘点单,一键跳转。 -
    -
  • -
  • - -
    - 永久保留 - 流水数据永不自动清除,满足审计与合规要求。 -
    -
  • -
-
- -
-
-
- 全部类型 - 入库 - 出库 - 盘点 - 本月 · 共 1,284 条 -
-
-
05-23 14:32
-
入库
-
茅台飞天 53° · 500ml×6RK20260523000007 · A 仓
-
+4
-
李建国
-
-
-
05-23 11:48
-
出库
-
五粮液第八代 52°CK20260523000012 · 客户 春和宴
-
−6
-
王芳
-
-
-
05-23 10:15
-
盘点
-
拉菲传奇波尔多PD20260523001 · 账实差异
-
−2
-
张磊
-
-
-
05-22 17:20
-
出库
-
人头马 X.O · 700mlCK20260522000008 · 客户 北辰
-
−2
-
王芳
-
-
-
05-22 14:08
-
入库
-
轩尼诗 VSOP · 700mlRK20260522000005 · B 仓
-
+24
-
李建国
-
-
-
-
-
- - -
-
-
-
库存状态
-

一眼看出哪些商品
需要补货

-

- 为每件商品设置最低库存量,系统根据当前库存自动标记「充足 / 偏低 / 告急」,无需手工比对。 -

-
    -
  • - -
    - 按 SKU 设置最低库存 - 热销商品阈值高,冷门商品阈值低 — 每件单独配置。 -
    -
  • -
  • - -
    - 库存列表一目了然 - 库存状态标签跟随列表显示,随时筛选「偏低」「告急」的 SKU。 -
    -
  • -
  • - -
    - 商品二维码防伪 - 每件商品生成专属二维码,顾客扫码即可查看批次与防伪信息。 -
    -
  • -
-
- -
-
-

库存状态一览

-

基于最低库存量 (min_stock) 自动标记状态。

-
-
- 茅台飞天 53° - 充足 -
-
- 剑南春水晶剑 - 偏低 -
-
- 轩尼诗 VSOP - 告急 -
-
-
-
-
-
- - -
-
-

典型场景

-

- 库存管理,落到实际操作里是什么样 -

-

- 三个常见门店场景,看库存管理如何省下你每天的时间。 -

- -
-
- -

客户来取货前,先看一眼批次

-

客户要 12 瓶飞天,先在批次追踪里看哪一批快到期;优先发出旧批次,新批次留着。

-
-
- -

月底盘点,2 小时变 30 分钟

-

用移动端扫码逐瓶录入,系统对比账面 → 实时显示差异。损耗自动转入应付,不用再算。

-
-
- -

财务来问"这瓶怎么少了",3 秒回答

-

打开库存流水,按 SKU 筛选,全部入库、出库、盘点变动一目了然,每条都关联到原始单据。

-
-
-
-
- - -
-
-
-
-

试试看你今天的库存有几瓶。

-

30 天免费试用,无需绑定支付。所有数据云端实时备份,可随时导出。

-
- -
-
-
- - - - - - - - diff --git a/web/features/inventory.njk b/web/features/inventory.njk new file mode 100644 index 0000000..295f456 --- /dev/null +++ b/web/features/inventory.njk @@ -0,0 +1,652 @@ +--- +layout: base.njk +title: 库存管理 +description: 岩美酒库管理系统库存管理功能:实时库存查询、批次追踪、全仓盘点、库存流水审计。 +permalink: /features/inventory.html +pageExtraCss: /assets/features-inventory.css +--- + + +
+
+
+
+ 首页 + + 产品功能 + + 库存管理 +
+

功能详情 · 库存管理

+

实时库存,
跨多仓追溯每一瓶酒。

+

+ 从入库到售出,每一次库存变动都有记录、可追溯、可回放。多仓库统一管理、批次级追踪、账实差异自动核算 — 不止是库存表。 +

+ +
+
+
SKU 上限
+
无限
+
+
+
仓库支持
+
多仓库
+
+
+
流水保留
+
永久
+
+
+
+ +
+ +
+
+
库存列表 · A 仓
+
+
列表
+
流水
+
盘点
+
+
+
+
+
库存总量
+
2,816↑ 4.2%
+
+
+
库存金额
+
¥486K↑ 2.8%
+
+
+
预警 SKU
+
7
+
+
+
+
+
SKU
商品
仓库
库存
批次
状态
+
+
+
WT-501-006
+
茅台飞天 53°
+
A 仓
+
148
+
L-2026-04
+
充足
+
+
+
XJ-468-001
+
五粮液第八代
+
A 仓
+
92
+
L-2026-03
+
充足
+
+
+
XJ-330-024
+
剑南春水晶剑
+
B 仓
+
12
+
L-2026-02
+
偏低
+
+
+
PJ-750-012
+
拉菲传奇波尔多
+
A 仓
+
64
+
L-2026-05
+
充足
+
+
+
PJ-700-008
+
轩尼诗 VSOP
+
B 仓
+
3
+
L-2026-01
+
告急
+
+
+
+ + +
+
+ + 低库存商品 +
+
+
轩尼诗 VSOP
+
仅 3 瓶
+
+
+
剑南春水晶剑
+
12 瓶
+
+
+
人头马 X.O
+
8 瓶
+
+ +
+ + +
+
+ + 批次追踪 · L-2026-05 +
+
+
入库 04-28
+
初始 50 瓶
+
A 仓
+
+50
+
+
+
出库 05-12
+
客户 · 春和宴
+
8 瓶
+
-8
+
+
+
出库 05-19
+
客户 · 北辰大酒店
+
12 瓶
+
-12
+
+
+
+
+
+ + +
+
+
+
实时库存查询
+

不止是库存表,是一个可筛选的工作面板

+

+ 库存数据自动跟随入库、出库、盘点变动 — 每一次写入都是一次事务,不存在「待同步」状态。 +

+
    +
  • + +
    + 多维筛选与列控制 + 按仓库、商品系列、库存状态、批次任意组合筛选,列可隐藏或重排。 +
    +
  • +
  • + +
    + 模糊搜索与拼音匹配 + 支持商品名、SKU、拼音首字母混合搜索,与你输入"飞天"同等结果。 +
    +
  • +
  • + +
    + 行内编辑备注 + 点击「备注」列即可弹出编辑框,不必进入详情页改字段。 +
    +
  • +
  • + +
    + 一键导出 Excel + 筛选后的视图与数据一同导出,可直接发给会计、税务。 +
    +
  • +
+
+ +
+
+
+
库存列表 · 全部仓库
+
共 2,816 瓶 · ¥486,290
+
+
+
+
SKU
商品名称 / 规格
仓库
库存
金额
状态
+
+
+
WT-501-006
+
茅台飞天 53°500ml×6 · L-2026-04
+
A 仓
+
148
+
¥298,400
+
充足
+
+
+
XJ-468-001
+
五粮液第八代 52°500ml · L-2026-03
+
A 仓
+
92
+
¥92,920
+
充足
+
+
+
XJ-330-024
+
剑南春水晶剑 52°500ml×6 · L-2026-02
+
B 仓
+
12
+
¥4,560
+
偏低
+
+
+
PJ-750-012
+
拉菲传奇波尔多750ml · L-2026-05
+
A 仓
+
64
+
¥18,560
+
充足
+
+
+
PJ-700-008
+
轩尼诗 VSOP700ml · L-2026-01
+
B 仓
+
3
+
¥1,770
+
告急
+
+
+
+
+
+
+ + +
+
+
+
批次追踪
+

每一批货的命运,
都可以回放给你看

+

+ 每张审批通过的入库单都会自动形成一个独立批次,可追踪其后续被谁出库、剩余多少、还在哪个仓库。 +

+
    +
  • + +
    + 批次自动生成 + 入库审批通过即建立批次,无需手动维护。 +
    +
  • +
  • + +
    + 逐瓶溯源 + 出库时按"先进先出"自动从最早批次扣减,留下完整的批次→客户对应关系。 +
    +
  • +
  • + +
    + 标签二维码 + 每个批次都可打印含二维码的标签,扫码即查批次详情。 +
    +
  • +
+
+ +
+
+
+
+
L-2026-05
+
茅台飞天 53°500ml×6 · A 仓
+
+
+
初始入库 50 瓶
+
累计出库 20 瓶
+
剩余 30 瓶
+
+
+
+
+
+
+ 已出库 60% + 40% 库存 +
+ +
+
+
+
2026-04-28
+
入库 · 供应商 茅台经销华东经办人:李建国
+
+50
+
RK0007
+
+
+
+
2026-05-12
+
出库 · 客户 春和宴酒楼经办人:王芳
+
−8
+
CK0034
+
+
+
+
2026-05-19
+
出库 · 客户 北辰大酒店经办人:王芳
+
−12
+
CK0041
+
+
+
+
— 当前 —
+
剩余 30 瓶预计 6 月底前售完
+
30
+
+
+
+
+
+
+
+ + +
+
+
+
全仓盘点
+

账面 vs 实际,差异一目了然

+

+ 逐 SKU 录入实盘数量,系统自动核算账实差异、损耗金额,并一键转为盘亏调整单。 +

+
    +
  • + +
    + 跨多仓库联动 + 一次盘点可同步多仓库,避免分仓时数据不一致。 +
    +
  • +
  • + +
    + 差异自动核算 + 实盘填好后立即显示账面、实际、差异数量与金额。 +
    +
  • +
  • + +
    + 历史盘点对比 + 可对比上一次盘点结果,识别长期偏差最大的 SKU。 +
    +
  • +
+
+ +
+
+
+
+
盘点商品
+
128
+
+
+
账面合计
+
2,816
+
+
+
实际合计
+
2,808
+
+
+
损耗金额
+
−¥6,840
+
+
+
+
商品
账面
实际
差异
损耗
+
+
+
茅台飞天 53° · 500ml×6
+
148
+
148
+
0
+
+
+
+
五粮液第八代 52° · 500ml
+
92
+
90
+
−2
+
−¥2,020
+
+
+
剑南春水晶剑 52° · 500ml×6
+
12
+
12
+
0
+
+
+
+
拉菲传奇波尔多 · 750ml
+
64
+
60
+
−4
+
−¥1,160
+
+
+
轩尼诗 VSOP · 700ml
+
3
+
3
+
0
+
+
+
+
人头马 X.O · 700ml
+
40
+
38
+
−2
+
−¥3,660
+
+
+
+
+
+ + +
+
+
+
库存流水审计
+

谁动了我的库存?
每一次都查得清楚

+

+ 所有库存变动都会自动写入流水表,包括入库、出库、盘点调整,附带时间戳与经办人。 +

+
    +
  • + +
    + 按类型 / 时间 / SKU 任意筛选 + 排查异常时可秒级定位。 +
    +
  • +
  • + +
    + 关联原单据 + 每条流水都关联到对应入库单 / 出库单 / 盘点单,一键跳转。 +
    +
  • +
  • + +
    + 永久保留 + 流水数据永不自动清除,满足审计与合规要求。 +
    +
  • +
+
+ +
+
+
+ 全部类型 + 入库 + 出库 + 盘点 + 本月 · 共 1,284 条 +
+
+
05-23 14:32
+
入库
+
茅台飞天 53° · 500ml×6RK20260523000007 · A 仓
+
+4
+
李建国
+
+
+
05-23 11:48
+
出库
+
五粮液第八代 52°CK20260523000012 · 客户 春和宴
+
−6
+
王芳
+
+
+
05-23 10:15
+
盘点
+
拉菲传奇波尔多PD20260523001 · 账实差异
+
−2
+
张磊
+
+
+
05-22 17:20
+
出库
+
人头马 X.O · 700mlCK20260522000008 · 客户 北辰
+
−2
+
王芳
+
+
+
05-22 14:08
+
入库
+
轩尼诗 VSOP · 700mlRK20260522000005 · B 仓
+
+24
+
李建国
+
+
+
+
+
+ + +
+
+
+
库存状态
+

一眼看出哪些商品
需要补货

+

+ 为每件商品设置最低库存量,系统根据当前库存自动标记「充足 / 偏低 / 告急」,无需手工比对。 +

+
    +
  • + +
    + 按 SKU 设置最低库存 + 热销商品阈值高,冷门商品阈值低 — 每件单独配置。 +
    +
  • +
  • + +
    + 库存列表一目了然 + 库存状态标签跟随列表显示,随时筛选「偏低」「告急」的 SKU。 +
    +
  • +
  • + +
    + 商品二维码防伪 + 每件商品生成专属二维码,顾客扫码即可查看批次与防伪信息。 +
    +
  • +
+
+ +
+
+

库存状态一览

+

基于最低库存量 (min_stock) 自动标记状态。

+
+
+ 茅台飞天 53° + 充足 +
+
+ 剑南春水晶剑 + 偏低 +
+
+ 轩尼诗 VSOP + 告急 +
+
+
+
+
+
+ + +
+
+

典型场景

+

+ 库存管理,落到实际操作里是什么样 +

+

+ 三个常见门店场景,看库存管理如何省下你每天的时间。 +

+ +
+
+ +

客户来取货前,先看一眼批次

+

客户要 12 瓶飞天,先在批次追踪里看哪一批快到期;优先发出旧批次,新批次留着。

+
+
+ +

月底盘点,2 小时变 30 分钟

+

用移动端扫码逐瓶录入,系统对比账面 → 实时显示差异。损耗自动转入应付,不用再算。

+
+
+ +

财务来问"这瓶怎么少了",3 秒回答

+

打开库存流水,按 SKU 筛选,全部入库、出库、盘点变动一目了然,每条都关联到原始单据。

+
+
+
+
+ + +
+
+
+
+

试试看你今天的库存有几瓶。

+

30 天免费试用,无需绑定支付。所有数据云端实时备份,可随时导出。

+
+ +
+
+
+