Files
ONE-OS/ONEOS-web/车辆状态说明.jsx
王冕 df824ae71c feat(web): 新增车辆状态说明响应式文档页
提供五维状态、关联规则、场景问答与 H5 手机布局,便于业务与运维查阅车辆状态口径。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-10 16:53:41 +08:00

1123 lines
65 KiB
JavaScript
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.
// 【重要】必须使用 const Component 作为组件变量名
// ONEOS-web · 车辆状态说明(响应式文档页)
function vsEnsureViewport() {
try {
var meta = document.querySelector('meta[name="viewport"]');
if (!meta) {
meta = document.createElement('meta');
meta.name = 'viewport';
document.head.appendChild(meta);
}
meta.content = 'width=device-width, initial-scale=1, maximum-scale=5, viewport-fit=cover';
} catch (e) { /* ignore */ }
}
function vsDetectMobileLayout(rootEl) {
vsEnsureViewport();
var ua = navigator.userAgent || '';
var isPhoneUa = /iPhone|iPod|Android.+Mobile|Opera Mini|IEMobile|Windows Phone|webOS|BlackBerry/i.test(ua);
var isWx = /MicroMessenger/i.test(ua);
var vw = window.visualViewport && window.visualViewport.width
? window.visualViewport.width
: window.innerWidth;
var docW = document.documentElement ? document.documentElement.clientWidth : 0;
var rootW = rootEl && rootEl.clientWidth ? rootEl.clientWidth : 0;
if (vw > 0 && vw <= 768) return true;
if (docW > 0 && docW <= 768) return true;
if (rootW > 0 && rootW <= 768) return true;
try {
if (window.matchMedia) {
if (window.matchMedia('(max-width: 768px)').matches) return true;
if (window.matchMedia('(max-device-width: 820px)').matches) return true;
if (window.matchMedia('(hover: none) and (pointer: coarse)').matches) return true;
}
} catch (e) { /* ignore */ }
var sw = 0;
if (window.screen) {
sw = Math.min(window.screen.width || 0, window.screen.height || 0);
if (sw > 0 && sw <= 820) return true;
}
var touch = ('ontouchstart' in window) || (navigator.maxTouchPoints > 0);
if (touch && sw > 0 && sw <= 820) return true;
if (isPhoneUa || (isWx && touch)) return true;
return false;
}
function vsApplyMobileEnv(isMobile) {
try {
if (!document.documentElement) return;
if (isMobile) document.documentElement.classList.add('vs-mobile-env');
else document.documentElement.classList.remove('vs-mobile-env');
} catch (e) { /* ignore */ }
}
if (typeof document !== 'undefined') {
try {
vsEnsureViewport();
vsApplyMobileEnv(vsDetectMobileLayout(document.getElementById('root')));
} catch (e) { /* ignore */ }
}
const Component = function (props) {
const { useState, useEffect, useLayoutEffect, useCallback, useMemo } = React;
const antd = window.antd || {};
const Tag = antd.Tag;
const Input = antd.Input;
const C_PRIMARY = '#0f766e';
const C_PRIMARY_L = '#14b8a6';
const C_PRIMARY_D = '#0d5c56';
const C_PAGE = '#f1f5f9';
const C_CARD = '#ffffff';
const C_TEXT = '#0f172a';
const C_MUTED = '#64748b';
const C_LINE = 'rgba(15, 23, 42, 0.08)';
const C_BLUE = '#2563eb';
const C_AMBER = '#d97706';
const C_VIOLET = '#7c3aed';
const C_ROSE = '#e11d48';
const C_GREEN = '#16a34a';
const FONT = '-apple-system, BlinkMacSystemFont, "PingFang SC", "Helvetica Neue", "Segoe UI", sans-serif';
const PAGE_STYLE = `
.vs-root { font-family: ${FONT}; background: ${C_PAGE}; color: ${C_TEXT}; min-height: 100vh; line-height: 1.6; -webkit-font-smoothing: antialiased; }
.vs-skip { position: absolute; left: -9999px; top: 0; z-index: 9999; padding: 10px 16px; background: ${C_PRIMARY}; color: #fff; border-radius: 8px; }
.vs-skip:focus { left: 12px; top: 12px; outline: 3px solid #5eead4; outline-offset: 2px; }
.vs-hero { position: relative; overflow: hidden; background: linear-gradient(145deg, #0c1222 0%, #132337 42%, #0f4c47 100%); color: #fff; padding: 48px 24px 56px; }
.vs-hero::before { content: ''; position: absolute; inset: 0; background: radial-gradient(ellipse 80% 60% at 90% 10%, rgba(20,184,166,0.22), transparent 55%), radial-gradient(ellipse 50% 40% at 10% 90%, rgba(37,99,235,0.12), transparent 50%); pointer-events: none; }
.vs-hero::after { content: ''; position: absolute; inset: 0; background-image: linear-gradient(rgba(255,255,255,0.03) 1px, transparent 1px), linear-gradient(90deg, rgba(255,255,255,0.03) 1px, transparent 1px); background-size: 32px 32px; mask-image: linear-gradient(180deg, rgba(0,0,0,0.5), transparent); pointer-events: none; }
.vs-hero-inner { position: relative; max-width: 1120px; margin: 0 auto; }
.vs-hero-tag { display: inline-flex; align-items: center; gap: 6px; font-size: 12px; font-weight: 600; padding: 5px 12px; border-radius: 999px; background: rgba(255,255,255,0.1); border: 1px solid rgba(255,255,255,0.14); color: #a7f3d0; margin-bottom: 14px; letter-spacing: 0.04em; }
.vs-hero h1 { margin: 0 0 14px; font-size: clamp(26px, 5vw, 38px); font-weight: 800; letter-spacing: -0.03em; line-height: 1.2; }
.vs-hero-desc { margin: 0; font-size: clamp(14px, 2.5vw, 16px); color: rgba(255,255,255,0.76); max-width: 680px; line-height: 1.7; }
.vs-hero-kpis { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; margin-top: 32px; }
@media (max-width: 640px) { .vs-hero-kpis { grid-template-columns: repeat(2, 1fr); } }
.vs-kpi { background: rgba(255,255,255,0.07); border: 1px solid rgba(255,255,255,0.12); border-radius: 14px; padding: 16px 18px; backdrop-filter: blur(8px); transition: transform 0.2s ease, border-color 0.2s ease; }
.vs-kpi:hover { transform: translateY(-2px); border-color: rgba(94,234,212,0.35); }
.vs-kpi-val { font-size: 26px; font-weight: 800; color: #5eead4; font-variant-numeric: tabular-nums; letter-spacing: -0.02em; }
.vs-kpi-lbl { font-size: 12px; color: rgba(255,255,255,0.62); margin-top: 4px; }
.vs-shell { max-width: 1120px; margin: 0 auto; padding: 0 16px 72px; }
.vs-layout { display: grid; grid-template-columns: 220px 1fr; gap: 28px; margin-top: -28px; position: relative; z-index: 2; align-items: start; }
@media (max-width: 900px) { .vs-layout { grid-template-columns: 1fr; margin-top: 20px; } }
.vs-sidebar { position: sticky; top: 16px; }
@media (max-width: 900px) { .vs-sidebar { position: sticky; top: 0; z-index: 20; margin: 0 -16px; padding: 0 16px 8px; background: linear-gradient(180deg, ${C_PAGE} 85%, transparent); } }
.vs-nav-card { background: ${C_CARD}; border: 1px solid ${C_LINE}; border-radius: 16px; padding: 14px 10px; box-shadow: 0 4px 24px rgba(15,23,42,0.06); }
.vs-nav-label { font-size: 11px; font-weight: 700; color: ${C_MUTED}; letter-spacing: 0.08em; padding: 0 12px 8px; text-transform: uppercase; }
@media (max-width: 900px) { .vs-nav-label { display: none; } }
.vs-nav-list { display: flex; flex-direction: column; gap: 2px; }
@media (max-width: 900px) { .vs-nav-list { flex-direction: row; flex-wrap: nowrap; overflow-x: auto; gap: 8px; padding-bottom: 4px; -webkit-overflow-scrolling: touch; scrollbar-width: none; }
.vs-nav-list::-webkit-scrollbar { display: none; } }
.vs-nav-item { display: flex; align-items: center; gap: 10px; width: 100%; min-height: 44px; text-align: left; border: none; background: transparent; padding: 10px 12px; font-size: 13px; color: ${C_MUTED}; cursor: pointer; border-radius: 10px; transition: background 0.2s ease, color 0.2s ease; touch-action: manipulation; white-space: nowrap; }
@media (max-width: 900px) { .vs-nav-item { width: auto; flex-shrink: 0; padding: 10px 16px; background: ${C_CARD}; border: 1px solid ${C_LINE}; } }
.vs-nav-item:hover { color: ${C_PRIMARY}; background: rgba(15,118,110,0.06); }
.vs-nav-item:focus-visible { outline: 2px solid ${C_PRIMARY_L}; outline-offset: 2px; }
.vs-nav-item.active { color: ${C_PRIMARY_D}; font-weight: 600; background: linear-gradient(90deg, rgba(15,118,110,0.12), rgba(15,118,110,0.04)); }
@media (max-width: 900px) { .vs-nav-item.active { background: ${C_PRIMARY}; color: #fff; border-color: ${C_PRIMARY}; } }
.vs-nav-dot { width: 8px; height: 8px; border-radius: 50%; background: ${C_LINE}; flex-shrink: 0; transition: background 0.2s, transform 0.2s; }
.vs-nav-item.active .vs-nav-dot { background: ${C_PRIMARY_L}; transform: scale(1.2); }
@media (max-width: 900px) { .vs-nav-dot { display: none; } }
.vs-main { min-width: 0; display: flex; flex-direction: column; gap: 20px; }
.vs-panel { background: ${C_CARD}; border: 1px solid ${C_LINE}; border-radius: 16px; padding: 24px 26px; box-shadow: 0 1px 3px rgba(15,23,42,0.04); scroll-margin-top: 88px; transition: box-shadow 0.25s ease; }
.vs-panel:hover { box-shadow: 0 8px 30px rgba(15,23,42,0.06); }
.vs-panel-head { margin-bottom: 20px; }
.vs-panel-tag { display: inline-block; font-size: 10px; font-weight: 700; letter-spacing: 0.1em; color: ${C_PRIMARY}; background: rgba(15,118,110,0.08); padding: 4px 10px; border-radius: 6px; margin-bottom: 10px; }
.vs-panel-title { margin: 0; font-size: clamp(18px, 3vw, 22px); font-weight: 800; letter-spacing: -0.02em; display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.vs-panel-num { display: inline-flex; align-items: center; justify-content: center; width: 32px; height: 32px; border-radius: 10px; background: linear-gradient(135deg, ${C_PRIMARY}, ${C_PRIMARY_L}); color: #fff; font-size: 14px; font-weight: 800; flex-shrink: 0; box-shadow: 0 4px 12px rgba(15,118,110,0.25); }
.vs-panel-desc { margin: 10px 0 0; font-size: 14px; color: ${C_MUTED}; max-width: 640px; }
.vs-bento { display: grid; grid-template-columns: repeat(6, 1fr); gap: 12px; }
@media (max-width: 768px) { .vs-bento { grid-template-columns: 1fr 1fr; } }
.vs-bento-item { grid-column: span 2; background: ${C_PAGE}; border: 1px solid ${C_LINE}; border-radius: 14px; padding: 18px; position: relative; overflow: hidden; transition: transform 0.2s ease, box-shadow 0.2s ease; cursor: default; }
.vs-bento-item:hover { transform: translateY(-3px); box-shadow: 0 12px 28px rgba(15,23,42,0.08); }
.vs-bento-item::before { content: ''; position: absolute; top: 0; left: 0; right: 0; height: 3px; background: var(--accent); }
.vs-bento-item h4 { margin: 0 0 8px; font-size: 15px; font-weight: 700; }
.vs-bento-item p { margin: 0; font-size: 13px; color: ${C_MUTED}; line-height: 1.55; }
.vs-bento-icon { width: 36px; height: 36px; border-radius: 10px; background: var(--accent-bg); display: flex; align-items: center; justify-content: center; margin-bottom: 12px; color: var(--accent); }
.vs-example { background: linear-gradient(135deg, #f0fdfa 0%, #ecfeff 100%); border: 1px solid #99f6e4; border-radius: 14px; padding: 18px 20px; margin-top: 20px; }
.vs-tags { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 12px; }
.vs-callout { display: flex; gap: 12px; align-items: flex-start; background: #fffbeb; border: 1px solid #fde68a; border-radius: 12px; padding: 14px 16px; margin-top: 20px; font-size: 13px; color: #92400e; line-height: 1.6; }
.vs-callout-icon { flex-shrink: 0; width: 22px; height: 22px; border-radius: 6px; background: #fef3c7; display: flex; align-items: center; justify-content: center; font-weight: 700; font-size: 12px; color: #b45309; }
.vs-rule-grid { display: grid; gap: 10px; }
@media (min-width: 640px) { .vs-rule-grid { grid-template-columns: 1fr 1fr; } }
.vs-rule-card { background: ${C_PAGE}; border: 1px solid ${C_LINE}; border-radius: 12px; padding: 14px 16px; font-size: 13px; color: #334155; line-height: 1.65; display: flex; gap: 10px; }
.vs-rule-idx { flex-shrink: 0; width: 24px; height: 24px; border-radius: 8px; background: ${C_PRIMARY}; color: #fff; font-size: 12px; font-weight: 700; display: flex; align-items: center; justify-content: center; }
.vs-timeline { display: flex; flex-direction: column; gap: 0; margin: 16px 0; position: relative; padding-left: 20px; }
.vs-timeline::before { content: ''; position: absolute; left: 7px; top: 8px; bottom: 8px; width: 2px; background: linear-gradient(180deg, ${C_PRIMARY_L}, ${C_LINE}); border-radius: 2px; }
.vs-timeline-step { position: relative; padding: 8px 0 8px 20px; font-size: 13px; font-weight: 500; color: #334155; }
.vs-timeline-step::before { content: ''; position: absolute; left: -17px; top: 14px; width: 10px; height: 10px; border-radius: 50%; background: ${C_CARD}; border: 2px solid ${C_PRIMARY_L}; box-shadow: 0 0 0 3px rgba(20,184,166,0.15); }
.vs-timeline-step:last-child::before { background: ${C_PRIMARY_L}; }
.vs-subtitle { font-size: 15px; font-weight: 700; margin: 24px 0 12px; color: ${C_TEXT}; }
.vs-table-wrap { overflow-x: auto; -webkit-overflow-scrolling: touch; margin: 12px 0; border-radius: 12px; border: 1px solid ${C_LINE}; box-shadow: inset 0 1px 0 rgba(255,255,255,0.8); }
.vs-table { width: 100%; border-collapse: collapse; font-size: 13px; min-width: 480px; }
.vs-table th { background: #f8fafc; text-align: left; padding: 12px 16px; font-weight: 600; color: ${C_TEXT}; border-bottom: 1px solid ${C_LINE}; font-size: 12px; letter-spacing: 0.02em; }
.vs-table td { padding: 12px 16px; border-bottom: 1px solid ${C_LINE}; color: #334155; vertical-align: top; line-height: 1.55; }
.vs-table tbody tr { transition: background 0.15s ease; }
.vs-table tbody tr:hover { background: #f8fafc; }
.vs-table tbody tr:nth-child(even) { background: rgba(248,250,252,0.6); }
.vs-table tbody tr:nth-child(even):hover { background: #f1f5f9; }
.vs-table tr:last-child td { border-bottom: none; }
.vs-status-grid { display: grid; gap: 10px; }
@media (min-width: 640px) { .vs-status-grid { grid-template-columns: 1fr 1fr; } }
.vs-status-card { background: ${C_PAGE}; border: 1px solid ${C_LINE}; border-radius: 12px; padding: 16px 18px; transition: border-color 0.2s ease, box-shadow 0.2s ease; }
.vs-status-card:hover { border-color: rgba(15,118,110,0.25); box-shadow: 0 4px 16px rgba(15,23,42,0.05); }
.vs-status-card h3 { margin: 0 0 8px; font-size: 14px; font-weight: 700; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.vs-status-card p { margin: 0; font-size: 13px; color: #475569; line-height: 1.6; }
.vs-toolbar { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; margin-bottom: 16px; }
.vs-chips { display: flex; flex-wrap: wrap; gap: 8px; }
.vs-chip { min-height: 36px; padding: 0 14px; border-radius: 999px; border: 1px solid ${C_LINE}; background: ${C_CARD}; font-size: 13px; color: ${C_MUTED}; cursor: pointer; transition: all 0.2s ease; touch-action: manipulation; }
.vs-chip:hover { border-color: ${C_PRIMARY_L}; color: ${C_PRIMARY}; }
.vs-chip:focus-visible { outline: 2px solid ${C_PRIMARY_L}; outline-offset: 2px; }
.vs-chip.active { background: ${C_PRIMARY}; border-color: ${C_PRIMARY}; color: #fff; font-weight: 600; }
.vs-search { flex: 1; min-width: 180px; max-width: 280px; }
.vs-faq-list { display: flex; flex-direction: column; gap: 8px; }
.vs-faq { border: 1px solid ${C_LINE}; border-radius: 12px; overflow: hidden; background: ${C_PAGE}; transition: border-color 0.2s ease, box-shadow 0.2s ease; }
.vs-faq.is-open { border-color: rgba(15,118,110,0.3); box-shadow: 0 4px 16px rgba(15,118,110,0.08); background: ${C_CARD}; }
.vs-faq-head { width: 100%; min-height: 52px; text-align: left; border: none; background: transparent; padding: 14px 18px; font-size: 14px; font-weight: 600; cursor: pointer; display: flex; justify-content: space-between; align-items: center; gap: 14px; color: ${C_TEXT}; touch-action: manipulation; }
.vs-faq-head:hover { background: rgba(15,118,110,0.04); }
.vs-faq-head:focus-visible { outline: 2px solid ${C_PRIMARY_L}; outline-offset: -2px; }
.vs-faq-q { display: flex; align-items: flex-start; gap: 12px; flex: 1; min-width: 0; }
.vs-faq-num { flex-shrink: 0; width: 26px; height: 26px; border-radius: 8px; background: rgba(15,118,110,0.1); color: ${C_PRIMARY}; font-size: 12px; font-weight: 800; display: flex; align-items: center; justify-content: center; }
.vs-faq.is-open .vs-faq-num { background: ${C_PRIMARY}; color: #fff; }
.vs-faq-chevron { flex-shrink: 0; width: 28px; height: 28px; border-radius: 8px; background: ${C_LINE}; display: flex; align-items: center; justify-content: center; transition: transform 0.25s ease, background 0.2s ease; font-size: 16px; color: ${C_MUTED}; }
.vs-faq.is-open .vs-faq-chevron { transform: rotate(180deg); background: rgba(15,118,110,0.12); color: ${C_PRIMARY}; }
.vs-faq-body { padding: 0 18px 16px 56px; font-size: 14px; color: #475569; line-height: 1.7; animation: vs-faq-in 0.25s ease; }
@keyframes vs-faq-in { from { opacity: 0; transform: translateY(-4px); } to { opacity: 1; transform: translateY(0); } }
.vs-faq-actions { display: flex; gap: 8px; margin-bottom: 14px; }
.vs-text-btn { min-height: 36px; padding: 0 12px; border: 1px solid ${C_LINE}; border-radius: 8px; background: ${C_CARD}; font-size: 12px; color: ${C_MUTED}; cursor: pointer; touch-action: manipulation; }
.vs-text-btn:hover { color: ${C_PRIMARY}; border-color: ${C_PRIMARY_L}; }
.vs-text-btn:focus-visible { outline: 2px solid ${C_PRIMARY_L}; outline-offset: 2px; }
.vs-pill { display: inline-flex; align-items: center; padding: 3px 10px; border-radius: 999px; font-size: 11px; font-weight: 600; line-height: 1.4; }
.vs-footer { text-align: center; padding: 28px 16px; font-size: 12px; color: ${C_MUTED}; border-top: 1px solid ${C_LINE}; max-width: 1120px; margin: 0 auto; }
.vs-top-btn { position: fixed; right: 20px; bottom: 24px; width: 48px; height: 48px; border-radius: 14px; border: none; background: ${C_PRIMARY}; color: #fff; font-size: 18px; cursor: pointer; box-shadow: 0 8px 24px rgba(15,118,110,0.35); z-index: 30; opacity: 0; pointer-events: none; transform: translateY(12px); transition: opacity 0.25s ease, transform 0.25s ease; touch-action: manipulation; }
.vs-top-btn.visible { opacity: 1; pointer-events: auto; transform: translateY(0); }
.vs-top-btn:hover { background: ${C_PRIMARY_D}; }
.vs-top-btn:focus-visible { outline: 3px solid #5eead4; outline-offset: 2px; }
.vs-empty { text-align: center; padding: 32px 16px; color: ${C_MUTED}; font-size: 14px; }
@media (prefers-reduced-motion: reduce) {
.vs-kpi, .vs-bento-item, .vs-status-card, .vs-faq-body, .vs-top-btn, .vs-nav-item, .vs-panel { transition: none !important; animation: none !important; }
.vs-kpi:hover, .vs-bento-item:hover { transform: none; }
}
/* —— H5 手机布局(纯 CSS 媒体查询,不依赖 JS class—— */
.vs-m-header { display: none; }
.vs-m-drawer-mask { display: none; }
.vs-m-bottom-bar { display: none; }
.vs-m-card-table { display: none; }
@media (max-width: 768px), (max-device-width: 820px), ((hover: none) and (pointer: coarse) and (max-width: 1024px)) {
.vs-root { max-width: 100vw; overflow-x: hidden; padding-bottom: env(safe-area-inset-bottom, 0px); }
.vs-m-header {
display: flex; align-items: center; justify-content: space-between; gap: 10px;
position: fixed; top: 0; left: 0; right: 0; z-index: 40;
height: calc(48px + env(safe-area-inset-top, 0px));
padding: env(safe-area-inset-top, 0px) 12px 0 12px;
background: rgba(255,255,255,0.92); backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px);
border-bottom: 1px solid ${C_LINE}; box-shadow: 0 2px 12px rgba(15,23,42,0.06);
}
.vs-m-header-title { font-size: 15px; font-weight: 700; color: ${C_TEXT}; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; flex: 1; min-width: 0; }
.vs-m-header-sub { font-size: 11px; color: ${C_MUTED}; margin-top: 1px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.vs-m-header-btn {
flex-shrink: 0; min-width: 44px; min-height: 44px; padding: 0 12px; border-radius: 10px;
border: 1px solid ${C_LINE}; background: ${C_CARD}; font-size: 13px; font-weight: 600; color: ${C_PRIMARY};
touch-action: manipulation; cursor: pointer;
}
.vs-m-header-btn:focus-visible { outline: 2px solid ${C_PRIMARY_L}; outline-offset: 2px; }
.vs-hero { padding: calc(56px + env(safe-area-inset-top, 0px)) 16px 28px; }
.vs-hero h1 { font-size: 24px; }
.vs-hero-desc { font-size: 14px; line-height: 1.65; }
.vs-hero-kpis { grid-template-columns: repeat(2, 1fr); gap: 10px; margin-top: 20px; }
.vs-kpi { padding: 12px 14px; border-radius: 12px; }
.vs-kpi-val { font-size: 22px; }
.vs-kpi:hover { transform: none; }
.vs-shell { padding: 0 12px calc(80px + env(safe-area-inset-bottom, 0px)); }
.vs-layout { margin-top: 12px; gap: 12px; }
.vs-sidebar { display: none !important; }
.vs-panel {
padding: 16px; border-radius: 14px; scroll-margin-top: calc(56px + env(safe-area-inset-top, 0px));
box-shadow: none;
}
.vs-panel:hover { box-shadow: none; }
.vs-panel-num { width: 28px; height: 28px; font-size: 13px; border-radius: 8px; }
.vs-panel-title { font-size: 17px; gap: 10px; }
.vs-panel-desc { font-size: 13px; }
.vs-bento { grid-template-columns: 1fr; gap: 10px; }
.vs-bento-item { grid-column: 1 / -1 !important; padding: 14px 16px; }
.vs-bento-item:hover { transform: none; box-shadow: none; }
.vs-rule-grid { grid-template-columns: 1fr; }
.vs-status-grid { grid-template-columns: 1fr; }
.vs-toolbar { flex-direction: column; align-items: stretch; }
.vs-chips { overflow-x: auto; flex-wrap: nowrap; padding-bottom: 4px; -webkit-overflow-scrolling: touch; scrollbar-width: none; }
.vs-chips::-webkit-scrollbar { display: none; }
.vs-chip { flex-shrink: 0; }
.vs-search { max-width: none; width: 100%; }
.vs-table-wrap { display: none !important; }
.vs-m-card-table { display: flex; flex-direction: column; gap: 10px; margin: 12px 0; }
.vs-faq-body { padding: 0 14px 14px 14px; font-size: 13px; }
.vs-faq-head { padding: 12px 14px; font-size: 13px; min-height: 48px; }
.vs-faq-actions { overflow-x: auto; flex-wrap: nowrap; -webkit-overflow-scrolling: touch; }
.vs-text-btn { flex-shrink: 0; }
.vs-footer { padding: 20px 12px calc(16px + env(safe-area-inset-bottom, 0px)); font-size: 11px; line-height: 1.7; }
.vs-top-btn {
right: 14px; bottom: calc(72px + env(safe-area-inset-bottom, 0px));
width: 44px; height: 44px; border-radius: 12px; font-size: 16px;
}
.vs-m-bottom-bar {
display: flex; position: fixed; left: 0; right: 0; bottom: 0; z-index: 35;
background: rgba(255,255,255,0.96); backdrop-filter: blur(14px); -webkit-backdrop-filter: blur(14px);
border-top: 1px solid ${C_LINE}; box-shadow: 0 -4px 20px rgba(15,23,42,0.06);
padding: 6px 8px calc(6px + env(safe-area-inset-bottom, 0px));
justify-content: space-around; gap: 4px;
}
.vs-m-tab {
flex: 1; min-width: 0; max-width: 72px; display: flex; flex-direction: column; align-items: center; justify-content: center;
gap: 3px; min-height: 48px; padding: 4px 2px; border: none; background: transparent; border-radius: 10px;
font-size: 10px; color: ${C_MUTED}; touch-action: manipulation; cursor: pointer;
}
.vs-m-tab-icon { font-size: 18px; line-height: 1; opacity: 0.75; }
.vs-m-tab.active { color: ${C_PRIMARY}; font-weight: 700; background: rgba(15,118,110,0.08); }
.vs-m-tab.active .vs-m-tab-icon { opacity: 1; }
.vs-m-tab:focus-visible { outline: 2px solid ${C_PRIMARY_L}; outline-offset: 1px; }
.vs-m-drawer-mask.is-open {
display: block; position: fixed; inset: 0; z-index: 50; background: rgba(15,23,42,0.45);
animation: vs-fade-in 0.2s ease;
}
.vs-m-drawer {
position: fixed; left: 0; right: 0; bottom: 0; z-index: 51;
background: ${C_CARD}; border-radius: 16px 16px 0 0;
max-height: min(72vh, 520px); display: flex; flex-direction: column;
transform: translateY(100%); transition: transform 0.28s cubic-bezier(0.32, 0.72, 0, 1);
padding-bottom: env(safe-area-inset-bottom, 0px);
box-shadow: 0 -8px 32px rgba(15,23,42,0.12);
}
.vs-m-drawer.is-open { transform: translateY(0); }
.vs-m-drawer-head { display: flex; align-items: center; justify-content: space-between; padding: 14px 16px 10px; border-bottom: 1px solid ${C_LINE}; flex-shrink: 0; }
.vs-m-drawer-head h3 { margin: 0; font-size: 16px; font-weight: 700; }
.vs-m-drawer-close { min-width: 44px; min-height: 44px; border: none; background: ${C_PAGE}; border-radius: 10px; font-size: 20px; color: ${C_MUTED}; cursor: pointer; }
.vs-m-drawer-list { overflow-y: auto; -webkit-overflow-scrolling: touch; padding: 8px 12px 16px; flex: 1; }
.vs-m-drawer-item {
display: flex; align-items: center; gap: 12px; width: 100%; min-height: 48px; padding: 12px 14px;
border: none; background: transparent; border-radius: 12px; text-align: left; font-size: 14px;
color: ${C_TEXT}; touch-action: manipulation; cursor: pointer; margin-bottom: 4px;
}
.vs-m-drawer-item.active { background: rgba(15,118,110,0.1); color: ${C_PRIMARY_D}; font-weight: 600; }
.vs-m-drawer-num { width: 26px; height: 26px; border-radius: 8px; background: ${C_PAGE}; font-size: 12px; font-weight: 700; color: ${C_MUTED}; display: flex; align-items: center; justify-content: center; flex-shrink: 0; }
.vs-m-drawer-item.active .vs-m-drawer-num { background: ${C_PRIMARY}; color: #fff; }
}
.vs-m-card-row {
background: ${C_PAGE}; border: 1px solid ${C_LINE}; border-radius: 12px; padding: 14px 16px;
}
.vs-m-card-row-label { font-size: 11px; font-weight: 700; color: ${C_MUTED}; letter-spacing: 0.04em; margin-bottom: 4px; }
.vs-m-card-row-value { font-size: 14px; color: #334155; line-height: 1.6; }
.vs-m-card-row-value strong { color: ${C_PRIMARY}; }
@keyframes vs-fade-in { from { opacity: 0; } to { opacity: 1; } }
/* JS / 设备 UA 兜底WebView、微信、iframe 内预览) */
html.vs-mobile-env .vs-root,
.vs-root--mobile { max-width: 100vw; overflow-x: hidden; padding-bottom: env(safe-area-inset-bottom, 0px); }
html.vs-mobile-env .vs-root .vs-m-header,
.vs-root--mobile .vs-m-header {
display: flex !important; align-items: center; justify-content: space-between; gap: 10px;
position: fixed; top: 0; left: 0; right: 0; z-index: 40;
height: calc(48px + env(safe-area-inset-top, 0px));
padding: env(safe-area-inset-top, 0px) 12px 0 12px;
background: rgba(255,255,255,0.92); backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px);
border-bottom: 1px solid ${C_LINE}; box-shadow: 0 2px 12px rgba(15,23,42,0.06);
}
html.vs-mobile-env .vs-root .vs-m-bottom-bar,
.vs-root--mobile .vs-m-bottom-bar {
display: flex !important; position: fixed; left: 0; right: 0; bottom: 0; z-index: 35;
background: rgba(255,255,255,0.96); backdrop-filter: blur(14px); -webkit-backdrop-filter: blur(14px);
border-top: 1px solid ${C_LINE}; box-shadow: 0 -4px 20px rgba(15,23,42,0.06);
padding: 6px 8px calc(6px + env(safe-area-inset-bottom, 0px));
justify-content: space-around; gap: 4px;
}
html.vs-mobile-env .vs-root .vs-sidebar,
.vs-root--mobile .vs-sidebar { display: none !important; }
html.vs-mobile-env .vs-root .vs-table-wrap,
.vs-root--mobile .vs-table-wrap { display: none !important; }
html.vs-mobile-env .vs-root .vs-m-card-table,
.vs-root--mobile .vs-m-card-table { display: flex !important; flex-direction: column; gap: 10px; margin: 12px 0; }
html.vs-mobile-env .vs-root .vs-hero,
.vs-root--mobile .vs-hero { padding: calc(56px + env(safe-area-inset-top, 0px)) 16px 28px; }
html.vs-mobile-env .vs-root .vs-hero h1,
.vs-root--mobile .vs-hero h1 { font-size: 24px; }
html.vs-mobile-env .vs-root .vs-hero-kpis,
.vs-root--mobile .vs-hero-kpis { grid-template-columns: repeat(2, 1fr); gap: 10px; margin-top: 20px; }
html.vs-mobile-env .vs-root .vs-shell,
.vs-root--mobile .vs-shell { padding: 0 12px calc(80px + env(safe-area-inset-bottom, 0px)); }
html.vs-mobile-env .vs-root .vs-layout,
.vs-root--mobile .vs-layout { margin-top: 12px; gap: 12px; }
html.vs-mobile-env .vs-root .vs-panel,
.vs-root--mobile .vs-panel { padding: 16px; border-radius: 14px; scroll-margin-top: calc(56px + env(safe-area-inset-top, 0px)); box-shadow: none; }
html.vs-mobile-env .vs-root .vs-bento,
.vs-root--mobile .vs-bento { grid-template-columns: 1fr; gap: 10px; }
html.vs-mobile-env .vs-root .vs-bento-item,
.vs-root--mobile .vs-bento-item { grid-column: 1 / -1 !important; padding: 14px 16px; }
html.vs-mobile-env .vs-root .vs-status-grid,
html.vs-mobile-env .vs-root .vs-rule-grid,
.vs-root--mobile .vs-status-grid,
.vs-root--mobile .vs-rule-grid { grid-template-columns: 1fr; }
html.vs-mobile-env .vs-root .vs-toolbar,
.vs-root--mobile .vs-toolbar { flex-direction: column; align-items: stretch; }
html.vs-mobile-env .vs-root .vs-search,
.vs-root--mobile .vs-search { max-width: none; width: 100%; }
html.vs-mobile-env .vs-root .vs-top-btn,
.vs-root--mobile .vs-top-btn { right: 14px; bottom: calc(72px + env(safe-area-inset-bottom, 0px)); width: 44px; height: 44px; }
html.vs-mobile-env .vs-root .vs-m-drawer-mask.is-open,
.vs-root--mobile .vs-m-drawer-mask.is-open { display: block !important; position: fixed; inset: 0; z-index: 50; background: rgba(15,23,42,0.45); }
`;
const sectionNav = [
{ key: 'overview', label: '五维总览', icon: '◎' },
{ key: 'relation', label: '状态关联', icon: '↔' },
{ key: 'ops', label: '运营状态', icon: '◇' },
{ key: 'vehicle', label: '车辆状态', icon: '▣' },
{ key: 'outbound', label: '出库状态', icon: '↗' },
{ key: 'license', label: '证照状态', icon: '▤' },
{ key: 'insurance', label: '保险状态', icon: '◆' },
{ key: 'faq', label: '场景问答', icon: '?' },
{ key: 'cheatsheet', label: '速查表', icon: '✓' },
];
const mobileBottomTabs = [
{ key: 'overview', label: '总览', icon: '◎' },
{ key: 'relation', label: '关联', icon: '↔' },
{ key: 'ops', label: '运营', icon: '◇' },
{ key: 'vehicle', label: '车辆', icon: '▣' },
{ key: '__menu', label: '目录', icon: '☰' },
];
const dimIcons = {
ops: 'M4 6h16v4H4zm0 6h10v4H4zm0 6h14v4H4',
vehicle: 'M5 17h14l-1.5-5H6.5L5 17zm2.5-7l1-3h7l1 3M7 9V7h10v2',
outbound: 'M4 8h16v10H4zm4-4h8v4H8z',
license: 'M6 4h12v16H6zm3 4h6v2H9zm0 4h6v2H9z',
insurance: 'M12 3l8 4v6c0 4-3.5 7-8 8-4.5-1-8-4-8-8V7l8-4z',
};
const dimensions = [
{ key: 'ops', name: '运营状态', question: '这辆车现在处于什么业务阶段?', color: C_PRIMARY },
{ key: 'vehicle', name: '车辆状态', question: '是否被某笔业务「占位」?', color: C_BLUE },
{ key: 'outbound', name: '出库状态', question: '若已离开车队,是因何原因出库?', color: C_VIOLET },
{ key: 'license', name: '证照状态', question: '行驶证/沪牌等评是否有效?', color: C_AMBER },
{ key: 'insurance', name: '保险状态', question: '交强险/商业险是否齐全有效?', color: C_ROSE },
];
const opsStatuses = [
{ name: '租赁', desc: '车辆当前在租赁业务合同下,运维已完成交车,客户尚未还车。车辆不在库,属于对外出租中。', tag: '在外运营', tagColor: C_BLUE },
{ name: '自营', desc: '车辆当前在自营业务合同下,运维已完成交车,尚未还车。与「租赁」类似,业务类型为自营。', tag: '在外运营', tagColor: C_VIOLET },
{ name: '可运营', desc: '车辆在库;证照、保险均为正常。可随时备车,备车完成后可随时交车。', tag: '在库·可交车', tagColor: C_GREEN },
{ name: '待运营', desc: '车辆在库,但证照或保险至少一项异常。可以备车,但禁止交车,避免证照过期或无保险上路风险。', tag: '在库·禁交车', tagColor: C_AMBER },
{ name: '退出运营', desc: '车辆已退出运营体系。不再生成年审任务,不可备车、不可交车。适用于报废、三方退租、销售等历史车辆。(完整流程部分尚未上线,可联系数智部人工处理。)', tag: '历史车辆', tagColor: C_MUTED },
];
const vehicleStatuses = [
{ name: '待验车', desc: '采购/三方租用入库后尚未完成验车。', upcoming: true, group: 'stock' },
{ name: '未备车', desc: '尚未备车,可随时发起备车。', group: 'stock' },
{ name: '已备车', desc: '已完成备车并在备车库中,可随时交车(须证照/保险正常、运营状态允许)。', group: 'stock' },
{ name: '待交车', desc: '租赁合同已确定车牌,签约后占位,防止被其他业务误交。', group: 'stock' },
{ name: '已交车', desc: '运维 E 签宝交车签章完成即算已交车,无需等待客户签章。此时运营状态必为租赁或自营。', group: 'stock' },
{ name: '待还车', desc: '还车任务已生成,还车流程尚未完成。', group: 'stock' },
{ name: '呆滞车', desc: '将随呆滞车管理功能上线。', upcoming: true, group: 'flow' },
{ name: '报废中', desc: '报废流程已审批通过,尚未完成报废情况填写。', group: 'flow' },
{ name: '维修中', desc: '维修单未完成或未标记已修复;修复后恢复维修前车辆状态。', upcoming: true, group: 'flow' },
{ name: '销售中', desc: '销售流程已通过,尚未完成销售结果填写;完成后车辆状态为无、出库为销售出库。', upcoming: true, group: 'flow' },
{ name: '过户中', desc: '内部过户进行中,尚未完成信息填写与证件更新。', upcoming: true, group: 'flow' },
{ name: '替换中', desc: '替换车流程通过后新车标记;完成后新车为已交车。旧车永久替换则还车后未备车,临时替换仍已交车。', group: 'flow' },
{ name: '调拨中', desc: '调拨流程通过后标记;接收人完成接车后恢复调拨前状态。', group: 'flow' },
{ name: '异动中', desc: '异动流程通过后标记;操作人完成异动结束后恢复异动前状态。', group: 'flow' },
{ name: '三方退租中', desc: '三方退租已通过,尚未完成退租确认。', upcoming: true, group: 'flow' },
{ name: '无', desc: '报废/销售/三方退租完成后的终态,不再参与在库运营占位。', group: 'end' },
];
const vehicleFilters = [
{ key: 'all', label: '全部' },
{ key: 'stock', label: '在库流转' },
{ key: 'flow', label: '流程占用' },
{ key: 'upcoming', label: '暂未上线' },
];
const outboundStatuses = [
{ name: '无', meaning: '车辆未出库,仍在车队管理中', when: '默认;还车在库后' },
{ name: '三方退租出库', meaning: '外部租赁合同终止并完成退租确认', when: '三方退租流程完结' },
{ name: '销售出库', meaning: '车辆已销售离场', when: '销售结果填写完成' },
{ name: '报废出库', meaning: '车辆已报废离场', when: '报废结果填写完成' },
];
const licenseStatuses = [
{ name: '正常', desc: '行驶证检验有效期、沪牌车等评时间在有效期内。', tone: C_GREEN },
{ name: '异常', desc: '行驶证已过期,和/或沪牌车等评时间已到期。', tone: C_ROSE },
{ name: '无', desc: '车辆已出库,无需再维护证照信息。', tone: C_MUTED },
];
const insuranceStatuses = [
{ name: '正常', desc: '交强险、商业险均存在,且最新一条记录在有效期内。', tone: C_GREEN },
{ name: '异常', desc: '交强险或商业险缺失、已过期,或最新记录为停保/退保。', tone: C_ROSE },
{ name: '无', desc: '车辆已退出运营,无需再维护保险。', tone: C_MUTED },
];
const relationRules = [
'运营状态为「租赁/自营」表示已交车在客户处,不再交车;「可运营/待运营」为在库待调度;「退出运营」不可备车交车。',
'车辆状态为「已备车」或合法「待交车」等才允许交车;维修中、调拨中、异动中等流程占用时,其他业务不可占用该车。',
'证照、保险均须为「正常」才能交车;任一项「异常」时,在库车辆运营状态体现为「待运营」——可备车,不可交车。',
'退出运营的车辆不再生成年审任务,且不可备车、不可交车。',
];
const flowRecoveries = [
{ from: '维修中', to: '维修标记「已修复」后,恢复维修前车辆状态' },
{ from: '调拨中', to: '接收方完成调拨接车后,恢复调拨前车辆状态' },
{ from: '异动中', to: '操作人完成异动结束后,恢复异动前车辆状态' },
{ from: '替换中(新车)', to: '替换完成后变为已交车;旧车永久替换还车后未备车,临时替换仍已交车' },
{ from: '报废/销售/三方退租中', to: '完成结果确认后,车辆状态为无,配合出库状态与退出运营' },
];
const lifecycleSteps = [
'采购/租用入库', '待验车', '未备车', '已备车', '待交车', '已交车', '待还车', '回库',
];
const faqs = [
{ q: '车在库里,为什么不让交车?', a: '请依次查看:① 运营状态是否为「待运营」(证照或保险异常);② 车辆状态是否为「已备车」或合法「待交车」;③ 是否处于维修中、调拨中、异动中等流程占用。任一不满足都可能禁止交车。' },
{ q: '合同已签、车牌已定,别的业务还能用这辆车吗?', a: '不能。车辆状态为「待交车」时会占用该车,直到交车完成变为「已交车」,防止误交给其他客户。' },
{ q: '运维 E 签宝交车签章后,客户还没签,算交车了吗?', a: '算。系统以运维交车签章完成为准,车辆状态变为「已交车」,运营状态变为「租赁」或「自营」。' },
{ q: '还车任务已建但客户还没还,车算什么状态?', a: '车辆状态为「待还车」;运营状态仍为「租赁」或「自营」,直到还车流程完成。' },
{ q: '还车完成后,为什么有的是「可运营」有的是「待运营」?', a: '还车后车辆回库。证照、保险均正常 →「可运营」;证照或保险有异常 →「待运营」,须先处理再交车。' },
{ q: '调拨中的车,对方还没接,能拿去交车吗?', a: '不能。调拨流程占用为「调拨中」,需等接收方完成接车流程后,车辆才恢复调拨前状态。' },
{ q: '维修中的车修好了,状态怎么变?', a: '维修结果标记为「已修复」后,车辆状态恢复为进入维修前的状态(如已备车、未备车等)。' },
{ q: '替换车完成后,新旧车分别是什么状态?', a: '新车:替换完成后为「已交车」,运营为租赁/自营。旧车:永久替换则还车后「未备车」;临时替换则仍为「已交车」。' },
{ q: '报废/销售/三方退租后,系统里还能看到哪些状态?', a: '车辆状态「无」、出库状态为对应出库类型、运营状态「退出运营」、证照/保险「无」。该车不再参与备车、交车、年审。' },
{ q: '有些状态显示但菜单里没有对应功能?', a: '待验车、呆滞车、销售、过户、三方退租、完整报废流程等尚未全部上线。当前异常处理可联系数智部人工协助。' },
];
const cheatSheet = [
{ action: '备车', condition: '运营:可运营或待运营(非退出运营);车辆状态:须为「未备车」' },
{ action: '交车', condition: '运营:可运营;车辆:已备车或待交车;证照/保险:正常;非各类「××中」占用' },
{ action: '还车', condition: '运营:租赁或自营;生成还车任务后 → 待还车' },
{ action: '调拨', condition: '流程通过 → 调拨中;接车完成 → 恢复原车辆状态' },
{ action: '退出车队', condition: '走完报废/销售/三方退租 → 无 + 出库 + 退出运营' },
];
const [activeNav, setActiveNav] = useState('overview');
const [openFaq, setOpenFaq] = useState(0);
const [faqExpandAll, setFaqExpandAll] = useState(false);
const [showTop, setShowTop] = useState(false);
const [vehicleFilter, setVehicleFilter] = useState('all');
const [vehicleSearch, setVehicleSearch] = useState('');
var mountRoot = props && props.container ? props.container : null;
const [isMobile, setIsMobile] = useState(function () {
try { return vsDetectMobileLayout(mountRoot); } catch (e) { return false; }
});
const [navDrawerOpen, setNavDrawerOpen] = useState(false);
vsEnsureViewport();
const activeNavLabel = useMemo(function () {
var found = sectionNav.filter(function (s) { return s.key === activeNav; })[0];
return found ? found.label : '';
}, [activeNav]);
const filteredVehicleStatuses = useMemo(function () {
var q = String(vehicleSearch || '').trim().toLowerCase();
return vehicleStatuses.filter(function (item) {
if (vehicleFilter === 'upcoming' && !item.upcoming) return false;
if (vehicleFilter === 'stock' && item.group !== 'stock') return false;
if (vehicleFilter === 'flow' && item.group !== 'flow') return false;
if (q && item.name.toLowerCase().indexOf(q) < 0 && item.desc.toLowerCase().indexOf(q) < 0) return false;
return true;
});
}, [vehicleSearch, vehicleFilter]);
const scrollTo = useCallback(function (key) {
setActiveNav(key);
var el = document.getElementById('vs-' + key);
if (el) el.scrollIntoView({ behavior: 'smooth', block: 'start' });
}, []);
const scrollToSection = useCallback(function (key) {
setNavDrawerOpen(false);
scrollTo(key);
}, [scrollTo]);
useLayoutEffect(function () {
var styleId = 'vs-page-styles';
var styleEl = document.getElementById(styleId);
if (!styleEl) {
styleEl = document.createElement('style');
styleEl.id = styleId;
document.head.appendChild(styleEl);
}
styleEl.textContent = PAGE_STYLE;
vsEnsureViewport();
var rootEl = mountRoot || document.getElementById('root');
function apply() {
var mobile = vsDetectMobileLayout(rootEl);
setIsMobile(mobile);
vsApplyMobileEnv(mobile);
}
apply();
var mqList = [];
if (window.matchMedia) {
['(max-width: 768px)', '(max-device-width: 820px)', '(hover: none) and (pointer: coarse)'].forEach(function (q) {
var mq = window.matchMedia(q);
mqList.push(mq);
if (mq.addEventListener) mq.addEventListener('change', apply);
else if (mq.addListener) mq.addListener(apply);
});
}
window.addEventListener('resize', apply);
window.addEventListener('orientationchange', apply);
if (window.visualViewport) {
window.visualViewport.addEventListener('resize', apply);
}
var ro = null;
if (rootEl && window.ResizeObserver) {
ro = new ResizeObserver(apply);
ro.observe(rootEl);
}
var t1 = setTimeout(apply, 100);
var t2 = setTimeout(apply, 500);
return function () {
clearTimeout(t1);
clearTimeout(t2);
mqList.forEach(function (mq) {
if (mq.removeEventListener) mq.removeEventListener('change', apply);
else if (mq.removeListener) mq.removeListener(apply);
});
window.removeEventListener('resize', apply);
window.removeEventListener('orientationchange', apply);
if (window.visualViewport) {
window.visualViewport.removeEventListener('resize', apply);
}
if (ro) ro.disconnect();
};
}, [mountRoot]);
useEffect(function () {
if (!navDrawerOpen) return;
var prev = document.body.style.overflow;
document.body.style.overflow = 'hidden';
return function () { document.body.style.overflow = prev; };
}, [navDrawerOpen]);
useEffect(function () {
var sections = sectionNav.map(function (s) { return document.getElementById('vs-' + s.key); }).filter(Boolean);
if (!sections.length) return;
var observer = new IntersectionObserver(
function (entries) {
entries.forEach(function (entry) {
if (entry.isIntersecting) {
setActiveNav(entry.target.id.replace('vs-', ''));
}
});
},
{ rootMargin: '-15% 0px -55% 0px', threshold: 0 }
);
sections.forEach(function (el) { observer.observe(el); });
return function () { observer.disconnect(); };
}, []);
useEffect(function () {
function onScroll() { setShowTop(window.scrollY > 480); }
onScroll();
window.addEventListener('scroll', onScroll, { passive: true });
return function () { window.removeEventListener('scroll', onScroll); };
}, []);
const renderPill = function (text, color) {
return React.createElement('span', {
className: 'vs-pill',
style: { background: color + '14', color: color, border: '1px solid ' + color + '33' }
}, text);
};
const renderSvgIcon = function (d, color) {
return React.createElement('svg', {
width: 20, height: 20, viewBox: '0 0 24 24', fill: 'none',
stroke: color || 'currentColor', strokeWidth: 1.8, strokeLinecap: 'round', strokeLinejoin: 'round',
'aria-hidden': true
}, React.createElement('path', { d: d }));
};
const renderPanelHead = function (num, tag, title, desc) {
return React.createElement('div', { className: 'vs-panel-head' },
React.createElement('span', { className: 'vs-panel-tag' }, tag),
React.createElement('h2', { className: 'vs-panel-title' },
React.createElement('span', { className: 'vs-panel-num' }, num),
title
),
desc ? React.createElement('p', { className: 'vs-panel-desc' }, desc) : null
);
};
const renderMobileCardTable = function (rows, columns, rowKey) {
return React.createElement('div', { className: 'vs-m-card-table' },
(rows || []).map(function (row, i) {
var rk = rowKey ? row[rowKey] : i;
return React.createElement('div', { key: rk, className: 'vs-m-card-row' },
columns.map(function (col, ci) {
var val = row[col.key];
return React.createElement('div', {
key: col.key,
style: { marginBottom: ci < columns.length - 1 ? 10 : 0 }
},
React.createElement('div', { className: 'vs-m-card-row-label' }, col.label),
React.createElement('div', { className: 'vs-m-card-row-value' },
col.strong ? React.createElement('strong', null, val) : val
)
);
})
);
})
);
};
const renderFlowTable = function () {
return React.createElement(React.Fragment, null,
React.createElement('div', { className: 'vs-table-wrap' },
React.createElement('table', { className: 'vs-table' },
React.createElement('thead', null,
React.createElement('tr', null,
React.createElement('th', { style: { width: 148 } }, '占用状态'),
React.createElement('th', null, '结束后')
)
),
React.createElement('tbody', null,
flowRecoveries.map(function (r) {
return React.createElement('tr', { key: r.from },
React.createElement('td', null, React.createElement('strong', null, r.from)),
React.createElement('td', null, r.to)
);
})
)
)
),
renderMobileCardTable(flowRecoveries, [
{ key: 'from', label: '占用状态', strong: true },
{ key: 'to', label: '结束后' }
], 'from')
);
};
const renderOutboundTable = function () {
return React.createElement(React.Fragment, null,
React.createElement('div', { className: 'vs-table-wrap' },
React.createElement('table', { className: 'vs-table' },
React.createElement('thead', null,
React.createElement('tr', null,
React.createElement('th', null, '状态'),
React.createElement('th', null, '含义'),
React.createElement('th', null, '何时产生')
)
),
React.createElement('tbody', null,
outboundStatuses.map(function (r) {
return React.createElement('tr', { key: r.name },
React.createElement('td', null, React.createElement('strong', null, r.name)),
React.createElement('td', null, r.meaning),
React.createElement('td', null, r.when)
);
})
)
)
),
renderMobileCardTable(outboundStatuses, [
{ key: 'name', label: '状态', strong: true },
{ key: 'meaning', label: '含义' },
{ key: 'when', label: '何时产生' }
], 'name')
);
};
const renderCheatTable = function () {
return React.createElement(React.Fragment, null,
React.createElement('div', { className: 'vs-table-wrap' },
React.createElement('table', { className: 'vs-table' },
React.createElement('thead', null,
React.createElement('tr', null,
React.createElement('th', { style: { width: 100 } }, '操作'),
React.createElement('th', null, '须满足(摘要)')
)
),
React.createElement('tbody', null,
cheatSheet.map(function (r) {
return React.createElement('tr', { key: r.action },
React.createElement('td', null, React.createElement('strong', { style: { color: C_PRIMARY } }, r.action)),
React.createElement('td', null, r.condition)
);
})
)
)
),
renderMobileCardTable(cheatSheet, [
{ key: 'action', label: '操作', strong: true },
{ key: 'condition', label: '须满足(摘要)' }
], 'action')
);
};
return React.createElement('div', { className: 'vs-root' + (isMobile ? ' vs-root--mobile' : ''), 'data-layout': isMobile ? 'mobile' : 'desktop' },
React.createElement('a', { href: '#vs-overview', className: 'vs-skip' }, '跳到正文'),
React.createElement('header', { className: 'vs-m-header' },
React.createElement('div', { style: { flex: 1, minWidth: 0 } },
React.createElement('div', { className: 'vs-m-header-title' }, '车辆状态说明'),
React.createElement('div', { className: 'vs-m-header-sub' }, activeNavLabel || '浏览中')
),
React.createElement('button', {
type: 'button',
className: 'vs-m-header-btn',
onClick: function () { setNavDrawerOpen(true); },
'aria-label': '打开目录'
}, '目录')
),
React.createElement('div', {
className: 'vs-m-drawer-mask' + (navDrawerOpen ? ' is-open' : ''),
onClick: function () { setNavDrawerOpen(false); },
role: 'presentation'
},
React.createElement('div', {
className: 'vs-m-drawer' + (navDrawerOpen ? ' is-open' : ''),
onClick: function (e) { e.stopPropagation(); },
role: 'dialog',
'aria-modal': true,
'aria-label': '文档目录'
},
React.createElement('div', { className: 'vs-m-drawer-head' },
React.createElement('h3', null, '章节目录'),
React.createElement('button', {
type: 'button',
className: 'vs-m-drawer-close',
onClick: function () { setNavDrawerOpen(false); },
'aria-label': '关闭目录'
}, '×')
),
React.createElement('div', { className: 'vs-m-drawer-list' },
sectionNav.map(function (item, idx) {
return React.createElement('button', {
key: item.key,
type: 'button',
className: 'vs-m-drawer-item' + (activeNav === item.key ? ' active' : ''),
onClick: function () { scrollToSection(item.key); }
},
React.createElement('span', { className: 'vs-m-drawer-num' }, idx + 1),
item.label
);
})
)
)
),
React.createElement('header', { className: 'vs-hero' },
React.createElement('div', { className: 'vs-hero-inner' },
React.createElement('span', { className: 'vs-hero-tag' }, 'ONE-OS · 车辆管理'),
React.createElement('h1', null, '车辆状态说明'),
React.createElement('p', { className: 'vs-hero-desc' },
'五类状态组合描述一辆车。读懂关联关系,快速判断能否备车、交车,以及流程占用时的恢复规则。'
),
React.createElement('div', { className: 'vs-hero-kpis' },
[{ v: '5', l: '状态维度' }, { v: '16', l: '车辆状态' }, { v: '5', l: '运营状态' }, { v: '10', l: '场景问答' }].map(function (k) {
return React.createElement('div', { key: k.l, className: 'vs-kpi' },
React.createElement('div', { className: 'vs-kpi-val' }, k.v),
React.createElement('div', { className: 'vs-kpi-lbl' }, k.l)
);
})
)
)
),
React.createElement('div', { className: 'vs-shell' },
React.createElement('div', { className: 'vs-layout' },
React.createElement('aside', { className: 'vs-sidebar' },
React.createElement('nav', { className: 'vs-nav-card', 'aria-label': '文档目录' },
React.createElement('div', { className: 'vs-nav-label' }, '目录'),
React.createElement('div', { className: 'vs-nav-list' },
sectionNav.map(function (item) {
return React.createElement('button', {
key: item.key,
type: 'button',
className: 'vs-nav-item' + (activeNav === item.key ? ' active' : ''),
onClick: function () { scrollTo(item.key); },
'aria-current': activeNav === item.key ? 'true' : undefined
},
React.createElement('span', { className: 'vs-nav-dot' }),
item.label
);
})
)
)
),
React.createElement('main', { className: 'vs-main', id: 'vs-main-content' },
React.createElement('section', { id: 'vs-overview', className: 'vs-panel' },
renderPanelHead('1', 'OVERVIEW', '五维总览', '同一辆车会同时带有五类状态,需要组合理解,不能只看其中一项。'),
React.createElement('div', { className: 'vs-bento' },
dimensions.map(function (d) {
return React.createElement('div', {
key: d.key,
className: 'vs-bento-item',
style: { '--accent': d.color, '--accent-bg': d.color + '18' }
},
React.createElement('div', { className: 'vs-bento-icon' }, renderSvgIcon(dimIcons[d.key], d.color)),
React.createElement('h4', null, d.name),
React.createElement('p', null, d.question)
);
})
),
React.createElement('div', { className: 'vs-example' },
React.createElement('strong', { style: { fontSize: 13, color: C_PRIMARY } }, '组合示例'),
React.createElement('p', { style: { margin: '8px 0 0', fontSize: 13, color: '#334155' } },
'一辆在客户处正常使用的租赁车,可能同时显示为:'
),
React.createElement('div', { className: 'vs-tags' },
renderPill('运营 = 租赁', C_PRIMARY),
renderPill('车辆 = 已交车', C_BLUE),
renderPill('出库 = 无', C_MUTED),
renderPill('证照 = 正常', C_GREEN),
renderPill('保险 = 正常', C_GREEN)
)
),
React.createElement('div', { className: 'vs-callout' },
React.createElement('span', { className: 'vs-callout-icon' }, '!'),
React.createElement('span', null,
React.createElement('strong', null, '提示:'),
'若证照或保险异常,即使车在库里显示「待运营」,也不能交车(通常仍可备车)。'
)
)
),
React.createElement('section', { id: 'vs-relation', className: 'vs-panel' },
renderPanelHead('2', 'RELATION', '状态关联与生命周期', '理解交车门槛、在库流转与流程占用的恢复规则。'),
React.createElement('div', { className: 'vs-subtitle' }, '2.1 决定「能不能交车」'),
React.createElement('div', { className: 'vs-rule-grid' },
relationRules.map(function (t, i) {
return React.createElement('div', { key: i, className: 'vs-rule-card' },
React.createElement('span', { className: 'vs-rule-idx' }, i + 1),
React.createElement('span', null, t)
);
})
),
React.createElement('div', { className: 'vs-subtitle' }, '2.2 典型生命周期'),
React.createElement('div', { className: 'vs-timeline' },
lifecycleSteps.map(function (step) {
return React.createElement('div', { key: step, className: 'vs-timeline-step' }, step);
})
),
React.createElement('p', { style: { fontSize: 13, color: C_MUTED, margin: '0 0 4px' } },
'交车签章后运营状态变为租赁/自营;还车完成后车辆状态回未备车,运营状态回可运营或待运营(视证照/保险)。'
),
React.createElement('div', { className: 'vs-subtitle' }, '2.3 流程占用与恢复'),
renderFlowTable()
),
React.createElement('section', { id: 'vs-ops', className: 'vs-panel' },
renderPanelHead('3', 'OPS STATUS', '运营状态', '界定车辆当前处于什么业务情况下,共 5 种。'),
React.createElement('div', { className: 'vs-status-grid' },
opsStatuses.map(function (item) {
return React.createElement('div', { key: item.name, className: 'vs-status-card' },
React.createElement('h3', null, item.name, renderPill(item.tag, item.tagColor)),
React.createElement('p', null, item.desc)
);
})
),
React.createElement('div', { className: 'vs-callout' },
React.createElement('span', { className: 'vs-callout-icon' }, 'i'),
React.createElement('span', null,
React.createElement('strong', null, '记忆要点:'),
'「可运营」「待运营」= 在库;「租赁」「自营」= 已交车在客户处;「退出运营」= 已离开运营体系。'
)
)
),
React.createElement('section', { id: 'vs-vehicle', className: 'vs-panel' },
renderPanelHead('4', 'VEHICLE STATUS', '车辆状态', '预占位状态,避免多笔业务同时占用同一台车。'),
React.createElement('div', { className: 'vs-toolbar' },
React.createElement('div', { className: 'vs-chips' },
vehicleFilters.map(function (f) {
return React.createElement('button', {
key: f.key,
type: 'button',
className: 'vs-chip' + (vehicleFilter === f.key ? ' active' : ''),
onClick: function () { setVehicleFilter(f.key); }
}, f.label);
})
),
Input ? React.createElement(Input, {
className: 'vs-search',
allowClear: true,
placeholder: '搜索状态名称…',
value: vehicleSearch,
onChange: function (e) { setVehicleSearch(e.target.value); },
'aria-label': '搜索车辆状态'
}) : React.createElement('input', {
className: 'vs-search',
type: 'search',
placeholder: '搜索状态名称…',
value: vehicleSearch,
onChange: function (e) { setVehicleSearch(e.target.value); },
'aria-label': '搜索车辆状态',
style: { padding: '8px 12px', borderRadius: 8, border: '1px solid ' + C_LINE, fontSize: 13 }
})
),
filteredVehicleStatuses.length === 0
? React.createElement('div', { className: 'vs-empty' }, '没有匹配的状态,请调整筛选或搜索关键词。')
: React.createElement('div', { className: 'vs-status-grid' },
filteredVehicleStatuses.map(function (item) {
return React.createElement('div', { key: item.name, className: 'vs-status-card' },
React.createElement('h3', null,
item.name,
item.upcoming ? renderPill('暂未上线', C_AMBER) : null
),
React.createElement('p', null, item.desc)
);
})
)
),
React.createElement('section', { id: 'vs-outbound', className: 'vs-panel' },
renderPanelHead('5', 'OUTBOUND', '出库状态', '定义车辆因何种原因离开车队。在库车辆出库状态为「无」。'),
renderOutboundTable(),
React.createElement('div', { className: 'vs-callout' },
React.createElement('span', { className: 'vs-callout-icon' }, '→'),
React.createElement('span', null,
React.createElement('strong', null, '出库后:'),
'车辆状态一般为「无」,运营状态「退出运营」,证照/保险「无」。'
)
)
),
React.createElement('section', { id: 'vs-license', className: 'vs-panel' },
renderPanelHead('6', 'LICENSE', '证照状态', '规避行驶证、沪牌等评等到期仍上路的风险。'),
React.createElement('div', { className: 'vs-status-grid' },
licenseStatuses.map(function (item) {
return React.createElement('div', { key: item.name, className: 'vs-status-card' },
React.createElement('h3', null, item.name, renderPill(item.name, item.tone)),
React.createElement('p', null, item.desc)
);
})
),
React.createElement('div', { className: 'vs-callout' },
React.createElement('span', { className: 'vs-callout-icon' }, '↔'),
React.createElement('span', null,
React.createElement('strong', null, '关联:'),
'证照异常 → 在库车辆运营状态为「待运营」→ 可备车,不可交车。'
)
)
),
React.createElement('section', { id: 'vs-insurance', className: 'vs-panel' },
renderPanelHead('7', 'INSURANCE', '保险状态', '确保交车前交强险、商业险齐全有效。'),
React.createElement('div', { className: 'vs-status-grid' },
insuranceStatuses.map(function (item) {
return React.createElement('div', { key: item.name, className: 'vs-status-card' },
React.createElement('h3', null, item.name, renderPill(item.name, item.tone)),
React.createElement('p', null, item.desc)
);
})
),
React.createElement('div', { className: 'vs-callout' },
React.createElement('span', { className: 'vs-callout-icon' }, '↔'),
React.createElement('span', null,
React.createElement('strong', null, '关联:'),
'保险异常 → 在库车辆运营状态为「待运营」→ 可备车,不可交车。停保/退保仍算异常。'
)
)
),
React.createElement('section', { id: 'vs-faq', className: 'vs-panel' },
renderPanelHead('8', 'FAQ', '常见场景问答', '点击问题展开答案;支持全部展开或收起。'),
React.createElement('div', { className: 'vs-faq-actions' },
React.createElement('button', {
type: 'button',
className: 'vs-text-btn',
onClick: function () { setFaqExpandAll(false); setOpenFaq(0); }
}, '展开首条'),
React.createElement('button', {
type: 'button',
className: 'vs-text-btn',
onClick: function () { setFaqExpandAll(true); }
}, '全部展开'),
React.createElement('button', {
type: 'button',
className: 'vs-text-btn',
onClick: function () { setFaqExpandAll(false); setOpenFaq(-1); }
}, '全部收起')
),
React.createElement('div', { className: 'vs-faq-list' },
faqs.map(function (item, i) {
var open = faqExpandAll || openFaq === i;
return React.createElement('div', { key: i, className: 'vs-faq' + (open ? ' is-open' : '') },
React.createElement('button', {
type: 'button',
className: 'vs-faq-head',
onClick: function () { setFaqExpandAll(false); setOpenFaq(open ? -1 : i); },
'aria-expanded': open
},
React.createElement('span', { className: 'vs-faq-q' },
React.createElement('span', { className: 'vs-faq-num' }, i + 1),
React.createElement('span', null, item.q)
),
React.createElement('span', { className: 'vs-faq-chevron', 'aria-hidden': true }, '⌄')
),
open ? React.createElement('div', { className: 'vs-faq-body' }, item.a) : null
);
})
)
),
React.createElement('section', { id: 'vs-cheatsheet', className: 'vs-panel' },
renderPanelHead('9', 'CHEATSHEET', '操作速查表', '备车、交车、还车等操作的前置条件摘要。'),
renderCheatTable()
)
)
)
),
React.createElement('footer', { className: 'vs-footer' },
'文档版本 V1.0 · 依据 ONE-OS 车辆管理业务规则整理 · 部分流程尚未上线以实际系统为准 · 疑问请联系数智部或产品负责人'
),
React.createElement('nav', { className: 'vs-m-bottom-bar', 'aria-label': '快捷导航' },
mobileBottomTabs.map(function (tab) {
var isMenu = tab.key === '__menu';
var isActive = !isMenu && activeNav === tab.key;
return React.createElement('button', {
key: tab.key,
type: 'button',
className: 'vs-m-tab' + (isActive || (isMenu && navDrawerOpen) ? ' active' : ''),
onClick: function () {
if (isMenu) setNavDrawerOpen(function (v) { return !v; });
else scrollToSection(tab.key);
},
'aria-current': isActive ? 'page' : undefined
},
React.createElement('span', { className: 'vs-m-tab-icon', 'aria-hidden': true }, tab.icon),
tab.label
);
})
),
React.createElement('button', {
type: 'button',
className: 'vs-top-btn' + (showTop ? ' visible' : ''),
onClick: function () { window.scrollTo({ top: 0, behavior: 'smooth' }); },
'aria-label': '回到顶部'
}, '↑')
);
};
export default Component;