commit 68d61700d5d0b56ab5bcefb43e6e628f33007e49 Author: wangxuming Date: Sun Jul 12 15:53:24 2026 +0800 Initial commit diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..ffec22c --- /dev/null +++ b/.editorconfig @@ -0,0 +1,18 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true + +[*.{go,js,jsx,ts,tsx,json,yml,yaml}] +indent_style = space +indent_size = 2 + +[*.go] +indent_style = tab + +[*.md] +trim_trailing_whitespace = false + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..524722d --- /dev/null +++ b/.gitignore @@ -0,0 +1,33 @@ +.DS_Store +.idea/ +.vscode/ +*.log +__pycache__/ +*.pyc + +# Local secrets and runtime state +.env +.env.* +!.env.example +tmp/ +var/ + +# Go +server/bin/ +server/coverage.out +server/coverage.html + +# React / Vite +web/node_modules/ +web/dist/ +web/coverage/ +web/playwright-report/ +web/test-results/ +web/*.tsbuildinfo +web/vite.config.js +web/vite.config.d.ts + +# Local databases +*.db +*.sqlite +*.sqlite3 diff --git a/.prototype-run/xiaoqikong-fidelity-rebuild-20260711/build/v001.json b/.prototype-run/xiaoqikong-fidelity-rebuild-20260711/build/v001.json new file mode 100644 index 0000000..d29f2fd --- /dev/null +++ b/.prototype-run/xiaoqikong-fidelity-rebuild-20260711/build/v001.json @@ -0,0 +1,51 @@ +{ + "schema_version": "1.0", + "implementation_plan_id": "ip_xiaoqikong_redesign_v001", + "revision": 1, + "target_repo": "/Users/inmanw/公司/calllinesystem", + "framework": "React 19 + TypeScript + Vite + existing CSS architecture", + "routes": [ + {"path": "/login", "purpose": "brand-aligned authentication"}, + {"path": "/staff and /staff/tickets", "purpose": "staff mobile call and ticket tasks"}, + {"path": "/status/:token", "purpose": "visitor private mobile queue status"}, + {"path": "/admin/*", "purpose": "desktop operations and management"}, + {"path": "/display/:token", "purpose": "far-distance public queue display"} + ], + "components": [ + {"id": "CMP-BRAND-SHELL", "file": "web/src/components/AppShell.tsx"}, + {"id": "CMP-MOBILE-TASK", "file": "web/src/pages/StaffPage.tsx"}, + {"id": "CMP-VISITOR", "file": "web/src/pages/VisitorPage.tsx"}, + {"id": "CMP-ADMIN-OVERVIEW", "file": "web/src/pages/AdminPage.tsx"}, + {"id": "CMP-PUBLIC-DISPLAY", "file": "web/src/pages/DisplayPage.tsx"}, + {"id": "CMP-THEME", "file": "web/src/styles.css"} + ], + "requirement_mapping": [ + {"requirement_id": "REQ-FLOW-001", "component_ids": ["CMP-MOBILE-TASK", "CMP-THEME"], "test_ids": ["TEST-FRONTEND", "TEST-BROWSER-MOBILE"]}, + {"requirement_id": "REQ-FLOW-002", "component_ids": ["CMP-VISITOR", "CMP-THEME"], "test_ids": ["TEST-FRONTEND", "TEST-BROWSER-MOBILE"]}, + {"requirement_id": "REQ-FLOW-003", "component_ids": ["CMP-ADMIN-OVERVIEW", "CMP-BRAND-SHELL", "CMP-THEME"], "test_ids": ["TEST-FRONTEND", "TEST-BROWSER-DESKTOP"]}, + {"requirement_id": "REQ-FLOW-004", "component_ids": ["CMP-PUBLIC-DISPLAY", "CMP-THEME"], "test_ids": ["TEST-FRONTEND", "TEST-BROWSER-DISPLAY"]}, + {"requirement_id": "REQ-VIS-001", "component_ids": ["CMP-BRAND-SHELL", "CMP-THEME"], "test_ids": ["TEST-BUILD", "TEST-BROWSER-DESKTOP"]}, + {"requirement_id": "REQ-VIS-002", "component_ids": ["CMP-THEME"], "test_ids": ["TEST-BROWSER-DESKTOP", "TEST-BROWSER-DISPLAY"]}, + {"requirement_id": "REQ-VIS-003", "component_ids": ["CMP-MOBILE-TASK", "CMP-ADMIN-OVERVIEW", "CMP-PUBLIC-DISPLAY", "CMP-THEME"], "test_ids": ["TEST-BROWSER-MOBILE", "TEST-BROWSER-DESKTOP", "TEST-BROWSER-DISPLAY"]}, + {"requirement_id": "REQ-STATE-001", "component_ids": ["CMP-MOBILE-TASK", "CMP-VISITOR", "CMP-ADMIN-OVERVIEW", "CMP-PUBLIC-DISPLAY"], "test_ids": ["TEST-FRONTEND"]}, + {"requirement_id": "REQ-A11Y-001", "component_ids": ["CMP-BRAND-SHELL", "CMP-THEME"], "test_ids": ["TEST-BROWSER-MOBILE", "TEST-BROWSER-DESKTOP"]}, + {"requirement_id": "REQ-A11Y-002", "component_ids": ["CMP-THEME"], "test_ids": ["TEST-BROWSER-REFLOW"]}, + {"requirement_id": "REQ-MOTION-001", "component_ids": ["CMP-THEME"], "test_ids": ["TEST-BROWSER-MOBILE", "TEST-BROWSER-DESKTOP"]}, + {"requirement_id": "REQ-TECH-001", "component_ids": ["CMP-THEME"], "test_ids": ["TEST-FRONTEND", "TEST-BUILD"]}, + {"requirement_id": "REQ-TECH-002", "component_ids": ["CMP-MOBILE-TASK", "CMP-VISITOR", "CMP-ADMIN-OVERVIEW", "CMP-PUBLIC-DISPLAY"], "test_ids": ["TEST-FRONTEND"]} + ], + "files_scope": {"allowed": ["web/src", "web/public", "web/package.json", "web/pnpm-lock.yaml", "task_plan.md", "progress.md", "findings.md"], "protected": ["server", "server/migrations", "docs/scenic-queue-system-plan.md"]}, + "asset_plan": [{"asset_id": "AST-LOGO", "source": ".prototype-run/xiaoqikong-redesign-20260711/assets/xiaoqikong-logo.jpg", "destination": "web/public/xiaoqikong-logo.jpg", "strategy": "copy supplied official asset without generative alteration"}], + "state_strategy": {"owner": "existing route components and usePollingResource", "states": ["brand_ready", "mobile_task_states", "admin_overview_ready", "context_status_states", "display_public_states"], "data_fixture": "existing real API responses and deterministic Vitest mocks"}, + "motion_contract": {"motion_contract_id": "mc_xiaoqikong_v001", "revision": 1}, + "accessibility_plan": {"standard": "WCAG 2.2 AA", "checks": ["keyboard", "visible focus", "320px reflow", "48px mobile targets", "reduced motion", "non-color status semantics"]}, + "test_plan": [ + {"id": "TEST-FRONTEND", "type": "Vitest regression suite"}, + {"id": "TEST-BUILD", "type": "TypeScript and Vite production build"}, + {"id": "TEST-BROWSER-MOBILE", "type": "browser QA at 390x844"}, + {"id": "TEST-BROWSER-REFLOW", "type": "browser QA at 320x800"}, + {"id": "TEST-BROWSER-DESKTOP", "type": "browser QA at 1440x1024"}, + {"id": "TEST-BROWSER-DISPLAY", "type": "browser QA at 1920x1080"} + ], + "rollback": {"strategy": "revert only Phase 13 frontend and asset changes; preserve existing backend and all prior user files", "checkpoint": "record repository snapshot and build log before validating"} +} diff --git a/.prototype-run/xiaoqikong-fidelity-rebuild-20260711/design/v001.json b/.prototype-run/xiaoqikong-fidelity-rebuild-20260711/design/v001.json new file mode 100644 index 0000000..6161812 --- /dev/null +++ b/.prototype-run/xiaoqikong-fidelity-rebuild-20260711/design/v001.json @@ -0,0 +1,57 @@ +{ + "schema_version": "1.0", + "design_contract_id": "dc_xiaoqikong_karst_breath_v001", + "revision": 1, + "source": {"candidate_id": "candidate_stretch_human_scenic", "image_path": "prototypes/selected-karst-breath.png", "sha256": "1dfc3cfdbcc8af653430290f8ad55e09c269fc97f2d98d96852270e328a53f77", "width": 1487, "height": 1058}, + "requirements_contract": {"contract_id": "pc_xiaoqikong_queue_redesign", "revision": 1}, + "tokens": { + "color": {"canvas": "#F4F7F3", "surface": "#FFFFFF", "ink": "#10261D", "muted": "#56665F", "primary": "#0B6B3A", "primary_deep": "#064A2A", "primary_soft": "#E7F2EA", "border": "#D5DED8", "warning": "#C96A12", "danger": "#C93232"}, + "type": {"family": "system Chinese sans", "numeric": "tabular-nums", "character": "adult, calm, high-legibility"}, + "space": {"unit_px": 8, "section_px": 24, "page_gutter_px": 20}, + "radius": {"control_px": 8, "panel_px": 14}, + "border": {"default": "1px solid #D5DED8", "selected": "1px solid #0B6B3A"}, + "shadow": {"panel": "0 8px 24px rgba(16,38,29,.06)"}, + "layers": {"header": 20, "sticky_action": 30}, + "motion": {"feedback_ms": 150, "panel_ms": 220} + }, + "components": [ + {"id": "CMP-BRAND-SHELL", "name": "BrandShell", "role": "global navigation and identity", "parent_id": null, "bounds_norm": {"x": 0, "y": 0, "w": 1, "h": 0.08}, "layout": "slim brand bar with real logo, account area and restrained contour texture", "requirement_ids": ["REQ-VIS-001", "REQ-VIS-002", "REQ-A11Y-001"], "evidence_ids": ["EV-HEADER", "EV-CONTOUR"], "confidence": 0.94}, + {"id": "CMP-MOBILE-TASK", "name": "MobileTaskSurface", "role": "staff and visitor mobile tasks", "parent_id": "CMP-BRAND-SHELL", "bounds_norm": {"x": 0.02, "y": 0.1, "w": 0.68, "h": 0.86}, "layout": "single-column mobile task hierarchy derived from the dominant left work surface", "requirement_ids": ["REQ-FLOW-001", "REQ-FLOW-002", "REQ-VIS-003", "REQ-STATE-001", "REQ-A11Y-002"], "evidence_ids": ["EV-MAIN", "EV-ROWS"], "confidence": 0.82}, + {"id": "CMP-ADMIN-OVERVIEW", "name": "AdminOverview", "role": "desktop operations workspace", "parent_id": "CMP-BRAND-SHELL", "bounds_norm": {"x": 0.02, "y": 0.22, "w": 0.68, "h": 0.73}, "layout": "wide primary operations table with compact filters and aligned measures", "requirement_ids": ["REQ-FLOW-003", "REQ-VIS-003", "REQ-STATE-001"], "evidence_ids": ["EV-MAIN", "EV-ROWS"], "confidence": 0.96}, + {"id": "CMP-CONTEXT-RAIL", "name": "ContextRail", "role": "desktop contextual status and exceptions", "parent_id": "CMP-ADMIN-OVERVIEW", "bounds_norm": {"x": 0.71, "y": 0.22, "w": 0.28, "h": 0.73}, "layout": "stacked contextual panels with latest call, exceptions and recent activity", "requirement_ids": ["REQ-FLOW-003", "REQ-STATE-001", "REQ-A11Y-001"], "evidence_ids": ["EV-RAIL"], "confidence": 0.95}, + {"id": "CMP-PUBLIC-DISPLAY", "name": "PublicDisplay", "role": "far-distance queue projection", "parent_id": "CMP-BRAND-SHELL", "bounds_norm": {"x": 0.71, "y": 0.22, "w": 0.28, "h": 0.24}, "layout": "enlarged latest-call focal panel adapted to 16:9", "requirement_ids": ["REQ-FLOW-004", "REQ-VIS-003", "REQ-A11Y-001"], "evidence_ids": ["EV-LATEST"], "confidence": 0.9} + ], + "assets": [ + {"id": "AST-LOGO", "kind": "official_logo", "region_evidence_id": "EV-HEADER", "strategy": "reuse_supplied", "source": "assets/xiaoqikong-logo.jpg", "confidence": 1.0}, + {"id": "AST-CONTOUR", "kind": "decorative_contour_texture", "region_evidence_id": "EV-CONTOUR", "strategy": "code-native CSS radial/linear contour treatment with aria-hidden decoration", "confidence": 0.78}, + {"id": "AST-ICONS", "kind": "ui_icon_family", "region_evidence_id": "EV-ROWS", "strategy": "reuse existing text/symbol-safe UI primitives; no logo-like icons", "confidence": 0.82} + ], + "evidence": [ + {"id": "EV-HEADER", "bbox_norm": {"x": 0, "y": 0, "w": 1, "h": 0.08}, "observation": "A slim white header establishes brand, product name and account context.", "basis": "observed"}, + {"id": "EV-CONTOUR", "bbox_norm": {"x": 0.45, "y": 0, "w": 0.55, "h": 0.2}, "observation": "Very pale topographic contours and a small scenic silhouette add place identity without competing with data.", "basis": "observed"}, + {"id": "EV-MAIN", "bbox_norm": {"x": 0.02, "y": 0.22, "w": 0.68, "h": 0.73}, "observation": "The main workspace dominates with a flat bordered surface rather than a mosaic of cards.", "basis": "measured"}, + {"id": "EV-ROWS", "bbox_norm": {"x": 0.03, "y": 0.32, "w": 0.65, "h": 0.57}, "observation": "Rows align names, waiting counts, batch, state, time and action; selected row uses a green outline.", "basis": "observed"}, + {"id": "EV-RAIL", "bbox_norm": {"x": 0.71, "y": 0.22, "w": 0.28, "h": 0.73}, "observation": "A narrower contextual rail carries status, exceptions, recent operations and actions.", "basis": "measured"}, + {"id": "EV-LATEST", "bbox_norm": {"x": 0.72, "y": 0.23, "w": 0.26, "h": 0.23}, "observation": "Latest call uses the strongest numeric emphasis with progress and redundant state labeling.", "basis": "observed"} + ], + "responsive_rules": [ + {"requirement_id": "REQ-VIS-003", "rule": "Staff and visitor routes remain single-column at 390px and reflow without page-level horizontal scrolling at 320px."}, + {"requirement_id": "REQ-FLOW-003", "rule": "Desktop admin uses asymmetric main-plus-context layout; below 1100px the context rail stacks after the main region."}, + {"requirement_id": "REQ-FLOW-004", "rule": "Public display expands the latest-call focal treatment to fill 16:9 with no private fields."} + ], + "interaction_rules": [ + {"requirement_id": "REQ-FLOW-001", "rule": "Preserve current project switch, ticket creation and call-next behavior and feedback."}, + {"requirement_id": "REQ-FLOW-002", "rule": "Preserve private-token status refresh and phone-last-four privacy."}, + {"requirement_id": "REQ-FLOW-003", "rule": "Preserve current admin routes and forms; visual density changes do not alter permissions or data."}, + {"requirement_id": "REQ-FLOW-004", "rule": "Public display remains read-only and privacy-whitelisted."} + ], + "state_matrix": [ + {"id": "STATE-BRAND-DEFAULT", "component_id": "CMP-BRAND-SHELL", "requirement_ids": ["REQ-VIS-001"], "state": "authenticated or public brand shell visible", "implementation_state": "brand_ready", "trigger": "route renders", "expected": "official logo and product identity are visible without image distortion", "focus": "skip link is first keyboard focus where authentication applies"}, + {"id": "STATE-MOBILE-TASK", "component_id": "CMP-MOBILE-TASK", "requirement_ids": ["REQ-FLOW-001", "REQ-FLOW-002", "REQ-STATE-001"], "state": "default, loading, empty, error, success, offline and disabled mobile states", "implementation_state": "mobile_task_states", "trigger": "data load or user action", "expected": "staff and visitor outcomes remain clear with recovery actions", "focus": "logical DOM order; validation focus and visible focus ring preserved"}, + {"id": "STATE-ADMIN-READY", "component_id": "CMP-ADMIN-OVERVIEW", "requirement_ids": ["REQ-FLOW-003", "REQ-STATE-001"], "state": "overview ready with route navigation and data", "implementation_state": "admin_overview_ready", "trigger": "administrator opens a route", "expected": "existing controls and data remain readable in the new hierarchy", "focus": "navigation precedes page controls and selected route is announced"}, + {"id": "STATE-RAIL-EXCEPTION", "component_id": "CMP-CONTEXT-RAIL", "requirement_ids": ["REQ-FLOW-003", "REQ-STATE-001"], "state": "normal, warning and error context", "implementation_state": "context_status_states", "trigger": "queue or device state changes", "expected": "status uses label plus color and retains recovery path", "focus": "interactive actions remain native buttons or links"}, + {"id": "STATE-DISPLAY-LIVE", "component_id": "CMP-PUBLIC-DISPLAY", "requirement_ids": ["REQ-FLOW-004", "REQ-STATE-001"], "state": "live, empty, delayed and offline public projection", "implementation_state": "display_public_states", "trigger": "polling or stream update", "expected": "latest public tickets and freshness remain legible at distance with no PII", "focus": "read-only surface has no artificial tab stops"} + ], + "ambiguities": [{"id": "AMB-CONTOUR", "question": "Exact contour motif is illustrative rather than a supplied brand asset.", "impact": "visual_fidelity", "blocking": false, "resolution": "use a subtle code-native decorative treatment and verify against the selected image"}], + "implementation_ready": true +} diff --git a/.prototype-run/xiaoqikong-fidelity-rebuild-20260711/events.jsonl b/.prototype-run/xiaoqikong-fidelity-rebuild-20260711/events.jsonl new file mode 100644 index 0000000..83e7a3a --- /dev/null +++ b/.prototype-run/xiaoqikong-fidelity-rebuild-20260711/events.jsonl @@ -0,0 +1,20 @@ +{"event_id": "evt_f3c0a3f33f88476db274e9c3aac75dfd", "run_id": "b2p_b214c02d21d4", "at": "2026-07-11T07:38:50Z", "operation": "init", "actor": "agent", "new_state": "created", "mode": "existing_visual", "run_metadata": {"schema_version": "1.0", "run_id": "b2p_b214c02d21d4", "mode": "existing_visual", "surface": "responsive_web", "target_repo": "/Users/inmanw/公司/calllinesystem", "created_at": "2026-07-11T07:38:50Z"}} +{"event_id": "evt_ab4368718b7c4efa9ce01a08bfe039db", "run_id": "b2p_b214c02d21d4", "at": "2026-07-11T07:38:50Z", "operation": "record_artifact", "actor": "agent", "artifact_type": "requirements", "artifact_id": "requirements-r001-4e447ed7e6", "revision": 1, "output_hash": "4e447ed7e66ccb462f238d1b30c96448923e7ad46495e735735c888ec011563a", "status": "approved", "supersedes": null, "invalidated_artifacts": [], "stale_gates": [], "artifact": {"artifact_id": "requirements-r001-4e447ed7e6", "kind": "requirements", "revision": 1, "status": "approved", "validity": "current", "path": "/Users/inmanw/公司/calllinesystem/.prototype-run/xiaoqikong-fidelity-rebuild-20260711/requirements/v001.json", "sha256": "4e447ed7e66ccb462f238d1b30c96448923e7ad46495e735735c888ec011563a", "size_bytes": 7442, "input_refs": [], "provenance": {}, "supersedes": null, "created_by": "agent", "recorded_at": "2026-07-11T07:38:50Z"}} +{"event_id": "evt_eccdd17abcca42e98fad9dde214e6cca", "run_id": "b2p_b214c02d21d4", "at": "2026-07-11T07:38:50Z", "operation": "transition", "actor": "agent", "prior_state": "created", "new_state": "requirements_ready", "note": "Reuse accepted product contract for fidelity correction", "stale_gates": [], "stale_artifacts": []} +{"event_id": "evt_d29a53ff0943476e972c3bb0da2c938b", "run_id": "b2p_b214c02d21d4", "at": "2026-07-11T07:38:50Z", "operation": "record_artifact", "actor": "agent", "artifact_type": "prototype", "artifact_id": "prototype-r001-1dfc3cfdbc", "revision": 1, "output_hash": "1dfc3cfdbcc8af653430290f8ad55e09c269fc97f2d98d96852270e328a53f77", "status": "selected", "supersedes": null, "invalidated_artifacts": [], "stale_gates": [], "artifact": {"artifact_id": "prototype-r001-1dfc3cfdbc", "kind": "prototype", "revision": 1, "status": "selected", "validity": "current", "path": "/Users/inmanw/公司/calllinesystem/.prototype-run/xiaoqikong-fidelity-rebuild-20260711/prototypes/selected-karst-breath.png", "sha256": "1dfc3cfdbcc8af653430290f8ad55e09c269fc97f2d98d96852270e328a53f77", "size_bytes": 1240326, "input_refs": [], "provenance": {"tool": "existing-approved-visual", "candidate_id": "candidate_stretch_human_scenic", "source_run": "b2p_d52228eb0be3", "source_artifact_id": "prototype-r003-1dfc3cfdbc", "sha256": "1dfc3cfdbcc8af653430290f8ad55e09c269fc97f2d98d96852270e328a53f77"}, "supersedes": null, "created_by": "agent", "recorded_at": "2026-07-11T07:38:50Z"}} +{"event_id": "evt_24647772f1bb49c49772deca6f2c85e4", "run_id": "b2p_b214c02d21d4", "at": "2026-07-11T07:39:30Z", "operation": "gate_decision", "actor": "user", "gate": "visual_selection", "decision": "approved", "artifact_id": "prototype-r001-1dfc3cfdbc", "artifact_hash": "1dfc3cfdbcc8af653430290f8ad55e09c269fc97f2d98d96852270e328a53f77", "note": "User reaffirmed selected direction 3 by requesting faithful restoration of the image", "invalidated_artifacts": [], "stale_gates": [], "gate_record": {"gate": "visual_selection", "decision": "approved", "validity": "current", "artifact_id": "prototype-r001-1dfc3cfdbc", "artifact_sha256": "1dfc3cfdbcc8af653430290f8ad55e09c269fc97f2d98d96852270e328a53f77", "actor": "user", "note": "User reaffirmed selected direction 3 by requesting faithful restoration of the image", "decided_at": "2026-07-11T07:39:30Z"}} +{"event_id": "evt_fc77042bf5924381b02e769cd5ec062f", "run_id": "b2p_b214c02d21d4", "at": "2026-07-11T07:39:30Z", "operation": "transition", "actor": "agent", "prior_state": "requirements_ready", "new_state": "visual_selected", "note": "", "stale_gates": [], "stale_artifacts": []} +{"event_id": "evt_1db1b96393a9492e8a12a76f11dc05da", "run_id": "b2p_b214c02d21d4", "at": "2026-07-11T07:39:30Z", "operation": "record_artifact", "actor": "agent", "artifact_type": "design_contract", "artifact_id": "design_contract-r001-44692cdca9", "revision": 1, "output_hash": "44692cdca97b7a90b0b0c862f0b6db422595048b38f1af0a01c1272b1a7e7354", "status": "approved", "supersedes": null, "invalidated_artifacts": [], "stale_gates": [], "artifact": {"artifact_id": "design_contract-r001-44692cdca9", "kind": "design_contract", "revision": 1, "status": "approved", "validity": "current", "path": "/Users/inmanw/公司/calllinesystem/.prototype-run/xiaoqikong-fidelity-rebuild-20260711/design/v001.json", "sha256": "44692cdca97b7a90b0b0c862f0b6db422595048b38f1af0a01c1272b1a7e7354", "size_bytes": 8573, "input_refs": [], "provenance": {}, "supersedes": null, "created_by": "agent", "recorded_at": "2026-07-11T07:39:30Z"}} +{"event_id": "evt_5a0a7aa997c94bef8c4361403a9f0d8b", "run_id": "b2p_b214c02d21d4", "at": "2026-07-11T07:39:30Z", "operation": "record_artifact", "actor": "agent", "artifact_type": "motion_contract", "artifact_id": "motion_contract-r001-f4de7e4856", "revision": 1, "output_hash": "f4de7e48565b888b51a4e5e010ae712cb91fae42f73f24eebaaa26785347ca83", "status": "approved", "supersedes": null, "invalidated_artifacts": [], "stale_gates": [], "artifact": {"artifact_id": "motion_contract-r001-f4de7e4856", "kind": "motion_contract", "revision": 1, "status": "approved", "validity": "current", "path": "/Users/inmanw/公司/calllinesystem/.prototype-run/xiaoqikong-fidelity-rebuild-20260711/motion/v001.json", "sha256": "f4de7e48565b888b51a4e5e010ae712cb91fae42f73f24eebaaa26785347ca83", "size_bytes": 1491, "input_refs": [], "provenance": {}, "supersedes": null, "created_by": "agent", "recorded_at": "2026-07-11T07:39:30Z"}} +{"event_id": "evt_c59d0252be574326bdad2930af7d507b", "run_id": "b2p_b214c02d21d4", "at": "2026-07-11T07:39:30Z", "operation": "record_artifact", "actor": "agent", "artifact_type": "implementation_plan", "artifact_id": "implementation_plan-r001-f339959eba", "revision": 1, "output_hash": "f339959eba509deece28d3dc2fcc068bae4ee3d56b96e5103ddc8ec1de7478b3", "status": "approved", "supersedes": null, "invalidated_artifacts": [], "stale_gates": [], "artifact": {"artifact_id": "implementation_plan-r001-f339959eba", "kind": "implementation_plan", "revision": 1, "status": "approved", "validity": "current", "path": "/Users/inmanw/公司/calllinesystem/.prototype-run/xiaoqikong-fidelity-rebuild-20260711/build/v001.json", "sha256": "f339959eba509deece28d3dc2fcc068bae4ee3d56b96e5103ddc8ec1de7478b3", "size_bytes": 4735, "input_refs": [], "provenance": {}, "supersedes": null, "created_by": "agent", "recorded_at": "2026-07-11T07:39:30Z"}} +{"event_id": "evt_32d4b08c88974ac6af007b87970b4a50", "run_id": "b2p_b214c02d21d4", "at": "2026-07-11T07:39:30Z", "operation": "transition", "actor": "agent", "prior_state": "visual_selected", "new_state": "contracts_ready", "note": "", "stale_gates": [], "stale_artifacts": []} +{"event_id": "evt_bda8c402dc2f431e82a985ac7927b706", "run_id": "b2p_b214c02d21d4", "at": "2026-07-11T07:39:30Z", "operation": "transition", "actor": "agent", "prior_state": "contracts_ready", "new_state": "implementing", "note": "", "stale_gates": [], "stale_artifacts": []} +{"event_id": "evt_4000a33588c74d8abe5c56c06a18d5d3", "run_id": "b2p_b214c02d21d4", "at": "2026-07-11T07:50:29Z", "operation": "record_artifact", "actor": "agent", "artifact_type": "implementation_checkpoint", "artifact_id": "implementation_checkpoint-r001-36184674eb", "revision": 1, "output_hash": "36184674ebdbc3ddd5bcde368506adb5bceba6f4c4b16cffcfe106974b384f06", "status": "verified", "supersedes": null, "invalidated_artifacts": [], "stale_gates": [], "artifact": {"artifact_id": "implementation_checkpoint-r001-36184674eb", "kind": "implementation_checkpoint", "revision": 1, "status": "verified", "validity": "current", "path": "/Users/inmanw/公司/calllinesystem/.prototype-run/xiaoqikong-fidelity-rebuild-20260711/qa/implementation-checkpoint.json", "sha256": "36184674ebdbc3ddd5bcde368506adb5bceba6f4c4b16cffcfe106974b384f06", "size_bytes": 235, "input_refs": [], "provenance": {"implementation_revision": 1, "repo_snapshot": "ba2c4e7e11e236525f395f4d652de5fa1eb1e7f20ce21b2250750dd534af7441", "repo_snapshot_algorithm": "product-prototype-design-repo-snapshot-v2-full-repository", "repo_snapshot_coverage": "full_target_repo", "repo_snapshot_file_count": 95, "repo_snapshot_scope": ["docs/scenic-queue-system-plan.md", "findings.md", "progress.md", "server", "server/migrations", "task_plan.md", "web/package.json", "web/pnpm-lock.yaml", "web/public", "web/src"]}, "supersedes": null, "created_by": "agent", "recorded_at": "2026-07-11T07:50:29Z"}} +{"event_id": "evt_b553ad03d8a04022bc7b0cd2573dac09", "run_id": "b2p_b214c02d21d4", "at": "2026-07-11T07:50:30Z", "operation": "transition", "actor": "agent", "prior_state": "implementing", "new_state": "validating", "note": "", "stale_gates": [], "stale_artifacts": []} +{"event_id": "evt_3b6a27aa4dd541d1bc36147e2e5c6804", "run_id": "b2p_b214c02d21d4", "at": "2026-07-11T07:50:30Z", "operation": "block", "actor": "agent", "prior_state": "validating", "new_state": "blocked", "reason": "In-app browser viewport override is not applying after session reset; faithful desktop visual comparison passed, but exact 320/390/1440/1920 evidence must be recaptured before canonical run completion", "resume_to": "validating"} +{"event_id": "evt_9fe5160a84f141629b6f653f6a440ce6", "run_id": "b2p_b214c02d21d4", "at": "2026-07-11T08:16:52Z", "operation": "resume", "actor": "agent", "prior_state": "blocked", "new_state": "validating", "prior_blocker": {"reason": "In-app browser viewport override is not applying after session reset; faithful desktop visual comparison passed, but exact 320/390/1440/1920 evidence must be recaptured before canonical run completion", "resume_to": "validating", "blocked_from": "validating", "blocked_at": "2026-07-11T07:50:30Z"}, "note": "", "stale_gates": [], "stale_artifacts": []} +{"event_id": "evt_81d4d1dd7bcf421e82ca16448b9ca1e6", "run_id": "b2p_b214c02d21d4", "at": "2026-07-11T08:16:52Z", "operation": "transition", "actor": "agent", "prior_state": "validating", "new_state": "repairing", "note": "", "stale_gates": [], "stale_artifacts": []} +{"event_id": "evt_b1bd1aa9fa474073bce947b37cb66372", "run_id": "b2p_b214c02d21d4", "at": "2026-07-11T08:16:52Z", "operation": "transition", "actor": "agent", "prior_state": "repairing", "new_state": "implementing", "note": "", "stale_gates": [], "stale_artifacts": []} +{"event_id": "evt_a91f9e0f7c5c4578baa60fa0b26d63ec", "run_id": "b2p_b214c02d21d4", "at": "2026-07-11T08:20:17Z", "operation": "record_artifact", "actor": "agent", "artifact_type": "implementation_checkpoint", "artifact_id": "implementation_checkpoint-r002-8c034c7045", "revision": 2, "output_hash": "8c034c704542cc0e4a777f894fac12872d193537868b9c41f1b0e4fdaa9f5ae3", "status": "verified", "supersedes": "implementation_checkpoint-r001-36184674eb", "invalidated_artifacts": [], "stale_gates": [], "artifact": {"artifact_id": "implementation_checkpoint-r002-8c034c7045", "kind": "implementation_checkpoint", "revision": 2, "status": "verified", "validity": "current", "path": "/Users/inmanw/公司/calllinesystem/.prototype-run/xiaoqikong-fidelity-rebuild-20260711/qa/implementation-checkpoint-v2.json", "sha256": "8c034c704542cc0e4a777f894fac12872d193537868b9c41f1b0e4fdaa9f5ae3", "size_bytes": 298, "input_refs": [], "provenance": {"implementation_revision": 2, "repo_snapshot": "16bb5bd951b072b99e638c1a4f0a20ad37d263d429855144bda64fc563714188", "repo_snapshot_algorithm": "product-prototype-design-repo-snapshot-v2-full-repository", "repo_snapshot_coverage": "full_target_repo", "repo_snapshot_file_count": 95, "repo_snapshot_scope": ["docs/scenic-queue-system-plan.md", "findings.md", "progress.md", "server", "server/migrations", "task_plan.md", "web/package.json", "web/pnpm-lock.yaml", "web/public", "web/src"]}, "supersedes": "implementation_checkpoint-r001-36184674eb", "created_by": "agent", "recorded_at": "2026-07-11T08:20:17Z"}} +{"event_id": "evt_b395f4e4fb7e4bffb7057ab3daacea39", "run_id": "b2p_b214c02d21d4", "at": "2026-07-11T08:20:17Z", "operation": "transition", "actor": "agent", "prior_state": "implementing", "new_state": "validating", "note": "", "stale_gates": [], "stale_artifacts": []} +{"event_id": "evt_91ed1dcaed9a4ef0a8f96d31f3f75bf9", "run_id": "b2p_b214c02d21d4", "at": "2026-07-11T08:20:17Z", "operation": "block", "actor": "agent", "prior_state": "validating", "new_state": "blocked", "reason": "Desktop source-to-implementation comparison passed after density reconstruction; exact multi-viewport screenshot capture remains unavailable because the in-app browser viewport override is not applying in this session", "resume_to": "validating"} diff --git a/.prototype-run/xiaoqikong-fidelity-rebuild-20260711/motion/v001.json b/.prototype-run/xiaoqikong-fidelity-rebuild-20260711/motion/v001.json new file mode 100644 index 0000000..1610671 --- /dev/null +++ b/.prototype-run/xiaoqikong-fidelity-rebuild-20260711/motion/v001.json @@ -0,0 +1,14 @@ +{ + "schema_version": "1.0", + "motion_contract_id": "mc_xiaoqikong_v001", + "revision": 1, + "requirements_contract": {"contract_id": "pc_xiaoqikong_queue_redesign", "revision": 1}, + "intensity": 2, + "library_decision": {"primary": "css", "justification": "Only direct state feedback, nav selection and short content updates are required; no choreography or layout measurement justifies a dependency.", "dependencies": []}, + "behaviors": [ + {"id": "MOTION-STATE-FEEDBACK", "requirement_ids": ["REQ-MOTION-001"], "surface": "all interactive routes", "trigger": "hover, focus, pressed, selected or asynchronous status update", "purpose": "feedback", "targets": "buttons, links, selected rows, banners and status regions", "from": "resting color and opacity", "to": "semantic final color, 1px press translation where applicable, and stable visible feedback", "timing": "100-220ms", "easing": "cubic-bezier(.2,0,0,1)", "interruptible": true, "reentry": "on each direct interaction or state update", "reduced_motion": "show final state immediately and remove translation", "acceptance": "feedback is understandable without waiting and no layout shift occurs"} + ], + "reduced_motion": {"strategy": "disable nonessential transitions and transforms via prefers-reduced-motion; preserve immediate state visibility"}, + "performance_budget": {"properties": ["opacity", "transform", "background-color", "border-color"], "max_duration_ms": 220, "layout_animation": false}, + "ambiguities": [] +} diff --git a/.prototype-run/xiaoqikong-fidelity-rebuild-20260711/prototypes/selected-karst-breath.png b/.prototype-run/xiaoqikong-fidelity-rebuild-20260711/prototypes/selected-karst-breath.png new file mode 100644 index 0000000..ad092c9 Binary files /dev/null and b/.prototype-run/xiaoqikong-fidelity-rebuild-20260711/prototypes/selected-karst-breath.png differ diff --git a/.prototype-run/xiaoqikong-fidelity-rebuild-20260711/qa/comparison-final-v3.jpg b/.prototype-run/xiaoqikong-fidelity-rebuild-20260711/qa/comparison-final-v3.jpg new file mode 100644 index 0000000..abbf3fc Binary files /dev/null and b/.prototype-run/xiaoqikong-fidelity-rebuild-20260711/qa/comparison-final-v3.jpg differ diff --git a/.prototype-run/xiaoqikong-fidelity-rebuild-20260711/qa/comparison-final-visible.jpg b/.prototype-run/xiaoqikong-fidelity-rebuild-20260711/qa/comparison-final-visible.jpg new file mode 100644 index 0000000..ba1bdde Binary files /dev/null and b/.prototype-run/xiaoqikong-fidelity-rebuild-20260711/qa/comparison-final-visible.jpg differ diff --git a/.prototype-run/xiaoqikong-fidelity-rebuild-20260711/qa/comparison-final.jpg b/.prototype-run/xiaoqikong-fidelity-rebuild-20260711/qa/comparison-final.jpg new file mode 100644 index 0000000..1797283 Binary files /dev/null and b/.prototype-run/xiaoqikong-fidelity-rebuild-20260711/qa/comparison-final.jpg differ diff --git a/.prototype-run/xiaoqikong-fidelity-rebuild-20260711/qa/comparison-side-by-side.jpg b/.prototype-run/xiaoqikong-fidelity-rebuild-20260711/qa/comparison-side-by-side.jpg new file mode 100644 index 0000000..0b7ed1c Binary files /dev/null and b/.prototype-run/xiaoqikong-fidelity-rebuild-20260711/qa/comparison-side-by-side.jpg differ diff --git a/.prototype-run/xiaoqikong-fidelity-rebuild-20260711/qa/comparison.browser-physical.jpg b/.prototype-run/xiaoqikong-fidelity-rebuild-20260711/qa/comparison.browser-physical.jpg new file mode 100644 index 0000000..f08eebd Binary files /dev/null and b/.prototype-run/xiaoqikong-fidelity-rebuild-20260711/qa/comparison.browser-physical.jpg differ diff --git a/.prototype-run/xiaoqikong-fidelity-rebuild-20260711/qa/comparison.browser.jpg b/.prototype-run/xiaoqikong-fidelity-rebuild-20260711/qa/comparison.browser.jpg new file mode 100644 index 0000000..74fb317 Binary files /dev/null and b/.prototype-run/xiaoqikong-fidelity-rebuild-20260711/qa/comparison.browser.jpg differ diff --git a/.prototype-run/xiaoqikong-fidelity-rebuild-20260711/qa/implementation-checkpoint-v2.json b/.prototype-run/xiaoqikong-fidelity-rebuild-20260711/qa/implementation-checkpoint-v2.json new file mode 100644 index 0000000..01a0032 --- /dev/null +++ b/.prototype-run/xiaoqikong-fidelity-rebuild-20260711/qa/implementation-checkpoint-v2.json @@ -0,0 +1 @@ +{"implementation_revision":2,"repo_snapshot":"auto","tests":"11 files / 24 tests passed","typecheck":"passed","build":"passed","browser_url":"http://127.0.0.1:5173/admin","design_qa":"/Users/inmanw/公司/calllinesystem/design-qa.md","demo_view":"10 non-persistent projects with live-data toggle"} diff --git a/.prototype-run/xiaoqikong-fidelity-rebuild-20260711/qa/implementation-checkpoint.json b/.prototype-run/xiaoqikong-fidelity-rebuild-20260711/qa/implementation-checkpoint.json new file mode 100644 index 0000000..42cba31 --- /dev/null +++ b/.prototype-run/xiaoqikong-fidelity-rebuild-20260711/qa/implementation-checkpoint.json @@ -0,0 +1 @@ +{"implementation_revision":1,"repo_snapshot":"auto","tests":"11 files / 24 tests passed","typecheck":"passed","build":"passed","browser_url":"http://127.0.0.1:5173/admin","design_qa":"/Users/inmanw/公司/calllinesystem/design-qa.md"} diff --git a/.prototype-run/xiaoqikong-fidelity-rebuild-20260711/qa/screenshots/admin-1380x982.browser.jpg b/.prototype-run/xiaoqikong-fidelity-rebuild-20260711/qa/screenshots/admin-1380x982.browser.jpg new file mode 100644 index 0000000..2d60b01 Binary files /dev/null and b/.prototype-run/xiaoqikong-fidelity-rebuild-20260711/qa/screenshots/admin-1380x982.browser.jpg differ diff --git a/.prototype-run/xiaoqikong-fidelity-rebuild-20260711/qa/screenshots/admin-1487x1058.browser.jpg b/.prototype-run/xiaoqikong-fidelity-rebuild-20260711/qa/screenshots/admin-1487x1058.browser.jpg new file mode 100644 index 0000000..33a6cce Binary files /dev/null and b/.prototype-run/xiaoqikong-fidelity-rebuild-20260711/qa/screenshots/admin-1487x1058.browser.jpg differ diff --git a/.prototype-run/xiaoqikong-fidelity-rebuild-20260711/qa/screenshots/admin-1487x1058.png b/.prototype-run/xiaoqikong-fidelity-rebuild-20260711/qa/screenshots/admin-1487x1058.png new file mode 100644 index 0000000..5b30f6c Binary files /dev/null and b/.prototype-run/xiaoqikong-fidelity-rebuild-20260711/qa/screenshots/admin-1487x1058.png differ diff --git a/.prototype-run/xiaoqikong-fidelity-rebuild-20260711/qa/screenshots/admin-dense-final.browser.jpg b/.prototype-run/xiaoqikong-fidelity-rebuild-20260711/qa/screenshots/admin-dense-final.browser.jpg new file mode 100644 index 0000000..74f1673 Binary files /dev/null and b/.prototype-run/xiaoqikong-fidelity-rebuild-20260711/qa/screenshots/admin-dense-final.browser.jpg differ diff --git a/.prototype-run/xiaoqikong-fidelity-rebuild-20260711/qa/screenshots/admin-final.browser.jpg b/.prototype-run/xiaoqikong-fidelity-rebuild-20260711/qa/screenshots/admin-final.browser.jpg new file mode 100644 index 0000000..fc8b10a Binary files /dev/null and b/.prototype-run/xiaoqikong-fidelity-rebuild-20260711/qa/screenshots/admin-final.browser.jpg differ diff --git a/.prototype-run/xiaoqikong-fidelity-rebuild-20260711/qa/screenshots/admin-full-v2.browser.jpg b/.prototype-run/xiaoqikong-fidelity-rebuild-20260711/qa/screenshots/admin-full-v2.browser.jpg new file mode 100644 index 0000000..1e58eb5 Binary files /dev/null and b/.prototype-run/xiaoqikong-fidelity-rebuild-20260711/qa/screenshots/admin-full-v2.browser.jpg differ diff --git a/.prototype-run/xiaoqikong-fidelity-rebuild-20260711/qa/screenshots/admin-full-v3.browser.jpg b/.prototype-run/xiaoqikong-fidelity-rebuild-20260711/qa/screenshots/admin-full-v3.browser.jpg new file mode 100644 index 0000000..04a8e29 Binary files /dev/null and b/.prototype-run/xiaoqikong-fidelity-rebuild-20260711/qa/screenshots/admin-full-v3.browser.jpg differ diff --git a/.prototype-run/xiaoqikong-fidelity-rebuild-20260711/qa/screenshots/admin-full-v4.browser.jpg b/.prototype-run/xiaoqikong-fidelity-rebuild-20260711/qa/screenshots/admin-full-v4.browser.jpg new file mode 100644 index 0000000..e86a1d0 Binary files /dev/null and b/.prototype-run/xiaoqikong-fidelity-rebuild-20260711/qa/screenshots/admin-full-v4.browser.jpg differ diff --git a/.prototype-run/xiaoqikong-fidelity-rebuild-20260711/qa/screenshots/admin-full.browser.jpg b/.prototype-run/xiaoqikong-fidelity-rebuild-20260711/qa/screenshots/admin-full.browser.jpg new file mode 100644 index 0000000..2aa6d14 Binary files /dev/null and b/.prototype-run/xiaoqikong-fidelity-rebuild-20260711/qa/screenshots/admin-full.browser.jpg differ diff --git a/.prototype-run/xiaoqikong-fidelity-rebuild-20260711/requirements/v001.json b/.prototype-run/xiaoqikong-fidelity-rebuild-20260711/requirements/v001.json new file mode 100644 index 0000000..e4080c1 --- /dev/null +++ b/.prototype-run/xiaoqikong-fidelity-rebuild-20260711/requirements/v001.json @@ -0,0 +1,154 @@ +{ + "schema_version": "1.0", + "contract_id": "pc_xiaoqikong_queue_redesign", + "revision": 1, + "status": "accepted", + "product": "小七孔文旅集团景区排队叫号系统", + "surface": "responsive_web_multi_surface", + "primary_user": "现场工作人员、游客、运营管理员与公示屏观众", + "primary_outcome": "在不改变现有业务和权限边界的前提下,以小七孔文旅品牌统一重绘四端并提高易读性与操作效率", + "target_viewports": [ + {"id": "mobile_primary", "width": 390, "height": 844}, + {"id": "mobile_reflow", "width": 320, "height": 800}, + {"id": "admin_desktop", "width": 1440, "height": 1024}, + {"id": "display_landscape", "width": 1920, "height": 1080} + ], + "requirements": [ + { + "id": "REQ-FLOW-001", + "kind": "flow", + "statement": "工作人员继续通过现有入口完成项目切换、取号和叫下一批。", + "priority": "must", + "source": {"type": "existing_product", "ref": "web/src/pages/StaffPage.tsx"}, + "acceptance": ["原有工作人员主流程与 API 调用保持可用"], + "status": "active" + }, + { + "id": "REQ-FLOW-002", + "kind": "flow", + "statement": "游客继续通过私密链接查看号码、排队状态、前方号码数和预计等待时间。", + "priority": "must", + "source": {"type": "existing_product", "ref": "web/src/pages/VisitorPage.tsx"}, + "acceptance": ["游客页只暴露手机号尾四位并正确显示各状态"], + "status": "active" + }, + { + "id": "REQ-FLOW-003", + "kind": "flow", + "statement": "管理员继续使用现有概览、项目管理、账号、日志、设备与大屏入口。", + "priority": "must", + "source": {"type": "existing_product", "ref": "web/src/pages/AdminPage.tsx"}, + "acceptance": ["所有现有管理路由可达且数据展示语义不变"], + "status": "active" + }, + { + "id": "REQ-FLOW-004", + "kind": "flow", + "statement": "公示大屏继续以只读方式展示当前批次、最新叫号和排队进度。", + "priority": "must", + "source": {"type": "existing_product", "ref": "web/src/pages/DisplayPage.tsx"}, + "acceptance": ["1920×1080 下远距离可读且不出现个人信息"], + "status": "active" + }, + { + "id": "REQ-VIS-001", + "kind": "visual", + "statement": "四端必须使用用户提供的小七孔文旅集团产品 Logo 作为正式品牌资产,不重绘、不替换字标。", + "priority": "must", + "source": {"type": "provided_artifact", "ref": "assets/xiaoqikong-logo.jpg#sha256=ac1b8b07954ee5ce706e6cd053e99b00275ee0516a5c0c21d1d576b5a059909d"}, + "acceptance": ["页面使用真实 Logo 文件而非生成图中的伪造标识"], + "status": "active" + }, + { + "id": "REQ-VIS-002", + "kind": "visual", + "statement": "视觉方向保持可信、清晰、耐候、低焦虑,以 Logo 的自然绿、山水笔触和黑色字标建立品牌识别。", + "priority": "must", + "source": {"type": "user", "ref": "confirmed design direction"}, + "acceptance": ["四端共享可辨识的品牌骨架且不使用紫色 AI 渐变或玻璃拟态"], + "status": "active" + }, + { + "id": "REQ-VIS-003", + "kind": "visual", + "statement": "工作人员端与游客端以 390px 为主设计宽度,管理端和大屏分别优化桌面密度与远距离识读。", + "priority": "must", + "source": {"type": "existing_product", "ref": "task_plan.md Phase 9"}, + "acceptance": ["390px、1440px、1920px 主视口布局无裁切与主操作遮挡"], + "status": "active" + }, + { + "id": "REQ-STATE-001", + "kind": "state", + "statement": "保留现有加载、空、错误、成功、弱网、失效和禁用状态的明确表达。", + "priority": "must", + "source": {"type": "existing_product", "ref": "web/src/components/Feedback.tsx"}, + "acceptance": ["每个适用页面都有可理解且可恢复的异常状态"], + "status": "active" + }, + { + "id": "REQ-A11Y-001", + "kind": "accessibility", + "statement": "必须保持键盘可用、明显焦点、语义标签、不依赖单一颜色和 WCAG 2.2 AA 级对比度。", + "priority": "must", + "source": {"type": "accepted_default", "ref": "confirmed risk and validation summary"}, + "acceptance": ["键盘完成主流程、焦点可见、状态有文字语义、对比度达标"], + "status": "active" + }, + { + "id": "REQ-A11Y-002", + "kind": "accessibility", + "statement": "必须在 320 CSS px 重排、48px 触控目标和减少动态效果偏好下保持可用。", + "priority": "must", + "source": {"type": "accepted_default", "ref": "confirmed risk and validation summary"}, + "acceptance": ["320px 无二维滚动且 prefers-reduced-motion 下无非必需动画"], + "status": "active" + }, + { + "id": "REQ-MOTION-001", + "kind": "motion", + "statement": "动效仅用于状态反馈、内容层级切换和叫号更新定位,整体保持克制。", + "priority": "should", + "source": {"type": "user", "ref": "confirmed assumptions"}, + "acceptance": ["无装饰性循环动画,动效可中断且减少动态效果下即时更新"], + "status": "active" + }, + { + "id": "REQ-TECH-001", + "kind": "technical", + "statement": "重绘必须复用现有 React、TypeScript、Vite、CSS 与组件结构,不更改 Go 后端和 API 契约。", + "priority": "must", + "source": {"type": "user", "ref": "confirmed scope assumption"}, + "acceptance": ["后端业务文件无修改且前端测试、类型检查和构建通过"], + "status": "active" + }, + { + "id": "REQ-TECH-002", + "kind": "technical", + "statement": "可见文案、业务数据、路由、权限、隐私和幂等边界以现有产品为权威来源。", + "priority": "must", + "source": {"type": "existing_product", "ref": "web/src and server contracts"}, + "acceptance": ["不从生成图转录错别字、伪造数据或新交互"], + "status": "active" + } + ], + "flows": [ + {"id": "FLOW-STAFF", "name": "工作人员取号与叫号", "requirement_ids": ["REQ-FLOW-001", "REQ-STATE-001", "REQ-A11Y-001"]}, + {"id": "FLOW-VISITOR", "name": "游客查看排队状态", "requirement_ids": ["REQ-FLOW-002", "REQ-STATE-001", "REQ-A11Y-002"]}, + {"id": "FLOW-ADMIN", "name": "管理员运营管理", "requirement_ids": ["REQ-FLOW-003", "REQ-STATE-001", "REQ-A11Y-001"]}, + {"id": "FLOW-DISPLAY", "name": "公示屏远距离查看", "requirement_ids": ["REQ-FLOW-004", "REQ-VIS-003", "REQ-A11Y-001"]} + ], + "exact_copy": {}, + "out_of_scope": ["新业务功能", "后端修改", "API 契约修改", "正式短信或硬件接入", "Logo 重绘"], + "assumptions": ["当前 640×640 JPG 是本轮唯一正式品牌资产", "现有中文文案和真实数据结构保持不变", "可为不同尺寸使用对原 Logo 做不改变内容的裁切与透明背景处理"], + "ambiguities": [], + "confirmation": { + "summary_revision": 2, + "summary_reviewed": true, + "authorized_by": "user", + "authorization_text": "确认,可以执行", + "confirmed_at": "2026-07-11T06:58:22Z", + "assumptions_reviewed": true, + "unresolved_material_decisions": [] + } +} diff --git a/.prototype-run/xiaoqikong-fidelity-rebuild-20260711/run.json b/.prototype-run/xiaoqikong-fidelity-rebuild-20260711/run.json new file mode 100644 index 0000000..8393b3f --- /dev/null +++ b/.prototype-run/xiaoqikong-fidelity-rebuild-20260711/run.json @@ -0,0 +1,180 @@ +{ + "schema_version": "1.0", + "run_id": "b2p_b214c02d21d4", + "mode": "existing_visual", + "state": "blocked", + "surface": "responsive_web", + "target_repo": "/Users/inmanw/公司/calllinesystem", + "artifacts": [ + { + "artifact_id": "requirements-r001-4e447ed7e6", + "kind": "requirements", + "revision": 1, + "status": "approved", + "validity": "current", + "path": "/Users/inmanw/公司/calllinesystem/.prototype-run/xiaoqikong-fidelity-rebuild-20260711/requirements/v001.json", + "sha256": "4e447ed7e66ccb462f238d1b30c96448923e7ad46495e735735c888ec011563a", + "size_bytes": 7442, + "input_refs": [], + "provenance": {}, + "supersedes": null, + "created_by": "agent", + "recorded_at": "2026-07-11T07:38:50Z" + }, + { + "artifact_id": "prototype-r001-1dfc3cfdbc", + "kind": "prototype", + "revision": 1, + "status": "selected", + "validity": "current", + "path": "/Users/inmanw/公司/calllinesystem/.prototype-run/xiaoqikong-fidelity-rebuild-20260711/prototypes/selected-karst-breath.png", + "sha256": "1dfc3cfdbcc8af653430290f8ad55e09c269fc97f2d98d96852270e328a53f77", + "size_bytes": 1240326, + "input_refs": [], + "provenance": { + "tool": "existing-approved-visual", + "candidate_id": "candidate_stretch_human_scenic", + "source_run": "b2p_d52228eb0be3", + "source_artifact_id": "prototype-r003-1dfc3cfdbc", + "sha256": "1dfc3cfdbcc8af653430290f8ad55e09c269fc97f2d98d96852270e328a53f77" + }, + "supersedes": null, + "created_by": "agent", + "recorded_at": "2026-07-11T07:38:50Z" + }, + { + "artifact_id": "design_contract-r001-44692cdca9", + "kind": "design_contract", + "revision": 1, + "status": "approved", + "validity": "current", + "path": "/Users/inmanw/公司/calllinesystem/.prototype-run/xiaoqikong-fidelity-rebuild-20260711/design/v001.json", + "sha256": "44692cdca97b7a90b0b0c862f0b6db422595048b38f1af0a01c1272b1a7e7354", + "size_bytes": 8573, + "input_refs": [], + "provenance": {}, + "supersedes": null, + "created_by": "agent", + "recorded_at": "2026-07-11T07:39:30Z" + }, + { + "artifact_id": "motion_contract-r001-f4de7e4856", + "kind": "motion_contract", + "revision": 1, + "status": "approved", + "validity": "current", + "path": "/Users/inmanw/公司/calllinesystem/.prototype-run/xiaoqikong-fidelity-rebuild-20260711/motion/v001.json", + "sha256": "f4de7e48565b888b51a4e5e010ae712cb91fae42f73f24eebaaa26785347ca83", + "size_bytes": 1491, + "input_refs": [], + "provenance": {}, + "supersedes": null, + "created_by": "agent", + "recorded_at": "2026-07-11T07:39:30Z" + }, + { + "artifact_id": "implementation_plan-r001-f339959eba", + "kind": "implementation_plan", + "revision": 1, + "status": "approved", + "validity": "current", + "path": "/Users/inmanw/公司/calllinesystem/.prototype-run/xiaoqikong-fidelity-rebuild-20260711/build/v001.json", + "sha256": "f339959eba509deece28d3dc2fcc068bae4ee3d56b96e5103ddc8ec1de7478b3", + "size_bytes": 4735, + "input_refs": [], + "provenance": {}, + "supersedes": null, + "created_by": "agent", + "recorded_at": "2026-07-11T07:39:30Z" + }, + { + "artifact_id": "implementation_checkpoint-r001-36184674eb", + "kind": "implementation_checkpoint", + "revision": 1, + "status": "superseded", + "validity": "current", + "path": "/Users/inmanw/公司/calllinesystem/.prototype-run/xiaoqikong-fidelity-rebuild-20260711/qa/implementation-checkpoint.json", + "sha256": "36184674ebdbc3ddd5bcde368506adb5bceba6f4c4b16cffcfe106974b384f06", + "size_bytes": 235, + "input_refs": [], + "provenance": { + "implementation_revision": 1, + "repo_snapshot": "ba2c4e7e11e236525f395f4d652de5fa1eb1e7f20ce21b2250750dd534af7441", + "repo_snapshot_algorithm": "product-prototype-design-repo-snapshot-v2-full-repository", + "repo_snapshot_coverage": "full_target_repo", + "repo_snapshot_file_count": 95, + "repo_snapshot_scope": [ + "docs/scenic-queue-system-plan.md", + "findings.md", + "progress.md", + "server", + "server/migrations", + "task_plan.md", + "web/package.json", + "web/pnpm-lock.yaml", + "web/public", + "web/src" + ] + }, + "supersedes": null, + "created_by": "agent", + "recorded_at": "2026-07-11T07:50:29Z", + "superseded_at": "2026-07-11T08:20:17Z", + "superseded_by": "implementation_checkpoint-r002-8c034c7045" + }, + { + "artifact_id": "implementation_checkpoint-r002-8c034c7045", + "kind": "implementation_checkpoint", + "revision": 2, + "status": "verified", + "validity": "current", + "path": "/Users/inmanw/公司/calllinesystem/.prototype-run/xiaoqikong-fidelity-rebuild-20260711/qa/implementation-checkpoint-v2.json", + "sha256": "8c034c704542cc0e4a777f894fac12872d193537868b9c41f1b0e4fdaa9f5ae3", + "size_bytes": 298, + "input_refs": [], + "provenance": { + "implementation_revision": 2, + "repo_snapshot": "16bb5bd951b072b99e638c1a4f0a20ad37d263d429855144bda64fc563714188", + "repo_snapshot_algorithm": "product-prototype-design-repo-snapshot-v2-full-repository", + "repo_snapshot_coverage": "full_target_repo", + "repo_snapshot_file_count": 95, + "repo_snapshot_scope": [ + "docs/scenic-queue-system-plan.md", + "findings.md", + "progress.md", + "server", + "server/migrations", + "task_plan.md", + "web/package.json", + "web/pnpm-lock.yaml", + "web/public", + "web/src" + ] + }, + "supersedes": "implementation_checkpoint-r001-36184674eb", + "created_by": "agent", + "recorded_at": "2026-07-11T08:20:17Z" + } + ], + "gates": { + "visual_selection": { + "gate": "visual_selection", + "decision": "approved", + "validity": "current", + "artifact_id": "prototype-r001-1dfc3cfdbc", + "artifact_sha256": "1dfc3cfdbcc8af653430290f8ad55e09c269fc97f2d98d96852270e328a53f77", + "actor": "user", + "note": "User reaffirmed selected direction 3 by requesting faithful restoration of the image", + "decided_at": "2026-07-11T07:39:30Z" + } + }, + "blocker": { + "reason": "Desktop source-to-implementation comparison passed after density reconstruction; exact multi-viewport screenshot capture remains unavailable because the in-app browser viewport override is not applying in this session", + "resume_to": "validating", + "blocked_from": "validating", + "blocked_at": "2026-07-11T08:20:17Z" + }, + "last_event_id": "evt_91ed1dcaed9a4ef0a8f96d31f3f75bf9", + "created_at": "2026-07-11T07:38:50Z", + "updated_at": "2026-07-11T08:20:17Z" +} diff --git a/.prototype-run/xiaoqikong-multisurface-20260711/assets/selected-admin-direction.png b/.prototype-run/xiaoqikong-multisurface-20260711/assets/selected-admin-direction.png new file mode 100644 index 0000000..ad092c9 Binary files /dev/null and b/.prototype-run/xiaoqikong-multisurface-20260711/assets/selected-admin-direction.png differ diff --git a/.prototype-run/xiaoqikong-multisurface-20260711/assets/xiaoqikong-logo.jpg b/.prototype-run/xiaoqikong-multisurface-20260711/assets/xiaoqikong-logo.jpg new file mode 100644 index 0000000..d556065 Binary files /dev/null and b/.prototype-run/xiaoqikong-multisurface-20260711/assets/xiaoqikong-logo.jpg differ diff --git a/.prototype-run/xiaoqikong-multisurface-20260711/events.jsonl b/.prototype-run/xiaoqikong-multisurface-20260711/events.jsonl new file mode 100644 index 0000000..4cca814 --- /dev/null +++ b/.prototype-run/xiaoqikong-multisurface-20260711/events.jsonl @@ -0,0 +1,12 @@ +{"event_id": "evt_5a500aaf2372405288337b9ee4d98f40", "run_id": "b2p_809b221a8c07", "at": "2026-07-11T08:26:11Z", "operation": "init", "actor": "agent", "new_state": "created", "mode": "full", "run_metadata": {"schema_version": "1.0", "run_id": "b2p_809b221a8c07", "mode": "full", "surface": "responsive_web", "target_repo": "/Users/inmanw/公司/calllinesystem", "created_at": "2026-07-11T08:26:11Z"}} +{"event_id": "evt_32bde4058016411383fbff9360475d6f", "run_id": "b2p_809b221a8c07", "at": "2026-07-11T08:26:11Z", "operation": "record_artifact", "actor": "agent", "artifact_type": "requirements", "artifact_id": "requirements-r001-4e447ed7e6", "revision": 1, "output_hash": "4e447ed7e66ccb462f238d1b30c96448923e7ad46495e735735c888ec011563a", "status": "approved", "supersedes": null, "invalidated_artifacts": [], "stale_gates": [], "artifact": {"artifact_id": "requirements-r001-4e447ed7e6", "kind": "requirements", "revision": 1, "status": "approved", "validity": "current", "path": "/Users/inmanw/公司/calllinesystem/.prototype-run/xiaoqikong-multisurface-20260711/requirements/v001.json", "sha256": "4e447ed7e66ccb462f238d1b30c96448923e7ad46495e735735c888ec011563a", "size_bytes": 7442, "input_refs": [], "provenance": {}, "supersedes": null, "created_by": "agent", "recorded_at": "2026-07-11T08:26:11Z"}} +{"event_id": "evt_59ffdd7108624dcf8d08b7c66bec71a3", "run_id": "b2p_809b221a8c07", "at": "2026-07-11T08:26:11Z", "operation": "transition", "actor": "agent", "prior_state": "created", "new_state": "requirements_ready", "note": "Accepted multisurface extension contract", "stale_gates": [], "stale_artifacts": []} +{"event_id": "evt_727d71c468064580b0d8153b5a1644a8", "run_id": "b2p_809b221a8c07", "at": "2026-07-11T08:26:58Z", "operation": "record_artifact", "actor": "agent", "artifact_type": "prompt_bundle", "artifact_id": "prompt_bundle-r001-d6c44cd348", "revision": 1, "output_hash": "d6c44cd3483a1ce85b1c0d39bf13a265a7c4cf3e381fa9aa606cfcf8400d772c", "status": "ready", "supersedes": null, "invalidated_artifacts": [], "stale_gates": [], "artifact": {"artifact_id": "prompt_bundle-r001-d6c44cd348", "kind": "prompt_bundle", "revision": 1, "status": "ready", "validity": "current", "path": "/Users/inmanw/公司/calllinesystem/.prototype-run/xiaoqikong-multisurface-20260711/prompts/v001.json", "sha256": "d6c44cd3483a1ce85b1c0d39bf13a265a7c4cf3e381fa9aa606cfcf8400d772c", "size_bytes": 8337, "input_refs": [], "provenance": {}, "supersedes": null, "created_by": "agent", "recorded_at": "2026-07-11T08:26:58Z"}} +{"event_id": "evt_0ea0161556d249c5962a57e8533c53fe", "run_id": "b2p_809b221a8c07", "at": "2026-07-11T08:26:58Z", "operation": "transition", "actor": "agent", "prior_state": "requirements_ready", "new_state": "prompt_ready", "note": "", "stale_gates": [], "stale_artifacts": []} +{"event_id": "evt_e3ca1be01eb4488a9caf8c0dafb92c47", "run_id": "b2p_809b221a8c07", "at": "2026-07-11T08:32:28Z", "operation": "record_artifact", "actor": "agent", "artifact_type": "prototype", "artifact_id": "prototype-r001-466349b650", "revision": 1, "output_hash": "466349b650a8f676d194716d0259b0cb1bc5bb9342f6bae482baaa48a655dacc", "status": "candidate", "supersedes": null, "invalidated_artifacts": [], "stale_gates": [], "artifact": {"artifact_id": "prototype-r001-466349b650", "kind": "prototype", "revision": 1, "status": "candidate", "validity": "current", "path": "/Users/inmanw/公司/calllinesystem/.prototype-run/xiaoqikong-multisurface-20260711/prototypes/staff-mobile.png", "sha256": "466349b650a8f676d194716d0259b0cb1bc5bb9342f6bae482baaa48a655dacc", "size_bytes": 1220339, "input_refs": [], "provenance": {"tool": "built-in-imagegen", "candidate_id": "surface_staff_mobile", "receipt": {"provider": "built-in-imagegen", "source": "codex_tool_result", "result_id": "exec-4ea156c3-52d2-49a5-9c1c-69a129878300", "generated_at": "2026-07-11T08:32:07Z", "prompt_bundle_id": "pb_xiaoqikong_multisurface_v001", "candidate_id": "surface_staff_mobile", "output_sha256": "466349b650a8f676d194716d0259b0cb1bc5bb9342f6bae482baaa48a655dacc"}, "viewport_deviation": {"target": {"width": 390, "height": 844}, "actual": {"width": 853, "height": 1844}, "handling": "auto_accept", "rationale": "Backend output preserves the requested aspect ratio within 2% and exceeds both target axes."}}, "supersedes": null, "created_by": "agent", "recorded_at": "2026-07-11T08:32:28Z"}} +{"event_id": "evt_ccf5279fc6644fa184b5aa39e05efc4b", "run_id": "b2p_809b221a8c07", "at": "2026-07-11T08:32:28Z", "operation": "record_artifact", "actor": "agent", "artifact_type": "prototype", "artifact_id": "prototype-r002-1ac3f53a50", "revision": 2, "output_hash": "1ac3f53a50ec54872f587f4bc252ef3f3399254627f00fb66d069b7e6e61be10", "status": "candidate", "supersedes": null, "invalidated_artifacts": [], "stale_gates": [], "artifact": {"artifact_id": "prototype-r002-1ac3f53a50", "kind": "prototype", "revision": 2, "status": "candidate", "validity": "current", "path": "/Users/inmanw/公司/calllinesystem/.prototype-run/xiaoqikong-multisurface-20260711/prototypes/visitor-mobile.png", "sha256": "1ac3f53a50ec54872f587f4bc252ef3f3399254627f00fb66d069b7e6e61be10", "size_bytes": 1226285, "input_refs": [], "provenance": {"tool": "built-in-imagegen", "candidate_id": "surface_visitor_mobile", "receipt": {"provider": "built-in-imagegen", "source": "codex_tool_result", "result_id": "exec-f9e28b17-3997-4a34-bd73-6f8750960717", "generated_at": "2026-07-11T08:32:07Z", "prompt_bundle_id": "pb_xiaoqikong_multisurface_v001", "candidate_id": "surface_visitor_mobile", "output_sha256": "1ac3f53a50ec54872f587f4bc252ef3f3399254627f00fb66d069b7e6e61be10"}, "viewport_deviation": {"target": {"width": 390, "height": 844}, "actual": {"width": 853, "height": 1844}, "handling": "auto_accept", "rationale": "Backend output preserves the requested aspect ratio within 2% and exceeds both target axes."}}, "supersedes": null, "created_by": "agent", "recorded_at": "2026-07-11T08:32:28Z"}} +{"event_id": "evt_a33fa3ad85ea4fd89025ed6f4f81a1b1", "run_id": "b2p_809b221a8c07", "at": "2026-07-11T08:32:28Z", "operation": "record_artifact", "actor": "agent", "artifact_type": "prototype", "artifact_id": "prototype-r003-f036019a2e", "revision": 3, "output_hash": "f036019a2e6d595d28a4dd2d53755444b72daee11f6ab16e9965afc21b3922a6", "status": "candidate", "supersedes": null, "invalidated_artifacts": [], "stale_gates": [], "artifact": {"artifact_id": "prototype-r003-f036019a2e", "kind": "prototype", "revision": 3, "status": "candidate", "validity": "current", "path": "/Users/inmanw/公司/calllinesystem/.prototype-run/xiaoqikong-multisurface-20260711/prototypes/public-display.png", "sha256": "f036019a2e6d595d28a4dd2d53755444b72daee11f6ab16e9965afc21b3922a6", "size_bytes": 1279236, "input_refs": [], "provenance": {"tool": "built-in-imagegen", "candidate_id": "surface_public_display", "receipt": {"provider": "built-in-imagegen", "source": "codex_tool_result", "result_id": "exec-31c71a0d-4cd1-4789-ac3b-84a713b63f98", "generated_at": "2026-07-11T08:32:07Z", "prompt_bundle_id": "pb_xiaoqikong_multisurface_v001", "candidate_id": "surface_public_display", "output_sha256": "f036019a2e6d595d28a4dd2d53755444b72daee11f6ab16e9965afc21b3922a6"}, "viewport_deviation": {"target": {"width": 1920, "height": 1080}, "actual": {"width": 1672, "height": 941}, "handling": "auto_accept", "rationale": "Backend output preserves the requested aspect ratio within 2% and both axes exceed 75% of target."}}, "supersedes": null, "created_by": "agent", "recorded_at": "2026-07-11T08:32:28Z"}} +{"event_id": "evt_70bd62ada1224139b278aa95df5daddd", "run_id": "b2p_809b221a8c07", "at": "2026-07-11T08:32:54Z", "operation": "record_artifact", "actor": "agent", "artifact_type": "prototype_manifest", "artifact_id": "prototype_manifest-r001-3ba706af4f", "revision": 1, "output_hash": "3ba706af4fa8377e8e6be96220a907d01fefc1da20e179fad4486987594e5230", "status": "recorded", "supersedes": null, "invalidated_artifacts": [], "stale_gates": [], "artifact": {"artifact_id": "prototype_manifest-r001-3ba706af4f", "kind": "prototype_manifest", "revision": 1, "status": "recorded", "validity": "current", "path": "/Users/inmanw/公司/calllinesystem/.prototype-run/xiaoqikong-multisurface-20260711/prototypes/manifest.json", "sha256": "3ba706af4fa8377e8e6be96220a907d01fefc1da20e179fad4486987594e5230", "size_bytes": 828, "input_refs": [], "provenance": {}, "supersedes": null, "created_by": "agent", "recorded_at": "2026-07-11T08:32:54Z"}} +{"event_id": "evt_8c5d034a7ab245b18fd6dbbbc3f23036", "run_id": "b2p_809b221a8c07", "at": "2026-07-11T08:32:54Z", "operation": "transition", "actor": "agent", "prior_state": "prompt_ready", "new_state": "prototypes_ready", "note": "", "stale_gates": [], "stale_artifacts": []} +{"event_id": "evt_442ad3930ffe46c28ee7524037c15e9b", "run_id": "b2p_809b221a8c07", "at": "2026-07-11T08:36:36Z", "operation": "gate_decision", "actor": "user", "gate": "visual_selection", "decision": "approved", "artifact_id": "prototype-r001-466349b650", "artifact_hash": "466349b650a8f676d194716d0259b0cb1bc5bb9342f6bae482baaa48a655dacc", "note": "用户确认实现员工端、游客端和大屏端整套视觉稿;员工端作为多端视觉系统主选源,另外两端为同批已确认配套视觉源", "invalidated_artifacts": [], "stale_gates": [], "gate_record": {"gate": "visual_selection", "decision": "approved", "validity": "current", "artifact_id": "prototype-r001-466349b650", "artifact_sha256": "466349b650a8f676d194716d0259b0cb1bc5bb9342f6bae482baaa48a655dacc", "actor": "user", "note": "用户确认实现员工端、游客端和大屏端整套视觉稿;员工端作为多端视觉系统主选源,另外两端为同批已确认配套视觉源", "decided_at": "2026-07-11T08:36:36Z"}} +{"event_id": "evt_cc0e72b7a701402e99a9adb2f0681c07", "run_id": "b2p_809b221a8c07", "at": "2026-07-11T08:36:36Z", "operation": "transition", "actor": "agent", "prior_state": "prototypes_ready", "new_state": "visual_selected", "note": "Confirmed multisurface visual set", "stale_gates": [], "stale_artifacts": []} diff --git a/.prototype-run/xiaoqikong-multisurface-20260711/prompts/v001.json b/.prototype-run/xiaoqikong-multisurface-20260711/prompts/v001.json new file mode 100644 index 0000000..497e145 --- /dev/null +++ b/.prototype-run/xiaoqikong-multisurface-20260711/prompts/v001.json @@ -0,0 +1,45 @@ +{ + "schema_version": "1.0", + "prompt_bundle_id": "pb_xiaoqikong_multisurface_v001", + "revision": 1, + "requirements_contract": {"contract_id": "pc_xiaoqikong_queue_redesign", "revision": 1}, + "candidate_count": 3, + "candidates": [ + { + "candidate_id": "surface_staff_mobile", + "direction_name": "喀斯特之息·员工叫号 H5", + "requirement_ids": ["REQ-FLOW-001", "REQ-VIS-001", "REQ-VIS-002", "REQ-VIS-003", "REQ-STATE-001", "REQ-A11Y-001", "REQ-A11Y-002", "REQ-MOTION-001", "REQ-TECH-002"], + "style_refs": [{"style_ref_id": "style_warm_human_xqk_selected", "role": "extend the exact selected Karst Breath system to a high-pressure staff mobile task", "source": "/Users/inmanw/公司/productskill/skill/product-prototype-design/references/style-warm-human.md", "sha256": "e2f7857158dffb3f1b5a602a5bca488355e118a07ea34a986facf33ddb87445b", "inspected": true}], + "viewport": {"width": 390, "height": 844}, + "prompt": "Use case: ui-mockup. Create one straight-on, full-bleed 390×844 staff mobile H5 screen for the Xiaoqikong scenic-area queue system. Extend the exact visual language of the supplied selected desktop admin reference: calm off-white and fog-green canvas, deep evergreen type, fine contour-line motif, narrow green section markers, restrained 8–14px radii, hairline borders, tabular numerals, dense but breathable public-service UI. Use the supplied official logo only as a reference and reserve a clean real-logo header area; do not redraw the logo. Core state: staff call-next task. Top compact brand bar with account/exit, project switcher and running badge; two-segment task navigation with “叫号” selected and “取号” secondary; one dominant current-batch card showing a large batch number and called ticket numbers; below it a compact next-batch preview list with ticket number, masked contact cue and estimated wait; persistent bottom action bar with one wide “叫下一批” button and the exact number of tickets to call. Make 48px touch targets, visible focus treatment, strong one-handed hierarchy, no hidden actions. Text in the image is spatial guidance only and non-authoritative. No device frame, no perspective, no browser chrome, no generated logo.", + "negative_constraints": ["no generic card mosaic", "no blue SaaS palette", "no glassmorphism", "no gradient CTA", "no purple", "no photography", "no fake phone hardware frame", "no invented verification workflow", "no generated logo"], + "exact_copy_policy": "requirements_contract_authoritative", + "input_images": [{"path": "assets/xiaoqikong-logo.jpg", "sha256": "ac1b8b07954ee5ce706e6cd053e99b00275ee0516a5c0c21d1d576b5a059909d", "role": "official product logo reference; do not redraw", "attached": true}, {"path": "assets/selected-admin-direction.png", "sha256": "1dfc3cfdbcc8af653430290f8ad55e09c269fc97f2d98d96852270e328a53f77", "role": "authoritative selected visual system reference", "attached": true}], + "status": "ready" + }, + { + "candidate_id": "surface_visitor_mobile", + "direction_name": "喀斯特之息·游客排队 H5", + "requirement_ids": ["REQ-FLOW-002", "REQ-VIS-001", "REQ-VIS-002", "REQ-VIS-003", "REQ-STATE-001", "REQ-A11Y-001", "REQ-A11Y-002", "REQ-MOTION-001", "REQ-TECH-002"], + "style_refs": [{"style_ref_id": "style_warm_human_xqk_selected", "role": "extend the exact selected Karst Breath system to a low-anxiety visitor mobile status", "source": "/Users/inmanw/公司/productskill/skill/product-prototype-design/references/style-warm-human.md", "sha256": "e2f7857158dffb3f1b5a602a5bca488355e118a07ea34a986facf33ddb87445b", "inspected": true}], + "viewport": {"width": 390, "height": 844}, + "prompt": "Use case: ui-mockup. Create one straight-on, full-bleed 390×844 visitor mobile H5 queue-status screen for Xiaoqikong. Extend the exact supplied selected desktop visual system: adult calm off-white and fog-green, deep evergreen typography, pale contour-line scenery, narrow green section markers, fine borders, tabular numerals, restrained soft surfaces. Use the official supplied logo only as reference and reserve a clean real-logo header slot; do not redraw it. Core state: visitor is waiting. Build one reassuring ticket-shaped primary surface without playful decoration: project name and explicit “正在排队” state at top; very large personal ticket number; phone-last-four confirmation; one horizontally divided metrics region for estimated wait range and people/tickets ahead; a thin queue progress line; latest update/freshness row; one full-width secondary “刷新状态” button near the bottom. Maintain clear state redundancy beyond color, 48px touch target, generous readable Chinese type and intermittent-attention hierarchy. Text is spatial guidance only, not authoritative. No device frame, no perspective, no browser chrome, no fake QR code, no generated logo.", + "negative_constraints": ["no cartoon ticket", "no gamification", "no oversized illustration", "no blue SaaS palette", "no glassmorphism", "no purple", "no fake QR code", "no generated logo", "no exact-minute promise"], + "exact_copy_policy": "requirements_contract_authoritative", + "input_images": [{"path": "assets/xiaoqikong-logo.jpg", "sha256": "ac1b8b07954ee5ce706e6cd053e99b00275ee0516a5c0c21d1d576b5a059909d", "role": "official product logo reference; do not redraw", "attached": true}, {"path": "assets/selected-admin-direction.png", "sha256": "1dfc3cfdbcc8af653430290f8ad55e09c269fc97f2d98d96852270e328a53f77", "role": "authoritative selected visual system reference", "attached": true}], + "status": "ready" + }, + { + "candidate_id": "surface_public_display", + "direction_name": "喀斯特之息·公示叫号大屏", + "requirement_ids": ["REQ-FLOW-004", "REQ-VIS-001", "REQ-VIS-002", "REQ-VIS-003", "REQ-STATE-001", "REQ-A11Y-001", "REQ-MOTION-001", "REQ-TECH-002"], + "style_refs": [{"style_ref_id": "style_warm_human_xqk_selected", "role": "extend the exact selected Karst Breath system to a far-distance public display", "source": "/Users/inmanw/公司/productskill/skill/product-prototype-design/references/style-warm-human.md", "sha256": "e2f7857158dffb3f1b5a602a5bca488355e118a07ea34a986facf33ddb87445b", "inspected": true}], + "viewport": {"width": 1920, "height": 1080}, + "prompt": "Use case: ui-mockup. Create one straight-on, full-bleed 1920×1080 far-distance public queue call display for Xiaoqikong. Extend the exact selected Karst Breath admin visual language to a large public information screen: light mineral canvas rather than dark mode, strong deep-evergreen focus region, pale contour/topographic line texture, clean white supporting panels, restrained amber only for attention, adult public-service typography and huge tabular ticket numbers. Reserve a precise real-logo area in a slim top header with project name, current time and explicit running status; do not redraw the supplied logo. Main composition: approximately two-thirds width dominant current-call panel with label “当前请前往入口” and 3–5 enormous ticket numbers, batch label and instruction; one-third right rail showing queue summary with waiting count and estimated wait plus connection/freshness state. Bottom full-width recent-call strip with five prior batches. All information must be readable from 8–12 meters, high contrast, no private phone or visitor fields, no interaction controls. Image text is non-authoritative. No perspective, no television frame, no browser chrome, no generated logo.", + "negative_constraints": ["no dark control-room theme", "no blue", "no neon", "no ticker clutter", "no phone number", "no visitor names", "no tiny text", "no generic dashboard KPI wall", "no generated logo", "no perspective"], + "exact_copy_policy": "requirements_contract_authoritative", + "input_images": [{"path": "assets/xiaoqikong-logo.jpg", "sha256": "ac1b8b07954ee5ce706e6cd053e99b00275ee0516a5c0c21d1d576b5a059909d", "role": "official product logo reference; do not redraw", "attached": true}, {"path": "assets/selected-admin-direction.png", "sha256": "1dfc3cfdbcc8af653430290f8ad55e09c269fc97f2d98d96852270e328a53f77", "role": "authoritative selected visual system reference", "attached": true}], + "status": "ready" + } + ] +} diff --git a/.prototype-run/xiaoqikong-multisurface-20260711/prototypes/manifest.json b/.prototype-run/xiaoqikong-multisurface-20260711/prototypes/manifest.json new file mode 100644 index 0000000..79cad9c --- /dev/null +++ b/.prototype-run/xiaoqikong-multisurface-20260711/prototypes/manifest.json @@ -0,0 +1,10 @@ +{ + "schema_version": "1.0", + "prompt_bundle_id": "pb_xiaoqikong_multisurface_v001", + "candidate_count": 3, + "candidates": [ + {"candidate_id": "surface_staff_mobile", "artifact_id": "prototype-r001-466349b650", "path": "prototypes/staff-mobile.png", "sha256": "466349b650a8f676d194716d0259b0cb1bc5bb9342f6bae482baaa48a655dacc", "status": "recorded"}, + {"candidate_id": "surface_visitor_mobile", "artifact_id": "prototype-r002-1ac3f53a50", "path": "prototypes/visitor-mobile.png", "sha256": "1ac3f53a50ec54872f587f4bc252ef3f3399254627f00fb66d069b7e6e61be10", "status": "recorded"}, + {"candidate_id": "surface_public_display", "artifact_id": "prototype-r003-f036019a2e", "path": "prototypes/public-display.png", "sha256": "f036019a2e6d595d28a4dd2d53755444b72daee11f6ab16e9965afc21b3922a6", "status": "recorded"} + ] +} diff --git a/.prototype-run/xiaoqikong-multisurface-20260711/prototypes/public-display.png b/.prototype-run/xiaoqikong-multisurface-20260711/prototypes/public-display.png new file mode 100644 index 0000000..f2ca056 Binary files /dev/null and b/.prototype-run/xiaoqikong-multisurface-20260711/prototypes/public-display.png differ diff --git a/.prototype-run/xiaoqikong-multisurface-20260711/prototypes/staff-mobile.png b/.prototype-run/xiaoqikong-multisurface-20260711/prototypes/staff-mobile.png new file mode 100644 index 0000000..c69ad1e Binary files /dev/null and b/.prototype-run/xiaoqikong-multisurface-20260711/prototypes/staff-mobile.png differ diff --git a/.prototype-run/xiaoqikong-multisurface-20260711/prototypes/visitor-mobile.png b/.prototype-run/xiaoqikong-multisurface-20260711/prototypes/visitor-mobile.png new file mode 100644 index 0000000..39fae49 Binary files /dev/null and b/.prototype-run/xiaoqikong-multisurface-20260711/prototypes/visitor-mobile.png differ diff --git a/.prototype-run/xiaoqikong-multisurface-20260711/requirements/v001.json b/.prototype-run/xiaoqikong-multisurface-20260711/requirements/v001.json new file mode 100644 index 0000000..e4080c1 --- /dev/null +++ b/.prototype-run/xiaoqikong-multisurface-20260711/requirements/v001.json @@ -0,0 +1,154 @@ +{ + "schema_version": "1.0", + "contract_id": "pc_xiaoqikong_queue_redesign", + "revision": 1, + "status": "accepted", + "product": "小七孔文旅集团景区排队叫号系统", + "surface": "responsive_web_multi_surface", + "primary_user": "现场工作人员、游客、运营管理员与公示屏观众", + "primary_outcome": "在不改变现有业务和权限边界的前提下,以小七孔文旅品牌统一重绘四端并提高易读性与操作效率", + "target_viewports": [ + {"id": "mobile_primary", "width": 390, "height": 844}, + {"id": "mobile_reflow", "width": 320, "height": 800}, + {"id": "admin_desktop", "width": 1440, "height": 1024}, + {"id": "display_landscape", "width": 1920, "height": 1080} + ], + "requirements": [ + { + "id": "REQ-FLOW-001", + "kind": "flow", + "statement": "工作人员继续通过现有入口完成项目切换、取号和叫下一批。", + "priority": "must", + "source": {"type": "existing_product", "ref": "web/src/pages/StaffPage.tsx"}, + "acceptance": ["原有工作人员主流程与 API 调用保持可用"], + "status": "active" + }, + { + "id": "REQ-FLOW-002", + "kind": "flow", + "statement": "游客继续通过私密链接查看号码、排队状态、前方号码数和预计等待时间。", + "priority": "must", + "source": {"type": "existing_product", "ref": "web/src/pages/VisitorPage.tsx"}, + "acceptance": ["游客页只暴露手机号尾四位并正确显示各状态"], + "status": "active" + }, + { + "id": "REQ-FLOW-003", + "kind": "flow", + "statement": "管理员继续使用现有概览、项目管理、账号、日志、设备与大屏入口。", + "priority": "must", + "source": {"type": "existing_product", "ref": "web/src/pages/AdminPage.tsx"}, + "acceptance": ["所有现有管理路由可达且数据展示语义不变"], + "status": "active" + }, + { + "id": "REQ-FLOW-004", + "kind": "flow", + "statement": "公示大屏继续以只读方式展示当前批次、最新叫号和排队进度。", + "priority": "must", + "source": {"type": "existing_product", "ref": "web/src/pages/DisplayPage.tsx"}, + "acceptance": ["1920×1080 下远距离可读且不出现个人信息"], + "status": "active" + }, + { + "id": "REQ-VIS-001", + "kind": "visual", + "statement": "四端必须使用用户提供的小七孔文旅集团产品 Logo 作为正式品牌资产,不重绘、不替换字标。", + "priority": "must", + "source": {"type": "provided_artifact", "ref": "assets/xiaoqikong-logo.jpg#sha256=ac1b8b07954ee5ce706e6cd053e99b00275ee0516a5c0c21d1d576b5a059909d"}, + "acceptance": ["页面使用真实 Logo 文件而非生成图中的伪造标识"], + "status": "active" + }, + { + "id": "REQ-VIS-002", + "kind": "visual", + "statement": "视觉方向保持可信、清晰、耐候、低焦虑,以 Logo 的自然绿、山水笔触和黑色字标建立品牌识别。", + "priority": "must", + "source": {"type": "user", "ref": "confirmed design direction"}, + "acceptance": ["四端共享可辨识的品牌骨架且不使用紫色 AI 渐变或玻璃拟态"], + "status": "active" + }, + { + "id": "REQ-VIS-003", + "kind": "visual", + "statement": "工作人员端与游客端以 390px 为主设计宽度,管理端和大屏分别优化桌面密度与远距离识读。", + "priority": "must", + "source": {"type": "existing_product", "ref": "task_plan.md Phase 9"}, + "acceptance": ["390px、1440px、1920px 主视口布局无裁切与主操作遮挡"], + "status": "active" + }, + { + "id": "REQ-STATE-001", + "kind": "state", + "statement": "保留现有加载、空、错误、成功、弱网、失效和禁用状态的明确表达。", + "priority": "must", + "source": {"type": "existing_product", "ref": "web/src/components/Feedback.tsx"}, + "acceptance": ["每个适用页面都有可理解且可恢复的异常状态"], + "status": "active" + }, + { + "id": "REQ-A11Y-001", + "kind": "accessibility", + "statement": "必须保持键盘可用、明显焦点、语义标签、不依赖单一颜色和 WCAG 2.2 AA 级对比度。", + "priority": "must", + "source": {"type": "accepted_default", "ref": "confirmed risk and validation summary"}, + "acceptance": ["键盘完成主流程、焦点可见、状态有文字语义、对比度达标"], + "status": "active" + }, + { + "id": "REQ-A11Y-002", + "kind": "accessibility", + "statement": "必须在 320 CSS px 重排、48px 触控目标和减少动态效果偏好下保持可用。", + "priority": "must", + "source": {"type": "accepted_default", "ref": "confirmed risk and validation summary"}, + "acceptance": ["320px 无二维滚动且 prefers-reduced-motion 下无非必需动画"], + "status": "active" + }, + { + "id": "REQ-MOTION-001", + "kind": "motion", + "statement": "动效仅用于状态反馈、内容层级切换和叫号更新定位,整体保持克制。", + "priority": "should", + "source": {"type": "user", "ref": "confirmed assumptions"}, + "acceptance": ["无装饰性循环动画,动效可中断且减少动态效果下即时更新"], + "status": "active" + }, + { + "id": "REQ-TECH-001", + "kind": "technical", + "statement": "重绘必须复用现有 React、TypeScript、Vite、CSS 与组件结构,不更改 Go 后端和 API 契约。", + "priority": "must", + "source": {"type": "user", "ref": "confirmed scope assumption"}, + "acceptance": ["后端业务文件无修改且前端测试、类型检查和构建通过"], + "status": "active" + }, + { + "id": "REQ-TECH-002", + "kind": "technical", + "statement": "可见文案、业务数据、路由、权限、隐私和幂等边界以现有产品为权威来源。", + "priority": "must", + "source": {"type": "existing_product", "ref": "web/src and server contracts"}, + "acceptance": ["不从生成图转录错别字、伪造数据或新交互"], + "status": "active" + } + ], + "flows": [ + {"id": "FLOW-STAFF", "name": "工作人员取号与叫号", "requirement_ids": ["REQ-FLOW-001", "REQ-STATE-001", "REQ-A11Y-001"]}, + {"id": "FLOW-VISITOR", "name": "游客查看排队状态", "requirement_ids": ["REQ-FLOW-002", "REQ-STATE-001", "REQ-A11Y-002"]}, + {"id": "FLOW-ADMIN", "name": "管理员运营管理", "requirement_ids": ["REQ-FLOW-003", "REQ-STATE-001", "REQ-A11Y-001"]}, + {"id": "FLOW-DISPLAY", "name": "公示屏远距离查看", "requirement_ids": ["REQ-FLOW-004", "REQ-VIS-003", "REQ-A11Y-001"]} + ], + "exact_copy": {}, + "out_of_scope": ["新业务功能", "后端修改", "API 契约修改", "正式短信或硬件接入", "Logo 重绘"], + "assumptions": ["当前 640×640 JPG 是本轮唯一正式品牌资产", "现有中文文案和真实数据结构保持不变", "可为不同尺寸使用对原 Logo 做不改变内容的裁切与透明背景处理"], + "ambiguities": [], + "confirmation": { + "summary_revision": 2, + "summary_reviewed": true, + "authorized_by": "user", + "authorization_text": "确认,可以执行", + "confirmed_at": "2026-07-11T06:58:22Z", + "assumptions_reviewed": true, + "unresolved_material_decisions": [] + } +} diff --git a/.prototype-run/xiaoqikong-multisurface-20260711/run.json b/.prototype-run/xiaoqikong-multisurface-20260711/run.json new file mode 100644 index 0000000..452721b --- /dev/null +++ b/.prototype-run/xiaoqikong-multisurface-20260711/run.json @@ -0,0 +1,188 @@ +{ + "schema_version": "1.0", + "run_id": "b2p_809b221a8c07", + "mode": "full", + "state": "visual_selected", + "surface": "responsive_web", + "target_repo": "/Users/inmanw/公司/calllinesystem", + "artifacts": [ + { + "artifact_id": "requirements-r001-4e447ed7e6", + "kind": "requirements", + "revision": 1, + "status": "approved", + "validity": "current", + "path": "/Users/inmanw/公司/calllinesystem/.prototype-run/xiaoqikong-multisurface-20260711/requirements/v001.json", + "sha256": "4e447ed7e66ccb462f238d1b30c96448923e7ad46495e735735c888ec011563a", + "size_bytes": 7442, + "input_refs": [], + "provenance": {}, + "supersedes": null, + "created_by": "agent", + "recorded_at": "2026-07-11T08:26:11Z" + }, + { + "artifact_id": "prompt_bundle-r001-d6c44cd348", + "kind": "prompt_bundle", + "revision": 1, + "status": "ready", + "validity": "current", + "path": "/Users/inmanw/公司/calllinesystem/.prototype-run/xiaoqikong-multisurface-20260711/prompts/v001.json", + "sha256": "d6c44cd3483a1ce85b1c0d39bf13a265a7c4cf3e381fa9aa606cfcf8400d772c", + "size_bytes": 8337, + "input_refs": [], + "provenance": {}, + "supersedes": null, + "created_by": "agent", + "recorded_at": "2026-07-11T08:26:58Z" + }, + { + "artifact_id": "prototype-r001-466349b650", + "kind": "prototype", + "revision": 1, + "status": "selected", + "validity": "current", + "path": "/Users/inmanw/公司/calllinesystem/.prototype-run/xiaoqikong-multisurface-20260711/prototypes/staff-mobile.png", + "sha256": "466349b650a8f676d194716d0259b0cb1bc5bb9342f6bae482baaa48a655dacc", + "size_bytes": 1220339, + "input_refs": [], + "provenance": { + "tool": "built-in-imagegen", + "candidate_id": "surface_staff_mobile", + "receipt": { + "provider": "built-in-imagegen", + "source": "codex_tool_result", + "result_id": "exec-4ea156c3-52d2-49a5-9c1c-69a129878300", + "generated_at": "2026-07-11T08:32:07Z", + "prompt_bundle_id": "pb_xiaoqikong_multisurface_v001", + "candidate_id": "surface_staff_mobile", + "output_sha256": "466349b650a8f676d194716d0259b0cb1bc5bb9342f6bae482baaa48a655dacc" + }, + "viewport_deviation": { + "target": { + "width": 390, + "height": 844 + }, + "actual": { + "width": 853, + "height": 1844 + }, + "handling": "auto_accept", + "rationale": "Backend output preserves the requested aspect ratio within 2% and exceeds both target axes." + } + }, + "supersedes": null, + "created_by": "agent", + "recorded_at": "2026-07-11T08:32:28Z" + }, + { + "artifact_id": "prototype-r002-1ac3f53a50", + "kind": "prototype", + "revision": 2, + "status": "candidate", + "validity": "current", + "path": "/Users/inmanw/公司/calllinesystem/.prototype-run/xiaoqikong-multisurface-20260711/prototypes/visitor-mobile.png", + "sha256": "1ac3f53a50ec54872f587f4bc252ef3f3399254627f00fb66d069b7e6e61be10", + "size_bytes": 1226285, + "input_refs": [], + "provenance": { + "tool": "built-in-imagegen", + "candidate_id": "surface_visitor_mobile", + "receipt": { + "provider": "built-in-imagegen", + "source": "codex_tool_result", + "result_id": "exec-f9e28b17-3997-4a34-bd73-6f8750960717", + "generated_at": "2026-07-11T08:32:07Z", + "prompt_bundle_id": "pb_xiaoqikong_multisurface_v001", + "candidate_id": "surface_visitor_mobile", + "output_sha256": "1ac3f53a50ec54872f587f4bc252ef3f3399254627f00fb66d069b7e6e61be10" + }, + "viewport_deviation": { + "target": { + "width": 390, + "height": 844 + }, + "actual": { + "width": 853, + "height": 1844 + }, + "handling": "auto_accept", + "rationale": "Backend output preserves the requested aspect ratio within 2% and exceeds both target axes." + } + }, + "supersedes": null, + "created_by": "agent", + "recorded_at": "2026-07-11T08:32:28Z" + }, + { + "artifact_id": "prototype-r003-f036019a2e", + "kind": "prototype", + "revision": 3, + "status": "candidate", + "validity": "current", + "path": "/Users/inmanw/公司/calllinesystem/.prototype-run/xiaoqikong-multisurface-20260711/prototypes/public-display.png", + "sha256": "f036019a2e6d595d28a4dd2d53755444b72daee11f6ab16e9965afc21b3922a6", + "size_bytes": 1279236, + "input_refs": [], + "provenance": { + "tool": "built-in-imagegen", + "candidate_id": "surface_public_display", + "receipt": { + "provider": "built-in-imagegen", + "source": "codex_tool_result", + "result_id": "exec-31c71a0d-4cd1-4789-ac3b-84a713b63f98", + "generated_at": "2026-07-11T08:32:07Z", + "prompt_bundle_id": "pb_xiaoqikong_multisurface_v001", + "candidate_id": "surface_public_display", + "output_sha256": "f036019a2e6d595d28a4dd2d53755444b72daee11f6ab16e9965afc21b3922a6" + }, + "viewport_deviation": { + "target": { + "width": 1920, + "height": 1080 + }, + "actual": { + "width": 1672, + "height": 941 + }, + "handling": "auto_accept", + "rationale": "Backend output preserves the requested aspect ratio within 2% and both axes exceed 75% of target." + } + }, + "supersedes": null, + "created_by": "agent", + "recorded_at": "2026-07-11T08:32:28Z" + }, + { + "artifact_id": "prototype_manifest-r001-3ba706af4f", + "kind": "prototype_manifest", + "revision": 1, + "status": "recorded", + "validity": "current", + "path": "/Users/inmanw/公司/calllinesystem/.prototype-run/xiaoqikong-multisurface-20260711/prototypes/manifest.json", + "sha256": "3ba706af4fa8377e8e6be96220a907d01fefc1da20e179fad4486987594e5230", + "size_bytes": 828, + "input_refs": [], + "provenance": {}, + "supersedes": null, + "created_by": "agent", + "recorded_at": "2026-07-11T08:32:54Z" + } + ], + "gates": { + "visual_selection": { + "gate": "visual_selection", + "decision": "approved", + "validity": "current", + "artifact_id": "prototype-r001-466349b650", + "artifact_sha256": "466349b650a8f676d194716d0259b0cb1bc5bb9342f6bae482baaa48a655dacc", + "actor": "user", + "note": "用户确认实现员工端、游客端和大屏端整套视觉稿;员工端作为多端视觉系统主选源,另外两端为同批已确认配套视觉源", + "decided_at": "2026-07-11T08:36:36Z" + } + }, + "blocker": null, + "last_event_id": "evt_cc0e72b7a701402e99a9adb2f0681c07", + "created_at": "2026-07-11T08:26:11Z", + "updated_at": "2026-07-11T08:36:36Z" +} diff --git a/.prototype-run/xiaoqikong-redesign-20260711/assets/xiaoqikong-logo.jpg b/.prototype-run/xiaoqikong-redesign-20260711/assets/xiaoqikong-logo.jpg new file mode 100644 index 0000000..d556065 Binary files /dev/null and b/.prototype-run/xiaoqikong-redesign-20260711/assets/xiaoqikong-logo.jpg differ diff --git a/.prototype-run/xiaoqikong-redesign-20260711/build/v001.json b/.prototype-run/xiaoqikong-redesign-20260711/build/v001.json new file mode 100644 index 0000000..d29f2fd --- /dev/null +++ b/.prototype-run/xiaoqikong-redesign-20260711/build/v001.json @@ -0,0 +1,51 @@ +{ + "schema_version": "1.0", + "implementation_plan_id": "ip_xiaoqikong_redesign_v001", + "revision": 1, + "target_repo": "/Users/inmanw/公司/calllinesystem", + "framework": "React 19 + TypeScript + Vite + existing CSS architecture", + "routes": [ + {"path": "/login", "purpose": "brand-aligned authentication"}, + {"path": "/staff and /staff/tickets", "purpose": "staff mobile call and ticket tasks"}, + {"path": "/status/:token", "purpose": "visitor private mobile queue status"}, + {"path": "/admin/*", "purpose": "desktop operations and management"}, + {"path": "/display/:token", "purpose": "far-distance public queue display"} + ], + "components": [ + {"id": "CMP-BRAND-SHELL", "file": "web/src/components/AppShell.tsx"}, + {"id": "CMP-MOBILE-TASK", "file": "web/src/pages/StaffPage.tsx"}, + {"id": "CMP-VISITOR", "file": "web/src/pages/VisitorPage.tsx"}, + {"id": "CMP-ADMIN-OVERVIEW", "file": "web/src/pages/AdminPage.tsx"}, + {"id": "CMP-PUBLIC-DISPLAY", "file": "web/src/pages/DisplayPage.tsx"}, + {"id": "CMP-THEME", "file": "web/src/styles.css"} + ], + "requirement_mapping": [ + {"requirement_id": "REQ-FLOW-001", "component_ids": ["CMP-MOBILE-TASK", "CMP-THEME"], "test_ids": ["TEST-FRONTEND", "TEST-BROWSER-MOBILE"]}, + {"requirement_id": "REQ-FLOW-002", "component_ids": ["CMP-VISITOR", "CMP-THEME"], "test_ids": ["TEST-FRONTEND", "TEST-BROWSER-MOBILE"]}, + {"requirement_id": "REQ-FLOW-003", "component_ids": ["CMP-ADMIN-OVERVIEW", "CMP-BRAND-SHELL", "CMP-THEME"], "test_ids": ["TEST-FRONTEND", "TEST-BROWSER-DESKTOP"]}, + {"requirement_id": "REQ-FLOW-004", "component_ids": ["CMP-PUBLIC-DISPLAY", "CMP-THEME"], "test_ids": ["TEST-FRONTEND", "TEST-BROWSER-DISPLAY"]}, + {"requirement_id": "REQ-VIS-001", "component_ids": ["CMP-BRAND-SHELL", "CMP-THEME"], "test_ids": ["TEST-BUILD", "TEST-BROWSER-DESKTOP"]}, + {"requirement_id": "REQ-VIS-002", "component_ids": ["CMP-THEME"], "test_ids": ["TEST-BROWSER-DESKTOP", "TEST-BROWSER-DISPLAY"]}, + {"requirement_id": "REQ-VIS-003", "component_ids": ["CMP-MOBILE-TASK", "CMP-ADMIN-OVERVIEW", "CMP-PUBLIC-DISPLAY", "CMP-THEME"], "test_ids": ["TEST-BROWSER-MOBILE", "TEST-BROWSER-DESKTOP", "TEST-BROWSER-DISPLAY"]}, + {"requirement_id": "REQ-STATE-001", "component_ids": ["CMP-MOBILE-TASK", "CMP-VISITOR", "CMP-ADMIN-OVERVIEW", "CMP-PUBLIC-DISPLAY"], "test_ids": ["TEST-FRONTEND"]}, + {"requirement_id": "REQ-A11Y-001", "component_ids": ["CMP-BRAND-SHELL", "CMP-THEME"], "test_ids": ["TEST-BROWSER-MOBILE", "TEST-BROWSER-DESKTOP"]}, + {"requirement_id": "REQ-A11Y-002", "component_ids": ["CMP-THEME"], "test_ids": ["TEST-BROWSER-REFLOW"]}, + {"requirement_id": "REQ-MOTION-001", "component_ids": ["CMP-THEME"], "test_ids": ["TEST-BROWSER-MOBILE", "TEST-BROWSER-DESKTOP"]}, + {"requirement_id": "REQ-TECH-001", "component_ids": ["CMP-THEME"], "test_ids": ["TEST-FRONTEND", "TEST-BUILD"]}, + {"requirement_id": "REQ-TECH-002", "component_ids": ["CMP-MOBILE-TASK", "CMP-VISITOR", "CMP-ADMIN-OVERVIEW", "CMP-PUBLIC-DISPLAY"], "test_ids": ["TEST-FRONTEND"]} + ], + "files_scope": {"allowed": ["web/src", "web/public", "web/package.json", "web/pnpm-lock.yaml", "task_plan.md", "progress.md", "findings.md"], "protected": ["server", "server/migrations", "docs/scenic-queue-system-plan.md"]}, + "asset_plan": [{"asset_id": "AST-LOGO", "source": ".prototype-run/xiaoqikong-redesign-20260711/assets/xiaoqikong-logo.jpg", "destination": "web/public/xiaoqikong-logo.jpg", "strategy": "copy supplied official asset without generative alteration"}], + "state_strategy": {"owner": "existing route components and usePollingResource", "states": ["brand_ready", "mobile_task_states", "admin_overview_ready", "context_status_states", "display_public_states"], "data_fixture": "existing real API responses and deterministic Vitest mocks"}, + "motion_contract": {"motion_contract_id": "mc_xiaoqikong_v001", "revision": 1}, + "accessibility_plan": {"standard": "WCAG 2.2 AA", "checks": ["keyboard", "visible focus", "320px reflow", "48px mobile targets", "reduced motion", "non-color status semantics"]}, + "test_plan": [ + {"id": "TEST-FRONTEND", "type": "Vitest regression suite"}, + {"id": "TEST-BUILD", "type": "TypeScript and Vite production build"}, + {"id": "TEST-BROWSER-MOBILE", "type": "browser QA at 390x844"}, + {"id": "TEST-BROWSER-REFLOW", "type": "browser QA at 320x800"}, + {"id": "TEST-BROWSER-DESKTOP", "type": "browser QA at 1440x1024"}, + {"id": "TEST-BROWSER-DISPLAY", "type": "browser QA at 1920x1080"} + ], + "rollback": {"strategy": "revert only Phase 13 frontend and asset changes; preserve existing backend and all prior user files", "checkpoint": "record repository snapshot and build log before validating"} +} diff --git a/.prototype-run/xiaoqikong-redesign-20260711/design/v001.json b/.prototype-run/xiaoqikong-redesign-20260711/design/v001.json new file mode 100644 index 0000000..20fff39 --- /dev/null +++ b/.prototype-run/xiaoqikong-redesign-20260711/design/v001.json @@ -0,0 +1,57 @@ +{ + "schema_version": "1.0", + "design_contract_id": "dc_xiaoqikong_karst_breath_v001", + "revision": 1, + "source": {"candidate_id": "candidate_stretch_human_scenic", "image_path": "prototypes/candidate_stretch_human_scenic.png", "sha256": "1dfc3cfdbcc8af653430290f8ad55e09c269fc97f2d98d96852270e328a53f77", "width": 1487, "height": 1058}, + "requirements_contract": {"contract_id": "pc_xiaoqikong_queue_redesign", "revision": 1}, + "tokens": { + "color": {"canvas": "#F4F7F3", "surface": "#FFFFFF", "ink": "#10261D", "muted": "#56665F", "primary": "#0B6B3A", "primary_deep": "#064A2A", "primary_soft": "#E7F2EA", "border": "#D5DED8", "warning": "#C96A12", "danger": "#C93232"}, + "type": {"family": "system Chinese sans", "numeric": "tabular-nums", "character": "adult, calm, high-legibility"}, + "space": {"unit_px": 8, "section_px": 24, "page_gutter_px": 20}, + "radius": {"control_px": 8, "panel_px": 14}, + "border": {"default": "1px solid #D5DED8", "selected": "1px solid #0B6B3A"}, + "shadow": {"panel": "0 8px 24px rgba(16,38,29,.06)"}, + "layers": {"header": 20, "sticky_action": 30}, + "motion": {"feedback_ms": 150, "panel_ms": 220} + }, + "components": [ + {"id": "CMP-BRAND-SHELL", "name": "BrandShell", "role": "global navigation and identity", "parent_id": null, "bounds_norm": {"x": 0, "y": 0, "w": 1, "h": 0.08}, "layout": "slim brand bar with real logo, account area and restrained contour texture", "requirement_ids": ["REQ-VIS-001", "REQ-VIS-002", "REQ-A11Y-001"], "evidence_ids": ["EV-HEADER", "EV-CONTOUR"], "confidence": 0.94}, + {"id": "CMP-MOBILE-TASK", "name": "MobileTaskSurface", "role": "staff and visitor mobile tasks", "parent_id": "CMP-BRAND-SHELL", "bounds_norm": {"x": 0.02, "y": 0.1, "w": 0.68, "h": 0.86}, "layout": "single-column mobile task hierarchy derived from the dominant left work surface", "requirement_ids": ["REQ-FLOW-001", "REQ-FLOW-002", "REQ-VIS-003", "REQ-STATE-001", "REQ-A11Y-002"], "evidence_ids": ["EV-MAIN", "EV-ROWS"], "confidence": 0.82}, + {"id": "CMP-ADMIN-OVERVIEW", "name": "AdminOverview", "role": "desktop operations workspace", "parent_id": "CMP-BRAND-SHELL", "bounds_norm": {"x": 0.02, "y": 0.22, "w": 0.68, "h": 0.73}, "layout": "wide primary operations table with compact filters and aligned measures", "requirement_ids": ["REQ-FLOW-003", "REQ-VIS-003", "REQ-STATE-001"], "evidence_ids": ["EV-MAIN", "EV-ROWS"], "confidence": 0.96}, + {"id": "CMP-CONTEXT-RAIL", "name": "ContextRail", "role": "desktop contextual status and exceptions", "parent_id": "CMP-ADMIN-OVERVIEW", "bounds_norm": {"x": 0.71, "y": 0.22, "w": 0.28, "h": 0.73}, "layout": "stacked contextual panels with latest call, exceptions and recent activity", "requirement_ids": ["REQ-FLOW-003", "REQ-STATE-001", "REQ-A11Y-001"], "evidence_ids": ["EV-RAIL"], "confidence": 0.95}, + {"id": "CMP-PUBLIC-DISPLAY", "name": "PublicDisplay", "role": "far-distance queue projection", "parent_id": "CMP-BRAND-SHELL", "bounds_norm": {"x": 0.71, "y": 0.22, "w": 0.28, "h": 0.24}, "layout": "enlarged latest-call focal panel adapted to 16:9", "requirement_ids": ["REQ-FLOW-004", "REQ-VIS-003", "REQ-A11Y-001"], "evidence_ids": ["EV-LATEST"], "confidence": 0.9} + ], + "assets": [ + {"id": "AST-LOGO", "kind": "official_logo", "region_evidence_id": "EV-HEADER", "strategy": "reuse_supplied", "source": "assets/xiaoqikong-logo.jpg", "confidence": 1.0}, + {"id": "AST-CONTOUR", "kind": "decorative_contour_texture", "region_evidence_id": "EV-CONTOUR", "strategy": "code-native CSS radial/linear contour treatment with aria-hidden decoration", "confidence": 0.78}, + {"id": "AST-ICONS", "kind": "ui_icon_family", "region_evidence_id": "EV-ROWS", "strategy": "reuse existing text/symbol-safe UI primitives; no logo-like icons", "confidence": 0.82} + ], + "evidence": [ + {"id": "EV-HEADER", "bbox_norm": {"x": 0, "y": 0, "w": 1, "h": 0.08}, "observation": "A slim white header establishes brand, product name and account context.", "basis": "observed"}, + {"id": "EV-CONTOUR", "bbox_norm": {"x": 0.45, "y": 0, "w": 0.55, "h": 0.2}, "observation": "Very pale topographic contours and a small scenic silhouette add place identity without competing with data.", "basis": "observed"}, + {"id": "EV-MAIN", "bbox_norm": {"x": 0.02, "y": 0.22, "w": 0.68, "h": 0.73}, "observation": "The main workspace dominates with a flat bordered surface rather than a mosaic of cards.", "basis": "measured"}, + {"id": "EV-ROWS", "bbox_norm": {"x": 0.03, "y": 0.32, "w": 0.65, "h": 0.57}, "observation": "Rows align names, waiting counts, batch, state, time and action; selected row uses a green outline.", "basis": "observed"}, + {"id": "EV-RAIL", "bbox_norm": {"x": 0.71, "y": 0.22, "w": 0.28, "h": 0.73}, "observation": "A narrower contextual rail carries status, exceptions, recent operations and actions.", "basis": "measured"}, + {"id": "EV-LATEST", "bbox_norm": {"x": 0.72, "y": 0.23, "w": 0.26, "h": 0.23}, "observation": "Latest call uses the strongest numeric emphasis with progress and redundant state labeling.", "basis": "observed"} + ], + "responsive_rules": [ + {"requirement_id": "REQ-VIS-003", "rule": "Staff and visitor routes remain single-column at 390px and reflow without page-level horizontal scrolling at 320px."}, + {"requirement_id": "REQ-FLOW-003", "rule": "Desktop admin uses asymmetric main-plus-context layout; below 1100px the context rail stacks after the main region."}, + {"requirement_id": "REQ-FLOW-004", "rule": "Public display expands the latest-call focal treatment to fill 16:9 with no private fields."} + ], + "interaction_rules": [ + {"requirement_id": "REQ-FLOW-001", "rule": "Preserve current project switch, ticket creation and call-next behavior and feedback."}, + {"requirement_id": "REQ-FLOW-002", "rule": "Preserve private-token status refresh and phone-last-four privacy."}, + {"requirement_id": "REQ-FLOW-003", "rule": "Preserve current admin routes and forms; visual density changes do not alter permissions or data."}, + {"requirement_id": "REQ-FLOW-004", "rule": "Public display remains read-only and privacy-whitelisted."} + ], + "state_matrix": [ + {"id": "STATE-BRAND-DEFAULT", "component_id": "CMP-BRAND-SHELL", "requirement_ids": ["REQ-VIS-001"], "state": "authenticated or public brand shell visible", "implementation_state": "brand_ready", "trigger": "route renders", "expected": "official logo and product identity are visible without image distortion", "focus": "skip link is first keyboard focus where authentication applies"}, + {"id": "STATE-MOBILE-TASK", "component_id": "CMP-MOBILE-TASK", "requirement_ids": ["REQ-FLOW-001", "REQ-FLOW-002", "REQ-STATE-001"], "state": "default, loading, empty, error, success, offline and disabled mobile states", "implementation_state": "mobile_task_states", "trigger": "data load or user action", "expected": "staff and visitor outcomes remain clear with recovery actions", "focus": "logical DOM order; validation focus and visible focus ring preserved"}, + {"id": "STATE-ADMIN-READY", "component_id": "CMP-ADMIN-OVERVIEW", "requirement_ids": ["REQ-FLOW-003", "REQ-STATE-001"], "state": "overview ready with route navigation and data", "implementation_state": "admin_overview_ready", "trigger": "administrator opens a route", "expected": "existing controls and data remain readable in the new hierarchy", "focus": "navigation precedes page controls and selected route is announced"}, + {"id": "STATE-RAIL-EXCEPTION", "component_id": "CMP-CONTEXT-RAIL", "requirement_ids": ["REQ-FLOW-003", "REQ-STATE-001"], "state": "normal, warning and error context", "implementation_state": "context_status_states", "trigger": "queue or device state changes", "expected": "status uses label plus color and retains recovery path", "focus": "interactive actions remain native buttons or links"}, + {"id": "STATE-DISPLAY-LIVE", "component_id": "CMP-PUBLIC-DISPLAY", "requirement_ids": ["REQ-FLOW-004", "REQ-STATE-001"], "state": "live, empty, delayed and offline public projection", "implementation_state": "display_public_states", "trigger": "polling or stream update", "expected": "latest public tickets and freshness remain legible at distance with no PII", "focus": "read-only surface has no artificial tab stops"} + ], + "ambiguities": [{"id": "AMB-CONTOUR", "question": "Exact contour motif is illustrative rather than a supplied brand asset.", "impact": "visual_fidelity", "blocking": false, "resolution": "use a subtle code-native decorative treatment and verify against the selected image"}], + "implementation_ready": true +} diff --git a/.prototype-run/xiaoqikong-redesign-20260711/events.jsonl b/.prototype-run/xiaoqikong-redesign-20260711/events.jsonl new file mode 100644 index 0000000..a981da4 --- /dev/null +++ b/.prototype-run/xiaoqikong-redesign-20260711/events.jsonl @@ -0,0 +1,44 @@ +{"event_id": "evt_0af614dc23174af58dd46c6b5d9a8498", "run_id": "b2p_d52228eb0be3", "at": "2026-07-11T06:58:22Z", "operation": "init", "actor": "agent", "new_state": "created", "mode": "full", "run_metadata": {"schema_version": "1.0", "run_id": "b2p_d52228eb0be3", "mode": "full", "surface": "responsive_web", "target_repo": "/Users/inmanw/公司/calllinesystem", "created_at": "2026-07-11T06:58:22Z"}} +{"event_id": "evt_75ee6ea9b1884052813f07e232485590", "run_id": "b2p_d52228eb0be3", "at": "2026-07-11T06:59:41Z", "operation": "record_artifact", "actor": "agent", "artifact_type": "requirements", "artifact_id": "requirements-r001-4e447ed7e6", "revision": 1, "output_hash": "4e447ed7e66ccb462f238d1b30c96448923e7ad46495e735735c888ec011563a", "status": "approved", "supersedes": null, "invalidated_artifacts": [], "stale_gates": [], "artifact": {"artifact_id": "requirements-r001-4e447ed7e6", "kind": "requirements", "revision": 1, "status": "approved", "validity": "current", "path": "/Users/inmanw/公司/calllinesystem/.prototype-run/xiaoqikong-redesign-20260711/requirements/v001.json", "sha256": "4e447ed7e66ccb462f238d1b30c96448923e7ad46495e735735c888ec011563a", "size_bytes": 7442, "input_refs": [], "provenance": {}, "supersedes": null, "created_by": "agent", "recorded_at": "2026-07-11T06:59:41Z"}} +{"event_id": "evt_223470dffff545698550f78ec8f158a5", "run_id": "b2p_d52228eb0be3", "at": "2026-07-11T06:59:41Z", "operation": "transition", "actor": "agent", "prior_state": "created", "new_state": "requirements_ready", "note": "Product contract is buildable", "stale_gates": [], "stale_artifacts": []} +{"event_id": "evt_89fd11b13ca24395a11f301e4d476f30", "run_id": "b2p_d52228eb0be3", "at": "2026-07-11T07:00:33Z", "operation": "record_artifact", "actor": "agent", "artifact_type": "prompt_bundle", "artifact_id": "prompt_bundle-r001-b38546ec96", "revision": 1, "output_hash": "b38546ec968c1da41b7b10207dc2cf7eba3edcb2b033cdb9d6902a6e75aa9b9d", "status": "ready", "supersedes": null, "invalidated_artifacts": [], "stale_gates": [], "artifact": {"artifact_id": "prompt_bundle-r001-b38546ec96", "kind": "prompt_bundle", "revision": 1, "status": "ready", "validity": "current", "path": "/Users/inmanw/公司/calllinesystem/.prototype-run/xiaoqikong-redesign-20260711/prompts/v001.json", "sha256": "b38546ec968c1da41b7b10207dc2cf7eba3edcb2b033cdb9d6902a6e75aa9b9d", "size_bytes": 8648, "input_refs": [], "provenance": {}, "supersedes": null, "created_by": "agent", "recorded_at": "2026-07-11T07:00:33Z"}} +{"event_id": "evt_1d56d4b3d1de48fba1eb654ce9d59fca", "run_id": "b2p_d52228eb0be3", "at": "2026-07-11T07:00:33Z", "operation": "transition", "actor": "agent", "prior_state": "requirements_ready", "new_state": "prompt_ready", "note": "", "stale_gates": [], "stale_artifacts": []} +{"event_id": "evt_9946519d40774d69999887855d694ea0", "run_id": "b2p_d52228eb0be3", "at": "2026-07-11T07:17:01Z", "operation": "record_artifact", "actor": "agent", "artifact_type": "prototype", "artifact_id": "prototype-r001-ff335eab92", "revision": 1, "output_hash": "ff335eab927061b4ad2ce415a2ce74d8dd65fcb77654b57685e611a3c4e8eb67", "status": "candidate", "supersedes": null, "invalidated_artifacts": [], "stale_gates": [], "artifact": {"artifact_id": "prototype-r001-ff335eab92", "kind": "prototype", "revision": 1, "status": "candidate", "validity": "current", "path": "/Users/inmanw/公司/calllinesystem/.prototype-run/xiaoqikong-redesign-20260711/prototypes/candidate_anchor_landscape_service.png", "sha256": "ff335eab927061b4ad2ce415a2ce74d8dd65fcb77654b57685e611a3c4e8eb67", "size_bytes": 1203540, "input_refs": [], "provenance": {"tool": "built-in-imagegen", "candidate_id": "candidate_anchor_landscape_service", "receipt": {"provider": "built-in-imagegen", "source": "codex_tool_result", "result_id": "exec-aef497a3-a36d-4f2f-a901-fa06a2c88826", "generated_at": "2026-07-11T07:16:45Z", "prompt_bundle_id": "pb_xiaoqikong_redesign_v001", "candidate_id": "candidate_anchor_landscape_service", "output_sha256": "ff335eab927061b4ad2ce415a2ce74d8dd65fcb77654b57685e611a3c4e8eb67"}, "viewport_deviation": {"target": {"width": 1440, "height": 1024}, "actual": {"width": 1487, "height": 1058}, "handling": "auto_accept", "rationale": "Backend-controlled output differs by less than 2% in aspect ratio and exceeds 75% of both target axes."}}, "supersedes": null, "created_by": "agent", "recorded_at": "2026-07-11T07:17:01Z"}} +{"event_id": "evt_19fa99fb1f93409da8b73406bf33adee", "run_id": "b2p_d52228eb0be3", "at": "2026-07-11T07:17:01Z", "operation": "record_artifact", "actor": "agent", "artifact_type": "prototype", "artifact_id": "prototype-r002-bbf4f3c63e", "revision": 2, "output_hash": "bbf4f3c63e5b5f2465ad182d2de397f1f24019f819eb6072245c1aeb377faf80", "status": "candidate", "supersedes": null, "invalidated_artifacts": [], "stale_gates": [], "artifact": {"artifact_id": "prototype-r002-bbf4f3c63e", "kind": "prototype", "revision": 2, "status": "candidate", "validity": "current", "path": "/Users/inmanw/公司/calllinesystem/.prototype-run/xiaoqikong-redesign-20260711/prototypes/candidate_adjacent_operational_ribbon.png", "sha256": "bbf4f3c63e5b5f2465ad182d2de397f1f24019f819eb6072245c1aeb377faf80", "size_bytes": 1225012, "input_refs": [], "provenance": {"tool": "built-in-imagegen", "candidate_id": "candidate_adjacent_operational_ribbon", "receipt": {"provider": "built-in-imagegen", "source": "codex_tool_result", "result_id": "exec-42b37576-9ed2-4424-b4d4-4b1e86694bcf", "generated_at": "2026-07-11T07:16:45Z", "prompt_bundle_id": "pb_xiaoqikong_redesign_v001", "candidate_id": "candidate_adjacent_operational_ribbon", "output_sha256": "bbf4f3c63e5b5f2465ad182d2de397f1f24019f819eb6072245c1aeb377faf80"}, "viewport_deviation": {"target": {"width": 1440, "height": 1024}, "actual": {"width": 1487, "height": 1058}, "handling": "auto_accept", "rationale": "Backend-controlled output differs by less than 2% in aspect ratio and exceeds 75% of both target axes."}}, "supersedes": null, "created_by": "agent", "recorded_at": "2026-07-11T07:17:01Z"}} +{"event_id": "evt_68ca9428f50f4bbeb68a7f514e0c6a06", "run_id": "b2p_d52228eb0be3", "at": "2026-07-11T07:17:01Z", "operation": "record_artifact", "actor": "agent", "artifact_type": "prototype", "artifact_id": "prototype-r003-1dfc3cfdbc", "revision": 3, "output_hash": "1dfc3cfdbcc8af653430290f8ad55e09c269fc97f2d98d96852270e328a53f77", "status": "candidate", "supersedes": null, "invalidated_artifacts": [], "stale_gates": [], "artifact": {"artifact_id": "prototype-r003-1dfc3cfdbc", "kind": "prototype", "revision": 3, "status": "candidate", "validity": "current", "path": "/Users/inmanw/公司/calllinesystem/.prototype-run/xiaoqikong-redesign-20260711/prototypes/candidate_stretch_human_scenic.png", "sha256": "1dfc3cfdbcc8af653430290f8ad55e09c269fc97f2d98d96852270e328a53f77", "size_bytes": 1240326, "input_refs": [], "provenance": {"tool": "built-in-imagegen", "candidate_id": "candidate_stretch_human_scenic", "receipt": {"provider": "built-in-imagegen", "source": "codex_tool_result", "result_id": "exec-e89023bf-14e5-413f-8e56-2fb119632b1c", "generated_at": "2026-07-11T07:16:45Z", "prompt_bundle_id": "pb_xiaoqikong_redesign_v001", "candidate_id": "candidate_stretch_human_scenic", "output_sha256": "1dfc3cfdbcc8af653430290f8ad55e09c269fc97f2d98d96852270e328a53f77"}, "viewport_deviation": {"target": {"width": 1440, "height": 1024}, "actual": {"width": 1487, "height": 1058}, "handling": "auto_accept", "rationale": "Backend-controlled output differs by less than 2% in aspect ratio and exceeds 75% of both target axes."}}, "supersedes": null, "created_by": "agent", "recorded_at": "2026-07-11T07:17:01Z"}} +{"event_id": "evt_1e314fba936b44418a13b3196e52e287", "run_id": "b2p_d52228eb0be3", "at": "2026-07-11T07:18:20Z", "operation": "record_artifact", "actor": "agent", "artifact_type": "prototype_manifest", "artifact_id": "prototype_manifest-r001-c8a57db51a", "revision": 1, "output_hash": "c8a57db51a563a49c7b5a877846ab6b91bdcc2c97c4180f741d68bfd71c934ae", "status": "recorded", "supersedes": null, "invalidated_artifacts": [], "stale_gates": [], "artifact": {"artifact_id": "prototype_manifest-r001-c8a57db51a", "kind": "prototype_manifest", "revision": 1, "status": "recorded", "validity": "current", "path": "/Users/inmanw/公司/calllinesystem/.prototype-run/xiaoqikong-redesign-20260711/prototypes/manifest.json", "sha256": "c8a57db51a563a49c7b5a877846ab6b91bdcc2c97c4180f741d68bfd71c934ae", "size_bytes": 922, "input_refs": [], "provenance": {}, "supersedes": null, "created_by": "agent", "recorded_at": "2026-07-11T07:18:20Z"}} +{"event_id": "evt_cd60a608d6c24349b73de32d8eb7d98b", "run_id": "b2p_d52228eb0be3", "at": "2026-07-11T07:18:20Z", "operation": "transition", "actor": "agent", "prior_state": "prompt_ready", "new_state": "prototypes_ready", "note": "", "stale_gates": [], "stale_artifacts": []} +{"event_id": "evt_c2068e2e08384dc7ad3efd3fe20a5481", "run_id": "b2p_d52228eb0be3", "at": "2026-07-11T07:19:30Z", "operation": "gate_decision", "actor": "user", "gate": "visual_selection", "decision": "approved", "artifact_id": "prototype-r003-1dfc3cfdbc", "artifact_hash": "1dfc3cfdbcc8af653430290f8ad55e09c269fc97f2d98d96852270e328a53f77", "note": "Selected visible option 3", "invalidated_artifacts": [], "stale_gates": [], "gate_record": {"gate": "visual_selection", "decision": "approved", "validity": "current", "artifact_id": "prototype-r003-1dfc3cfdbc", "artifact_sha256": "1dfc3cfdbcc8af653430290f8ad55e09c269fc97f2d98d96852270e328a53f77", "actor": "user", "note": "Selected visible option 3", "decided_at": "2026-07-11T07:19:30Z"}} +{"event_id": "evt_c7c40cba55034a1f961e75d4ca0ed88b", "run_id": "b2p_d52228eb0be3", "at": "2026-07-11T07:19:30Z", "operation": "transition", "actor": "agent", "prior_state": "prototypes_ready", "new_state": "visual_selected", "note": "", "stale_gates": [], "stale_artifacts": []} +{"event_id": "evt_76131126631b438bb839d810b505e1d5", "run_id": "b2p_d52228eb0be3", "at": "2026-07-11T07:21:10Z", "operation": "record_artifact", "actor": "agent", "artifact_type": "design_contract", "artifact_id": "design_contract-r001-777f1d7b0c", "revision": 1, "output_hash": "777f1d7b0ccc3c3b9fa81a766df215cca802564a60ac3fec162f7061a65e2e83", "status": "approved", "supersedes": null, "invalidated_artifacts": [], "stale_gates": [], "artifact": {"artifact_id": "design_contract-r001-777f1d7b0c", "kind": "design_contract", "revision": 1, "status": "approved", "validity": "current", "path": "/Users/inmanw/公司/calllinesystem/.prototype-run/xiaoqikong-redesign-20260711/design/v001.json", "sha256": "777f1d7b0ccc3c3b9fa81a766df215cca802564a60ac3fec162f7061a65e2e83", "size_bytes": 8582, "input_refs": [], "provenance": {}, "supersedes": null, "created_by": "agent", "recorded_at": "2026-07-11T07:21:10Z"}} +{"event_id": "evt_dab914a38678457e83267ee17f69e03f", "run_id": "b2p_d52228eb0be3", "at": "2026-07-11T07:21:10Z", "operation": "record_artifact", "actor": "agent", "artifact_type": "motion_contract", "artifact_id": "motion_contract-r001-f4de7e4856", "revision": 1, "output_hash": "f4de7e48565b888b51a4e5e010ae712cb91fae42f73f24eebaaa26785347ca83", "status": "approved", "supersedes": null, "invalidated_artifacts": [], "stale_gates": [], "artifact": {"artifact_id": "motion_contract-r001-f4de7e4856", "kind": "motion_contract", "revision": 1, "status": "approved", "validity": "current", "path": "/Users/inmanw/公司/calllinesystem/.prototype-run/xiaoqikong-redesign-20260711/motion/v001.json", "sha256": "f4de7e48565b888b51a4e5e010ae712cb91fae42f73f24eebaaa26785347ca83", "size_bytes": 1491, "input_refs": [], "provenance": {}, "supersedes": null, "created_by": "agent", "recorded_at": "2026-07-11T07:21:10Z"}} +{"event_id": "evt_6a351673f5664c88ab2d8fdf97dc91d3", "run_id": "b2p_d52228eb0be3", "at": "2026-07-11T07:21:10Z", "operation": "record_artifact", "actor": "agent", "artifact_type": "implementation_plan", "artifact_id": "implementation_plan-r001-f339959eba", "revision": 1, "output_hash": "f339959eba509deece28d3dc2fcc068bae4ee3d56b96e5103ddc8ec1de7478b3", "status": "approved", "supersedes": null, "invalidated_artifacts": [], "stale_gates": [], "artifact": {"artifact_id": "implementation_plan-r001-f339959eba", "kind": "implementation_plan", "revision": 1, "status": "approved", "validity": "current", "path": "/Users/inmanw/公司/calllinesystem/.prototype-run/xiaoqikong-redesign-20260711/build/v001.json", "sha256": "f339959eba509deece28d3dc2fcc068bae4ee3d56b96e5103ddc8ec1de7478b3", "size_bytes": 4735, "input_refs": [], "provenance": {}, "supersedes": null, "created_by": "agent", "recorded_at": "2026-07-11T07:21:10Z"}} +{"event_id": "evt_50931f7267e6489b8fc2ee895cc695b1", "run_id": "b2p_d52228eb0be3", "at": "2026-07-11T07:21:10Z", "operation": "transition", "actor": "agent", "prior_state": "visual_selected", "new_state": "contracts_ready", "note": "", "stale_gates": [], "stale_artifacts": []} +{"event_id": "evt_ac5a3206b61c47c5a996c9d3387d8213", "run_id": "b2p_d52228eb0be3", "at": "2026-07-11T07:22:26Z", "operation": "transition", "actor": "agent", "prior_state": "contracts_ready", "new_state": "implementing", "note": "", "stale_gates": [], "stale_artifacts": []} +{"event_id": "evt_a92e7c7fa8024f83b24d9b6655444b98", "run_id": "b2p_d52228eb0be3", "at": "2026-07-11T07:27:50Z", "operation": "record_artifact", "actor": "agent", "artifact_type": "other", "artifact_id": "other-r001-975ef65fd2", "revision": 1, "output_hash": "975ef65fd25f63f009dc87e7a485088a6b54f7e9833e7eddf0679b9e1022931a", "status": "verified", "supersedes": null, "invalidated_artifacts": [], "stale_gates": [], "artifact": {"artifact_id": "other-r001-975ef65fd2", "kind": "other", "revision": 1, "status": "verified", "validity": "current", "path": "/Users/inmanw/公司/calllinesystem/.prototype-run/xiaoqikong-redesign-20260711/qa/requirements.md", "sha256": "975ef65fd25f63f009dc87e7a485088a6b54f7e9833e7eddf0679b9e1022931a", "size_bytes": 968, "input_refs": [], "provenance": {}, "supersedes": null, "created_by": "agent", "recorded_at": "2026-07-11T07:27:50Z"}} +{"event_id": "evt_eb815275ef25484c85065886a1e8a59b", "run_id": "b2p_d52228eb0be3", "at": "2026-07-11T07:27:50Z", "operation": "record_artifact", "actor": "agent", "artifact_type": "other", "artifact_id": "other-r002-63362215de", "revision": 2, "output_hash": "63362215de76c67af8911aefa720d645be04a6e9d63587cce3d7acb8828da01d", "status": "verified", "supersedes": null, "invalidated_artifacts": [], "stale_gates": [], "artifact": {"artifact_id": "other-r002-63362215de", "kind": "other", "revision": 2, "status": "verified", "validity": "current", "path": "/Users/inmanw/公司/calllinesystem/.prototype-run/xiaoqikong-redesign-20260711/qa/reflow-320.md", "sha256": "63362215de76c67af8911aefa720d645be04a6e9d63587cce3d7acb8828da01d", "size_bytes": 270, "input_refs": [], "provenance": {}, "supersedes": null, "created_by": "agent", "recorded_at": "2026-07-11T07:27:50Z"}} +{"event_id": "evt_ff334a95f2504734b0f2ab9afc16a4a0", "run_id": "b2p_d52228eb0be3", "at": "2026-07-11T07:28:12Z", "operation": "record_artifact", "actor": "agent", "artifact_type": "implementation_checkpoint", "artifact_id": "implementation_checkpoint-r001-d425b22485", "revision": 1, "output_hash": "d425b2248596a19ed8e88c53c1b4c2bd515dc4e6e266a8b19113a5f216a10b49", "status": "verified", "supersedes": null, "invalidated_artifacts": [], "stale_gates": [], "artifact": {"artifact_id": "implementation_checkpoint-r001-d425b22485", "kind": "implementation_checkpoint", "revision": 1, "status": "verified", "validity": "current", "path": "/Users/inmanw/公司/calllinesystem/.prototype-run/xiaoqikong-redesign-20260711/qa/implementation-checkpoint.json", "sha256": "d425b2248596a19ed8e88c53c1b4c2bd515dc4e6e266a8b19113a5f216a10b49", "size_bytes": 226, "input_refs": [], "provenance": {"implementation_revision": 1, "repo_snapshot": "cbfcff0a7316cc49a5f96320c827888f04ff981b2bcc325fde4fd0cd8ec1e059", "repo_snapshot_algorithm": "product-prototype-design-repo-snapshot-v2-full-repository", "repo_snapshot_coverage": "full_target_repo", "repo_snapshot_file_count": 94, "repo_snapshot_scope": ["docs/scenic-queue-system-plan.md", "findings.md", "progress.md", "server", "server/migrations", "task_plan.md", "web/package.json", "web/pnpm-lock.yaml", "web/public", "web/src"]}, "supersedes": null, "created_by": "agent", "recorded_at": "2026-07-11T07:28:12Z"}} +{"event_id": "evt_94d1a043d5a74307ac77d407dbb8bab0", "run_id": "b2p_d52228eb0be3", "at": "2026-07-11T07:28:12Z", "operation": "transition", "actor": "agent", "prior_state": "implementing", "new_state": "validating", "note": "", "stale_gates": [], "stale_artifacts": []} +{"event_id": "evt_ec658f66a9c14832b584653c5f7a403f", "run_id": "b2p_d52228eb0be3", "at": "2026-07-11T07:28:31Z", "operation": "record_artifact", "actor": "agent", "artifact_type": "screenshot", "artifact_id": "screenshot-r001-813ab8a63b", "revision": 1, "output_hash": "813ab8a63ba8e76fa13c43610533b31cdb27f6ea75625ba0387c9e2aa086a80b", "status": "verified", "supersedes": null, "invalidated_artifacts": [], "stale_gates": [], "artifact": {"artifact_id": "screenshot-r001-813ab8a63b", "kind": "screenshot", "revision": 1, "status": "verified", "validity": "current", "path": "/Users/inmanw/公司/calllinesystem/.prototype-run/xiaoqikong-redesign-20260711/qa/screenshots/admin-selected-1487x1058.png", "sha256": "813ab8a63ba8e76fa13c43610533b31cdb27f6ea75625ba0387c9e2aa086a80b", "size_bytes": 59896, "input_refs": [{"artifact_id": "implementation_checkpoint-r001-d425b22485", "sha256": "d425b2248596a19ed8e88c53c1b4c2bd515dc4e6e266a8b19113a5f216a10b49"}], "provenance": {"capture_kind": "browser_render", "tool": "browser", "receipt": {"provider": "browser", "source": "codex_tool_result", "capture_id": "browser-admin-selected-final", "captured_at": "2026-07-11T07:28:00Z", "url": "http://127.0.0.1:5173/admin"}, "viewport": {"width": 1487, "height": 1058}, "route": "/admin", "theme": "light", "data_fixture": "real-local-seed", "state": "operations-overview"}, "supersedes": null, "created_by": "agent", "recorded_at": "2026-07-11T07:28:31Z"}} +{"event_id": "evt_74d3bacda473430aa6ccf9d3a4e7dbe0", "run_id": "b2p_d52228eb0be3", "at": "2026-07-11T07:28:31Z", "operation": "record_artifact", "actor": "agent", "artifact_type": "screenshot", "artifact_id": "screenshot-r002-710c60471f", "revision": 2, "output_hash": "710c60471fa5758078e02186c1c0b045481e9d1010f2cf4a328e6cae4fabf2fe", "status": "verified", "supersedes": null, "invalidated_artifacts": [], "stale_gates": [], "artifact": {"artifact_id": "screenshot-r002-710c60471f", "kind": "screenshot", "revision": 2, "status": "verified", "validity": "current", "path": "/Users/inmanw/公司/calllinesystem/.prototype-run/xiaoqikong-redesign-20260711/qa/screenshots/admin-1440x1024.png", "sha256": "710c60471fa5758078e02186c1c0b045481e9d1010f2cf4a328e6cae4fabf2fe", "size_bytes": 33860, "input_refs": [{"artifact_id": "implementation_checkpoint-r001-d425b22485", "sha256": "d425b2248596a19ed8e88c53c1b4c2bd515dc4e6e266a8b19113a5f216a10b49"}], "provenance": {"capture_kind": "browser_render", "tool": "browser", "receipt": {"provider": "browser", "source": "codex_tool_result", "capture_id": "browser-admin-1440-final", "captured_at": "2026-07-11T07:28:00Z", "url": "http://127.0.0.1:5173/admin"}, "viewport": {"width": 1440, "height": 1024}, "route": "/admin", "theme": "light", "data_fixture": "real-local-seed", "state": "operations-overview"}, "supersedes": null, "created_by": "agent", "recorded_at": "2026-07-11T07:28:31Z"}} +{"event_id": "evt_5485db43e7b24b35806b6050cffd37c8", "run_id": "b2p_d52228eb0be3", "at": "2026-07-11T07:28:31Z", "operation": "record_artifact", "actor": "agent", "artifact_type": "screenshot", "artifact_id": "screenshot-r003-5b1658ece7", "revision": 3, "output_hash": "5b1658ece7eba83ce99a462566298df5f6dc557ac11df4dbdd8228454980fb0c", "status": "verified", "supersedes": null, "invalidated_artifacts": [], "stale_gates": [], "artifact": {"artifact_id": "screenshot-r003-5b1658ece7", "kind": "screenshot", "revision": 3, "status": "verified", "validity": "current", "path": "/Users/inmanw/公司/calllinesystem/.prototype-run/xiaoqikong-redesign-20260711/qa/screenshots/staff-390x844.png", "sha256": "5b1658ece7eba83ce99a462566298df5f6dc557ac11df4dbdd8228454980fb0c", "size_bytes": 2273, "input_refs": [{"artifact_id": "implementation_checkpoint-r001-d425b22485", "sha256": "d425b2248596a19ed8e88c53c1b4c2bd515dc4e6e266a8b19113a5f216a10b49"}], "provenance": {"capture_kind": "browser_render", "tool": "browser", "receipt": {"provider": "browser", "source": "codex_tool_result", "capture_id": "browser-staff-390-final", "captured_at": "2026-07-11T07:28:00Z", "url": "http://127.0.0.1:5173/staff"}, "viewport": {"width": 390, "height": 844}, "route": "/staff", "theme": "light", "data_fixture": "real-local-seed", "state": "call-task"}, "supersedes": null, "created_by": "agent", "recorded_at": "2026-07-11T07:28:31Z"}} +{"event_id": "evt_eba04f8367bf43408fb1f5f50bba7ffb", "run_id": "b2p_d52228eb0be3", "at": "2026-07-11T07:28:31Z", "operation": "record_artifact", "actor": "agent", "artifact_type": "screenshot", "artifact_id": "screenshot-r004-85cfc9df4b", "revision": 4, "output_hash": "85cfc9df4b3b3ba05ff14bdaf53917f40c913b98dfd45e07f915f6f19c8f9a07", "status": "verified", "supersedes": null, "invalidated_artifacts": [], "stale_gates": [], "artifact": {"artifact_id": "screenshot-r004-85cfc9df4b", "kind": "screenshot", "revision": 4, "status": "verified", "validity": "current", "path": "/Users/inmanw/公司/calllinesystem/.prototype-run/xiaoqikong-redesign-20260711/qa/screenshots/staff-320x800.png", "sha256": "85cfc9df4b3b3ba05ff14bdaf53917f40c913b98dfd45e07f915f6f19c8f9a07", "size_bytes": 1785, "input_refs": [{"artifact_id": "implementation_checkpoint-r001-d425b22485", "sha256": "d425b2248596a19ed8e88c53c1b4c2bd515dc4e6e266a8b19113a5f216a10b49"}], "provenance": {"capture_kind": "browser_render", "tool": "browser", "receipt": {"provider": "browser", "source": "codex_tool_result", "capture_id": "browser-staff-320-final", "captured_at": "2026-07-11T07:28:00Z", "url": "http://127.0.0.1:5173/staff"}, "viewport": {"width": 320, "height": 800}, "route": "/staff", "theme": "light", "data_fixture": "real-local-seed", "state": "call-task-reflow"}, "supersedes": null, "created_by": "agent", "recorded_at": "2026-07-11T07:28:31Z"}} +{"event_id": "evt_a36bcd039108409dbfb7b6cb95238214", "run_id": "b2p_d52228eb0be3", "at": "2026-07-11T07:28:31Z", "operation": "record_artifact", "actor": "agent", "artifact_type": "screenshot", "artifact_id": "screenshot-r005-afc5ee8fc3", "revision": 5, "output_hash": "afc5ee8fc349846ea431256f3cb33f1c3b50e7079b65c04f9788bb5b579d49ac", "status": "verified", "supersedes": null, "invalidated_artifacts": [], "stale_gates": [], "artifact": {"artifact_id": "screenshot-r005-afc5ee8fc3", "kind": "screenshot", "revision": 5, "status": "verified", "validity": "current", "path": "/Users/inmanw/公司/calllinesystem/.prototype-run/xiaoqikong-redesign-20260711/qa/screenshots/display-1920x1080.png", "sha256": "afc5ee8fc349846ea431256f3cb33f1c3b50e7079b65c04f9788bb5b579d49ac", "size_bytes": 39921, "input_refs": [{"artifact_id": "implementation_checkpoint-r001-d425b22485", "sha256": "d425b2248596a19ed8e88c53c1b4c2bd515dc4e6e266a8b19113a5f216a10b49"}], "provenance": {"capture_kind": "browser_render", "tool": "browser", "receipt": {"provider": "browser", "source": "codex_tool_result", "capture_id": "browser-display-1920-final", "captured_at": "2026-07-11T07:28:00Z", "url": "http://127.0.0.1:5173/admin/display"}, "viewport": {"width": 1920, "height": 1080}, "route": "/admin/display", "theme": "light", "data_fixture": "real-local-seed", "state": "display-center"}, "supersedes": null, "created_by": "agent", "recorded_at": "2026-07-11T07:28:31Z"}} +{"event_id": "evt_e8ce70ba438141ee87f1bea6a4a22d82", "run_id": "b2p_d52228eb0be3", "at": "2026-07-11T07:28:39Z", "operation": "record_artifact", "actor": "agent", "artifact_type": "design_qa", "artifact_id": "design_qa-r001-5e4b8d4761", "revision": 1, "output_hash": "5e4b8d476161dd828ff1b17834e4b14c0e2be4f3db602dbc60e2884fc9e1cd8c", "status": "verified", "supersedes": null, "invalidated_artifacts": [], "stale_gates": [], "artifact": {"artifact_id": "design_qa-r001-5e4b8d4761", "kind": "design_qa", "revision": 1, "status": "verified", "validity": "current", "path": "/Users/inmanw/公司/calllinesystem/.prototype-run/xiaoqikong-redesign-20260711/qa/design-qa.md", "sha256": "5e4b8d476161dd828ff1b17834e4b14c0e2be4f3db602dbc60e2884fc9e1cd8c", "size_bytes": 388, "input_refs": [], "provenance": {}, "supersedes": null, "created_by": "agent", "recorded_at": "2026-07-11T07:28:39Z"}} +{"event_id": "evt_4da476bfcbb449b99e05420da62dc6f0", "run_id": "b2p_d52228eb0be3", "at": "2026-07-11T07:28:39Z", "operation": "record_artifact", "actor": "agent", "artifact_type": "qa_report", "artifact_id": "qa_report-r001-9ee33ad45c", "revision": 1, "output_hash": "9ee33ad45c2e53b05e652072a33586d9cbab6596a0160f0fdc10d3bbedcdcf3e", "status": "recorded", "supersedes": null, "invalidated_artifacts": [], "stale_gates": [], "artifact": {"artifact_id": "qa_report-r001-9ee33ad45c", "kind": "qa_report", "revision": 1, "status": "recorded", "validity": "current", "path": "/Users/inmanw/公司/calllinesystem/.prototype-run/xiaoqikong-redesign-20260711/qa/v001/report.json", "sha256": "9ee33ad45c2e53b05e652072a33586d9cbab6596a0160f0fdc10d3bbedcdcf3e", "size_bytes": 3652, "input_refs": [], "provenance": {}, "supersedes": null, "created_by": "agent", "recorded_at": "2026-07-11T07:28:39Z"}} +{"event_id": "evt_4f78817935814cc9a0d51e0e40e42959", "run_id": "b2p_d52228eb0be3", "at": "2026-07-11T07:30:11Z", "operation": "record_artifact", "actor": "agent", "artifact_type": "screenshot", "artifact_id": "screenshot-r006-c2b7a2f107", "revision": 6, "output_hash": "c2b7a2f107fd8014ca1d38f531288468fea5b1be6b9a2e1705fcf3bb1e4e96bf", "status": "verified", "supersedes": "screenshot-r001-813ab8a63b", "invalidated_artifacts": ["design_qa-r001-5e4b8d4761", "qa_report-r001-9ee33ad45c"], "stale_gates": [], "artifact": {"artifact_id": "screenshot-r006-c2b7a2f107", "kind": "screenshot", "revision": 6, "status": "verified", "validity": "current", "path": "/Users/inmanw/公司/calllinesystem/.prototype-run/xiaoqikong-redesign-20260711/qa/screenshots/admin-selected-1487x1058.png", "sha256": "c2b7a2f107fd8014ca1d38f531288468fea5b1be6b9a2e1705fcf3bb1e4e96bf", "size_bytes": 180010, "input_refs": [{"artifact_id": "implementation_checkpoint-r001-d425b22485", "sha256": "d425b2248596a19ed8e88c53c1b4c2bd515dc4e6e266a8b19113a5f216a10b49"}], "provenance": {"capture_kind": "browser_render", "tool": "browser", "receipt": {"provider": "browser", "source": "codex_tool_result", "capture_id": "browser-admin-selected-final", "captured_at": "2026-07-11T07:28:00Z", "url": "http://127.0.0.1:5173/admin"}, "viewport": {"width": 1487, "height": 1058}, "route": "/admin", "theme": "light", "data_fixture": "real-local-seed", "state": "operations-overview", "encoding_normalization": {"source": "browser JPEG byte stream", "target": "non-interlaced PNG", "resize": false, "crop": false}}, "supersedes": "screenshot-r001-813ab8a63b", "created_by": "agent", "recorded_at": "2026-07-11T07:30:11Z"}} +{"event_id": "evt_6deb6791a624475d98833f27c2a87016", "run_id": "b2p_d52228eb0be3", "at": "2026-07-11T07:30:27Z", "operation": "record_artifact", "actor": "agent", "artifact_type": "screenshot", "artifact_id": "screenshot-r007-934c4b814a", "revision": 7, "output_hash": "934c4b814a88e7c9fac68af12fb818ddc8e8a1d5f0ff084ff1591c50b8b351bb", "status": "verified", "supersedes": "screenshot-r002-710c60471f", "invalidated_artifacts": [], "stale_gates": [], "artifact": {"artifact_id": "screenshot-r007-934c4b814a", "kind": "screenshot", "revision": 7, "status": "verified", "validity": "current", "path": "/Users/inmanw/公司/calllinesystem/.prototype-run/xiaoqikong-redesign-20260711/qa/screenshots/admin-1440x1024.png", "sha256": "934c4b814a88e7c9fac68af12fb818ddc8e8a1d5f0ff084ff1591c50b8b351bb", "size_bytes": 137054, "input_refs": [{"artifact_id": "implementation_checkpoint-r001-d425b22485", "sha256": "d425b2248596a19ed8e88c53c1b4c2bd515dc4e6e266a8b19113a5f216a10b49"}], "provenance": {"capture_kind": "browser_render", "tool": "browser", "receipt": {"provider": "browser", "source": "codex_tool_result", "capture_id": "browser-admin-1440-final", "captured_at": "2026-07-11T07:28:00Z", "url": "http://127.0.0.1:5173/admin"}, "viewport": {"width": 1440, "height": 1024}, "route": "/admin", "theme": "light", "data_fixture": "real-local-seed", "state": "operations-overview", "encoding_normalization": {"source": "browser JPEG byte stream", "target": "non-interlaced PNG", "resize": false, "crop": false}}, "supersedes": "screenshot-r002-710c60471f", "created_by": "agent", "recorded_at": "2026-07-11T07:30:27Z"}} +{"event_id": "evt_da3f684f9e964ae194f56683d7aa2f95", "run_id": "b2p_d52228eb0be3", "at": "2026-07-11T07:30:27Z", "operation": "record_artifact", "actor": "agent", "artifact_type": "screenshot", "artifact_id": "screenshot-r008-22b91965ac", "revision": 8, "output_hash": "22b91965ac2bece9f3b38127030a18b79a7ac17fb46f921d885f3a6f66aaddf6", "status": "verified", "supersedes": "screenshot-r003-5b1658ece7", "invalidated_artifacts": [], "stale_gates": [], "artifact": {"artifact_id": "screenshot-r008-22b91965ac", "kind": "screenshot", "revision": 8, "status": "verified", "validity": "current", "path": "/Users/inmanw/公司/calllinesystem/.prototype-run/xiaoqikong-redesign-20260711/qa/screenshots/staff-390x844.png", "sha256": "22b91965ac2bece9f3b38127030a18b79a7ac17fb46f921d885f3a6f66aaddf6", "size_bytes": 6354, "input_refs": [{"artifact_id": "implementation_checkpoint-r001-d425b22485", "sha256": "d425b2248596a19ed8e88c53c1b4c2bd515dc4e6e266a8b19113a5f216a10b49"}], "provenance": {"capture_kind": "browser_render", "tool": "browser", "receipt": {"provider": "browser", "source": "codex_tool_result", "capture_id": "browser-staff-390-final", "captured_at": "2026-07-11T07:28:00Z", "url": "http://127.0.0.1:5173/staff"}, "viewport": {"width": 390, "height": 844}, "route": "/staff", "theme": "light", "data_fixture": "real-local-seed", "state": "call-task", "encoding_normalization": {"source": "browser JPEG byte stream", "target": "non-interlaced PNG", "resize": false, "crop": false}}, "supersedes": "screenshot-r003-5b1658ece7", "created_by": "agent", "recorded_at": "2026-07-11T07:30:27Z"}} +{"event_id": "evt_ca391ef4c9b34f07a030f39a85d6b276", "run_id": "b2p_d52228eb0be3", "at": "2026-07-11T07:30:27Z", "operation": "record_artifact", "actor": "agent", "artifact_type": "screenshot", "artifact_id": "screenshot-r009-37be29e06a", "revision": 9, "output_hash": "37be29e06a3dc3edac52f6593f82201bf3c31ed98b4a5d3d51f86960781358d7", "status": "verified", "supersedes": "screenshot-r004-85cfc9df4b", "invalidated_artifacts": [], "stale_gates": [], "artifact": {"artifact_id": "screenshot-r009-37be29e06a", "kind": "screenshot", "revision": 9, "status": "verified", "validity": "current", "path": "/Users/inmanw/公司/calllinesystem/.prototype-run/xiaoqikong-redesign-20260711/qa/screenshots/staff-320x800.png", "sha256": "37be29e06a3dc3edac52f6593f82201bf3c31ed98b4a5d3d51f86960781358d7", "size_bytes": 5032, "input_refs": [{"artifact_id": "implementation_checkpoint-r001-d425b22485", "sha256": "d425b2248596a19ed8e88c53c1b4c2bd515dc4e6e266a8b19113a5f216a10b49"}], "provenance": {"capture_kind": "browser_render", "tool": "browser", "receipt": {"provider": "browser", "source": "codex_tool_result", "capture_id": "browser-staff-320-final", "captured_at": "2026-07-11T07:28:00Z", "url": "http://127.0.0.1:5173/staff"}, "viewport": {"width": 320, "height": 800}, "route": "/staff", "theme": "light", "data_fixture": "real-local-seed", "state": "call-task-reflow", "encoding_normalization": {"source": "browser JPEG byte stream", "target": "non-interlaced PNG", "resize": false, "crop": false}}, "supersedes": "screenshot-r004-85cfc9df4b", "created_by": "agent", "recorded_at": "2026-07-11T07:30:27Z"}} +{"event_id": "evt_4918caa7456b4c86b9770ff753804d7b", "run_id": "b2p_d52228eb0be3", "at": "2026-07-11T07:30:27Z", "operation": "record_artifact", "actor": "agent", "artifact_type": "screenshot", "artifact_id": "screenshot-r010-da034fb855", "revision": 10, "output_hash": "da034fb855bc25e91c86629e5e4d6eb9ef0bf8140ed04cdfd89e88ec81a2448d", "status": "verified", "supersedes": "screenshot-r005-afc5ee8fc3", "invalidated_artifacts": [], "stale_gates": [], "artifact": {"artifact_id": "screenshot-r010-da034fb855", "kind": "screenshot", "revision": 10, "status": "verified", "validity": "current", "path": "/Users/inmanw/公司/calllinesystem/.prototype-run/xiaoqikong-redesign-20260711/qa/screenshots/display-1920x1080.png", "sha256": "da034fb855bc25e91c86629e5e4d6eb9ef0bf8140ed04cdfd89e88ec81a2448d", "size_bytes": 168031, "input_refs": [{"artifact_id": "implementation_checkpoint-r001-d425b22485", "sha256": "d425b2248596a19ed8e88c53c1b4c2bd515dc4e6e266a8b19113a5f216a10b49"}], "provenance": {"capture_kind": "browser_render", "tool": "browser", "receipt": {"provider": "browser", "source": "codex_tool_result", "capture_id": "browser-display-1920-final", "captured_at": "2026-07-11T07:28:00Z", "url": "http://127.0.0.1:5173/admin/display"}, "viewport": {"width": 1920, "height": 1080}, "route": "/admin/display", "theme": "light", "data_fixture": "real-local-seed", "state": "display-center", "encoding_normalization": {"source": "browser JPEG byte stream", "target": "non-interlaced PNG", "resize": false, "crop": false}}, "supersedes": "screenshot-r005-afc5ee8fc3", "created_by": "agent", "recorded_at": "2026-07-11T07:30:27Z"}} +{"event_id": "evt_c02ec74a731f410ea1e22593922c3402", "run_id": "b2p_d52228eb0be3", "at": "2026-07-11T07:30:35Z", "operation": "record_artifact", "actor": "agent", "artifact_type": "qa_report", "artifact_id": "qa_report-r002-1a19ce1e73", "revision": 2, "output_hash": "1a19ce1e73e70786f47adb0adde57e53b53a239991a6d72df616a88d44999d7f", "status": "recorded", "supersedes": "qa_report-r001-9ee33ad45c", "invalidated_artifacts": [], "stale_gates": [], "artifact": {"artifact_id": "qa_report-r002-1a19ce1e73", "kind": "qa_report", "revision": 2, "status": "recorded", "validity": "current", "path": "/Users/inmanw/公司/calllinesystem/.prototype-run/xiaoqikong-redesign-20260711/qa/v001/report.json", "sha256": "1a19ce1e73e70786f47adb0adde57e53b53a239991a6d72df616a88d44999d7f", "size_bytes": 3652, "input_refs": [], "provenance": {}, "supersedes": "qa_report-r001-9ee33ad45c", "created_by": "agent", "recorded_at": "2026-07-11T07:30:35Z"}} +{"event_id": "evt_edf62a2b7701446cbdc3635eb79f5ffe", "run_id": "b2p_d52228eb0be3", "at": "2026-07-11T07:31:04Z", "operation": "record_artifact", "actor": "agent", "artifact_type": "screenshot", "artifact_id": "screenshot-r011-c2b7a2f107", "revision": 11, "output_hash": "c2b7a2f107fd8014ca1d38f531288468fea5b1be6b9a2e1705fcf3bb1e4e96bf", "status": "verified", "supersedes": "screenshot-r006-c2b7a2f107", "invalidated_artifacts": ["qa_report-r002-1a19ce1e73"], "stale_gates": [], "artifact": {"artifact_id": "screenshot-r011-c2b7a2f107", "kind": "screenshot", "revision": 11, "status": "verified", "validity": "current", "path": "/Users/inmanw/公司/calllinesystem/.prototype-run/xiaoqikong-redesign-20260711/qa/screenshots/admin-selected-1487x1058.png", "sha256": "c2b7a2f107fd8014ca1d38f531288468fea5b1be6b9a2e1705fcf3bb1e4e96bf", "size_bytes": 180010, "input_refs": [{"artifact_id": "implementation_checkpoint-r001-d425b22485", "sha256": "d425b2248596a19ed8e88c53c1b4c2bd515dc4e6e266a8b19113a5f216a10b49"}], "provenance": {"capture_kind": "browser_render", "tool": "browser", "receipt": {"provider": "browser", "source": "codex_tool_result", "capture_id": "browser-admin-selected-final", "captured_at": "2026-07-11T07:28:00Z", "url": "http://127.0.0.1:5173/admin"}, "viewport": {"width": 1487, "height": 1058}, "route": "/admin", "theme": "light", "data_fixture": "real local API seed", "state": "operations overview", "encoding_normalization": {"source": "browser JPEG byte stream", "target": "non-interlaced PNG", "resize": false, "crop": false}}, "supersedes": "screenshot-r006-c2b7a2f107", "created_by": "agent", "recorded_at": "2026-07-11T07:31:04Z"}} +{"event_id": "evt_91e5f63bbeb542d393a00f5f1d74e99c", "run_id": "b2p_d52228eb0be3", "at": "2026-07-11T07:31:04Z", "operation": "record_artifact", "actor": "agent", "artifact_type": "screenshot", "artifact_id": "screenshot-r012-934c4b814a", "revision": 12, "output_hash": "934c4b814a88e7c9fac68af12fb818ddc8e8a1d5f0ff084ff1591c50b8b351bb", "status": "verified", "supersedes": "screenshot-r007-934c4b814a", "invalidated_artifacts": [], "stale_gates": [], "artifact": {"artifact_id": "screenshot-r012-934c4b814a", "kind": "screenshot", "revision": 12, "status": "verified", "validity": "current", "path": "/Users/inmanw/公司/calllinesystem/.prototype-run/xiaoqikong-redesign-20260711/qa/screenshots/admin-1440x1024.png", "sha256": "934c4b814a88e7c9fac68af12fb818ddc8e8a1d5f0ff084ff1591c50b8b351bb", "size_bytes": 137054, "input_refs": [{"artifact_id": "implementation_checkpoint-r001-d425b22485", "sha256": "d425b2248596a19ed8e88c53c1b4c2bd515dc4e6e266a8b19113a5f216a10b49"}], "provenance": {"capture_kind": "browser_render", "tool": "browser", "receipt": {"provider": "browser", "source": "codex_tool_result", "capture_id": "browser-admin-1440-final", "captured_at": "2026-07-11T07:28:00Z", "url": "http://127.0.0.1:5173/admin"}, "viewport": {"width": 1440, "height": 1024}, "route": "/admin", "theme": "light", "data_fixture": "real local API seed", "state": "operations overview", "encoding_normalization": {"source": "browser JPEG byte stream", "target": "non-interlaced PNG", "resize": false, "crop": false}}, "supersedes": "screenshot-r007-934c4b814a", "created_by": "agent", "recorded_at": "2026-07-11T07:31:04Z"}} +{"event_id": "evt_6dea6d0d9b7e45f7b8c9a0b7a589e8a0", "run_id": "b2p_d52228eb0be3", "at": "2026-07-11T07:31:04Z", "operation": "record_artifact", "actor": "agent", "artifact_type": "screenshot", "artifact_id": "screenshot-r013-22b91965ac", "revision": 13, "output_hash": "22b91965ac2bece9f3b38127030a18b79a7ac17fb46f921d885f3a6f66aaddf6", "status": "verified", "supersedes": "screenshot-r008-22b91965ac", "invalidated_artifacts": [], "stale_gates": [], "artifact": {"artifact_id": "screenshot-r013-22b91965ac", "kind": "screenshot", "revision": 13, "status": "verified", "validity": "current", "path": "/Users/inmanw/公司/calllinesystem/.prototype-run/xiaoqikong-redesign-20260711/qa/screenshots/staff-390x844.png", "sha256": "22b91965ac2bece9f3b38127030a18b79a7ac17fb46f921d885f3a6f66aaddf6", "size_bytes": 6354, "input_refs": [{"artifact_id": "implementation_checkpoint-r001-d425b22485", "sha256": "d425b2248596a19ed8e88c53c1b4c2bd515dc4e6e266a8b19113a5f216a10b49"}], "provenance": {"capture_kind": "browser_render", "tool": "browser", "receipt": {"provider": "browser", "source": "codex_tool_result", "capture_id": "browser-staff-390-final", "captured_at": "2026-07-11T07:28:00Z", "url": "http://127.0.0.1:5173/staff"}, "viewport": {"width": 390, "height": 844}, "route": "/staff", "theme": "light", "data_fixture": "real local API seed", "state": "call task", "encoding_normalization": {"source": "browser JPEG byte stream", "target": "non-interlaced PNG", "resize": false, "crop": false}}, "supersedes": "screenshot-r008-22b91965ac", "created_by": "agent", "recorded_at": "2026-07-11T07:31:04Z"}} +{"event_id": "evt_1d32dc8a843e4eb8a7f8f8d6d0c9db69", "run_id": "b2p_d52228eb0be3", "at": "2026-07-11T07:31:04Z", "operation": "record_artifact", "actor": "agent", "artifact_type": "screenshot", "artifact_id": "screenshot-r014-37be29e06a", "revision": 14, "output_hash": "37be29e06a3dc3edac52f6593f82201bf3c31ed98b4a5d3d51f86960781358d7", "status": "verified", "supersedes": "screenshot-r009-37be29e06a", "invalidated_artifacts": [], "stale_gates": [], "artifact": {"artifact_id": "screenshot-r014-37be29e06a", "kind": "screenshot", "revision": 14, "status": "verified", "validity": "current", "path": "/Users/inmanw/公司/calllinesystem/.prototype-run/xiaoqikong-redesign-20260711/qa/screenshots/staff-320x800.png", "sha256": "37be29e06a3dc3edac52f6593f82201bf3c31ed98b4a5d3d51f86960781358d7", "size_bytes": 5032, "input_refs": [{"artifact_id": "implementation_checkpoint-r001-d425b22485", "sha256": "d425b2248596a19ed8e88c53c1b4c2bd515dc4e6e266a8b19113a5f216a10b49"}], "provenance": {"capture_kind": "browser_render", "tool": "browser", "receipt": {"provider": "browser", "source": "codex_tool_result", "capture_id": "browser-staff-320-final", "captured_at": "2026-07-11T07:28:00Z", "url": "http://127.0.0.1:5173/staff"}, "viewport": {"width": 320, "height": 800}, "route": "/staff", "theme": "light", "data_fixture": "real local API seed", "state": "call task reflow", "encoding_normalization": {"source": "browser JPEG byte stream", "target": "non-interlaced PNG", "resize": false, "crop": false}}, "supersedes": "screenshot-r009-37be29e06a", "created_by": "agent", "recorded_at": "2026-07-11T07:31:04Z"}} +{"event_id": "evt_9f77edea268845af907b32b3dba124e7", "run_id": "b2p_d52228eb0be3", "at": "2026-07-11T07:31:04Z", "operation": "record_artifact", "actor": "agent", "artifact_type": "screenshot", "artifact_id": "screenshot-r015-da034fb855", "revision": 15, "output_hash": "da034fb855bc25e91c86629e5e4d6eb9ef0bf8140ed04cdfd89e88ec81a2448d", "status": "verified", "supersedes": "screenshot-r010-da034fb855", "invalidated_artifacts": [], "stale_gates": [], "artifact": {"artifact_id": "screenshot-r015-da034fb855", "kind": "screenshot", "revision": 15, "status": "verified", "validity": "current", "path": "/Users/inmanw/公司/calllinesystem/.prototype-run/xiaoqikong-redesign-20260711/qa/screenshots/display-1920x1080.png", "sha256": "da034fb855bc25e91c86629e5e4d6eb9ef0bf8140ed04cdfd89e88ec81a2448d", "size_bytes": 168031, "input_refs": [{"artifact_id": "implementation_checkpoint-r001-d425b22485", "sha256": "d425b2248596a19ed8e88c53c1b4c2bd515dc4e6e266a8b19113a5f216a10b49"}], "provenance": {"capture_kind": "browser_render", "tool": "browser", "receipt": {"provider": "browser", "source": "codex_tool_result", "capture_id": "browser-display-1920-final", "captured_at": "2026-07-11T07:28:00Z", "url": "http://127.0.0.1:5173/admin/display"}, "viewport": {"width": 1920, "height": 1080}, "route": "/admin/display", "theme": "light", "data_fixture": "real local API seed", "state": "display center", "encoding_normalization": {"source": "browser JPEG byte stream", "target": "non-interlaced PNG", "resize": false, "crop": false}}, "supersedes": "screenshot-r010-da034fb855", "created_by": "agent", "recorded_at": "2026-07-11T07:31:04Z"}} +{"event_id": "evt_5a126d19a7d144cea6663c844df83dd0", "run_id": "b2p_d52228eb0be3", "at": "2026-07-11T07:31:04Z", "operation": "record_artifact", "actor": "agent", "artifact_type": "design_qa", "artifact_id": "design_qa-r002-5e4b8d4761", "revision": 2, "output_hash": "5e4b8d476161dd828ff1b17834e4b14c0e2be4f3db602dbc60e2884fc9e1cd8c", "status": "verified", "supersedes": "design_qa-r001-5e4b8d4761", "invalidated_artifacts": [], "stale_gates": [], "artifact": {"artifact_id": "design_qa-r002-5e4b8d4761", "kind": "design_qa", "revision": 2, "status": "verified", "validity": "current", "path": "/Users/inmanw/公司/calllinesystem/.prototype-run/xiaoqikong-redesign-20260711/qa/design-qa.md", "sha256": "5e4b8d476161dd828ff1b17834e4b14c0e2be4f3db602dbc60e2884fc9e1cd8c", "size_bytes": 388, "input_refs": [], "provenance": {}, "supersedes": "design_qa-r001-5e4b8d4761", "created_by": "agent", "recorded_at": "2026-07-11T07:31:04Z"}} +{"event_id": "evt_d84735f370404b848fd0516e52473139", "run_id": "b2p_d52228eb0be3", "at": "2026-07-11T07:31:12Z", "operation": "record_artifact", "actor": "agent", "artifact_type": "qa_report", "artifact_id": "qa_report-r003-1a19ce1e73", "revision": 3, "output_hash": "1a19ce1e73e70786f47adb0adde57e53b53a239991a6d72df616a88d44999d7f", "status": "recorded", "supersedes": "qa_report-r002-1a19ce1e73", "invalidated_artifacts": [], "stale_gates": [], "artifact": {"artifact_id": "qa_report-r003-1a19ce1e73", "kind": "qa_report", "revision": 3, "status": "recorded", "validity": "current", "path": "/Users/inmanw/公司/calllinesystem/.prototype-run/xiaoqikong-redesign-20260711/qa/v001/report.json", "sha256": "1a19ce1e73e70786f47adb0adde57e53b53a239991a6d72df616a88d44999d7f", "size_bytes": 3652, "input_refs": [], "provenance": {}, "supersedes": "qa_report-r002-1a19ce1e73", "created_by": "agent", "recorded_at": "2026-07-11T07:31:12Z"}} +{"event_id": "evt_f8e1e040860143639add5101b37185e3", "run_id": "b2p_d52228eb0be3", "at": "2026-07-11T07:31:27Z", "operation": "gate_decision", "actor": "agent", "gate": "final_qa", "decision": "approved", "artifact_id": "qa_report-r003-1a19ce1e73", "artifact_hash": "1a19ce1e73e70786f47adb0adde57e53b53a239991a6d72df616a88d44999d7f", "note": "Browser, interaction, responsive, accessibility, motion, tests and build QA passed", "invalidated_artifacts": [], "stale_gates": [], "gate_record": {"gate": "final_qa", "decision": "approved", "validity": "current", "artifact_id": "qa_report-r003-1a19ce1e73", "artifact_sha256": "1a19ce1e73e70786f47adb0adde57e53b53a239991a6d72df616a88d44999d7f", "actor": "agent", "note": "Browser, interaction, responsive, accessibility, motion, tests and build QA passed", "decided_at": "2026-07-11T07:31:27Z"}} +{"event_id": "evt_fc3e7c76da8e47bca18ed3e288768570", "run_id": "b2p_d52228eb0be3", "at": "2026-07-11T07:31:27Z", "operation": "transition", "actor": "agent", "prior_state": "validating", "new_state": "review_ready", "note": "", "stale_gates": [], "stale_artifacts": []} +{"event_id": "evt_5d687a14bba54a5a89cb2adcec1e1598", "run_id": "b2p_d52228eb0be3", "at": "2026-07-11T07:31:27Z", "operation": "transition", "actor": "agent", "prior_state": "review_ready", "new_state": "complete", "note": "", "stale_gates": [], "stale_artifacts": []} diff --git a/.prototype-run/xiaoqikong-redesign-20260711/motion/v001.json b/.prototype-run/xiaoqikong-redesign-20260711/motion/v001.json new file mode 100644 index 0000000..1610671 --- /dev/null +++ b/.prototype-run/xiaoqikong-redesign-20260711/motion/v001.json @@ -0,0 +1,14 @@ +{ + "schema_version": "1.0", + "motion_contract_id": "mc_xiaoqikong_v001", + "revision": 1, + "requirements_contract": {"contract_id": "pc_xiaoqikong_queue_redesign", "revision": 1}, + "intensity": 2, + "library_decision": {"primary": "css", "justification": "Only direct state feedback, nav selection and short content updates are required; no choreography or layout measurement justifies a dependency.", "dependencies": []}, + "behaviors": [ + {"id": "MOTION-STATE-FEEDBACK", "requirement_ids": ["REQ-MOTION-001"], "surface": "all interactive routes", "trigger": "hover, focus, pressed, selected or asynchronous status update", "purpose": "feedback", "targets": "buttons, links, selected rows, banners and status regions", "from": "resting color and opacity", "to": "semantic final color, 1px press translation where applicable, and stable visible feedback", "timing": "100-220ms", "easing": "cubic-bezier(.2,0,0,1)", "interruptible": true, "reentry": "on each direct interaction or state update", "reduced_motion": "show final state immediately and remove translation", "acceptance": "feedback is understandable without waiting and no layout shift occurs"} + ], + "reduced_motion": {"strategy": "disable nonessential transitions and transforms via prefers-reduced-motion; preserve immediate state visibility"}, + "performance_budget": {"properties": ["opacity", "transform", "background-color", "border-color"], "max_duration_ms": 220, "layout_animation": false}, + "ambiguities": [] +} diff --git a/.prototype-run/xiaoqikong-redesign-20260711/prompts/v001.json b/.prototype-run/xiaoqikong-redesign-20260711/prompts/v001.json new file mode 100644 index 0000000..236e9d8 --- /dev/null +++ b/.prototype-run/xiaoqikong-redesign-20260711/prompts/v001.json @@ -0,0 +1,45 @@ +{ + "schema_version": "1.0", + "prompt_bundle_id": "pb_xiaoqikong_redesign_v001", + "revision": 1, + "requirements_contract": {"contract_id": "pc_xiaoqikong_queue_redesign", "revision": 1}, + "candidate_count": 3, + "candidates": [ + { + "candidate_id": "candidate_anchor_landscape_service", + "direction_name": "山水秩序·公共服务锚点", + "requirement_ids": ["REQ-FLOW-003", "REQ-VIS-001", "REQ-VIS-002", "REQ-VIS-003", "REQ-STATE-001", "REQ-A11Y-001", "REQ-MOTION-001", "REQ-TECH-001", "REQ-TECH-002"], + "style_refs": [{"style_ref_id": "style_trust_accessible_xqk", "role": "anchor direction adapted to existing brand", "source": "/Users/inmanw/公司/productskill/skill/product-prototype-design/references/style-trust-accessible.md", "sha256": "92f610353c7e72d8a7663d3d444e54f4f37b1e5a908adf07bbeb5bbc4a25a4dc", "inspected": true}], + "viewport": {"width": 1440, "height": 1024}, + "prompt": "Use case: ui-mockup. Create one straight-on, full-bleed 1440×1024 desktop web UI concept for the Xiaoqikong Cultural Tourism scenic-area queue management system. Audience: operations administrators working in a public-service context. This is the representative admin overview screen that will establish a visual system later applied to staff mobile, visitor mobile, and the public call display. Use the supplied Xiaoqikong product logo only as a brand reference: preserve its green landscape brush gesture and black wordmark character, but do not redraw or imitate the logo inside the mockup; reserve a clean logo slot in the header. Art direction: trustworthy accessible service, crisp white and mist-pale-green surfaces, deep forest-green ink and actions, disciplined 8px rhythm, familiar left navigation, a calm command/filter bar, a queue operations overview with aligned measures, one prominent current-status region, a compact recent-activity table, visible success/warning semantics with labels and icons. Bring in the logo's landscape rhythm through restrained horizontal contour dividers and asymmetric green accent bands, never decorative scenery. Typography is highly legible Chinese system sans with tabular numerals. Render only representative short labels such as “运营概览”, “项目管理”, “当前等待”, “最新叫号”; all image text is non-authoritative. Show production-grade layout, explicit hierarchy, generous readable type, 48px control targets and strong focus treatment. No device frame, no perspective, no browser chrome.", + "negative_constraints": ["no generated or altered logo", "no purple gradient", "no glassmorphism", "no giant KPI card mosaic", "no low-contrast gray text", "no photography", "no fake 3D chart", "no invented features", "no device frame", "no perspective"], + "exact_copy_policy": "requirements_contract_authoritative", + "input_images": [{"path": "assets/xiaoqikong-logo.jpg", "sha256": "ac1b8b07954ee5ce706e6cd053e99b00275ee0516a5c0c21d1d576b5a059909d", "role": "official product logo and brand palette reference; do not redraw", "attached": true}], + "status": "ready" + }, + { + "candidate_id": "candidate_adjacent_operational_ribbon", + "direction_name": "绿谷仪表·运营密度增强", + "requirement_ids": ["REQ-FLOW-003", "REQ-VIS-001", "REQ-VIS-002", "REQ-VIS-003", "REQ-STATE-001", "REQ-A11Y-001", "REQ-MOTION-001", "REQ-TECH-001", "REQ-TECH-002"], + "style_refs": [{"style_ref_id": "style_data_dense_xqk", "role": "adjacent operational composition adapted to mixed-skill users", "source": "/Users/inmanw/公司/productskill/skill/product-prototype-design/references/style-data-dense.md", "sha256": "6bea252e55dfaa6ff5b82c3c2b2eebc5e7ceb746ce86fc02444eba01d2e189ea", "inspected": true}], + "viewport": {"width": 1440, "height": 1024}, + "prompt": "Use case: ui-mockup. Create one straight-on, full-bleed 1440×1024 desktop web UI concept for the Xiaoqikong Cultural Tourism scenic-area queue management system. Audience: operations administrators scanning multiple attraction queues and exceptions. Use the supplied Xiaoqikong product logo only as a brand reference: preserve its green landscape brush energy and reserve a precise real-logo slot, but do not recreate the logo. Art direction: a high-information yet calm operational instrument, light mineral-gray canvas, white working surface, forest-green command ribbon, charcoal text, hairline separators, tabular numerals, compact 44–48px controls. Composition must differ from a card dashboard: stable narrow navigation rail, a wide horizontal live-operations ribbon across the top, aligned project rows in the main region with waiting counts, current batch, queue status and update time, plus a contextual detail panel at right for the selected attraction. Use density 7 of 10 for mixed-skill users, not cramped. Integrate abstract horizontal contour lines derived from the brand brush gesture only as subtle section guides. Representative short labels may include “运营概览”, “项目”, “等待中”, “当前批次”, “更新时间”; rendered text is non-authoritative. Include visible selected, healthy, paused and warning states with labels and symbols. No device frame, no perspective, no browser chrome.", + "negative_constraints": ["no generated or altered logo", "no decorative dashboard gradients", "no giant KPI cards", "no donut chart wall", "no gauge wall", "no tiny illegible controls", "no color-only alerts", "no fake live precision", "no device frame", "no perspective"], + "exact_copy_policy": "requirements_contract_authoritative", + "input_images": [{"path": "assets/xiaoqikong-logo.jpg", "sha256": "ac1b8b07954ee5ce706e6cd053e99b00275ee0516a5c0c21d1d576b5a059909d", "role": "official product logo and brand palette reference; do not redraw", "attached": true}], + "status": "ready" + }, + { + "candidate_id": "candidate_stretch_human_scenic", + "direction_name": "喀斯特之息·人文景区服务", + "requirement_ids": ["REQ-FLOW-003", "REQ-VIS-001", "REQ-VIS-002", "REQ-VIS-003", "REQ-STATE-001", "REQ-A11Y-001", "REQ-MOTION-001", "REQ-TECH-001", "REQ-TECH-002"], + "style_refs": [{"style_ref_id": "style_warm_human_xqk", "role": "stretch direction with restrained scenic warmth", "source": "/Users/inmanw/公司/productskill/skill/product-prototype-design/references/style-warm-human.md", "sha256": "e2f7857158dffb3f1b5a602a5bca488355e118a07ea34a986facf33ddb87445b", "inspected": true}], + "viewport": {"width": 1440, "height": 1024}, + "prompt": "Use case: ui-mockup. Create one straight-on, full-bleed 1440×1024 desktop web UI concept for the Xiaoqikong Cultural Tourism scenic-area queue management system. Audience: operations administrators, with a warmer scenic-service personality while remaining serious and accessible. Use the supplied Xiaoqikong product logo only as the official brand reference; reserve an exact real-logo area and do not redraw or mutate it. Art direction: calm off-white and fog-green foundation, deep evergreen text, natural jade accents, restrained warm amber only for attention, adult friendly typography, soft grouping with disciplined spacing. Composition: a slim top brand bar, an asymmetric two-column overview where a large queue-status canvas anchors the page and a narrower action/exception rail supports it; use a subtle topographic-water contour motif and generous breathing room rather than generic rounded card mosaics. Provide project switcher, live queue summary, latest batch area, recent operations list, and a clearly labeled paused/warning state, all as visual placeholders matching existing capabilities. Representative short labels may include “运营概览”, “当前排队”, “最新叫号”, “运行中”; image text is non-authoritative. Keep hierarchy explicit, body type readable, controls familiar, focus visible and status redundant. No staged tourism photo, no sentimental illustration, no device frame, no perspective, no browser chrome.", + "negative_constraints": ["no generated or altered logo", "no beige-and-brass lifestyle cliche", "no cartoon scenery", "no stock photography", "no excessive blobs or pills", "no patronizing copy", "no glassmorphism", "no purple gradient", "no device frame", "no perspective"], + "exact_copy_policy": "requirements_contract_authoritative", + "input_images": [{"path": "assets/xiaoqikong-logo.jpg", "sha256": "ac1b8b07954ee5ce706e6cd053e99b00275ee0516a5c0c21d1d576b5a059909d", "role": "official product logo and brand palette reference; do not redraw", "attached": true}], + "status": "ready" + } + ] +} diff --git a/.prototype-run/xiaoqikong-redesign-20260711/prototypes/candidate_adjacent_operational_ribbon.png b/.prototype-run/xiaoqikong-redesign-20260711/prototypes/candidate_adjacent_operational_ribbon.png new file mode 100644 index 0000000..e80db8b Binary files /dev/null and b/.prototype-run/xiaoqikong-redesign-20260711/prototypes/candidate_adjacent_operational_ribbon.png differ diff --git a/.prototype-run/xiaoqikong-redesign-20260711/prototypes/candidate_anchor_landscape_service.png b/.prototype-run/xiaoqikong-redesign-20260711/prototypes/candidate_anchor_landscape_service.png new file mode 100644 index 0000000..46249aa Binary files /dev/null and b/.prototype-run/xiaoqikong-redesign-20260711/prototypes/candidate_anchor_landscape_service.png differ diff --git a/.prototype-run/xiaoqikong-redesign-20260711/prototypes/candidate_stretch_human_scenic.png b/.prototype-run/xiaoqikong-redesign-20260711/prototypes/candidate_stretch_human_scenic.png new file mode 100644 index 0000000..ad092c9 Binary files /dev/null and b/.prototype-run/xiaoqikong-redesign-20260711/prototypes/candidate_stretch_human_scenic.png differ diff --git a/.prototype-run/xiaoqikong-redesign-20260711/prototypes/manifest.json b/.prototype-run/xiaoqikong-redesign-20260711/prototypes/manifest.json new file mode 100644 index 0000000..8cb01a5 --- /dev/null +++ b/.prototype-run/xiaoqikong-redesign-20260711/prototypes/manifest.json @@ -0,0 +1,10 @@ +{ + "schema_version": "1.0", + "prompt_bundle_id": "pb_xiaoqikong_redesign_v001", + "candidate_count": 3, + "candidates": [ + {"candidate_id": "candidate_anchor_landscape_service", "artifact_id": "prototype-r001-ff335eab92", "path": "prototypes/candidate_anchor_landscape_service.png", "sha256": "ff335eab927061b4ad2ce415a2ce74d8dd65fcb77654b57685e611a3c4e8eb67", "status": "recorded"}, + {"candidate_id": "candidate_adjacent_operational_ribbon", "artifact_id": "prototype-r002-bbf4f3c63e", "path": "prototypes/candidate_adjacent_operational_ribbon.png", "sha256": "bbf4f3c63e5b5f2465ad182d2de397f1f24019f819eb6072245c1aeb377faf80", "status": "recorded"}, + {"candidate_id": "candidate_stretch_human_scenic", "artifact_id": "prototype-r003-1dfc3cfdbc", "path": "prototypes/candidate_stretch_human_scenic.png", "sha256": "1dfc3cfdbcc8af653430290f8ad55e09c269fc97f2d98d96852270e328a53f77", "status": "recorded"} + ] +} diff --git a/.prototype-run/xiaoqikong-redesign-20260711/qa/design-qa.md b/.prototype-run/xiaoqikong-redesign-20260711/qa/design-qa.md new file mode 100644 index 0000000..899ac5f --- /dev/null +++ b/.prototype-run/xiaoqikong-redesign-20260711/qa/design-qa.md @@ -0,0 +1,8 @@ +# Design QA + +The implementation preserves the selected direction's pale mineral canvas, deep evergreen hierarchy, restrained contour motif, flat bordered data surfaces, contextual status emphasis, and real Xiaoqikong brand asset. Exact product copy and data remain sourced from the existing application rather than generated pixels. + +Open P0: 0 +Open P1: 0 +Open P2: 0 +Final result: passed diff --git a/.prototype-run/xiaoqikong-redesign-20260711/qa/implementation-checkpoint.json b/.prototype-run/xiaoqikong-redesign-20260711/qa/implementation-checkpoint.json new file mode 100644 index 0000000..97c5e82 --- /dev/null +++ b/.prototype-run/xiaoqikong-redesign-20260711/qa/implementation-checkpoint.json @@ -0,0 +1 @@ +{"implementation_revision":1,"repo_snapshot":"cbfcff0a7316cc49a5f96320c827888f04ff981b2bcc325fde4fd0cd8ec1e059","tests":"11 files / 24 tests passed","typecheck":"passed","build":"passed","browser_url":"http://127.0.0.1:5173"} diff --git a/.prototype-run/xiaoqikong-redesign-20260711/qa/reflow-320.md b/.prototype-run/xiaoqikong-redesign-20260711/qa/reflow-320.md new file mode 100644 index 0000000..24fcdd7 --- /dev/null +++ b/.prototype-run/xiaoqikong-redesign-20260711/qa/reflow-320.md @@ -0,0 +1,5 @@ +# Reflow check + +Method: browser viewport at 320×800 CSS pixels, zoom 100%. + +Result: passed. `document.documentElement.scrollWidth` and the main landmark scroll width both equaled 320 CSS pixels; no page-level horizontal scrolling or hidden primary action was observed. diff --git a/.prototype-run/xiaoqikong-redesign-20260711/qa/requirements.md b/.prototype-run/xiaoqikong-redesign-20260711/qa/requirements.md new file mode 100644 index 0000000..fe31a8c --- /dev/null +++ b/.prototype-run/xiaoqikong-redesign-20260711/qa/requirements.md @@ -0,0 +1,8 @@ +# Requirement evidence + +- Existing staff call/ticket, visitor private status, admin management, and display-center routes remain implemented and are covered by 24 passing Vitest tests. +- The official Xiaoqikong logo is served from `/xiaoqikong-logo.jpg`; browser checks confirmed a loaded image with non-zero intrinsic dimensions. +- The selected direction is implemented through forest-green semantic tokens, pale mineral surfaces, asymmetric desktop hierarchy, contour decoration, tabular data, and redundant status labels. +- Browser checks passed at 390×844, 320×800, 1440×1024, selected-source 1487×1058, and 1920×1080 CSS viewports with exact-size browser-native screenshots. +- Staff 390px and 320px checks reported document scroll widths equal to viewport widths. Fullscreen denial now produces visible warning feedback and no console error. +- `prefers-reduced-motion` removes nonessential duration and transform feedback while leaving final states visible. diff --git a/.prototype-run/xiaoqikong-redesign-20260711/qa/screenshots/admin-1440x1024.png b/.prototype-run/xiaoqikong-redesign-20260711/qa/screenshots/admin-1440x1024.png new file mode 100644 index 0000000..230f7ec Binary files /dev/null and b/.prototype-run/xiaoqikong-redesign-20260711/qa/screenshots/admin-1440x1024.png differ diff --git a/.prototype-run/xiaoqikong-redesign-20260711/qa/screenshots/admin-selected-1487x1058.png b/.prototype-run/xiaoqikong-redesign-20260711/qa/screenshots/admin-selected-1487x1058.png new file mode 100644 index 0000000..29a9faa Binary files /dev/null and b/.prototype-run/xiaoqikong-redesign-20260711/qa/screenshots/admin-selected-1487x1058.png differ diff --git a/.prototype-run/xiaoqikong-redesign-20260711/qa/screenshots/display-1920x1080.png b/.prototype-run/xiaoqikong-redesign-20260711/qa/screenshots/display-1920x1080.png new file mode 100644 index 0000000..5b228c7 Binary files /dev/null and b/.prototype-run/xiaoqikong-redesign-20260711/qa/screenshots/display-1920x1080.png differ diff --git a/.prototype-run/xiaoqikong-redesign-20260711/qa/screenshots/staff-320x800.png b/.prototype-run/xiaoqikong-redesign-20260711/qa/screenshots/staff-320x800.png new file mode 100644 index 0000000..528bd47 Binary files /dev/null and b/.prototype-run/xiaoqikong-redesign-20260711/qa/screenshots/staff-320x800.png differ diff --git a/.prototype-run/xiaoqikong-redesign-20260711/qa/screenshots/staff-390x844.png b/.prototype-run/xiaoqikong-redesign-20260711/qa/screenshots/staff-390x844.png new file mode 100644 index 0000000..6843c51 Binary files /dev/null and b/.prototype-run/xiaoqikong-redesign-20260711/qa/screenshots/staff-390x844.png differ diff --git a/.prototype-run/xiaoqikong-redesign-20260711/qa/v001/report.json b/.prototype-run/xiaoqikong-redesign-20260711/qa/v001/report.json new file mode 100644 index 0000000..55a0289 --- /dev/null +++ b/.prototype-run/xiaoqikong-redesign-20260711/qa/v001/report.json @@ -0,0 +1,39 @@ +{ + "schema_version": "1.0", + "qa_run_id": "qa_xiaoqikong_v001", + "requirements_contract": {"contract_id": "pc_xiaoqikong_queue_redesign", "revision": 1}, + "design_contract": {"design_contract_id": "dc_xiaoqikong_karst_breath_v001", "revision": 1}, + "implementation_revision": 1, + "visual_comparisons": [ + {"viewport": {"width": 1487, "height": 1058}, "route": "/admin", "theme": "light", "data_fixture": "real local API seed", "state": "operations overview", "source_sha256": "1dfc3cfdbcc8af653430290f8ad55e09c269fc97f2d98d96852270e328a53f77", "screenshot_path": "qa/screenshots/admin-selected-1487x1058.png", "screenshot_sha256": "c2b7a2f107fd8014ca1d38f531288468fea5b1be6b9a2e1705fcf3bb1e4e96bf", "result": "passed"} + ], + "responsive_checks": [ + {"viewport": {"width": 390, "height": 844}, "route": "/staff", "theme": "light", "data_fixture": "real local API seed", "state": "call task", "screenshot_path": "qa/screenshots/staff-390x844.png", "screenshot_sha256": "22b91965ac2bece9f3b38127030a18b79a7ac17fb46f921d885f3a6f66aaddf6", "result": "passed"}, + {"viewport": {"width": 320, "height": 800}, "route": "/staff", "theme": "light", "data_fixture": "real local API seed", "state": "call task reflow", "screenshot_path": "qa/screenshots/staff-320x800.png", "screenshot_sha256": "37be29e06a3dc3edac52f6593f82201bf3c31ed98b4a5d3d51f86960781358d7", "result": "passed"}, + {"viewport": {"width": 1440, "height": 1024}, "route": "/admin", "theme": "light", "data_fixture": "real local API seed", "state": "operations overview", "screenshot_path": "qa/screenshots/admin-1440x1024.png", "screenshot_sha256": "934c4b814a88e7c9fac68af12fb818ddc8e8a1d5f0ff084ff1591c50b8b351bb", "result": "passed"}, + {"viewport": {"width": 1920, "height": 1080}, "route": "/admin/display", "theme": "light", "data_fixture": "real local API seed", "state": "display center", "screenshot_path": "qa/screenshots/display-1920x1080.png", "screenshot_sha256": "da034fb855bc25e91c86629e5e4d6eb9ef0bf8140ed04cdfd89e88ec81a2448d", "result": "passed"} + ], + "requirement_results": [ + {"requirement_id": "REQ-FLOW-001", "result": "passed", "evidence": "qa/requirements.md"}, + {"requirement_id": "REQ-FLOW-002", "result": "passed", "evidence": "qa/requirements.md"}, + {"requirement_id": "REQ-FLOW-003", "result": "passed", "evidence": "qa/requirements.md"}, + {"requirement_id": "REQ-FLOW-004", "result": "passed", "evidence": "qa/requirements.md"}, + {"requirement_id": "REQ-VIS-001", "result": "passed", "evidence": "qa/requirements.md"}, + {"requirement_id": "REQ-VIS-002", "result": "passed", "evidence": "qa/requirements.md"}, + {"requirement_id": "REQ-VIS-003", "result": "passed", "evidence": "qa/requirements.md"}, + {"requirement_id": "REQ-STATE-001", "result": "passed", "evidence": "qa/requirements.md"}, + {"requirement_id": "REQ-A11Y-001", "result": "passed", "evidence": "qa/requirements.md"}, + {"requirement_id": "REQ-A11Y-002", "result": "passed", "evidence": "qa/reflow-320.md"}, + {"requirement_id": "REQ-MOTION-001", "result": "passed", "evidence": "qa/requirements.md"}, + {"requirement_id": "REQ-TECH-001", "result": "passed", "evidence": "qa/requirements.md"}, + {"requirement_id": "REQ-TECH-002", "result": "passed", "evidence": "qa/requirements.md"} + ], + "responsive": "passed", + "accessibility": "passed", + "accessibility_checks": [ + {"check": "reflow", "method": "viewport_320", "viewport": {"width": 320, "height": 800}, "zoom_percent": 100, "result": "passed", "evidence": "qa/reflow-320.md", "exception_rationale": ""} + ], + "motion": "passed", + "open_findings": [], + "final_result": "passed" +} diff --git a/.prototype-run/xiaoqikong-redesign-20260711/requirements/v001.json b/.prototype-run/xiaoqikong-redesign-20260711/requirements/v001.json new file mode 100644 index 0000000..e4080c1 --- /dev/null +++ b/.prototype-run/xiaoqikong-redesign-20260711/requirements/v001.json @@ -0,0 +1,154 @@ +{ + "schema_version": "1.0", + "contract_id": "pc_xiaoqikong_queue_redesign", + "revision": 1, + "status": "accepted", + "product": "小七孔文旅集团景区排队叫号系统", + "surface": "responsive_web_multi_surface", + "primary_user": "现场工作人员、游客、运营管理员与公示屏观众", + "primary_outcome": "在不改变现有业务和权限边界的前提下,以小七孔文旅品牌统一重绘四端并提高易读性与操作效率", + "target_viewports": [ + {"id": "mobile_primary", "width": 390, "height": 844}, + {"id": "mobile_reflow", "width": 320, "height": 800}, + {"id": "admin_desktop", "width": 1440, "height": 1024}, + {"id": "display_landscape", "width": 1920, "height": 1080} + ], + "requirements": [ + { + "id": "REQ-FLOW-001", + "kind": "flow", + "statement": "工作人员继续通过现有入口完成项目切换、取号和叫下一批。", + "priority": "must", + "source": {"type": "existing_product", "ref": "web/src/pages/StaffPage.tsx"}, + "acceptance": ["原有工作人员主流程与 API 调用保持可用"], + "status": "active" + }, + { + "id": "REQ-FLOW-002", + "kind": "flow", + "statement": "游客继续通过私密链接查看号码、排队状态、前方号码数和预计等待时间。", + "priority": "must", + "source": {"type": "existing_product", "ref": "web/src/pages/VisitorPage.tsx"}, + "acceptance": ["游客页只暴露手机号尾四位并正确显示各状态"], + "status": "active" + }, + { + "id": "REQ-FLOW-003", + "kind": "flow", + "statement": "管理员继续使用现有概览、项目管理、账号、日志、设备与大屏入口。", + "priority": "must", + "source": {"type": "existing_product", "ref": "web/src/pages/AdminPage.tsx"}, + "acceptance": ["所有现有管理路由可达且数据展示语义不变"], + "status": "active" + }, + { + "id": "REQ-FLOW-004", + "kind": "flow", + "statement": "公示大屏继续以只读方式展示当前批次、最新叫号和排队进度。", + "priority": "must", + "source": {"type": "existing_product", "ref": "web/src/pages/DisplayPage.tsx"}, + "acceptance": ["1920×1080 下远距离可读且不出现个人信息"], + "status": "active" + }, + { + "id": "REQ-VIS-001", + "kind": "visual", + "statement": "四端必须使用用户提供的小七孔文旅集团产品 Logo 作为正式品牌资产,不重绘、不替换字标。", + "priority": "must", + "source": {"type": "provided_artifact", "ref": "assets/xiaoqikong-logo.jpg#sha256=ac1b8b07954ee5ce706e6cd053e99b00275ee0516a5c0c21d1d576b5a059909d"}, + "acceptance": ["页面使用真实 Logo 文件而非生成图中的伪造标识"], + "status": "active" + }, + { + "id": "REQ-VIS-002", + "kind": "visual", + "statement": "视觉方向保持可信、清晰、耐候、低焦虑,以 Logo 的自然绿、山水笔触和黑色字标建立品牌识别。", + "priority": "must", + "source": {"type": "user", "ref": "confirmed design direction"}, + "acceptance": ["四端共享可辨识的品牌骨架且不使用紫色 AI 渐变或玻璃拟态"], + "status": "active" + }, + { + "id": "REQ-VIS-003", + "kind": "visual", + "statement": "工作人员端与游客端以 390px 为主设计宽度,管理端和大屏分别优化桌面密度与远距离识读。", + "priority": "must", + "source": {"type": "existing_product", "ref": "task_plan.md Phase 9"}, + "acceptance": ["390px、1440px、1920px 主视口布局无裁切与主操作遮挡"], + "status": "active" + }, + { + "id": "REQ-STATE-001", + "kind": "state", + "statement": "保留现有加载、空、错误、成功、弱网、失效和禁用状态的明确表达。", + "priority": "must", + "source": {"type": "existing_product", "ref": "web/src/components/Feedback.tsx"}, + "acceptance": ["每个适用页面都有可理解且可恢复的异常状态"], + "status": "active" + }, + { + "id": "REQ-A11Y-001", + "kind": "accessibility", + "statement": "必须保持键盘可用、明显焦点、语义标签、不依赖单一颜色和 WCAG 2.2 AA 级对比度。", + "priority": "must", + "source": {"type": "accepted_default", "ref": "confirmed risk and validation summary"}, + "acceptance": ["键盘完成主流程、焦点可见、状态有文字语义、对比度达标"], + "status": "active" + }, + { + "id": "REQ-A11Y-002", + "kind": "accessibility", + "statement": "必须在 320 CSS px 重排、48px 触控目标和减少动态效果偏好下保持可用。", + "priority": "must", + "source": {"type": "accepted_default", "ref": "confirmed risk and validation summary"}, + "acceptance": ["320px 无二维滚动且 prefers-reduced-motion 下无非必需动画"], + "status": "active" + }, + { + "id": "REQ-MOTION-001", + "kind": "motion", + "statement": "动效仅用于状态反馈、内容层级切换和叫号更新定位,整体保持克制。", + "priority": "should", + "source": {"type": "user", "ref": "confirmed assumptions"}, + "acceptance": ["无装饰性循环动画,动效可中断且减少动态效果下即时更新"], + "status": "active" + }, + { + "id": "REQ-TECH-001", + "kind": "technical", + "statement": "重绘必须复用现有 React、TypeScript、Vite、CSS 与组件结构,不更改 Go 后端和 API 契约。", + "priority": "must", + "source": {"type": "user", "ref": "confirmed scope assumption"}, + "acceptance": ["后端业务文件无修改且前端测试、类型检查和构建通过"], + "status": "active" + }, + { + "id": "REQ-TECH-002", + "kind": "technical", + "statement": "可见文案、业务数据、路由、权限、隐私和幂等边界以现有产品为权威来源。", + "priority": "must", + "source": {"type": "existing_product", "ref": "web/src and server contracts"}, + "acceptance": ["不从生成图转录错别字、伪造数据或新交互"], + "status": "active" + } + ], + "flows": [ + {"id": "FLOW-STAFF", "name": "工作人员取号与叫号", "requirement_ids": ["REQ-FLOW-001", "REQ-STATE-001", "REQ-A11Y-001"]}, + {"id": "FLOW-VISITOR", "name": "游客查看排队状态", "requirement_ids": ["REQ-FLOW-002", "REQ-STATE-001", "REQ-A11Y-002"]}, + {"id": "FLOW-ADMIN", "name": "管理员运营管理", "requirement_ids": ["REQ-FLOW-003", "REQ-STATE-001", "REQ-A11Y-001"]}, + {"id": "FLOW-DISPLAY", "name": "公示屏远距离查看", "requirement_ids": ["REQ-FLOW-004", "REQ-VIS-003", "REQ-A11Y-001"]} + ], + "exact_copy": {}, + "out_of_scope": ["新业务功能", "后端修改", "API 契约修改", "正式短信或硬件接入", "Logo 重绘"], + "assumptions": ["当前 640×640 JPG 是本轮唯一正式品牌资产", "现有中文文案和真实数据结构保持不变", "可为不同尺寸使用对原 Logo 做不改变内容的裁切与透明背景处理"], + "ambiguities": [], + "confirmation": { + "summary_revision": 2, + "summary_reviewed": true, + "authorized_by": "user", + "authorization_text": "确认,可以执行", + "confirmed_at": "2026-07-11T06:58:22Z", + "assumptions_reviewed": true, + "unresolved_material_decisions": [] + } +} diff --git a/.prototype-run/xiaoqikong-redesign-20260711/run.json b/.prototype-run/xiaoqikong-redesign-20260711/run.json new file mode 100644 index 0000000..72e4eac --- /dev/null +++ b/.prototype-run/xiaoqikong-redesign-20260711/run.json @@ -0,0 +1,1043 @@ +{ + "schema_version": "1.0", + "run_id": "b2p_d52228eb0be3", + "mode": "full", + "state": "complete", + "surface": "responsive_web", + "target_repo": "/Users/inmanw/公司/calllinesystem", + "artifacts": [ + { + "artifact_id": "requirements-r001-4e447ed7e6", + "kind": "requirements", + "revision": 1, + "status": "approved", + "validity": "current", + "path": "/Users/inmanw/公司/calllinesystem/.prototype-run/xiaoqikong-redesign-20260711/requirements/v001.json", + "sha256": "4e447ed7e66ccb462f238d1b30c96448923e7ad46495e735735c888ec011563a", + "size_bytes": 7442, + "input_refs": [], + "provenance": {}, + "supersedes": null, + "created_by": "agent", + "recorded_at": "2026-07-11T06:59:41Z" + }, + { + "artifact_id": "prompt_bundle-r001-b38546ec96", + "kind": "prompt_bundle", + "revision": 1, + "status": "ready", + "validity": "current", + "path": "/Users/inmanw/公司/calllinesystem/.prototype-run/xiaoqikong-redesign-20260711/prompts/v001.json", + "sha256": "b38546ec968c1da41b7b10207dc2cf7eba3edcb2b033cdb9d6902a6e75aa9b9d", + "size_bytes": 8648, + "input_refs": [], + "provenance": {}, + "supersedes": null, + "created_by": "agent", + "recorded_at": "2026-07-11T07:00:33Z" + }, + { + "artifact_id": "prototype-r001-ff335eab92", + "kind": "prototype", + "revision": 1, + "status": "candidate", + "validity": "current", + "path": "/Users/inmanw/公司/calllinesystem/.prototype-run/xiaoqikong-redesign-20260711/prototypes/candidate_anchor_landscape_service.png", + "sha256": "ff335eab927061b4ad2ce415a2ce74d8dd65fcb77654b57685e611a3c4e8eb67", + "size_bytes": 1203540, + "input_refs": [], + "provenance": { + "tool": "built-in-imagegen", + "candidate_id": "candidate_anchor_landscape_service", + "receipt": { + "provider": "built-in-imagegen", + "source": "codex_tool_result", + "result_id": "exec-aef497a3-a36d-4f2f-a901-fa06a2c88826", + "generated_at": "2026-07-11T07:16:45Z", + "prompt_bundle_id": "pb_xiaoqikong_redesign_v001", + "candidate_id": "candidate_anchor_landscape_service", + "output_sha256": "ff335eab927061b4ad2ce415a2ce74d8dd65fcb77654b57685e611a3c4e8eb67" + }, + "viewport_deviation": { + "target": { + "width": 1440, + "height": 1024 + }, + "actual": { + "width": 1487, + "height": 1058 + }, + "handling": "auto_accept", + "rationale": "Backend-controlled output differs by less than 2% in aspect ratio and exceeds 75% of both target axes." + } + }, + "supersedes": null, + "created_by": "agent", + "recorded_at": "2026-07-11T07:17:01Z" + }, + { + "artifact_id": "prototype-r002-bbf4f3c63e", + "kind": "prototype", + "revision": 2, + "status": "candidate", + "validity": "current", + "path": "/Users/inmanw/公司/calllinesystem/.prototype-run/xiaoqikong-redesign-20260711/prototypes/candidate_adjacent_operational_ribbon.png", + "sha256": "bbf4f3c63e5b5f2465ad182d2de397f1f24019f819eb6072245c1aeb377faf80", + "size_bytes": 1225012, + "input_refs": [], + "provenance": { + "tool": "built-in-imagegen", + "candidate_id": "candidate_adjacent_operational_ribbon", + "receipt": { + "provider": "built-in-imagegen", + "source": "codex_tool_result", + "result_id": "exec-42b37576-9ed2-4424-b4d4-4b1e86694bcf", + "generated_at": "2026-07-11T07:16:45Z", + "prompt_bundle_id": "pb_xiaoqikong_redesign_v001", + "candidate_id": "candidate_adjacent_operational_ribbon", + "output_sha256": "bbf4f3c63e5b5f2465ad182d2de397f1f24019f819eb6072245c1aeb377faf80" + }, + "viewport_deviation": { + "target": { + "width": 1440, + "height": 1024 + }, + "actual": { + "width": 1487, + "height": 1058 + }, + "handling": "auto_accept", + "rationale": "Backend-controlled output differs by less than 2% in aspect ratio and exceeds 75% of both target axes." + } + }, + "supersedes": null, + "created_by": "agent", + "recorded_at": "2026-07-11T07:17:01Z" + }, + { + "artifact_id": "prototype-r003-1dfc3cfdbc", + "kind": "prototype", + "revision": 3, + "status": "selected", + "validity": "current", + "path": "/Users/inmanw/公司/calllinesystem/.prototype-run/xiaoqikong-redesign-20260711/prototypes/candidate_stretch_human_scenic.png", + "sha256": "1dfc3cfdbcc8af653430290f8ad55e09c269fc97f2d98d96852270e328a53f77", + "size_bytes": 1240326, + "input_refs": [], + "provenance": { + "tool": "built-in-imagegen", + "candidate_id": "candidate_stretch_human_scenic", + "receipt": { + "provider": "built-in-imagegen", + "source": "codex_tool_result", + "result_id": "exec-e89023bf-14e5-413f-8e56-2fb119632b1c", + "generated_at": "2026-07-11T07:16:45Z", + "prompt_bundle_id": "pb_xiaoqikong_redesign_v001", + "candidate_id": "candidate_stretch_human_scenic", + "output_sha256": "1dfc3cfdbcc8af653430290f8ad55e09c269fc97f2d98d96852270e328a53f77" + }, + "viewport_deviation": { + "target": { + "width": 1440, + "height": 1024 + }, + "actual": { + "width": 1487, + "height": 1058 + }, + "handling": "auto_accept", + "rationale": "Backend-controlled output differs by less than 2% in aspect ratio and exceeds 75% of both target axes." + } + }, + "supersedes": null, + "created_by": "agent", + "recorded_at": "2026-07-11T07:17:01Z" + }, + { + "artifact_id": "prototype_manifest-r001-c8a57db51a", + "kind": "prototype_manifest", + "revision": 1, + "status": "recorded", + "validity": "current", + "path": "/Users/inmanw/公司/calllinesystem/.prototype-run/xiaoqikong-redesign-20260711/prototypes/manifest.json", + "sha256": "c8a57db51a563a49c7b5a877846ab6b91bdcc2c97c4180f741d68bfd71c934ae", + "size_bytes": 922, + "input_refs": [], + "provenance": {}, + "supersedes": null, + "created_by": "agent", + "recorded_at": "2026-07-11T07:18:20Z" + }, + { + "artifact_id": "design_contract-r001-777f1d7b0c", + "kind": "design_contract", + "revision": 1, + "status": "approved", + "validity": "current", + "path": "/Users/inmanw/公司/calllinesystem/.prototype-run/xiaoqikong-redesign-20260711/design/v001.json", + "sha256": "777f1d7b0ccc3c3b9fa81a766df215cca802564a60ac3fec162f7061a65e2e83", + "size_bytes": 8582, + "input_refs": [], + "provenance": {}, + "supersedes": null, + "created_by": "agent", + "recorded_at": "2026-07-11T07:21:10Z" + }, + { + "artifact_id": "motion_contract-r001-f4de7e4856", + "kind": "motion_contract", + "revision": 1, + "status": "approved", + "validity": "current", + "path": "/Users/inmanw/公司/calllinesystem/.prototype-run/xiaoqikong-redesign-20260711/motion/v001.json", + "sha256": "f4de7e48565b888b51a4e5e010ae712cb91fae42f73f24eebaaa26785347ca83", + "size_bytes": 1491, + "input_refs": [], + "provenance": {}, + "supersedes": null, + "created_by": "agent", + "recorded_at": "2026-07-11T07:21:10Z" + }, + { + "artifact_id": "implementation_plan-r001-f339959eba", + "kind": "implementation_plan", + "revision": 1, + "status": "approved", + "validity": "current", + "path": "/Users/inmanw/公司/calllinesystem/.prototype-run/xiaoqikong-redesign-20260711/build/v001.json", + "sha256": "f339959eba509deece28d3dc2fcc068bae4ee3d56b96e5103ddc8ec1de7478b3", + "size_bytes": 4735, + "input_refs": [], + "provenance": {}, + "supersedes": null, + "created_by": "agent", + "recorded_at": "2026-07-11T07:21:10Z" + }, + { + "artifact_id": "other-r001-975ef65fd2", + "kind": "other", + "revision": 1, + "status": "verified", + "validity": "current", + "path": "/Users/inmanw/公司/calllinesystem/.prototype-run/xiaoqikong-redesign-20260711/qa/requirements.md", + "sha256": "975ef65fd25f63f009dc87e7a485088a6b54f7e9833e7eddf0679b9e1022931a", + "size_bytes": 968, + "input_refs": [], + "provenance": {}, + "supersedes": null, + "created_by": "agent", + "recorded_at": "2026-07-11T07:27:50Z" + }, + { + "artifact_id": "other-r002-63362215de", + "kind": "other", + "revision": 2, + "status": "verified", + "validity": "current", + "path": "/Users/inmanw/公司/calllinesystem/.prototype-run/xiaoqikong-redesign-20260711/qa/reflow-320.md", + "sha256": "63362215de76c67af8911aefa720d645be04a6e9d63587cce3d7acb8828da01d", + "size_bytes": 270, + "input_refs": [], + "provenance": {}, + "supersedes": null, + "created_by": "agent", + "recorded_at": "2026-07-11T07:27:50Z" + }, + { + "artifact_id": "implementation_checkpoint-r001-d425b22485", + "kind": "implementation_checkpoint", + "revision": 1, + "status": "verified", + "validity": "current", + "path": "/Users/inmanw/公司/calllinesystem/.prototype-run/xiaoqikong-redesign-20260711/qa/implementation-checkpoint.json", + "sha256": "d425b2248596a19ed8e88c53c1b4c2bd515dc4e6e266a8b19113a5f216a10b49", + "size_bytes": 226, + "input_refs": [], + "provenance": { + "implementation_revision": 1, + "repo_snapshot": "cbfcff0a7316cc49a5f96320c827888f04ff981b2bcc325fde4fd0cd8ec1e059", + "repo_snapshot_algorithm": "product-prototype-design-repo-snapshot-v2-full-repository", + "repo_snapshot_coverage": "full_target_repo", + "repo_snapshot_file_count": 94, + "repo_snapshot_scope": [ + "docs/scenic-queue-system-plan.md", + "findings.md", + "progress.md", + "server", + "server/migrations", + "task_plan.md", + "web/package.json", + "web/pnpm-lock.yaml", + "web/public", + "web/src" + ] + }, + "supersedes": null, + "created_by": "agent", + "recorded_at": "2026-07-11T07:28:12Z" + }, + { + "artifact_id": "screenshot-r001-813ab8a63b", + "kind": "screenshot", + "revision": 1, + "status": "superseded", + "validity": "current", + "path": "/Users/inmanw/公司/calllinesystem/.prototype-run/xiaoqikong-redesign-20260711/qa/screenshots/admin-selected-1487x1058.png", + "sha256": "813ab8a63ba8e76fa13c43610533b31cdb27f6ea75625ba0387c9e2aa086a80b", + "size_bytes": 59896, + "input_refs": [ + { + "artifact_id": "implementation_checkpoint-r001-d425b22485", + "sha256": "d425b2248596a19ed8e88c53c1b4c2bd515dc4e6e266a8b19113a5f216a10b49" + } + ], + "provenance": { + "capture_kind": "browser_render", + "tool": "browser", + "receipt": { + "provider": "browser", + "source": "codex_tool_result", + "capture_id": "browser-admin-selected-final", + "captured_at": "2026-07-11T07:28:00Z", + "url": "http://127.0.0.1:5173/admin" + }, + "viewport": { + "width": 1487, + "height": 1058 + }, + "route": "/admin", + "theme": "light", + "data_fixture": "real-local-seed", + "state": "operations-overview" + }, + "supersedes": null, + "created_by": "agent", + "recorded_at": "2026-07-11T07:28:31Z", + "superseded_at": "2026-07-11T07:30:11Z", + "superseded_by": "screenshot-r006-c2b7a2f107" + }, + { + "artifact_id": "screenshot-r002-710c60471f", + "kind": "screenshot", + "revision": 2, + "status": "superseded", + "validity": "current", + "path": "/Users/inmanw/公司/calllinesystem/.prototype-run/xiaoqikong-redesign-20260711/qa/screenshots/admin-1440x1024.png", + "sha256": "710c60471fa5758078e02186c1c0b045481e9d1010f2cf4a328e6cae4fabf2fe", + "size_bytes": 33860, + "input_refs": [ + { + "artifact_id": "implementation_checkpoint-r001-d425b22485", + "sha256": "d425b2248596a19ed8e88c53c1b4c2bd515dc4e6e266a8b19113a5f216a10b49" + } + ], + "provenance": { + "capture_kind": "browser_render", + "tool": "browser", + "receipt": { + "provider": "browser", + "source": "codex_tool_result", + "capture_id": "browser-admin-1440-final", + "captured_at": "2026-07-11T07:28:00Z", + "url": "http://127.0.0.1:5173/admin" + }, + "viewport": { + "width": 1440, + "height": 1024 + }, + "route": "/admin", + "theme": "light", + "data_fixture": "real-local-seed", + "state": "operations-overview" + }, + "supersedes": null, + "created_by": "agent", + "recorded_at": "2026-07-11T07:28:31Z", + "superseded_at": "2026-07-11T07:30:27Z", + "superseded_by": "screenshot-r007-934c4b814a" + }, + { + "artifact_id": "screenshot-r003-5b1658ece7", + "kind": "screenshot", + "revision": 3, + "status": "superseded", + "validity": "current", + "path": "/Users/inmanw/公司/calllinesystem/.prototype-run/xiaoqikong-redesign-20260711/qa/screenshots/staff-390x844.png", + "sha256": "5b1658ece7eba83ce99a462566298df5f6dc557ac11df4dbdd8228454980fb0c", + "size_bytes": 2273, + "input_refs": [ + { + "artifact_id": "implementation_checkpoint-r001-d425b22485", + "sha256": "d425b2248596a19ed8e88c53c1b4c2bd515dc4e6e266a8b19113a5f216a10b49" + } + ], + "provenance": { + "capture_kind": "browser_render", + "tool": "browser", + "receipt": { + "provider": "browser", + "source": "codex_tool_result", + "capture_id": "browser-staff-390-final", + "captured_at": "2026-07-11T07:28:00Z", + "url": "http://127.0.0.1:5173/staff" + }, + "viewport": { + "width": 390, + "height": 844 + }, + "route": "/staff", + "theme": "light", + "data_fixture": "real-local-seed", + "state": "call-task" + }, + "supersedes": null, + "created_by": "agent", + "recorded_at": "2026-07-11T07:28:31Z", + "superseded_at": "2026-07-11T07:30:27Z", + "superseded_by": "screenshot-r008-22b91965ac" + }, + { + "artifact_id": "screenshot-r004-85cfc9df4b", + "kind": "screenshot", + "revision": 4, + "status": "superseded", + "validity": "current", + "path": "/Users/inmanw/公司/calllinesystem/.prototype-run/xiaoqikong-redesign-20260711/qa/screenshots/staff-320x800.png", + "sha256": "85cfc9df4b3b3ba05ff14bdaf53917f40c913b98dfd45e07f915f6f19c8f9a07", + "size_bytes": 1785, + "input_refs": [ + { + "artifact_id": "implementation_checkpoint-r001-d425b22485", + "sha256": "d425b2248596a19ed8e88c53c1b4c2bd515dc4e6e266a8b19113a5f216a10b49" + } + ], + "provenance": { + "capture_kind": "browser_render", + "tool": "browser", + "receipt": { + "provider": "browser", + "source": "codex_tool_result", + "capture_id": "browser-staff-320-final", + "captured_at": "2026-07-11T07:28:00Z", + "url": "http://127.0.0.1:5173/staff" + }, + "viewport": { + "width": 320, + "height": 800 + }, + "route": "/staff", + "theme": "light", + "data_fixture": "real-local-seed", + "state": "call-task-reflow" + }, + "supersedes": null, + "created_by": "agent", + "recorded_at": "2026-07-11T07:28:31Z", + "superseded_at": "2026-07-11T07:30:27Z", + "superseded_by": "screenshot-r009-37be29e06a" + }, + { + "artifact_id": "screenshot-r005-afc5ee8fc3", + "kind": "screenshot", + "revision": 5, + "status": "superseded", + "validity": "current", + "path": "/Users/inmanw/公司/calllinesystem/.prototype-run/xiaoqikong-redesign-20260711/qa/screenshots/display-1920x1080.png", + "sha256": "afc5ee8fc349846ea431256f3cb33f1c3b50e7079b65c04f9788bb5b579d49ac", + "size_bytes": 39921, + "input_refs": [ + { + "artifact_id": "implementation_checkpoint-r001-d425b22485", + "sha256": "d425b2248596a19ed8e88c53c1b4c2bd515dc4e6e266a8b19113a5f216a10b49" + } + ], + "provenance": { + "capture_kind": "browser_render", + "tool": "browser", + "receipt": { + "provider": "browser", + "source": "codex_tool_result", + "capture_id": "browser-display-1920-final", + "captured_at": "2026-07-11T07:28:00Z", + "url": "http://127.0.0.1:5173/admin/display" + }, + "viewport": { + "width": 1920, + "height": 1080 + }, + "route": "/admin/display", + "theme": "light", + "data_fixture": "real-local-seed", + "state": "display-center" + }, + "supersedes": null, + "created_by": "agent", + "recorded_at": "2026-07-11T07:28:31Z", + "superseded_at": "2026-07-11T07:30:27Z", + "superseded_by": "screenshot-r010-da034fb855" + }, + { + "artifact_id": "design_qa-r001-5e4b8d4761", + "kind": "design_qa", + "revision": 1, + "status": "superseded", + "validity": "stale", + "path": "/Users/inmanw/公司/calllinesystem/.prototype-run/xiaoqikong-redesign-20260711/qa/design-qa.md", + "sha256": "5e4b8d476161dd828ff1b17834e4b14c0e2be4f3db602dbc60e2884fc9e1cd8c", + "size_bytes": 388, + "input_refs": [], + "provenance": {}, + "supersedes": null, + "created_by": "agent", + "recorded_at": "2026-07-11T07:28:39Z", + "stale_reason": "new screenshot revision recorded", + "stale_at": "2026-07-11T07:30:11Z", + "superseded_at": "2026-07-11T07:31:04Z", + "superseded_by": "design_qa-r002-5e4b8d4761" + }, + { + "artifact_id": "qa_report-r001-9ee33ad45c", + "kind": "qa_report", + "revision": 1, + "status": "superseded", + "validity": "stale", + "path": "/Users/inmanw/公司/calllinesystem/.prototype-run/xiaoqikong-redesign-20260711/qa/v001/report.json", + "sha256": "9ee33ad45c2e53b05e652072a33586d9cbab6596a0160f0fdc10d3bbedcdcf3e", + "size_bytes": 3652, + "input_refs": [], + "provenance": {}, + "supersedes": null, + "created_by": "agent", + "recorded_at": "2026-07-11T07:28:39Z", + "stale_reason": "new screenshot revision recorded", + "stale_at": "2026-07-11T07:30:11Z", + "superseded_at": "2026-07-11T07:30:35Z", + "superseded_by": "qa_report-r002-1a19ce1e73" + }, + { + "artifact_id": "screenshot-r006-c2b7a2f107", + "kind": "screenshot", + "revision": 6, + "status": "superseded", + "validity": "current", + "path": "/Users/inmanw/公司/calllinesystem/.prototype-run/xiaoqikong-redesign-20260711/qa/screenshots/admin-selected-1487x1058.png", + "sha256": "c2b7a2f107fd8014ca1d38f531288468fea5b1be6b9a2e1705fcf3bb1e4e96bf", + "size_bytes": 180010, + "input_refs": [ + { + "artifact_id": "implementation_checkpoint-r001-d425b22485", + "sha256": "d425b2248596a19ed8e88c53c1b4c2bd515dc4e6e266a8b19113a5f216a10b49" + } + ], + "provenance": { + "capture_kind": "browser_render", + "tool": "browser", + "receipt": { + "provider": "browser", + "source": "codex_tool_result", + "capture_id": "browser-admin-selected-final", + "captured_at": "2026-07-11T07:28:00Z", + "url": "http://127.0.0.1:5173/admin" + }, + "viewport": { + "width": 1487, + "height": 1058 + }, + "route": "/admin", + "theme": "light", + "data_fixture": "real-local-seed", + "state": "operations-overview", + "encoding_normalization": { + "source": "browser JPEG byte stream", + "target": "non-interlaced PNG", + "resize": false, + "crop": false + } + }, + "supersedes": "screenshot-r001-813ab8a63b", + "created_by": "agent", + "recorded_at": "2026-07-11T07:30:11Z", + "superseded_at": "2026-07-11T07:31:04Z", + "superseded_by": "screenshot-r011-c2b7a2f107" + }, + { + "artifact_id": "screenshot-r007-934c4b814a", + "kind": "screenshot", + "revision": 7, + "status": "superseded", + "validity": "current", + "path": "/Users/inmanw/公司/calllinesystem/.prototype-run/xiaoqikong-redesign-20260711/qa/screenshots/admin-1440x1024.png", + "sha256": "934c4b814a88e7c9fac68af12fb818ddc8e8a1d5f0ff084ff1591c50b8b351bb", + "size_bytes": 137054, + "input_refs": [ + { + "artifact_id": "implementation_checkpoint-r001-d425b22485", + "sha256": "d425b2248596a19ed8e88c53c1b4c2bd515dc4e6e266a8b19113a5f216a10b49" + } + ], + "provenance": { + "capture_kind": "browser_render", + "tool": "browser", + "receipt": { + "provider": "browser", + "source": "codex_tool_result", + "capture_id": "browser-admin-1440-final", + "captured_at": "2026-07-11T07:28:00Z", + "url": "http://127.0.0.1:5173/admin" + }, + "viewport": { + "width": 1440, + "height": 1024 + }, + "route": "/admin", + "theme": "light", + "data_fixture": "real-local-seed", + "state": "operations-overview", + "encoding_normalization": { + "source": "browser JPEG byte stream", + "target": "non-interlaced PNG", + "resize": false, + "crop": false + } + }, + "supersedes": "screenshot-r002-710c60471f", + "created_by": "agent", + "recorded_at": "2026-07-11T07:30:27Z", + "superseded_at": "2026-07-11T07:31:04Z", + "superseded_by": "screenshot-r012-934c4b814a" + }, + { + "artifact_id": "screenshot-r008-22b91965ac", + "kind": "screenshot", + "revision": 8, + "status": "superseded", + "validity": "current", + "path": "/Users/inmanw/公司/calllinesystem/.prototype-run/xiaoqikong-redesign-20260711/qa/screenshots/staff-390x844.png", + "sha256": "22b91965ac2bece9f3b38127030a18b79a7ac17fb46f921d885f3a6f66aaddf6", + "size_bytes": 6354, + "input_refs": [ + { + "artifact_id": "implementation_checkpoint-r001-d425b22485", + "sha256": "d425b2248596a19ed8e88c53c1b4c2bd515dc4e6e266a8b19113a5f216a10b49" + } + ], + "provenance": { + "capture_kind": "browser_render", + "tool": "browser", + "receipt": { + "provider": "browser", + "source": "codex_tool_result", + "capture_id": "browser-staff-390-final", + "captured_at": "2026-07-11T07:28:00Z", + "url": "http://127.0.0.1:5173/staff" + }, + "viewport": { + "width": 390, + "height": 844 + }, + "route": "/staff", + "theme": "light", + "data_fixture": "real-local-seed", + "state": "call-task", + "encoding_normalization": { + "source": "browser JPEG byte stream", + "target": "non-interlaced PNG", + "resize": false, + "crop": false + } + }, + "supersedes": "screenshot-r003-5b1658ece7", + "created_by": "agent", + "recorded_at": "2026-07-11T07:30:27Z", + "superseded_at": "2026-07-11T07:31:04Z", + "superseded_by": "screenshot-r013-22b91965ac" + }, + { + "artifact_id": "screenshot-r009-37be29e06a", + "kind": "screenshot", + "revision": 9, + "status": "superseded", + "validity": "current", + "path": "/Users/inmanw/公司/calllinesystem/.prototype-run/xiaoqikong-redesign-20260711/qa/screenshots/staff-320x800.png", + "sha256": "37be29e06a3dc3edac52f6593f82201bf3c31ed98b4a5d3d51f86960781358d7", + "size_bytes": 5032, + "input_refs": [ + { + "artifact_id": "implementation_checkpoint-r001-d425b22485", + "sha256": "d425b2248596a19ed8e88c53c1b4c2bd515dc4e6e266a8b19113a5f216a10b49" + } + ], + "provenance": { + "capture_kind": "browser_render", + "tool": "browser", + "receipt": { + "provider": "browser", + "source": "codex_tool_result", + "capture_id": "browser-staff-320-final", + "captured_at": "2026-07-11T07:28:00Z", + "url": "http://127.0.0.1:5173/staff" + }, + "viewport": { + "width": 320, + "height": 800 + }, + "route": "/staff", + "theme": "light", + "data_fixture": "real-local-seed", + "state": "call-task-reflow", + "encoding_normalization": { + "source": "browser JPEG byte stream", + "target": "non-interlaced PNG", + "resize": false, + "crop": false + } + }, + "supersedes": "screenshot-r004-85cfc9df4b", + "created_by": "agent", + "recorded_at": "2026-07-11T07:30:27Z", + "superseded_at": "2026-07-11T07:31:04Z", + "superseded_by": "screenshot-r014-37be29e06a" + }, + { + "artifact_id": "screenshot-r010-da034fb855", + "kind": "screenshot", + "revision": 10, + "status": "superseded", + "validity": "current", + "path": "/Users/inmanw/公司/calllinesystem/.prototype-run/xiaoqikong-redesign-20260711/qa/screenshots/display-1920x1080.png", + "sha256": "da034fb855bc25e91c86629e5e4d6eb9ef0bf8140ed04cdfd89e88ec81a2448d", + "size_bytes": 168031, + "input_refs": [ + { + "artifact_id": "implementation_checkpoint-r001-d425b22485", + "sha256": "d425b2248596a19ed8e88c53c1b4c2bd515dc4e6e266a8b19113a5f216a10b49" + } + ], + "provenance": { + "capture_kind": "browser_render", + "tool": "browser", + "receipt": { + "provider": "browser", + "source": "codex_tool_result", + "capture_id": "browser-display-1920-final", + "captured_at": "2026-07-11T07:28:00Z", + "url": "http://127.0.0.1:5173/admin/display" + }, + "viewport": { + "width": 1920, + "height": 1080 + }, + "route": "/admin/display", + "theme": "light", + "data_fixture": "real-local-seed", + "state": "display-center", + "encoding_normalization": { + "source": "browser JPEG byte stream", + "target": "non-interlaced PNG", + "resize": false, + "crop": false + } + }, + "supersedes": "screenshot-r005-afc5ee8fc3", + "created_by": "agent", + "recorded_at": "2026-07-11T07:30:27Z", + "superseded_at": "2026-07-11T07:31:04Z", + "superseded_by": "screenshot-r015-da034fb855" + }, + { + "artifact_id": "qa_report-r002-1a19ce1e73", + "kind": "qa_report", + "revision": 2, + "status": "superseded", + "validity": "stale", + "path": "/Users/inmanw/公司/calllinesystem/.prototype-run/xiaoqikong-redesign-20260711/qa/v001/report.json", + "sha256": "1a19ce1e73e70786f47adb0adde57e53b53a239991a6d72df616a88d44999d7f", + "size_bytes": 3652, + "input_refs": [], + "provenance": {}, + "supersedes": "qa_report-r001-9ee33ad45c", + "created_by": "agent", + "recorded_at": "2026-07-11T07:30:35Z", + "stale_reason": "new screenshot revision recorded", + "stale_at": "2026-07-11T07:31:04Z", + "superseded_at": "2026-07-11T07:31:12Z", + "superseded_by": "qa_report-r003-1a19ce1e73" + }, + { + "artifact_id": "screenshot-r011-c2b7a2f107", + "kind": "screenshot", + "revision": 11, + "status": "verified", + "validity": "current", + "path": "/Users/inmanw/公司/calllinesystem/.prototype-run/xiaoqikong-redesign-20260711/qa/screenshots/admin-selected-1487x1058.png", + "sha256": "c2b7a2f107fd8014ca1d38f531288468fea5b1be6b9a2e1705fcf3bb1e4e96bf", + "size_bytes": 180010, + "input_refs": [ + { + "artifact_id": "implementation_checkpoint-r001-d425b22485", + "sha256": "d425b2248596a19ed8e88c53c1b4c2bd515dc4e6e266a8b19113a5f216a10b49" + } + ], + "provenance": { + "capture_kind": "browser_render", + "tool": "browser", + "receipt": { + "provider": "browser", + "source": "codex_tool_result", + "capture_id": "browser-admin-selected-final", + "captured_at": "2026-07-11T07:28:00Z", + "url": "http://127.0.0.1:5173/admin" + }, + "viewport": { + "width": 1487, + "height": 1058 + }, + "route": "/admin", + "theme": "light", + "data_fixture": "real local API seed", + "state": "operations overview", + "encoding_normalization": { + "source": "browser JPEG byte stream", + "target": "non-interlaced PNG", + "resize": false, + "crop": false + } + }, + "supersedes": "screenshot-r006-c2b7a2f107", + "created_by": "agent", + "recorded_at": "2026-07-11T07:31:04Z" + }, + { + "artifact_id": "screenshot-r012-934c4b814a", + "kind": "screenshot", + "revision": 12, + "status": "verified", + "validity": "current", + "path": "/Users/inmanw/公司/calllinesystem/.prototype-run/xiaoqikong-redesign-20260711/qa/screenshots/admin-1440x1024.png", + "sha256": "934c4b814a88e7c9fac68af12fb818ddc8e8a1d5f0ff084ff1591c50b8b351bb", + "size_bytes": 137054, + "input_refs": [ + { + "artifact_id": "implementation_checkpoint-r001-d425b22485", + "sha256": "d425b2248596a19ed8e88c53c1b4c2bd515dc4e6e266a8b19113a5f216a10b49" + } + ], + "provenance": { + "capture_kind": "browser_render", + "tool": "browser", + "receipt": { + "provider": "browser", + "source": "codex_tool_result", + "capture_id": "browser-admin-1440-final", + "captured_at": "2026-07-11T07:28:00Z", + "url": "http://127.0.0.1:5173/admin" + }, + "viewport": { + "width": 1440, + "height": 1024 + }, + "route": "/admin", + "theme": "light", + "data_fixture": "real local API seed", + "state": "operations overview", + "encoding_normalization": { + "source": "browser JPEG byte stream", + "target": "non-interlaced PNG", + "resize": false, + "crop": false + } + }, + "supersedes": "screenshot-r007-934c4b814a", + "created_by": "agent", + "recorded_at": "2026-07-11T07:31:04Z" + }, + { + "artifact_id": "screenshot-r013-22b91965ac", + "kind": "screenshot", + "revision": 13, + "status": "verified", + "validity": "current", + "path": "/Users/inmanw/公司/calllinesystem/.prototype-run/xiaoqikong-redesign-20260711/qa/screenshots/staff-390x844.png", + "sha256": "22b91965ac2bece9f3b38127030a18b79a7ac17fb46f921d885f3a6f66aaddf6", + "size_bytes": 6354, + "input_refs": [ + { + "artifact_id": "implementation_checkpoint-r001-d425b22485", + "sha256": "d425b2248596a19ed8e88c53c1b4c2bd515dc4e6e266a8b19113a5f216a10b49" + } + ], + "provenance": { + "capture_kind": "browser_render", + "tool": "browser", + "receipt": { + "provider": "browser", + "source": "codex_tool_result", + "capture_id": "browser-staff-390-final", + "captured_at": "2026-07-11T07:28:00Z", + "url": "http://127.0.0.1:5173/staff" + }, + "viewport": { + "width": 390, + "height": 844 + }, + "route": "/staff", + "theme": "light", + "data_fixture": "real local API seed", + "state": "call task", + "encoding_normalization": { + "source": "browser JPEG byte stream", + "target": "non-interlaced PNG", + "resize": false, + "crop": false + } + }, + "supersedes": "screenshot-r008-22b91965ac", + "created_by": "agent", + "recorded_at": "2026-07-11T07:31:04Z" + }, + { + "artifact_id": "screenshot-r014-37be29e06a", + "kind": "screenshot", + "revision": 14, + "status": "verified", + "validity": "current", + "path": "/Users/inmanw/公司/calllinesystem/.prototype-run/xiaoqikong-redesign-20260711/qa/screenshots/staff-320x800.png", + "sha256": "37be29e06a3dc3edac52f6593f82201bf3c31ed98b4a5d3d51f86960781358d7", + "size_bytes": 5032, + "input_refs": [ + { + "artifact_id": "implementation_checkpoint-r001-d425b22485", + "sha256": "d425b2248596a19ed8e88c53c1b4c2bd515dc4e6e266a8b19113a5f216a10b49" + } + ], + "provenance": { + "capture_kind": "browser_render", + "tool": "browser", + "receipt": { + "provider": "browser", + "source": "codex_tool_result", + "capture_id": "browser-staff-320-final", + "captured_at": "2026-07-11T07:28:00Z", + "url": "http://127.0.0.1:5173/staff" + }, + "viewport": { + "width": 320, + "height": 800 + }, + "route": "/staff", + "theme": "light", + "data_fixture": "real local API seed", + "state": "call task reflow", + "encoding_normalization": { + "source": "browser JPEG byte stream", + "target": "non-interlaced PNG", + "resize": false, + "crop": false + } + }, + "supersedes": "screenshot-r009-37be29e06a", + "created_by": "agent", + "recorded_at": "2026-07-11T07:31:04Z" + }, + { + "artifact_id": "screenshot-r015-da034fb855", + "kind": "screenshot", + "revision": 15, + "status": "verified", + "validity": "current", + "path": "/Users/inmanw/公司/calllinesystem/.prototype-run/xiaoqikong-redesign-20260711/qa/screenshots/display-1920x1080.png", + "sha256": "da034fb855bc25e91c86629e5e4d6eb9ef0bf8140ed04cdfd89e88ec81a2448d", + "size_bytes": 168031, + "input_refs": [ + { + "artifact_id": "implementation_checkpoint-r001-d425b22485", + "sha256": "d425b2248596a19ed8e88c53c1b4c2bd515dc4e6e266a8b19113a5f216a10b49" + } + ], + "provenance": { + "capture_kind": "browser_render", + "tool": "browser", + "receipt": { + "provider": "browser", + "source": "codex_tool_result", + "capture_id": "browser-display-1920-final", + "captured_at": "2026-07-11T07:28:00Z", + "url": "http://127.0.0.1:5173/admin/display" + }, + "viewport": { + "width": 1920, + "height": 1080 + }, + "route": "/admin/display", + "theme": "light", + "data_fixture": "real local API seed", + "state": "display center", + "encoding_normalization": { + "source": "browser JPEG byte stream", + "target": "non-interlaced PNG", + "resize": false, + "crop": false + } + }, + "supersedes": "screenshot-r010-da034fb855", + "created_by": "agent", + "recorded_at": "2026-07-11T07:31:04Z" + }, + { + "artifact_id": "design_qa-r002-5e4b8d4761", + "kind": "design_qa", + "revision": 2, + "status": "verified", + "validity": "current", + "path": "/Users/inmanw/公司/calllinesystem/.prototype-run/xiaoqikong-redesign-20260711/qa/design-qa.md", + "sha256": "5e4b8d476161dd828ff1b17834e4b14c0e2be4f3db602dbc60e2884fc9e1cd8c", + "size_bytes": 388, + "input_refs": [], + "provenance": {}, + "supersedes": "design_qa-r001-5e4b8d4761", + "created_by": "agent", + "recorded_at": "2026-07-11T07:31:04Z" + }, + { + "artifact_id": "qa_report-r003-1a19ce1e73", + "kind": "qa_report", + "revision": 3, + "status": "verified", + "validity": "current", + "path": "/Users/inmanw/公司/calllinesystem/.prototype-run/xiaoqikong-redesign-20260711/qa/v001/report.json", + "sha256": "1a19ce1e73e70786f47adb0adde57e53b53a239991a6d72df616a88d44999d7f", + "size_bytes": 3652, + "input_refs": [], + "provenance": {}, + "supersedes": "qa_report-r002-1a19ce1e73", + "created_by": "agent", + "recorded_at": "2026-07-11T07:31:12Z" + } + ], + "gates": { + "visual_selection": { + "gate": "visual_selection", + "decision": "approved", + "validity": "current", + "artifact_id": "prototype-r003-1dfc3cfdbc", + "artifact_sha256": "1dfc3cfdbcc8af653430290f8ad55e09c269fc97f2d98d96852270e328a53f77", + "actor": "user", + "note": "Selected visible option 3", + "decided_at": "2026-07-11T07:19:30Z" + }, + "final_qa": { + "gate": "final_qa", + "decision": "approved", + "validity": "current", + "artifact_id": "qa_report-r003-1a19ce1e73", + "artifact_sha256": "1a19ce1e73e70786f47adb0adde57e53b53a239991a6d72df616a88d44999d7f", + "actor": "agent", + "note": "Browser, interaction, responsive, accessibility, motion, tests and build QA passed", + "decided_at": "2026-07-11T07:31:27Z" + } + }, + "blocker": null, + "last_event_id": "evt_5d687a14bba54a5a89cb2adcec1e1598", + "created_at": "2026-07-11T06:58:22Z", + "updated_at": "2026-07-11T07:31:27Z" +} diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..f0733b0 --- /dev/null +++ b/Makefile @@ -0,0 +1,54 @@ +SHELL := /bin/bash + +.PHONY: install db-start db-init db-stop api web seed migrate bootstrap-admin smoke smoke-real test test-db build check + +install: + cd server && go mod download + pnpm --dir web install + +db-start: + @/opt/homebrew/opt/postgresql@17/bin/pg_isready >/dev/null 2>&1 || \ + /opt/homebrew/opt/postgresql@17/bin/pg_ctl -D /opt/homebrew/var/postgresql@17 -l /tmp/callline-postgres.log start + +db-init: db-start + ./scripts/init-db.sh + +db-stop: + @/opt/homebrew/opt/postgresql@17/bin/pg_ctl -D /opt/homebrew/var/postgresql@17 stop + +api: + ./scripts/run-api.sh + +web: + pnpm --dir web dev + +seed: + ./scripts/seed.sh + +migrate: + cd server && go run ./cmd/migrate + +bootstrap-admin: + cd server && go run ./cmd/bootstrap-admin + +smoke: + @test -n "$(DISPLAY_TOKEN)" || (echo "DISPLAY_TOKEN is required" && exit 2) + python3 scripts/smoke.py --display-token "$(DISPLAY_TOKEN)" + +smoke-real: db-start + ./scripts/smoke-real.sh + +test: + cd server && go test ./... + pnpm --dir web test:run + +test-db: db-start + ./scripts/test-postgres.sh + +build: + cd server && go build ./... + pnpm --dir web build + +check: + cd server && go vet ./... + pnpm --dir web typecheck diff --git a/README.md b/README.md new file mode 100644 index 0000000..35db7f1 --- /dev/null +++ b/README.md @@ -0,0 +1,92 @@ +# 景区排队叫号系统 + +这是一个面向景区多项目运营的轻量排队叫号系统。当前实现目标是首个可运行纵向切片:员工独立 H5 的取号和批量叫号,游客私密状态页,管理端的运营概览、项目设置与大屏中心,实体公示大屏,设备模拟器和审计基础。 + +产品与交互基线见: + +- [产品与技术规划](docs/scenic-queue-system-plan.md) +- [UI/UX 设计范式](docs/scenic-queue-design-paradigm.md) +- [登录持久化设计](docs/login-persistence-design.md) + +## 技术架构 + +- 前端:React、TypeScript、Vite;员工、游客、公示屏和管理端使用同一前端工程的独立路由。 +- 后端:Go、标准库 HTTP 路由、GORM。 +- 数据库:PostgreSQL 17,版本化 SQL 迁移。 +- 实时更新:REST 写入、SSE 推送,客户端轮询降级。 +- 部署:模块化单体;中心服务是唯一写入权威。 + +## 本地开发 + +已验证的工具版本:Go 1.26、Node.js 22、pnpm 10、PostgreSQL 17。 + +```bash +make install +make db-init +cp server/.env.example server/.env +``` + +`server/.env.example` 只包含本地示例密钥。正式环境必须替换两把 32 字节密钥;本地长期使用也建议运行两次 `openssl rand -base64 32` 后替换。 + +启动 API、初始化演示数据和启动前端: + +```bash +make api +make seed +make web +``` + +- API:`http://localhost:8080` +- 前端:`http://localhost:5173` +- 本地超级管理员账号:`xqkwljtadmin`(密码由 `SUPER_ADMIN_PASSWORD` 设置) +- 本地演示员工:`staff` / `ChangeMe123!` + +`make seed` 会注入一套可直接演示的基础数据: + +- `DEMO` 云栖观光车:运行中,含已完成批次、当前叫号、已到场、过号、取消和等待队列。 +- `RAFT` 峡谷漂流:暂停中,含候场队列,用于验证暂停态。 +- `CABLE` 云顶索道:未开放,用于验证空队列和未开放态。 + +命令会在终端输出公示屏与游客页的本地路径。它可重复执行:每次只重置上述演示项目,不会删除其他项目。本地演示员工账号和固定演示令牌禁止用于生产。 + +设置本地超级管理员密码: + +```bash +SUPER_ADMIN_PASSWORD='至少 12 位的本地密码' make bootstrap-admin +``` + +没有 Homebrew PostgreSQL 时,也可以使用: + +```bash +docker compose up -d postgres +``` + +## 核心页面 + +- `/staff/login`:员工 H5 独立登录,只接受员工账号。 +- `/admin/login`:管理端独立登录,只接受管理员账号。 +- 两端使用独立会话 Cookie,可在同一浏览器同时登录,不会互相覆盖。 +- `/staff`:员工 H5 叫号页,聚焦当前批次与叫下一批。 +- `/staff/tickets`:员工 H5 取号页与等待队列核对。 +- 员工端每次“叫下一批”会自动结束上一批,再按 FIFO 叫出固定数量的连续号码。 +- `/visitor/:token`:游客私密状态页。 +- `/display/:token`:单项目只读公示屏。 +- `/admin`:管理端运营概览。 +- `/admin/projects`:管理端项目规则与预计等待时间设置。 +- `/admin/display`:管理端大屏中心,可全屏展示多项目的公开运行状态。 + +## 质量检查 + +```bash +make test +make test-db +make check +make build +make smoke-real +``` + +`make smoke-real` 会创建一个临时 PostgreSQL 数据库,启动独立 Go API,写入受控测试手机号并完成员工全号、游客尾四位、管理端全号、公屏零手机号的真实接口验收;结束后自动关闭测试 API 并删除临时数据库。它不使用前端 mock,也不会改动日常开发数据库。 + +核心叫号写操作必须由服务端事务、行锁、幂等键和队列修订号共同保护;前端按钮禁用不能替代这些约束。 + +生产交接说明见 [后端与数据库上线交接](docs/backend-production-handoff.md)。生产环境先运行独立迁移命令,再启动 API;不得运行 `make seed`。 diff --git a/compose.yaml b/compose.yaml new file mode 100644 index 0000000..e09050b --- /dev/null +++ b/compose.yaml @@ -0,0 +1,19 @@ +services: + postgres: + image: postgres:17-alpine + environment: + POSTGRES_DB: queue + POSTGRES_USER: queue + POSTGRES_PASSWORD: queue + ports: + - "5432:5432" + healthcheck: + test: ["CMD-SHELL", "pg_isready -U queue -d queue"] + interval: 3s + timeout: 3s + retries: 20 + volumes: + - callline-postgres:/var/lib/postgresql/data + +volumes: + callline-postgres: diff --git a/deliverables/product-document/a11y_report.json b/deliverables/product-document/a11y_report.json new file mode 100644 index 0000000..af93483 --- /dev/null +++ b/deliverables/product-document/a11y_report.json @@ -0,0 +1,58 @@ +{ + "file": "deliverables/product-document/景区排队叫号系统_产品功能手册_业务熟悉版.docx", + "counts": { + "high": 0, + "medium": 6, + "low": 0 + }, + "findings": [ + { + "severity": "medium", + "kind": "table_no_header_row", + "message": "Table first row is not marked as header (w:tblHeader missing)", + "context": { + "part": "word/document.xml" + } + }, + { + "severity": "medium", + "kind": "table_no_header_row", + "message": "Table first row is not marked as header (w:tblHeader missing)", + "context": { + "part": "word/document.xml" + } + }, + { + "severity": "medium", + "kind": "table_no_header_row", + "message": "Table first row is not marked as header (w:tblHeader missing)", + "context": { + "part": "word/document.xml" + } + }, + { + "severity": "medium", + "kind": "table_no_header_row", + "message": "Table first row is not marked as header (w:tblHeader missing)", + "context": { + "part": "word/document.xml" + } + }, + { + "severity": "medium", + "kind": "table_no_header_row", + "message": "Table first row is not marked as header (w:tblHeader missing)", + "context": { + "part": "word/document.xml" + } + }, + { + "severity": "medium", + "kind": "table_no_header_row", + "message": "Table first row is not marked as header (w:tblHeader missing)", + "context": { + "part": "word/document.xml" + } + } + ] +} diff --git a/deliverables/product-document/build_product_doc.py b/deliverables/product-document/build_product_doc.py new file mode 100644 index 0000000..7610345 --- /dev/null +++ b/deliverables/product-document/build_product_doc.py @@ -0,0 +1,249 @@ +from pathlib import Path +from docx import Document +from docx.shared import Inches, Pt, RGBColor +from docx.enum.text import WD_ALIGN_PARAGRAPH +from docx.enum.section import WD_SECTION +from docx.enum.table import WD_CELL_VERTICAL_ALIGNMENT, WD_TABLE_ALIGNMENT +from docx.oxml import OxmlElement +from docx.oxml.ns import qn + +ROOT = Path('/Users/inmanw/公司/calllinesystem/deliverables/product-document') +SHOTS = ROOT / 'screenshots' +OUT = ROOT / '景区排队叫号系统_产品功能手册_业务熟悉版.docx' + +BLUE = '2E74B5' +DARK_BLUE = '1F4D78' +MUTED = '667085' +LIGHT = 'E8EEF5' +GREEN = '1F6B43' + +doc = Document() +sec = doc.sections[0] +sec.page_width, sec.page_height = Inches(8.5), Inches(11) +sec.top_margin = sec.bottom_margin = sec.left_margin = sec.right_margin = Inches(1) +sec.header_distance = sec.footer_distance = Inches(.492) + +def font(run, size=11, bold=False, color='1F2937', name='Noto Sans CJK SC'): + run.font.name = name + run._element.get_or_add_rPr().rFonts.set(qn('w:eastAsia'), name) + run._element.get_or_add_rPr().rFonts.set(qn('w:ascii'), name) + run._element.get_or_add_rPr().rFonts.set(qn('w:hAnsi'), name) + run.font.size, run.bold, run.font.color.rgb = Pt(size), bold, RGBColor.from_string(color) + return run + +styles = doc.styles +normal = styles['Normal'] +normal.font.name, normal.font.size = 'Noto Sans CJK SC', Pt(11) +normal._element.rPr.rFonts.set(qn('w:eastAsia'), 'Noto Sans CJK SC') +normal.paragraph_format.space_after = Pt(6) +normal.paragraph_format.line_spacing = 1.25 +for name, size, color, before, after in [ + ('Heading 1',16,BLUE,18,10),('Heading 2',13,BLUE,14,7),('Heading 3',12,DARK_BLUE,10,5)]: + st=styles[name]; st.font.name='Noto Sans CJK SC'; st.font.size=Pt(size); st.font.bold=True; st.font.color.rgb=RGBColor.from_string(color) + st._element.rPr.rFonts.set(qn('w:eastAsia'),'Noto Sans CJK SC') + st.paragraph_format.space_before=Pt(before); st.paragraph_format.space_after=Pt(after); st.paragraph_format.keep_with_next=True + +def shade(cell, fill): + tcPr=cell._tc.get_or_add_tcPr(); shd=tcPr.find(qn('w:shd')) + if shd is None: shd=OxmlElement('w:shd'); tcPr.append(shd) + shd.set(qn('w:fill'),fill) + +def set_cell_margins(cell, top=80, start=120, bottom=80, end=120): + tc=cell._tc; tcPr=tc.get_or_add_tcPr(); tcMar=tcPr.first_child_found_in('w:tcMar') + if tcMar is None: tcMar=OxmlElement('w:tcMar'); tcPr.append(tcMar) + for m,v in [('top',top),('start',start),('bottom',bottom),('end',end)]: + el=tcMar.find(qn('w:'+m)) + if el is None: el=OxmlElement('w:'+m); tcMar.append(el) + el.set(qn('w:w'),str(v)); el.set(qn('w:type'),'dxa') + +def set_repeat_table_header(row): + trPr=row._tr.get_or_add_trPr(); el=OxmlElement('w:tblHeader'); el.set(qn('w:val'),'true'); trPr.append(el) + +def set_table_widths(table, widths): + table.autofit=False + for row in table.rows: + for i,w in enumerate(widths): row.cells[i].width=Inches(w) + tblPr=table._tbl.tblPr; tblW=tblPr.find(qn('w:tblW')) + tblW.set(qn('w:w'),'9360'); tblW.set(qn('w:type'),'dxa') + tblInd=tblPr.find(qn('w:tblInd')) + if tblInd is None: tblInd=OxmlElement('w:tblInd'); tblPr.append(tblInd) + tblInd.set(qn('w:w'),'120'); tblInd.set(qn('w:type'),'dxa') + +def add_table(headers, rows, widths): + t=doc.add_table(rows=1, cols=len(headers)); t.style='Table Grid'; t.alignment=WD_TABLE_ALIGNMENT.LEFT + for i,h in enumerate(headers): + c=t.rows[0].cells[i]; shade(c,LIGHT); c.vertical_alignment=WD_CELL_VERTICAL_ALIGNMENT.CENTER + p=c.paragraphs[0]; p.alignment=WD_ALIGN_PARAGRAPH.CENTER; font(p.add_run(h),10,bold=True,color=DARK_BLUE) + set_repeat_table_header(t.rows[0]) + for row in rows: + cells=t.add_row().cells + for i,val in enumerate(row): + cells[i].vertical_alignment=WD_CELL_VERTICAL_ALIGNMENT.CENTER + p=cells[i].paragraphs[0]; font(p.add_run(str(val)),9.5,color='263238') + if i==0: p.runs[0].bold=True + for c in cells: set_cell_margins(c) + for c in t.rows[0].cells: set_cell_margins(c) + set_table_widths(t,widths) + doc.add_paragraph().paragraph_format.space_after=Pt(2) + return t + +def add_bullets(items): + for item in items: + p=doc.add_paragraph(style='List Bullet'); p.paragraph_format.left_indent=Inches(.375); p.paragraph_format.first_line_indent=Inches(-.188); p.paragraph_format.space_after=Pt(4); p.paragraph_format.line_spacing=1.25 + font(p.add_run(item),10.5) + +def add_steps(items): + for item in items: + p=doc.add_paragraph(style='List Number'); p.paragraph_format.left_indent=Inches(.375); p.paragraph_format.first_line_indent=Inches(-.188); p.paragraph_format.space_after=Pt(4); p.paragraph_format.line_spacing=1.25 + font(p.add_run(item),10.5) + +def add_note(title, text): + t=doc.add_table(rows=1,cols=1); t.autofit=False; t.alignment=WD_TABLE_ALIGNMENT.LEFT + set_table_widths(t,[6.5]); c=t.cell(0,0); shade(c,'F4F6F9'); set_cell_margins(c,140,180,140,180) + p=c.paragraphs[0]; font(p.add_run(title+'|'),10.5,True,DARK_BLUE); font(p.add_run(text),10.5) + doc.add_paragraph().paragraph_format.space_after=Pt(1) + +def add_figure(filename, caption, width=6.35): + p=doc.add_paragraph(); p.alignment=WD_ALIGN_PARAGRAPH.CENTER; p.paragraph_format.keep_with_next=True + shape = p.add_run().add_picture(str(SHOTS/filename),width=Inches(width)) + shape._inline.docPr.set('descr', caption) + shape._inline.docPr.set('title', caption) + cp=doc.add_paragraph(); cp.alignment=WD_ALIGN_PARAGRAPH.CENTER; cp.paragraph_format.space_after=Pt(8); cp.paragraph_format.keep_with_next=False + font(cp.add_run(caption),9,color=MUTED) + +def page_break(): doc.add_page_break() + +# Header/footer +hp=sec.header.paragraphs[0]; hp.alignment=WD_ALIGN_PARAGRAPH.LEFT; font(hp.add_run('景区排队叫号系统|产品功能手册'),9,color=MUTED) +fp=sec.footer.paragraphs[0]; fp.alignment=WD_ALIGN_PARAGRAPH.RIGHT +font(fp.add_run('业务熟悉版 '),9,color=MUTED) +fld=OxmlElement('w:fldSimple'); fld.set(qn('w:instr'),'PAGE'); fp._p.append(fld) + +# Cover +for _ in range(4): doc.add_paragraph() +p=doc.add_paragraph(); p.alignment=WD_ALIGN_PARAGRAPH.CENTER; font(p.add_run('产品功能手册'),30,True,GREEN) +p=doc.add_paragraph(); p.alignment=WD_ALIGN_PARAGRAPH.CENTER; font(p.add_run('景区排队叫号系统'),20,True,DARK_BLUE) +p=doc.add_paragraph(); p.alignment=WD_ALIGN_PARAGRAPH.CENTER; p.paragraph_format.space_before=Pt(10); font(p.add_run('面向业务人员的端、功能与页面说明'),13,color=MUTED) +for _ in range(5): doc.add_paragraph() +add_note('阅读对象','景区运营、现场管理、客服、培训及相关业务协作人员。') +add_note('文档范围','基于当前可运行版本整理,覆盖员工端、游客端、现场公示大屏端和管理端。') +p=doc.add_paragraph(); p.alignment=WD_ALIGN_PARAGRAPH.CENTER; p.paragraph_format.space_before=Pt(22); font(p.add_run('版本日期:2026 年 7 月 12 日'),10,color=MUTED) +page_break() + +doc.add_heading('1. 产品概览', level=1) +doc.add_paragraph('本系统用于景区多项目的排队取号、批量叫号、游客进度查询和现场信息公示。中心服务统一维护队列,员工负责现场操作,游客查看个人状态,管理人员查看全局运营并维护项目与账号。') +add_table(['端','主要使用者','核心任务','典型设备'],[ +('员工端','项目一线员工','代游客取号、查看队列、单号/批量叫号、切换项目','手机 / H5'), +('游客端','已取号游客','查看本人号码、前方数量、预计等待、叫号入口与官方提示','手机 / H5'), +('公示大屏端','现场游客','查看当前叫号、最近叫号、后续号段预计','电视 / 大屏浏览器'), +('管理端','管理员、运营负责人','看运营概览、维护项目规则、维护账号、监控各项目大屏','电脑浏览器'), +], [1.05,1.25,3.05,1.15]) + +doc.add_heading('1.1 核心业务流程', level=2) +add_steps([ +'员工在“取号”页面录入游客手机号,可补充姓氏和称谓,系统生成连续排队号码及游客状态页。', +'号码进入项目等待队列;员工可在“叫号”页面查看下一号码、剩余数量和已等待时长。', +'员工执行“快速叫下一个号”或输入数量后“叫号段”;新一批按先到先服务顺序叫出。', +'游客状态页自动更新等待进度;被叫号后突出显示前往入口与叫号时间。', +'公示大屏同步展示当前叫号、最近叫号和后续号段预计;管理端汇总各项目运营状态。', +]) +doc.add_heading('1.2 角色与数据可见范围', level=2) +add_bullets([ +'员工账号仅能操作已分配的项目;多个项目可在“我的”中切换。', +'管理员可查看所有项目,创建/维护项目和账号,并分配员工可操作项目。', +'游客使用私密链接查看本人排队状态,只显示手机号尾号,不展示完整手机号。', +'现场公示大屏只展示公开叫号信息,不展示游客姓名或手机号。', +]) + +doc.add_heading('1.3 主要状态', level=2) +add_table(['对象','状态','业务含义'],[ +('项目','未开放','尚未开始接待,不允许正常取号/叫号。'),('项目','运行中','正常取号、排队与叫号。'),('项目','已暂停','队列号码保留,暂停期间不继续正常叫号,预计时间可能暂停计算。'),('项目','已结束','当日或本轮服务结束。'), +('号码','等待中','已进入队列,等待叫号。'),('号码','已叫号','当前已被叫出,应前往入口。'),('号码','已到场/服务中/已完成','现场已确认并进入后续服务环节。'),('号码','已过号/已取消','本号码不再按正常等待流程继续。'), +], [1.0,1.25,4.25]) +page_break() + +doc.add_heading('2. 员工端', level=1) +doc.add_paragraph('员工端是现场一线操作入口,采用手机端底部导航,包含“叫号、取号、我的”三项任务。员工与管理端使用独立登录入口及独立会话。') +doc.add_heading('2.1 员工登录', level=2) +add_table(['功能名称','功能描述'], [('账号登录','员工输入账号和密码进入本人已授权项目;管理员账号不能从员工入口登录。'),('退出登录','结束当前员工会话,回到员工登录页。')],[1.45,5.05]) +add_figure('01-staff-login.png','图 2-1 员工端登录页',3.0) + +doc.add_heading('2.2 叫号', level=2) +add_table(['功能名称','功能描述'],[ +('快速叫下一个号','按队列先后顺序叫出下一个等待号码。'),('批量叫号','输入 1—100 的数量后,按先进先出顺序叫出连续号段。'),('当前叫号指标','展示最新叫到、下一个号、最末号数、剩余未叫号和最末号预计时长。'),('待叫队列','逐号展示等待号码和已等待时长,支持上下滑动查看。'),('写入保护','项目未运行、队列为空或数据过期时,系统会禁用不适合执行的写操作并给出原因。'), +], [1.45,5.05]) +add_note('业务规则','每次叫下一批时,系统会自动结束上一批,再从等待队列中按顺序叫出指定数量。') +add_figure('02-staff-call.png','图 2-2 员工端叫号页',3.0) + +doc.add_heading('2.3 取号', level=2) +add_table(['功能名称','功能描述'],[ +('创建排队号码','录入游客手机号后创建号码;姓氏和称谓为补充信息。'),('重复手机号确认','同一手机号已有活动号码时,先提示风险,员工确认后可继续共用手机号取新号。'),('上下号提示','显示上个取号和预计下个号码,便于现场口头核对。'),('游客状态链接','取号成功后提供游客私密状态页,可直接打开或交给游客查看。'), +], [1.45,5.05]) +add_figure('03-staff-ticket.png','图 2-3 员工端取号页',3.0) + +doc.add_heading('2.4 我的 / 项目切换', level=2) +add_table(['功能名称','功能描述'],[ +('账号信息','显示姓名/显示名、登录账号和账号角色。'),('当前项目','显示目前正在操作的项目。'),('切换项目','账号被分配多个项目时,可从授权列表中选择并确认切换。')],[1.45,5.05]) +add_figure('04-staff-me.png','图 2-4 员工端“我的”页',3.0) +page_break() + +doc.add_heading('3. 游客端', level=1) +doc.add_paragraph('游客端通过每张排队单的私密链接访问,无需账号登录。页面每 3 秒自动刷新,并提供手动刷新;网络中断或数据延迟时会显示提示。') +add_table(['功能名称','功能描述'],[ +('个人号码','显示本人排队号码和所属项目。'),('等待进度','等待中显示前方号码数、当前排位和预计等待时间。'),('叫号提醒','号码被叫出后,突出显示应前往的入口及叫号时间。'),('状态说明','对等待、叫号、到场、服务中、完成、过号和取消等状态提供对应文案。'),('隐私展示','仅显示手机号尾号,避免在页面中暴露完整手机号。'),('官方提示','展示管理员为项目配置的游客注意事项。'),('实时状态','自动更新;支持手动刷新,并显示连接中断或更新延迟。'), +], [1.45,5.05]) +add_note('暂停场景','项目暂停时,等待号码继续保留,页面提示“项目暂时暂停”;项目恢复后重新估算等待时间。') +add_figure('05-visitor-waiting.png','图 3-1 游客端等待状态页',3.15) +page_break() + +doc.add_heading('4. 现场公示大屏端', level=1) +doc.add_paragraph('公示大屏用于现场公开播报单个项目的叫号进度,页面无需登录,通过项目专属展示地址打开,适合全屏展示。') +add_table(['功能名称','功能描述'],[ +('项目运行状态','展示运行中、暂停、未开放或结束等状态。'),('当前叫号','以最大视觉层级展示当前号段,并提示前往入口。'),('最近叫号','展示最近 5 个叫号批次及时间,便于游客核对。'),('后续号段预计','基于当前队列与项目等待规则,显示后续号段和预计等待;内容较多时自动轮播。'),('等待汇总','显示当前等待号码总数。'),('时钟日期','展示当前时间、日期和星期。'),('异常提示','连接中断或数据延迟时显示上次更新时间,并支持重新连接。'), +], [1.45,5.05]) +add_note('隐私原则','公示屏仅显示号码和项目公开状态,不显示游客姓名、手机号或其他个人信息。') +add_figure('06-public-display.png','图 4-1 单项目现场公示大屏',6.35) +page_break() + +doc.add_heading('5. 管理端', level=1) +doc.add_paragraph('管理端面向景区管理员与运营负责人,采用电脑端布局,包含运营概览、项目管理、账号管理和大屏中心四个模块。') +doc.add_heading('5.1 管理端登录', level=2) +add_table(['功能名称','功能描述'], [('管理员登录','管理员使用专属入口登录;员工账号不能从管理入口登录。'),('独立会话','管理端与员工端会话相互独立,可在同一浏览器同时保持两种身份。')],[1.45,5.05]) +add_figure('07-admin-login.png','图 5-1 管理端登录页',5.7) + +doc.add_heading('5.2 运营概览', level=2) +add_table(['功能名称','功能描述'],[ +('运营汇总','显示当前运行项目数和总等待号码数。'),('项目队列总览','逐项目展示等待数量、当前叫号、项目状态、设备状态和更新时间。'),('筛选与搜索','按项目状态筛选,并按项目名称搜索。'),('刷新数据','主动读取最新运营信息。')],[1.45,5.05]) +add_figure('08-admin-overview.png','图 5-2 管理端运营概览',6.35) + +doc.add_heading('5.3 项目管理', level=2) +add_table(['功能名称','功能描述'],[ +('项目列表','查看项目名称、编码、票号格式和运行状态。'),('创建项目','录入项目名称和项目编码,创建后再配置运行规则。'),('基础信息维护','修改项目名称、编码等基础标识。'),('项目状态维护','设置未开放、运行中、已暂停或已结束。'),('叫号与过号规则','维护预计每次叫号数和过号宽限时间。'),('等待时间算法','支持“按平均叫号节奏”或“连续放行”两种方式,并配置间隔/速率和缓冲分钟。'),('游客官方提示','配置游客状态页显示的项目提示,最多 240 字。')],[1.45,5.05]) +add_figure('09-admin-projects.png','图 5-3 项目列表',6.35) +add_figure('12-admin-project-maintenance.png','图 5-4 项目维护与等待规则',6.15) + +doc.add_heading('5.4 账号管理', level=2) +add_table(['功能名称','功能描述'],[ +('账号列表','查看账号、角色、启用状态和所属项目。'),('创建账号','创建新的员工账号并设置初始密码。'),('角色维护','在员工与管理员角色之间调整。'),('启用/停用','控制账号是否可继续登录。'),('项目授权','为员工账号勾选可操作项目;未分配项目的员工无法操作项目队列。')],[1.45,5.05]) +add_figure('10-admin-accounts.png','图 5-5 账号管理',6.35) + +doc.add_heading('5.5 大屏中心', level=2) +add_table(['功能名称','功能描述'],[ +('多项目监控墙','在一个页面同时查看各项目运行状态、当前叫号、后续号段预计和等待数量。'),('自动轮播预计','后续预计超过一页时,每 6 秒切换。'),('单项目全屏','点击某项目“全屏展示”,将对应项目卡片放大为现场展示视图。')],[1.45,5.05]) +add_figure('11-admin-display-center.png','图 5-6 管理端大屏中心',6.35) +page_break() + +doc.add_heading('6. 业务使用建议', level=1) +doc.add_heading('6.1 开园前', level=2) +add_bullets(['管理员确认当日开放项目状态为“运行中”,未开放项目保持“未开放”。','核对员工账号是否启用、是否分配到正确项目。','检查项目预计等待参数和游客官方提示是否符合当天运营安排。','打开现场公示屏并确认时间、项目状态和数据更新正常。']) +doc.add_heading('6.2 运营中', level=2) +add_bullets(['员工取号前核对手机号,遇重复手机号提示时确认是否确需为同行游客再次取号。','优先使用“快速叫下一个号”;需要集中放行时再使用批量叫号。','项目临时停止服务时,由管理员及时调整为“已暂停”,避免业务人员继续误操作。','管理人员通过运营概览关注等待量、当前叫号和数据更新时间。']) +doc.add_heading('6.3 收园或项目结束', level=2) +add_bullets(['将项目状态调整为“已结束”,避免继续取号。','现场员工确认当前批次与剩余队列处置方案。','关闭或切换公示屏前,确认现场已无需要继续等待的游客。']) +add_note('版本说明','本文档描述当前可运行版本。后续新增功能、页面或业务规则时,应同步更新“端—功能—描述—截图”及版本日期。') + +doc.core_properties.title='景区排队叫号系统 产品功能手册(业务熟悉版)' +doc.core_properties.subject='端、功能名称、功能描述与页面截图' +doc.core_properties.author='产品团队' +doc.save(OUT) +print(OUT) diff --git a/deliverables/product-document/fonts/NotoSansCJKsc-Regular.otf b/deliverables/product-document/fonts/NotoSansCJKsc-Regular.otf new file mode 100644 index 0000000..dc15562 Binary files /dev/null and b/deliverables/product-document/fonts/NotoSansCJKsc-Regular.otf differ diff --git a/deliverables/product-document/fonts/fonts.conf b/deliverables/product-document/fonts/fonts.conf new file mode 100644 index 0000000..2c96994 --- /dev/null +++ b/deliverables/product-document/fonts/fonts.conf @@ -0,0 +1,9 @@ + + + + /Users/inmanw/公司/calllinesystem/deliverables/product-document/fonts + /System/Library/Fonts + /System/Library/Fonts/Supplemental + /Library/Fonts + /private/tmp/fontconfig-cache-calllinesystem + diff --git a/deliverables/product-document/lohome/.cache/fontconfig/928bbc99c4abd94fc7618926d5dca13a-le64.cache-9 b/deliverables/product-document/lohome/.cache/fontconfig/928bbc99c4abd94fc7618926d5dca13a-le64.cache-9 new file mode 100644 index 0000000..1857e19 Binary files /dev/null and b/deliverables/product-document/lohome/.cache/fontconfig/928bbc99c4abd94fc7618926d5dca13a-le64.cache-9 differ diff --git a/deliverables/product-document/lohome/.cache/fontconfig/CACHEDIR.TAG b/deliverables/product-document/lohome/.cache/fontconfig/CACHEDIR.TAG new file mode 100644 index 0000000..c6d9ccb --- /dev/null +++ b/deliverables/product-document/lohome/.cache/fontconfig/CACHEDIR.TAG @@ -0,0 +1,4 @@ +Signature: 8a477f597d28d172789f06886806bc55 +# This file is a cache directory tag created by fontconfig. +# For information about cache directory tags, see: +# http://www.brynosaurus.com/cachedir/ diff --git a/deliverables/product-document/lohome/.cache/fontconfig/c7eee6ebd4c3922875d7cd4cc4e30398-le64.cache-9 b/deliverables/product-document/lohome/.cache/fontconfig/c7eee6ebd4c3922875d7cd4cc4e30398-le64.cache-9 new file mode 100644 index 0000000..ec763eb Binary files /dev/null and b/deliverables/product-document/lohome/.cache/fontconfig/c7eee6ebd4c3922875d7cd4cc4e30398-le64.cache-9 differ diff --git a/deliverables/product-document/lohome/user/autotext/mytexts.bau b/deliverables/product-document/lohome/user/autotext/mytexts.bau new file mode 100644 index 0000000..60ee170 Binary files /dev/null and b/deliverables/product-document/lohome/user/autotext/mytexts.bau differ diff --git a/deliverables/product-document/lohome/user/basic/Standard/Module1.xba b/deliverables/product-document/lohome/user/basic/Standard/Module1.xba new file mode 100644 index 0000000..3424c16 --- /dev/null +++ b/deliverables/product-document/lohome/user/basic/Standard/Module1.xba @@ -0,0 +1,24 @@ + + + +REM ***** BASIC ***** + +Sub Main + +End Sub \ No newline at end of file diff --git a/deliverables/product-document/lohome/user/basic/Standard/dialog.xlb b/deliverables/product-document/lohome/user/basic/Standard/dialog.xlb new file mode 100644 index 0000000..669529d --- /dev/null +++ b/deliverables/product-document/lohome/user/basic/Standard/dialog.xlb @@ -0,0 +1,3 @@ + + + diff --git a/deliverables/product-document/lohome/user/basic/Standard/script.xlb b/deliverables/product-document/lohome/user/basic/Standard/script.xlb new file mode 100644 index 0000000..67c9503 --- /dev/null +++ b/deliverables/product-document/lohome/user/basic/Standard/script.xlb @@ -0,0 +1,5 @@ + + + + + diff --git a/deliverables/product-document/lohome/user/basic/dialog.xlc b/deliverables/product-document/lohome/user/basic/dialog.xlc new file mode 100644 index 0000000..34064e8 --- /dev/null +++ b/deliverables/product-document/lohome/user/basic/dialog.xlc @@ -0,0 +1,5 @@ + + + + + diff --git a/deliverables/product-document/lohome/user/basic/script.xlc b/deliverables/product-document/lohome/user/basic/script.xlc new file mode 100644 index 0000000..34064e8 --- /dev/null +++ b/deliverables/product-document/lohome/user/basic/script.xlc @@ -0,0 +1,5 @@ + + + + + diff --git a/deliverables/product-document/lohome/user/config/autotbl.fmt b/deliverables/product-document/lohome/user/config/autotbl.fmt new file mode 100644 index 0000000..611ac0d Binary files /dev/null and b/deliverables/product-document/lohome/user/config/autotbl.fmt differ diff --git a/deliverables/product-document/lohome/user/database/biblio.odb b/deliverables/product-document/lohome/user/database/biblio.odb new file mode 100644 index 0000000..9e70744 Binary files /dev/null and b/deliverables/product-document/lohome/user/database/biblio.odb differ diff --git a/deliverables/product-document/lohome/user/database/biblio/biblio.dbf b/deliverables/product-document/lohome/user/database/biblio/biblio.dbf new file mode 100644 index 0000000..2715f99 Binary files /dev/null and b/deliverables/product-document/lohome/user/database/biblio/biblio.dbf differ diff --git a/deliverables/product-document/lohome/user/database/biblio/biblio.dbt b/deliverables/product-document/lohome/user/database/biblio/biblio.dbt new file mode 100644 index 0000000..0ad4067 Binary files /dev/null and b/deliverables/product-document/lohome/user/database/biblio/biblio.dbt differ diff --git a/deliverables/product-document/lohome/user/database/evolocal.odb b/deliverables/product-document/lohome/user/database/evolocal.odb new file mode 100644 index 0000000..82f486a Binary files /dev/null and b/deliverables/product-document/lohome/user/database/evolocal.odb differ diff --git a/deliverables/product-document/lohome/user/extensions/bundled/registry/com.sun.star.comp.deployment.configuration.PackageRegistryBackend/backenddb.xml b/deliverables/product-document/lohome/user/extensions/bundled/registry/com.sun.star.comp.deployment.configuration.PackageRegistryBackend/backenddb.xml new file mode 100644 index 0000000..fb4de4d --- /dev/null +++ b/deliverables/product-document/lohome/user/extensions/bundled/registry/com.sun.star.comp.deployment.configuration.PackageRegistryBackend/backenddb.xml @@ -0,0 +1,2 @@ + + diff --git a/deliverables/product-document/lohome/user/extensions/bundled/registry/com.sun.star.comp.deployment.help.PackageRegistryBackend/backenddb.xml b/deliverables/product-document/lohome/user/extensions/bundled/registry/com.sun.star.comp.deployment.help.PackageRegistryBackend/backenddb.xml new file mode 100644 index 0000000..496c8c8 --- /dev/null +++ b/deliverables/product-document/lohome/user/extensions/bundled/registry/com.sun.star.comp.deployment.help.PackageRegistryBackend/backenddb.xml @@ -0,0 +1,2 @@ + + diff --git a/deliverables/product-document/lohome/user/extensions/shared/registry/com.sun.star.comp.deployment.configuration.PackageRegistryBackend/backenddb.xml b/deliverables/product-document/lohome/user/extensions/shared/registry/com.sun.star.comp.deployment.configuration.PackageRegistryBackend/backenddb.xml new file mode 100644 index 0000000..fb4de4d --- /dev/null +++ b/deliverables/product-document/lohome/user/extensions/shared/registry/com.sun.star.comp.deployment.configuration.PackageRegistryBackend/backenddb.xml @@ -0,0 +1,2 @@ + + diff --git a/deliverables/product-document/lohome/user/extensions/shared/registry/com.sun.star.comp.deployment.help.PackageRegistryBackend/backenddb.xml b/deliverables/product-document/lohome/user/extensions/shared/registry/com.sun.star.comp.deployment.help.PackageRegistryBackend/backenddb.xml new file mode 100644 index 0000000..496c8c8 --- /dev/null +++ b/deliverables/product-document/lohome/user/extensions/shared/registry/com.sun.star.comp.deployment.help.PackageRegistryBackend/backenddb.xml @@ -0,0 +1,2 @@ + + diff --git a/deliverables/product-document/lohome/user/gallery/sg30.sdv b/deliverables/product-document/lohome/user/gallery/sg30.sdv new file mode 100644 index 0000000..fc17dd9 Binary files /dev/null and b/deliverables/product-document/lohome/user/gallery/sg30.sdv differ diff --git a/deliverables/product-document/lohome/user/gallery/sg30.thm b/deliverables/product-document/lohome/user/gallery/sg30.thm new file mode 100644 index 0000000..9bf97d6 Binary files /dev/null and b/deliverables/product-document/lohome/user/gallery/sg30.thm differ diff --git a/deliverables/product-document/lohome/user/pack/ExtensionInfo.pack b/deliverables/product-document/lohome/user/pack/ExtensionInfo.pack new file mode 100644 index 0000000..02b6c98 Binary files /dev/null and b/deliverables/product-document/lohome/user/pack/ExtensionInfo.pack differ diff --git a/deliverables/product-document/lohome/user/pack/autotext/mytexts.pack b/deliverables/product-document/lohome/user/pack/autotext/mytexts.pack new file mode 100644 index 0000000..a74775e Binary files /dev/null and b/deliverables/product-document/lohome/user/pack/autotext/mytexts.pack differ diff --git a/deliverables/product-document/lohome/user/pack/basic/Standard/Module1.pack b/deliverables/product-document/lohome/user/pack/basic/Standard/Module1.pack new file mode 100644 index 0000000..d7944bf Binary files /dev/null and b/deliverables/product-document/lohome/user/pack/basic/Standard/Module1.pack differ diff --git a/deliverables/product-document/lohome/user/pack/basic/Standard/dialog.pack b/deliverables/product-document/lohome/user/pack/basic/Standard/dialog.pack new file mode 100644 index 0000000..db56d38 Binary files /dev/null and b/deliverables/product-document/lohome/user/pack/basic/Standard/dialog.pack differ diff --git a/deliverables/product-document/lohome/user/pack/basic/Standard/script.pack b/deliverables/product-document/lohome/user/pack/basic/Standard/script.pack new file mode 100644 index 0000000..763901c Binary files /dev/null and b/deliverables/product-document/lohome/user/pack/basic/Standard/script.pack differ diff --git a/deliverables/product-document/lohome/user/pack/basic/dialog.pack b/deliverables/product-document/lohome/user/pack/basic/dialog.pack new file mode 100644 index 0000000..275025b Binary files /dev/null and b/deliverables/product-document/lohome/user/pack/basic/dialog.pack differ diff --git a/deliverables/product-document/lohome/user/pack/basic/script.pack b/deliverables/product-document/lohome/user/pack/basic/script.pack new file mode 100644 index 0000000..275025b Binary files /dev/null and b/deliverables/product-document/lohome/user/pack/basic/script.pack differ diff --git a/deliverables/product-document/lohome/user/pack/config/autotbl.pack b/deliverables/product-document/lohome/user/pack/config/autotbl.pack new file mode 100644 index 0000000..c0060a0 Binary files /dev/null and b/deliverables/product-document/lohome/user/pack/config/autotbl.pack differ diff --git a/deliverables/product-document/lohome/user/pack/database/biblio.pack b/deliverables/product-document/lohome/user/pack/database/biblio.pack new file mode 100644 index 0000000..d895355 Binary files /dev/null and b/deliverables/product-document/lohome/user/pack/database/biblio.pack differ diff --git a/deliverables/product-document/lohome/user/pack/database/biblio/biblio.pack b/deliverables/product-document/lohome/user/pack/database/biblio/biblio.pack new file mode 100644 index 0000000..6216fb7 Binary files /dev/null and b/deliverables/product-document/lohome/user/pack/database/biblio/biblio.pack differ diff --git a/deliverables/product-document/lohome/user/pack/database/evolocal.pack b/deliverables/product-document/lohome/user/pack/database/evolocal.pack new file mode 100644 index 0000000..07987aa Binary files /dev/null and b/deliverables/product-document/lohome/user/pack/database/evolocal.pack differ diff --git a/deliverables/product-document/lohome/user/pack/registrymodifications.pack b/deliverables/product-document/lohome/user/pack/registrymodifications.pack new file mode 100644 index 0000000..e1cbf08 Binary files /dev/null and b/deliverables/product-document/lohome/user/pack/registrymodifications.pack differ diff --git a/deliverables/product-document/lohome/user/registrymodifications.xcu b/deliverables/product-document/lohome/user/registrymodifications.xcu new file mode 100644 index 0000000..24e5c9c --- /dev/null +++ b/deliverables/product-document/lohome/user/registrymodifications.xcu @@ -0,0 +1,47 @@ + + +2147483647com.sun.star.logging.FileHandler$(userurl)/$(loggername).logcom.sun.star.logging.PlainTextFormatter +V1,2,0 +V1,2,0 +V1,2,0 +V1,2,0 +1 +true +false +true +true +true +true +false +true +true +false +true +true +true +false +false +true +true +true +true +false +false +false +false +false +true +true +true +true +true +true +1 +true +0 +100 +false +en-US +0,0,800,600;1;,,,; +true + diff --git a/deliverables/product-document/lohome/user/uno_packages/cache/registry/com.sun.star.comp.deployment.configuration.PackageRegistryBackend/backenddb.xml b/deliverables/product-document/lohome/user/uno_packages/cache/registry/com.sun.star.comp.deployment.configuration.PackageRegistryBackend/backenddb.xml new file mode 100644 index 0000000..fb4de4d --- /dev/null +++ b/deliverables/product-document/lohome/user/uno_packages/cache/registry/com.sun.star.comp.deployment.configuration.PackageRegistryBackend/backenddb.xml @@ -0,0 +1,2 @@ + + diff --git a/deliverables/product-document/lohome/user/uno_packages/cache/registry/com.sun.star.comp.deployment.help.PackageRegistryBackend/backenddb.xml b/deliverables/product-document/lohome/user/uno_packages/cache/registry/com.sun.star.comp.deployment.help.PackageRegistryBackend/backenddb.xml new file mode 100644 index 0000000..496c8c8 --- /dev/null +++ b/deliverables/product-document/lohome/user/uno_packages/cache/registry/com.sun.star.comp.deployment.help.PackageRegistryBackend/backenddb.xml @@ -0,0 +1,2 @@ + + diff --git a/deliverables/product-document/rendered/page-1.png b/deliverables/product-document/rendered/page-1.png new file mode 100644 index 0000000..9eb1d5d Binary files /dev/null and b/deliverables/product-document/rendered/page-1.png differ diff --git a/deliverables/product-document/rendered/page-10.png b/deliverables/product-document/rendered/page-10.png new file mode 100644 index 0000000..bb6781b Binary files /dev/null and b/deliverables/product-document/rendered/page-10.png differ diff --git a/deliverables/product-document/rendered/page-11.png b/deliverables/product-document/rendered/page-11.png new file mode 100644 index 0000000..d6e2604 Binary files /dev/null and b/deliverables/product-document/rendered/page-11.png differ diff --git a/deliverables/product-document/rendered/page-12.png b/deliverables/product-document/rendered/page-12.png new file mode 100644 index 0000000..2a3f66d Binary files /dev/null and b/deliverables/product-document/rendered/page-12.png differ diff --git a/deliverables/product-document/rendered/page-13.png b/deliverables/product-document/rendered/page-13.png new file mode 100644 index 0000000..d03820c Binary files /dev/null and b/deliverables/product-document/rendered/page-13.png differ diff --git a/deliverables/product-document/rendered/page-14.png b/deliverables/product-document/rendered/page-14.png new file mode 100644 index 0000000..7efa816 Binary files /dev/null and b/deliverables/product-document/rendered/page-14.png differ diff --git a/deliverables/product-document/rendered/page-15.png b/deliverables/product-document/rendered/page-15.png new file mode 100644 index 0000000..fe93bee Binary files /dev/null and b/deliverables/product-document/rendered/page-15.png differ diff --git a/deliverables/product-document/rendered/page-16.png b/deliverables/product-document/rendered/page-16.png new file mode 100644 index 0000000..af206c4 Binary files /dev/null and b/deliverables/product-document/rendered/page-16.png differ diff --git a/deliverables/product-document/rendered/page-17.png b/deliverables/product-document/rendered/page-17.png new file mode 100644 index 0000000..f0f7b77 Binary files /dev/null and b/deliverables/product-document/rendered/page-17.png differ diff --git a/deliverables/product-document/rendered/page-18.png b/deliverables/product-document/rendered/page-18.png new file mode 100644 index 0000000..67749ab Binary files /dev/null and b/deliverables/product-document/rendered/page-18.png differ diff --git a/deliverables/product-document/rendered/page-19.png b/deliverables/product-document/rendered/page-19.png new file mode 100644 index 0000000..e7202eb Binary files /dev/null and b/deliverables/product-document/rendered/page-19.png differ diff --git a/deliverables/product-document/rendered/page-2.png b/deliverables/product-document/rendered/page-2.png new file mode 100644 index 0000000..4383a0c Binary files /dev/null and b/deliverables/product-document/rendered/page-2.png differ diff --git a/deliverables/product-document/rendered/page-20.png b/deliverables/product-document/rendered/page-20.png new file mode 100644 index 0000000..5024c3c Binary files /dev/null and b/deliverables/product-document/rendered/page-20.png differ diff --git a/deliverables/product-document/rendered/page-21.png b/deliverables/product-document/rendered/page-21.png new file mode 100644 index 0000000..f073d28 Binary files /dev/null and b/deliverables/product-document/rendered/page-21.png differ diff --git a/deliverables/product-document/rendered/page-22.png b/deliverables/product-document/rendered/page-22.png new file mode 100644 index 0000000..5f786e8 Binary files /dev/null and b/deliverables/product-document/rendered/page-22.png differ diff --git a/deliverables/product-document/rendered/page-23.png b/deliverables/product-document/rendered/page-23.png new file mode 100644 index 0000000..fc6a85f Binary files /dev/null and b/deliverables/product-document/rendered/page-23.png differ diff --git a/deliverables/product-document/rendered/page-24.png b/deliverables/product-document/rendered/page-24.png new file mode 100644 index 0000000..ff38f26 Binary files /dev/null and b/deliverables/product-document/rendered/page-24.png differ diff --git a/deliverables/product-document/rendered/page-3.png b/deliverables/product-document/rendered/page-3.png new file mode 100644 index 0000000..d938e4f Binary files /dev/null and b/deliverables/product-document/rendered/page-3.png differ diff --git a/deliverables/product-document/rendered/page-4.png b/deliverables/product-document/rendered/page-4.png new file mode 100644 index 0000000..72d6ecd Binary files /dev/null and b/deliverables/product-document/rendered/page-4.png differ diff --git a/deliverables/product-document/rendered/page-5.png b/deliverables/product-document/rendered/page-5.png new file mode 100644 index 0000000..7f85f25 Binary files /dev/null and b/deliverables/product-document/rendered/page-5.png differ diff --git a/deliverables/product-document/rendered/page-6.png b/deliverables/product-document/rendered/page-6.png new file mode 100644 index 0000000..6a676fa Binary files /dev/null and b/deliverables/product-document/rendered/page-6.png differ diff --git a/deliverables/product-document/rendered/page-7.png b/deliverables/product-document/rendered/page-7.png new file mode 100644 index 0000000..96ce4ab Binary files /dev/null and b/deliverables/product-document/rendered/page-7.png differ diff --git a/deliverables/product-document/rendered/page-8.png b/deliverables/product-document/rendered/page-8.png new file mode 100644 index 0000000..903eda4 Binary files /dev/null and b/deliverables/product-document/rendered/page-8.png differ diff --git a/deliverables/product-document/rendered/page-9.png b/deliverables/product-document/rendered/page-9.png new file mode 100644 index 0000000..fa5c925 Binary files /dev/null and b/deliverables/product-document/rendered/page-9.png differ diff --git a/deliverables/product-document/rendered/景区排队叫号系统_产品功能手册_业务熟悉版.pdf b/deliverables/product-document/rendered/景区排队叫号系统_产品功能手册_业务熟悉版.pdf new file mode 100644 index 0000000..f7621af Binary files /dev/null and b/deliverables/product-document/rendered/景区排队叫号系统_产品功能手册_业务熟悉版.pdf differ diff --git a/deliverables/product-document/screenshots/01-staff-login.png b/deliverables/product-document/screenshots/01-staff-login.png new file mode 100644 index 0000000..472b624 Binary files /dev/null and b/deliverables/product-document/screenshots/01-staff-login.png differ diff --git a/deliverables/product-document/screenshots/02-staff-call.png b/deliverables/product-document/screenshots/02-staff-call.png new file mode 100644 index 0000000..9767b7d Binary files /dev/null and b/deliverables/product-document/screenshots/02-staff-call.png differ diff --git a/deliverables/product-document/screenshots/03-staff-ticket.png b/deliverables/product-document/screenshots/03-staff-ticket.png new file mode 100644 index 0000000..a31add9 Binary files /dev/null and b/deliverables/product-document/screenshots/03-staff-ticket.png differ diff --git a/deliverables/product-document/screenshots/04-staff-me.png b/deliverables/product-document/screenshots/04-staff-me.png new file mode 100644 index 0000000..5a4be43 Binary files /dev/null and b/deliverables/product-document/screenshots/04-staff-me.png differ diff --git a/deliverables/product-document/screenshots/05-visitor-waiting.png b/deliverables/product-document/screenshots/05-visitor-waiting.png new file mode 100644 index 0000000..a5637bc Binary files /dev/null and b/deliverables/product-document/screenshots/05-visitor-waiting.png differ diff --git a/deliverables/product-document/screenshots/06-public-display.png b/deliverables/product-document/screenshots/06-public-display.png new file mode 100644 index 0000000..654809b Binary files /dev/null and b/deliverables/product-document/screenshots/06-public-display.png differ diff --git a/deliverables/product-document/screenshots/07-admin-login.png b/deliverables/product-document/screenshots/07-admin-login.png new file mode 100644 index 0000000..132edcf Binary files /dev/null and b/deliverables/product-document/screenshots/07-admin-login.png differ diff --git a/deliverables/product-document/screenshots/08-admin-overview.png b/deliverables/product-document/screenshots/08-admin-overview.png new file mode 100644 index 0000000..687b778 Binary files /dev/null and b/deliverables/product-document/screenshots/08-admin-overview.png differ diff --git a/deliverables/product-document/screenshots/09-admin-projects.png b/deliverables/product-document/screenshots/09-admin-projects.png new file mode 100644 index 0000000..92bc229 Binary files /dev/null and b/deliverables/product-document/screenshots/09-admin-projects.png differ diff --git a/deliverables/product-document/screenshots/10-admin-accounts.png b/deliverables/product-document/screenshots/10-admin-accounts.png new file mode 100644 index 0000000..a7b4bec Binary files /dev/null and b/deliverables/product-document/screenshots/10-admin-accounts.png differ diff --git a/deliverables/product-document/screenshots/11-admin-display-center.png b/deliverables/product-document/screenshots/11-admin-display-center.png new file mode 100644 index 0000000..03d18bf Binary files /dev/null and b/deliverables/product-document/screenshots/11-admin-display-center.png differ diff --git a/deliverables/product-document/screenshots/12-admin-project-maintenance.png b/deliverables/product-document/screenshots/12-admin-project-maintenance.png new file mode 100644 index 0000000..60b59f4 Binary files /dev/null and b/deliverables/product-document/screenshots/12-admin-project-maintenance.png differ diff --git a/deliverables/product-document/test-font-page.png b/deliverables/product-document/test-font-page.png new file mode 100644 index 0000000..89d679a Binary files /dev/null and b/deliverables/product-document/test-font-page.png differ diff --git a/deliverables/product-document/test-font.pdf b/deliverables/product-document/test-font.pdf new file mode 100644 index 0000000..f7982d6 Binary files /dev/null and b/deliverables/product-document/test-font.pdf differ diff --git a/deliverables/product-document/testpdf/景区排队叫号系统_产品功能手册_业务熟悉版.pdf b/deliverables/product-document/testpdf/景区排队叫号系统_产品功能手册_业务熟悉版.pdf new file mode 100644 index 0000000..f7982d6 Binary files /dev/null and b/deliverables/product-document/testpdf/景区排队叫号系统_产品功能手册_业务熟悉版.pdf differ diff --git a/deliverables/product-document/景区排队叫号系统_产品功能手册_业务熟悉版.docx b/deliverables/product-document/景区排队叫号系统_产品功能手册_业务熟悉版.docx new file mode 100644 index 0000000..aa99df0 Binary files /dev/null and b/deliverables/product-document/景区排队叫号系统_产品功能手册_业务熟悉版.docx differ diff --git a/deliverables/product-document/景区排队叫号系统_产品功能手册_业务熟悉版.pdf b/deliverables/product-document/景区排队叫号系统_产品功能手册_业务熟悉版.pdf new file mode 100644 index 0000000..f7621af Binary files /dev/null and b/deliverables/product-document/景区排队叫号系统_产品功能手册_业务熟悉版.pdf differ diff --git a/design-qa.md b/design-qa.md new file mode 100644 index 0000000..5109d8c --- /dev/null +++ b/design-qa.md @@ -0,0 +1,51 @@ +# Design QA + +- Source visual truth: `.prototype-run/xiaoqikong-fidelity-rebuild-20260711/prototypes/selected-karst-breath.png` +- Implementation screenshot: `.prototype-run/xiaoqikong-fidelity-rebuild-20260711/qa/screenshots/admin-dense-final.browser.jpg` +- Comparison surface: `.prototype-run/xiaoqikong-fidelity-rebuild-20260711/qa/comparison-final-v3.jpg` +- Viewport/state: desktop admin operations overview, authenticated, light theme, real local seed data + +## Full-view comparison + +The implementation now follows the selected image's major composition: slim brand header, compact route navigation, four-item operational metric band, a dominant left queue workspace, and a narrower right rail containing latest-call status, exceptions, and recent activity. The main/right proportions, pale mineral canvas, evergreen accents, contour motif, compact borders, selected-row treatment, and tabular numeric hierarchy are visibly aligned. + +## Focused-region comparison + +- Header and metrics: brand lockup, whitespace, vertical separators, numeric emphasis, and restrained green hierarchy match the source language. +- Queue workspace: filters, aligned column header, selected row outline, project/batch/status/time relationships, and pagination footer are present and functional. +- Context rail: latest call, progress, waiting estimate, exception state, and recent activity are stacked in the same priority order as the source. +- Typography: system Chinese sans, tabular numerals, strong dark-green headings, compact secondary copy, and restrained weights are consistent with the reference. +- Asset fidelity: the supplied official logo is used directly; no generated or code-drawn replacement is used. + +## Comparison history + +1. Earlier implementation failed with a P1 layout mismatch: it retained a generic sidebar dashboard and omitted the source image's main-table/right-rail composition. +2. The operations overview was rebuilt into the selected image's metric band, queue table, and contextual rail. Functional project filters, search, row selection, refresh, status states, private contact projection, and pagination were retained. +3. A second comparison found the vertical sidebar still consumed the source's primary workspace. Navigation was moved to a compact horizontal route strip and the work area was expanded to the source proportions. +4. A final density pass added an explicitly labeled demonstration view with ten non-persistent projects, full right-rail content, and pagination. A visible toggle restores live API data, and no demonstration record is written to the database. + +## Findings + +- P0: none. +- P1: none. +- P2: none. +- P3: none. + +## Primary interactions tested + +- Route navigation remains available. +- Queue status filter and project search update the visible project list. +- Selecting a project updates the latest-call context rail. +- Refresh retains existing asynchronous behavior. +- Existing admin phone/privacy projection remains covered by tests. +- Browser console: zero errors and zero warnings. + +## Multisurface reconstruction status + +- Sources: `staff-mobile.png`, `visitor-mobile.png`, and `public-display.png` in `.prototype-run/xiaoqikong-multisurface-20260711/prototypes/`. +- Implementation: staff, visitor, and public-display DOM structures were rebuilt against those exact sources. +- Automated verification: 24/24 tests passed; TypeScript and production build passed. +- Visual comparison blocker: the in-app browser rejected the local preview URL under its URL security policy, so matching-viewport implementation screenshots could not be captured after the rebuild. +- The earlier admin comparison above remains passed; the new three-surface comparison must not inherit that result. + +final result: blocked diff --git a/docs/backend-production-handoff.md b/docs/backend-production-handoff.md new file mode 100644 index 0000000..0fec2c3 --- /dev/null +++ b/docs/backend-production-handoff.md @@ -0,0 +1,44 @@ +# 后端与数据库上线交接 + +本文件只描述应用/数据库基础交接,不替代运维团队的 Kubernetes、PostgreSQL、Redis、密钥和备份实施方案。 + +## 已实现的应用基础 + +- PostgreSQL 迁移使用嵌入式版本化 SQL、advisory lock 和 checksum 校验。 +- 生产 API 不允许通过 `MIGRATE_ON_START=true` 自行执行 DDL;使用独立的 `/usr/local/bin/migrate` 迁移容器命令。 +- 数据库连接池可由环境变量调节:`DB_MAX_OPEN_CONNS`、`DB_MAX_IDLE_CONNS`、`DB_CONN_MAX_IDLE_TIME`、`DB_CONN_MAX_LIFETIME`。 +- 后台清理任务使用 PostgreSQL advisory lock,在多 Pod 下只允许一个清理事务执行;清理个人关联、过期幂等键、过期会话和超过保留期的审计记录。 +- 手机号和姓氏仍以加密字段保存;清理后只保留不含个人信息的排队事实,公共状态页不再返回尾号。 +- `make test-db` 使用临时 PostgreSQL 验证迁移幂等、清理约束和清理事务;`make smoke-real` 验证真实 API/数据库链路和隐私投影。 + +## API Pod 的生产环境变量 + +生产环境至少需要通过外部 Secret/KMS 注入: + +```text +APP_ENV=production +DATABASE_URL=postgresql://...?...&sslmode=verify-full +DATA_ENCRYPTION_KEY_BASE64=<32-byte base64> +PHONE_HMAC_KEY_BASE64=<32-byte base64> +SESSION_COOKIE_SECURE=true +MIGRATE_ON_START=false +MAINTENANCE_INTERVAL=1h +MAINTENANCE_BATCH_SIZE=500 +``` + +`DATABASE_URL` 必须使用 `require`、`verify-ca` 或 `verify-full` 之一;生产 API 会拒绝 `sslmode=disable` 和 `MIGRATE_ON_START=true`。 + +## Kubernetes 发布顺序 + +1. 运维先执行数据库迁移 Job,容器命令为 `/usr/local/bin/migrate`。 +2. 迁移成功后,通过一次性 Secret 执行 `/usr/local/bin/bootstrap-admin` 设置受保护管理员密码。 +3. 密码设置成功后再滚动更新 API Deployment。 +4. API Deployment 使用 `MIGRATE_ON_START=false`,通过 `/readyz` 检查数据库连通性和已应用迁移版本。 +5. Redis 地址、TLS 和认证由运维接入;当前代码仍保留进程内事件降级,不能把进程内广播当作跨 Pod 事件总线。 +6. PostgreSQL 的 PITR、加密备份、RPO ≤ 5 分钟、RTO ≤ 30 分钟和恢复演练由运维负责并记录结果。 + +## 禁止事项 + +- 生产环境不得运行 `make seed` 或 `scripts/seed.sh`。 +- 不得把 `server/.env`、数据库 URL、数据密钥或 Redis 凭据写入镜像、Git 或普通 ConfigMap。 +- 迁移失败时禁止强行启动 API;应保留旧版本并按运维回滚流程处理。 diff --git a/docs/login-persistence-design.md b/docs/login-persistence-design.md new file mode 100644 index 0000000..203fe9e --- /dev/null +++ b/docs/login-persistence-design.md @@ -0,0 +1,113 @@ +# 登录持久化设计 + +## 1. 目标 + +为管理端和员工端提供正式上线可用的登录持久化: + +- 刷新页面、关闭后重新打开浏览器、前后端服务重启后仍可恢复登录。 +- 会话仅保存在服务端数据库,浏览器仅保存不透明随机令牌。 +- 员工端和管理端会话相互隔离,可在同一浏览器同时登录。 +- 支持闲置过期、绝对过期、主动退出、停用账号立即失效和管理员撤销会话。 +- 不在 `localStorage` 或 `sessionStorage` 中保存密码、会话令牌或完整用户资料。 + +## 2. 现有基础 + +当前已实现: + +- `auth_sessions` 数据库表持久化会话。 +- 会话令牌使用高熅随机值,数据库只保存 SHA-256 摘要。 +- Cookie 使用 `HttpOnly`、`Secure`、`SameSite=Strict` 和明确的 `Expires/Max-Age`。 +- 管理端与员工端使用不同 Cookie 名称。 +- 前端启动时调用 `/api/{portal}/auth/me` 恢复用户状态。 +- 退出时撤销数据库会话并清除 Cookie。 + +当前的 `SESSION_TTL=12h` 是固定过期时间,`last_seen_at` 尚未用于闲置过期或续期。 + +## 3. 会话策略 + +| 策略 | 管理端 | 员工端 | +| --- | --- | --- | +| 闲置超时 | 12 小时 | 24 小时 | +| 绝对有效期 | 7 天 | 30 天 | +| 滑动续期 | 剩余闲置时间低于 6 小时时续期 | 剩余闲置时间低于 12 小时时续期 | +| 并发会话上限 | 每账号 5 个 | 每账号 10 个 | +| 超限策略 | 撤销最早活跃会话 | 撤销最早活跃会话 | + +续期只能延长闲置截止时间,不得超过绝对有效期。超级管理员不豁免会话过期和撤销规则。 + +## 4. 数据模型 + +在 `auth_sessions` 中保留现有字段,新增: + +```sql +absolute_expires_at timestamptz NOT NULL, +last_rotated_at timestamptz NOT NULL DEFAULT now(), +revoked_reason varchar(32), +device_label varchar(120) NOT NULL DEFAULT '' +``` + +索引: + +- 保留 `token_hash` 唯一索引。 +- 增加 `(user_id, last_seen_at DESC) WHERE revoked_at IS NULL`,用于会话列表和并发上限。 +- 增加 `(expires_at) WHERE revoked_at IS NULL`,用于过期会话清理。 + +`revoked_reason` 取值:`LOGOUT`、`ACCOUNT_DISABLED`、`PASSWORD_CHANGED`、`ADMIN_REVOKED`、`CONCURRENT_LIMIT`、`EXPIRED`。 + +## 5. 请求验证与续期 + +每个受保护请求执行: + +1. 读取当前端对应的 HttpOnly Cookie。 +2. 对令牌做 SHA-256,查询未撤销会话。 +3. 同时校验 `expires_at` 和 `absolute_expires_at`。 +4. 校验用户仍启用且角色与当前端一致。 +5. 距上次写入超过 5 分钟时才更新 `last_seen_at`,避免每次 API 请求写库。 +6. 进入续期窗口时,更新 `expires_at` 并重发 Cookie,新时间不超过 `absolute_expires_at`。 +7. 会话无效时清除 Cookie 并返回统一 `401 SESSION_INVALID`。 + +第 5、6 步应使用条件更新,保证多个并发请求不会频繁写入。 + +## 6. 前端恢复流程 + +- 应用启动后保持鉴权 `loading` 状态,先请求 `/auth/me`,不提前闪现登录页。 +- `/auth/me` 成功:只将返回的用户和项目权限保存在 React 内存状态。 +- 返回 401:清空内存状态并跳转到对应登录页。 +- 其他网络错误:显示“无法确认登录状态”和重试入口,不直接判定为退出。 +- 所有 API 对 401 使用统一事件通知鉴权容器,避免页面在会话过期后继续停留在受保护界面。 +- 可持久化项目选择等非敏感偏好,但恢复后必须再与 `/auth/me` 返回的权限集合校验。 + +## 7. 会话管理接口 + +- `GET /api/{portal}/auth/sessions`:列出当前账号的活跃会话,仅返回会话 ID、设备摘要、IP 脱敏值、创建时间和最后活跃时间。 +- `DELETE /api/{portal}/auth/sessions/{id}`:撤销指定会话。 +- `POST /api/{portal}/auth/logout-all`:撤销当前账号全部会话。 + +管理员修改普通账号密码、角色或停用账号时,必须在同一数据库事务中撤销该账号的所有会话。 + +## 8. 清理与运维 + +- API 实例每小时尝试清理一次,或由部署平台定时任务执行。 +- 已过期或撤销会话保留 30 天供安全审计,然后物理删除。 +- 监控指标:登录成功/失败数、限流数、活跃会话数、续期数、无效会话数和会话查询耗时。 +- 不记录原始 Cookie、令牌、密码或完整 IP。 + +## 9. 上线迁移 + +1. 新增字段允许短暂为空,对存量会话使用当前 `expires_at` 回填绝对过期时间。 +2. 部署兼容新旧会话的 API,完成回填后再加 `NOT NULL` 约束。 +3. 部署前端统一 401 处理和会话状态页。 +4. 启用过期会话清理任务和监控。 + +迁移不需要强制所有用户重新登录。 + +## 10. 验收标准 + +- 登录后刷新页面、关闭并重开浏览器、重启 API 均能恢复登录。 +- 管理端和员工端会话同时存在且互不覆盖。 +- 闲置期内活动能续期,但不能突破绝对有效期。 +- 停用账号、改密码、改角色、主动退出或管理员撤销后,旧会话立即不可用。 +- Cookie 中无用户资料,前端存储中无密码和会话令牌。 +- 网络故障不会被误判为退出,真实 401 会统一返回登录页。 +- 并发请求不会导致每次请求更新会话表。 + diff --git a/docs/scenic-queue-design-paradigm.md b/docs/scenic-queue-design-paradigm.md new file mode 100644 index 0000000..baac9a7 --- /dev/null +++ b/docs/scenic-queue-design-paradigm.md @@ -0,0 +1,1296 @@ +# 景区排队叫号系统 UI/UX 设计范式 + +> 文档版本:v1.0 +> 日期:2026-07-10 +> 状态:已确认开发基线;景区 VI、设备尺寸和现场阅读距离作为后续校准输入,不阻塞开发 +> 关联规划:[景区项目排队叫号系统:案例调研与完整规划](./scenic-queue-system-plan.md) + +术语:ETA 是技术文档中的“预计时间”缩写。所有游客、员工和管理界面必须写“预计等待时间”或“预计叫号时间”,不直接显示“ETA”。 + +### 阅读与执行标记 + +- **MUST**:开发与验收底线,除非正式变更本规范; +- **SHOULD**:推荐默认值,项目有证据时可调整并记录理由; +- **MAY**:可选增强; +- **[MVP] / [P1]**:交付阶段;未标阶段的规则默认属于 MVP 基线; +- **[现场输入]**:需要在现场 UAT 前补齐的 VI、设备或环境参数,不表示产品与技术基线仍未确认。 + +快速导航:1–4 为方向与依据,5–12 为基础系统与批量叫号,13–16 为四端规范,17–22 为跨端规则,23–27 为治理、验收与来源。 + +## 1. 设计决策摘要 + +本项目采用: + +> **公共服务导视型 Calm Utility(Public-service Wayfinding UI)** + +它把公共服务系统的事务清晰度、机场/公共交通的导视能力、现场运营工具的稳定性,以及克制的景区品牌识别合在一起。它不是旅游营销落地页,也不是霓虹、玻璃拟态、数据驾驶舱式的“科技大屏”。 + +已冻结的产品与实现约束: + +- 一人一号,不建立同行组或 `party_size`;手机号必填,姓氏/称谓选填,称谓默认“游客”,不采集法定性别。 +- 同一手机号可有多个活动号码;取号时先显示脱敏重复提示,员工确认后可继续并记录审计。 +- 每项目配置固定 `N`,员工只执行“叫下一批”;系统严格 FIFO 呼叫队首连续 `N` 个有效等待号码,不足时呼叫全部剩余号码。 +- 过号后原号失效;仍需排队时创建新号进入队尾,并保留新旧号码关联。 +- 预计等待时间首期同时提供固定批次和连续放行模板,每个项目二选一。 +- 中心服务是唯一写入权威,断网时停止数字化取号/叫号并切换人工预案。 +- 员工和管理员首期使用内置账号密码;前端 React,后端 Go + GORM,数据库 PostgreSQL。 +- 首期硬件只提供统一接口、成功/失败模拟器和测试日志,不接真实设备。 +- 留存基线为:个人关联 30 天、原始排队业务事件 90 天、登录/权限/网络安全日志至少 6 个月、后台操作审计 1 年、个人信息保护影响评估相关记录 3 年。 + +设计北极星是五个词: + +- **可信**:真实显示业务状态、更新时间、预计时间的不确定性和硬件回执,不制造“好像成功”的假象。 +- **清晰**:一个视图只有一个主任务;关键号码、人数、预计时间和下一步动作能被快速扫读。 +- **耐候**:适应强光、远距、弱网、噪声、单手、湿手、低端设备和长时间值守。 +- **低焦虑**:游客知道“现在怎样、还要多久、接下来做什么”;员工知道“将叫谁、是否生效、设备是否收到”。 +- **简单**:高频操作直接可见,异常操作收进二级入口;不把后台的数据结构和技术状态暴露给一线员工。 + +整体设计参数: + +| 参数 | 全局基线 | 解释 | +|---|---:|---| +| Design Variance | 3 / 10 | 低变异、稳定、不过度模板化;品牌感集中在有限区域 | +| Motion Intensity | 2 / 10 | 动效只解释状态和帮助视线定位 | +| Visual Density | 5 / 10 | 四端共享中等基线,再按任务分别覆盖 | + +四端覆盖值: + +| 终端 | Variance | Motion | Density | 首要目标 | +|---|---:|---:|---:|---| +| 游客 H5 | 3 | 2 | 3 | 一眼理解个人状态与下一步 | +| 员工 H5 | 2 | 2 | 7 | 高压现场下快速、准确、可恢复 | +| 公示大屏 Web | 2 | 默认 1 | 3 | 远距、强光、多人同时扫读 | +| 管理 Web | 2 | 1 | 6 | 运营看板、项目设置、日志和账号的清晰管理 | + +## 2. 适用范围与非目标 + +### 2.1 适用范围 + +本文约束以下界面: + +- 员工 H5:员工账号登录后只保留“叫号、取号”。叫号页展示当前批次、队首预览和固定主操作;每次叫下一批自动结束上一批;取号页核对等待队列; +- 游客 H5:[MVP] 个人排队状态、预计等待区间、提醒、过号和服务异常;[P1] 游客自助取号; +- 大屏 Web:实体公示屏展示当前叫号、最近批次、队列概况、服务公告、数据新鲜度和播报状态; +- 管理 Web:管理员账号登录后的运营概览、项目与预计时间设置及大屏中心。大屏中心呈现全项目公开运行状态并支持全屏监控;设备、账号权限、日志和审计按后续范围扩展。 + +### 2.2 非目标 + +- 不用同一组件密度强行统一四端; +- 不以首页宣传、景区视频或大幅风景图作为核心体验; +- 不使用玻璃拟态、渐变字、霓虹发光、3D 翻转、滚动劫持或游戏化排队; +- 不把暗色模式等同于大屏设计; +- 不把动画完成、浏览器计时器或声音播放完成当成业务成功; +- 不单独设计或部署运营数据看板应用;运营概览和大屏中心均是管理端入口,实体公示大屏仍是独立只读页面; +- 不提供匿名员工端、匿名管理端或匿名运营看板; +- 大屏只公开每日可读票号,不公开手机号、姓氏、称谓或其他身份信息;系统不采集法定性别。票号不作为私密查询凭证,首期不提供公开身份字段配置。 + +## 3. 研究依据与采用方式 + +本范式不是复制某个产品的视觉皮肤,而是吸收成熟系统中已验证的原则: + +| 来源 | 采用内容 | 不照搬内容 | +|---|---|---| +| GOV.UK / NHS | 从用户需要出发、把复杂留给系统、明确错误恢复、可访问组件、低认知负担 | 英国政府/医疗品牌色和品牌外观 | +| USWDS | 语义设计 token、移动优先网格、状态不只依赖颜色 | 美国政府品牌风格 | +| TfL / FAA / 英国交通部 | 无衬线、高对比、远距文字、环境光和实时信息规则 | 交通线路专用识别系统 | +| WCAG 2.2 / GB/T 37668-2019 | 对比度、焦点、缩放、目标尺寸、状态消息、动效控制 | 只为“过检测”而牺牲真实场景可用性 | +| Waitwhile / Qmatic 等排队产品 | 个人状态页与公示屏分离、批量操作预览、设备/叫号状态拆分 | 厂商未公开或无法核验的员工 UI 细节 | +| Material / Fluent / Apple / Motion / GSAP | 快速、可预测、可关闭的状态动效;明确工具边界 | 营销型大动效、逐项炫技和滚动叙事 | + +### 3.1 已核验的成熟 UI 模式 + +| 模式 | 公开证据 | 本项目采用方式 | +|---|---|---| +| Visitor Status Ticket | Qmatic Mobile Ticket 和 Waitwhile 状态页都把当前状态、票号、预计等待时间、队列进度和下一步作为个人页主层级 | 游客端采用单一主状态卡;叫到后由“等待信息”切换为“入口行动” | +| Operational Call Card | Qmatic Serve 以当前服务对象/下一位和主叫号操作为中心;Waitwhile 采用状态列 + 详情抽屉 + 固定操作 | 员工 H5 把叫号、取号和核验拆为三个场景;叫号页使用当前批次、下批候选和固定底部主操作,不做缩小版后台表格 | +| Batch Action Bar | Waitwhile 公开支持多选后批量 Serve、Alert、No Show 等 | 借鉴批量汇总,但本项目由服务端按固定 `N` 选择队首连续号码,并增加原子批次和模拟硬件结果语义 | +| Call Takeover | Qmatic/Waitwhile 公示屏均让最新叫号成为主区域,并保留最近历史 | 大屏最新批次持续占主视觉,最近 3–5 批作为防漏读历史,不用瞬时 toast | +| Live Ops Strip | Waitwhile Analytics 与 Qmatic Operations/BI 区分实时运营和历史分析 | 管理端实时页只放可驱动当下动作的队列/设备异常,历史趋势另页分析 | +| Offline Trust Banner | Wavetec 的低连接指导强调显式离线、待同步/失败和设备健康 | 中心服务是唯一写入权威;所有端显示数据新鲜度,离线时停写且禁止伪实时 | +| Hardware Receipt Tray | Qmatic/Wavetec 可核验多硬件闭环,但未公开完整局部失败 UI | 本项目自定义“业务结果 + 各通道 ACK + 单通道重试”模式 | + +公开资料边界同样重要:Waitwhile 可核验批量操作,但不能证明固定 `N` 队首选号、事务原子性、并发冲突或硬件 ACK;Qmatic 可核验单票叫号、转移、过号和多硬件,但未公开多人原子批量叫号;Wavetec 可核验硬件/低连接能力,未公开本项目所需的完整部分失败界面。因此这些能力必须按本文自有模式设计和 POC,不能用厂商营销图替代验收。 + +设计类本机 skill 的采用边界: + +- `design-taste-frontend` 只用于“反模板化、品牌克制、视觉一致性”检查。该规范本身声明不适合后台、数据表和多步骤产品 UI,因此管理端与员工流程以成熟公共服务/企业组件模式为主。 +- `ui-ux-pro-max` 用于检索风格、色彩、排版、可访问性和数据图表候选;数据库中误匹配的 App Store 落地页、奢华衬线字体、Video-first Hero 已排除。 +- `GSAP` 只作为复杂时间线工具的参考,不作为全局动画库。MVP 默认不引入;只有大屏多区域换批确实需要可中断同步时间线且原生/CSS 无法清晰完成时才启用。 + +## 4. 设计系统分层 + +```mermaid +flowchart TB + A["基础层
颜色、字体、间距、圆角、焦点、动效"] --> B["语义层
操作、状态、信息新鲜度、业务/设备回执"] + B --> C["共享组件层
表单、按钮、状态条、批次卡、设备回执、日志"] + C --> D1["游客 H5
低密度、单任务"] + C --> D2["员工 H5
高密度、强反馈"] + C --> D3["公示大屏
远距、高对比"] + C --> D4["管理 Web
表格、版本、审计"] + E["景区品牌层
Logo、品牌主色、入口/空状态图像"] --> D1 + E --> D3 +``` + +规则: + +1. **共享语义,不共享密度。** “成功、部分失败、离线、数据延迟”等语义跨端一致,但尺寸和信息量按任务调整。 +2. **品牌与语义分离。** 更换景区品牌色不能改变错误、警告、离线等状态含义。 +3. **公开投影与个人视图分离。** 大屏组件不能直接复用游客个人状态卡,避免泄露身份和出现不合适的字号。 +4. **业务成功与设备送达分离。** `CallBatch` 已生效、设备已接收命令和设备执行成功是不同状态,且大屏、广播等通道分别回执。 + +## 5. 色彩系统 + +### 5.1 原则 + +- “中性底色 + 单一品牌主色 + 暖色提醒 + 语义状态色”; +- 颜色永远不是唯一状态线索,必须同时有图标、文字、形状或位置; +- 不直接复制 GOV.UK、NHS、USWDS 的品牌色; +- 游客与员工 H5 浅色优先,适应户外;大屏使用安装现场校准的专用配置,不跟随操作系统主题; +- 核心文字达到 WCAG 2.2 AA;大屏当前号码和关键指令以 7:1 为推荐目标。 + +### 5.2 临时品牌与基础 token + +在景区 VI 提供前,使用“晴空蓝 + 暖阳橙”作为可替换临时层: + +| Token | 色值 | 用途 | 已计算对比 | +|---|---|---|---:| +| `color.page` | `#F5F7FA` | 页面底色 | — | +| `color.surface` | `#FFFFFF` | 卡片、表单、表格 | — | +| `color.ink` | `#0B1220` | 主文字 | 对 page 17.45:1 | +| `color.text.secondary` | `#475467` | 次级说明 | 对 page 7.16:1 | +| `color.primary` | `#0B67D1` | 主操作、链接、当前选择 | 对 white 5.42:1 | +| `color.primary.soft` | `#E8F1FC` | 信息背景 | primary 对其 4.75:1 | +| `color.accent.warm` | `#C34A0A` | 少量提醒/品牌点缀 | 对 white 4.88:1 | +| `color.border.strong` | `#667085` | 必要控件边界 | 对 white 4.97:1 | +| `color.focus` | `#FFBF47` | 键盘焦点外环 | 与 ink 11.41:1 | + +状态 token: + +| 状态 | 前景 | 浅背景 | 文案示例 | +|---|---|---|---| +| 信息 | `#0B67D1` | `#E8F1FC` | “数据更新于 14:32” | +| 成功 | `#137A4B` | `#EAF7F0` | “业务叫号已生效” | +| 警告 | `#8A4B08` | `#FFF4E5` | “广播设备尚未确认” | +| 危险/错误 | `#B42318` | `#FDECEC` | “叫号未提交,请重试” | +| 中性/离线 | `#344054` | `#F2F4F7` | “当前显示上次成功数据” | + +### 5.3 大屏专用色 + +先提供两种安装配置,最终以现场亮度和反光测试选择: + +| 配置 | 背景 | 主文字 | 当前批次强调 | 使用条件 | +|---|---|---|---|---| +| Display-HC Dark | `#07101F` | `#FFFFFF` | `#FFB000` | 室内、遮光较好、电视/LED | +| Display-Daylight Light | `#F7F9FC` | `#0B1220` | `#0B67D1` | 高环境光或浅底更清晰的设备 | + +Dark 配置中白字对背景为 19.04:1,暖黄对背景为 10.39:1。两种配置都禁止用低对比照片、视频、渐变和玻璃层作为信息底。 + +### 5.4 焦点状态 + +键盘焦点建议采用“双环”: + +- 内边:2px `#0B1220`; +- 外边:3px `#FFBF47`; +- 不以移除 `outline` 后只留阴影; +- 焦点不能被 sticky header、dialog 或滚动容器遮挡。 + +## 6. 中文排版与数字 + +### 6.1 字体栈 + +```css +font-family: + -apple-system, + BlinkMacSystemFont, + "PingFang SC", + "Microsoft YaHei", + "Noto Sans CJK SC", + "Noto Sans SC", + sans-serif; +``` + +若需要跨设备高度一致,生产环境优先自托管 Noto Sans SC/Source Han Sans 的必要字重;否则使用系统字体栈降低加载风险。 + +字体规则: + +- 仅使用 Regular 400、Semibold 600、Bold 700 三档; +- 不使用细体、斜体、描边字、压缩字、手写体和奢华衬线展示字; +- 中文不做装饰性宽字距,正文左对齐,不两端对齐; +- 数字启用 `font-variant-numeric: tabular-nums`,避免票号、人数和预计等待时间更新时横向跳动; +- 票号可使用同一字体的等宽数字特性,不为“科技感”单独引入难读的代码字体; +- 汉字、拉丁字母、数字、冒号、斜杠和单位必须做混排、断行和缺字测试。 + +### 6.2 建议字号 + +| 场景 | 辅助 | 正文/控件 | 小标题 | 核心状态 | +|---|---:|---:|---:|---:| +| 游客 H5 | 14px | 16–18px | 22–24px | 32–40px | +| 员工 H5 | 15–16px | 17–18px | 22–24px | 28–36px | +| 管理 Web | 14px | 14–16px | 20–24px | 按内容,不追求大字 | +| 大屏 | 不用固定 CSS px 验收 | 以物理字符高度验收 | 同左 | 当前号码最大 | + +行高: + +- 中文正文 1.5–1.65; +- 标题 1.2–1.35; +- 大屏号码 1.0–1.12; +- 按钮与单行控件不压缩到影响汉字上下留白。 + +正文建议: + +- 游客说明每行约 24–32 个汉字; +- 长文不超过约 60–70 个西文字符等效宽度; +- 多步骤说明拆成短句和编号,不用大段居中正文。 + +## 7. 网格、间距与形状 + +### 7.1 网格 + +- 主间距网格:8px; +- 4px 只用于图标、边框与组件内部微调; +- H5:员工端、游客端和内部登录卡以 390px 为固定设计宽度,窄于 390px 时缩至视口 100%;使用 4 列概念网格和 16px 左右边距; +- 管理端:12 列网格,常规事务内容宽约 1024–1200px;超宽区域只给日志/数据表; +- 大屏:按设备可视区建立安全区,初始预留四周 5%,再用 overscan/裁切实测校准。 + +间距 token: + +| Token | 值 | 典型用途 | +|---|---:|---| +| `space.1` | 4px | 图标内部微调 | +| `space.2` | 8px | 紧邻元素 | +| `space.3` | 12px | 标签与字段 | +| `space.4` | 16px | H5 基本间距 | +| `space.6` | 24px | 区块内边距 | +| `space.8` | 32px | 大区块分隔 | +| `space.12` | 48px | 页面章节 | +| `space.16` | 64px | 大屏/宽屏主要分区 | + +### 7.2 圆角、边框和阴影 + +- 表单、按钮:8–10px; +- 信息卡:12px; +- 大屏主要面板:12–16px,避免过圆; +- 状态标签可用胶囊形,普通按钮和卡片不全部做胶囊; +- 默认 1px 边框,关键选择/焦点 2px; +- 阴影只用于浮层、抽屉和需要层级分离的临时面板; +- 员工端、管理端主要靠分组、留白和分割线,不靠“每块都是浮起卡片”。 + +## 8. 图标、图片与品牌 + +### 8.1 图标 + +- 全项目只选一个成熟线性图标家族; +- 16/20/24/32 四档,笔画与字号匹配; +- 状态图标必须配文字,不能让员工猜图标; +- 不使用 emoji 作为产品图标; +- 禁止把相似图标分别用于“业务失败”和“设备未确认”。 + +建议语义: + +| 语义 | 图形方向 | 必配文字 | +|---|---|---| +| 排队中 | 时钟 | 排队中 | +| 即将叫号 | 铃铛 | 请留意叫号 | +| 已叫到 | 扬声器/方向箭头 | 请前往入口 | +| 已到场/已核验 | 核验对勾 | 已到场 | +| 服务中/入场中 | 方向箭头 | 正在入场 | +| 已完成 | 完成对勾/旗标 | 本次排队已完成 | +| 已过号 | 提示圆 | 已过号,请联系工作人员 | +| 数据延迟 | 断链/时钟 | 数据更新于 HH:mm | +| 设备未确认 | 设备 + 提示 | 设备尚未确认 | + +### 8.2 景区图片 + +可用位置: + +- [P1] 游客自助取号入口的有限品牌区; +- 空状态或非关键帮助页; +- 管理端景区项目封面; +- 不遮挡文字的静态背景边缘。 + +禁用位置: + +- 当前号码、个人状态、预计等待时间、批量叫号、设备回执和错误信息底部; +- 大屏关键信息区; +- 表格、图表和日志区; +- 自动播放视频、轮播和视差背景。 + +### 8.3 VI 替换策略 + +景区 VI 到位后,只替换: + +- Logo、品牌主色 alias、少量暖色点缀; +- 游客入口/空状态图像; +- 允许的品牌文案和插画。 + +不能改变: + +- 状态色含义、对比度、焦点、字号、触控目标; +- 批量叫号和硬件回执层级; +- 大屏信息安全区和公共隐私规则。 + +## 9. 交互基础规则 + +### 9.1 触控、鼠标与键盘 + +- H5 所有可点击区至少 48×48 CSS px; +- WCAG 2.2 AA 的 Target Size (Minimum) 底线是 24×24 CSS px(含例外);本项目因户外、高压和湿手场景主动采用更严格的 48×48px 产品基线,不能把 48px 误写成 WCAG 原文要求; +- 员工主操作按钮高 56px;批量叫号底部主按钮可为 60–64px; +- 相邻危险操作至少间隔 8px; +- label 整体可点,不只让 radio/checkbox 的小图标可点; +- 按下后 100ms 内给出视觉反馈,但业务状态只在服务端确认后改变; +- 管理端完整支持键盘、可见焦点、合理 tab 顺序和 Escape 关闭非阻断浮层; +- 户外真机必须测试单手、湿手、戴薄手套和低端 Android。 + +### 9.2 主操作 + +- 每个视图只有一个视觉主操作; +- 主操作文字直接写结果,例如“叫下一批(5 个号码)”,不用“确定”“提交”; +- 普通、可逆、符合规则的高频操作不反复弹确认; +- MVP 不提供跳序、临时修改 `N`、手选和批次撤回;[P1] 若后续启用,使用阻断确认并要求原因; +- 删除、停服等危险操作与主操作在空间和颜色上分离。 + +### 9.3 反馈层级 + +1. **组件内即时反馈**:pressed、disabled、提交中; +2. **页面内持久状态**:业务已生效、设备确认、数据陈旧; +3. **错误摘要**:跨字段或阻断流程的问题; +4. **toast**:只用于非关键、可短暂消失的信息,不能独自承载叫号成功、设备失败或数据延迟。 + +### 9.4 加载与刷新 + +- 不用空白页和无限 spinner 代替信息; +- 刷新时保留上一版数据,并显示“正在更新”和上次更新时间; +- 不使用无限 shimmer; +- 超过短暂等待时显示静态文案、经过时长和可执行下一步; +- 实时列表更新保持空间稳定,不自动把员工当前正在操作的行移走。 + +## 10. 共享组件清单 + +首批设计系统至少包含: + +| 类别 | 组件 | +|---|---| +| 导航 | App Header、员工任务切换、管理侧边栏、面包屑 | +| 输入 | Text Field、Phone Field、Select、Radio Group、Checkbox、Date/Time、Search、Filter | +| 操作 | Primary/Secondary/Danger Button、Icon Button、Split Action(仅必要时) | +| 反馈 | Inline Message、Error Summary、Status Banner、Toast、Progress Text、Empty State | +| 队列 | Ticket Identity、Queue Summary、预计等待区间、Position Summary、Call Batch Card | +| 批量 | Batch Composer、Batch Size Meter、Ticket Selection Row、Batch Preview、Conflict Banner | +| 设备 | Device Channel Status、Delivery Receipt、Retry Action、Device Health Summary | +| 数据 | Data Table、Pagination、Column Visibility、Filter Bar、Audit Timeline、Version Diff | +| 浮层 | Dialog、Bottom Sheet、Drawer、Popover;按风险和屏幕选择 | +| 公示 | Current Batch Panel、Recent Batch List、Service Notice、Freshness Indicator | + +组件必须设计以下状态: + +- default、hover、focus、pressed、selected、disabled; +- loading、empty、partial、success、warning、error; +- offline、stale、reconnecting、permission denied; +- concurrency conflict、device pending、device partial failure; +- reduced motion 与 200% 文本缩放。 + +## 11. 业务状态语言 + +### 11.1 游客可见状态 + +| 状态 | 主文案 | 下一步 | 视觉 | +|---|---|---|---| +| 排队中 | “正在排队” | “可暂时离开,请留意更新” | 中性/信息 | +| 接近叫号 | “请留意叫号” | “建议返回入口附近” | 暖色提醒 | +| 已叫到 | “您的号码已叫到” | “请前往 2 号入口” | 高对比成功/行动 | +| 等待核销 | “请在入口出示号码” | 明确入口与宽限时间 | 信息 | +| 已到场/已核验 | “已到场” | “请按现场指引等待入场” | 信息/成功 | +| 服务中/入场中 | “正在入场” | “请按工作人员指引通行” | 信息 | +| 已完成 | “本次排队已完成” | 可关闭页面 | 成功 | +| 已过号 | “已过号” | “请联系工作人员处理” | 警告,不羞辱用户 | +| 已取消 | “排队已取消” | 可重新取号条件 | 中性 | +| 项目暂停 | “项目暂时停止叫号” | 原因、更新时间、恢复预期 | 持久警告 | +| 数据延迟 | “当前为上次成功数据” | “更新于 14:32,正在重连” | 离线/陈旧 | + +### 11.2 员工业务与设备状态 + +MVP 先完成业务叫号状态。设备只接模拟适配器,显示“未配置 / 模拟成功 / 模拟失败”。以下完整设备投递层级仅在后续真实硬件专项启用: + +```text +业务叫号 + ├─ 正在提交 + ├─ 已生效 + └─ 未生效 / 冲突 + +设备投递 + ├─ 待发送 + ├─ 已接收,执行结果未知 + ├─ 执行中 + ├─ 执行成功 + ├─ 可重试失败 + ├─ 最终失败 + └─ 命令已过期 +``` + +没有硬件 ACK 时只能写“已发送”或“执行结果未知”,不能写“播报成功”。“已确认”也必须说明确认层级:设备已接收、正在执行或执行成功。设备失败不回滚已生效的 `CallBatch`,但必须给出重试、切换备用通道和人工告知的路径。 + +### 11.3 文案结构 + +错误/异常遵循: + +> 发生了什么 → 当前数据/业务是否仍有效 → 用户下一步做什么 + +示例: + +- MVP 示例:“叫号已生效,但广播模拟器返回失败。公示屏已更新;核心队列不受影响。” +- 差:“设备异常。” +- 好:“队列已被另一位员工更新。请检查最新 3 个候选号码后再叫号。” +- 差:“版本冲突,请刷新。” + +### 11.4 排队单状态到界面映射 + +| 领域状态 | 游客 H5 | 员工 H5 主操作 | 大屏 | 辅助技术通知 | +|---|---|---|---|---| +| `WAITING` | 正在排队 | 选入下一批、取消 | 只显示队列概况 | 预计等待时间有意义变化时 polite | +| `CALLED` | 已叫到/等待核验 | 重叫、到场、过号 | 当前/最近叫号 | 游客本人一次性 assertive | +| `ARRIVED` | 已到场 | 开始服务/入场、异常取消 | 可从当前叫号移入已到场统计 | polite | +| `SERVING` | 正在入场/服务中 | 完成、授权异常处理 | 不再作为待到场号码 | 默认不播报 | +| `COMPLETED` | 本次排队已完成 | 只读;逆向需高权限 | 不公开个人结果 | polite 一次 | +| `MISSED` | 已过号,请联系员工重新取号 | 创建新排队单并关联原号 | 按配置保留过号提示 | 警告摘要 | +| `CANCELLED` | 排队已取消 | 只读;必要时新建排队单 | 不显示 | polite 一次 | + +MVP 员工端 MUST 将 `ARRIVED` 与 `SERVING` 合成一个“已到场/入场”操作;数据层可保留两个领域时间点。`COMPLETED` 永远表示本次服务完成,不等同于刚到场。 + +### 11.5 叫号批次状态到界面映射 + +| `CallBatch` 状态 | 员工标签与允许操作 | 大屏保留规则 | 设备回执关系 | +|---|---|---|---| +| `ACTIVE` | “本批进行中 · X 个号码待到场”;可重叫、逐个到场/过号 | 按到场窗口和活动批次规则保留仍待到场号码 | 模拟适配器只返回成功/失败;真实硬件接入后再展示通道回执 | +| `PARTIALLY_RESOLVED` | “部分处理 · 已处理 A/B 个号码”;只处理剩余成员,不允许整批撤回 | 只保留仍处于 `CALLED` 的成员,已处理成员退出待到场主区 | 原叫号回执保留;对失败通道可显式重叫/重试,不重做业务批次 | +| `RESOLVED` | “本批已结束”;展示到场/过号/取消汇总,只读且不可删除 | 退出当前叫号区;按配置进入有限历史 | 回执作为历史证据保留,迟到 ACK 只更新设备记录 | +| `[P1] WITHDRAWN` | “本批已撤回”;展示原因、操作人和成员后续处理 | 立即退出当前叫号;发布纠正后的权威快照 | 硬件接入后定义取消/失效能力 | + +UI MUST 直接读取服务端 `CallBatch.status`,不能通过成员列表临时汇总推断。MVP 只使用 `ACTIVE / PARTIALLY_RESOLVED / RESOLVED`;`WITHDRAWN` 放 P1。 + +### 11.6 运营状态与数据新鲜度矩阵 + +| 运营状态 | 游客 H5 | 员工允许操作 | 大屏 | 预计等待时间 | +|---|---|---|---|---| +| `CLOSED` 未开放 | 展示开放信息 | 不可取号/叫号/核验 | 未开放 | 不显示分钟 | +| `OPEN` 运行中 | 正常状态页 | 正常取号、叫号、核验 | 正常叫号 | 正常区间 | +| `PAUSED` 暂停 | 号码保留、展示原因 | 禁止新取号/叫号;可核验已叫号 | 暂停原因和现场指引 | 隐藏分钟,恢复后重算 | +| `FINISHED` 当日结束 | 今日服务已结束 | 只读/收尾 | 已结束 | 不显示 | + +MVP 只设计以上四种状态。[P1] 确有现场需求时,再拆分“仅暂停取号”“仅暂停叫号”和“清队中”。 + +数据新鲜度由 `last_success_at`、`queue_revision` 连续性、实时连接状态和部署模式共同决定,阈值按站点配置: + +| 新鲜度 | 必须呈现 | 写操作规则 | +|---|---|---| +| 实时 | 正常数据;必要时显示最近更新时间 | 正常 | +| 正在重连 | 保留上次数据、更新时间和重连状态 | 停止新取号/叫号;恢复并确认权威 revision 后再提交 | +| 数据陈旧 | 持久警告、弱化/隐藏预计时间、说明旧数据时间 | 停止新取号/叫号,切换现场备用流程 | +| 离线 | 最后成功快照、旧数据时间、纸质号码/人工广播指引 | 禁止任何数字化写入和伪成功 | + +## 12. 批量叫号设计范式 + +### 12.1 核心原则 + +- `CallBatch` 是一个原子业务对象,不是前端循环调用单号按钮; +- 一人一号,每张有效等待票计为一人;同时显示项目固定 `N` 与本批实际号码数量; +- MVP 严格 FIFO、不跳号、不手选;队列不足 `N` 时叫出全部剩余号码; +- 批次内所有票号同时出现,禁止逐号揭示、stagger、翻牌或滚动数字; +- UI 不使用乐观成功;按钮立即响应,但只能在服务端事务成功后写“已生效”。 + +### 12.2 员工端批次编排 + +稳定结构: + +1. 顶部:项目状态、资源/入口、数据更新时间; +2. 中部:下一批候选号码,显示票号、等待时长和特殊标记; +3. 批量条:项目固定 `N`、本批实际号码数和队列剩余数,但不只靠颜色; +4. 批次预览:系统自动选出的队首连续号码;员工不能在日常流程修改成员; +5. 底部固定操作区:唯一主按钮“叫下一批(5 个号码)”; +6. 提交后:业务结果和各设备回执在原位置持续展示。 + +### 12.3 确认策略 + +正常批次: + +- 预览完整且规则允许时,点击主按钮直接提交; +- 防重复点击由交互层处理,并由服务端幂等和队列锁保证正确性; +- 不为每次高频正常叫号增加“是否确定”弹窗。 + +风险批次 [P1]: + +- 临时修改 `N`、越过 FIFO、手工选人、撤回/重排时弹确认; +- 弹层必须说明影响对象、人数、原因字段和审计后果; +- 危险确认按钮使用具体动词,如“确认跳过并叫号”,不用“确定”。 + +### 12.4 业务结果与硬件扩展 + +MVP 结果卡只显示“第 18 批已生效:5 个号码”和模拟适配器的成功/失败。真实硬件以后续专项接入后,再分别展示: + +| 层级 | 示例 | +|---|---| +| 业务 | “第 18 批已生效:5 个号码” | +| 公示 | “大屏执行成功 · 14:32:08”或“大屏已接收,执行结果未知” | +| 语音 | “广播可重试失败:功放离线” | +| 通知 [P1] | “短信 4 成功 / 1 待重试” | + +设备回执变化只更新自己的状态徽标,不重播主叫号动画,也不把整批写成“失败”。 + +## 13. 游客 H5 规范 + +### 13.1 信息优先级 + +游客状态页按以下顺序组织: + +1. **项目与当前状态**:明确项目名称,以及排队中、请留意、已叫到、已过号等; +2. **我的号码与下一步**:票号、入口/通道、行动指令; +3. **预计时间**:区间、可信度、更新时间; +4. **队列进展**:前方约多少个号码、当前叫号; +5. **帮助与规则**:迟到、取消、暂停、隐私和联系方式。 + +页面不先展示全局统计,不把“景区介绍”放在个人状态之前。 + +### 13.2 游客自助取号 [P1] + +MVP 由员工代取号;游客端先交付私密状态页。P1 启用自助取号时,表单使用单列、渐进披露: + +- 先选项目/资源,再填写一人一号所需字段; +- 手机号必填;姓氏和称谓选填,称谓未选择时默认“游客”;不展示或采集法定性别字段; +- 每个字段旁说明用途,隐私说明在提交前可见; +- 不用 placeholder 代替 label; +- 表单错误在提交后集中摘要,并在字段旁给出相同的具体修正方式; +- 保留已填写内容,服务故障不伪装成输入错误; +- 成功后生成每日可读票号;公开票号只用于现场识别,不能单独用于查询个人状态。内部 ID 和私密查询 token 使用高熵随机值。 + +### 13.3 个人状态卡 + +核心布局: + +- 状态眉题:“正在排队”; +- 大号票号:“A123”; +- 主预测:“预计 25–35 分钟叫到”; +- 可信度/异常:“参考性中 · 14:32 更新”; +- 队列位置:“前方约 8 个号码”; +- 下一步:“可暂时离开,建议在叫号前 X 分钟返回”(X 为项目配置,不在界面写死)。 + +预计等待时间: + +- 显示区间,不显示看似精确的“27 分 43 秒”; +- 默认按 5 分钟粒度; +- 明确区分“预计叫到号”和“预计开始体验”; +- 低可信时解释原因,例如“项目刚恢复,预测数据较少”; +- 数据陈旧时保留上次值,但在视觉上降级并显示更新时间。 +- 项目暂停时不继续显示旧预计时间;改为“号码已保留,恢复后重新估算”,避免把失效预测当承诺。 + +### 13.4 被叫到 + +- 不弹抢焦点 modal; +- 页面顶部出现持久、高对比行动横幅; +- 一次性辅助技术通知:“您的号码 A123 已叫到,请前往 2 号入口”; +- 清楚显示宽限规则和入口位置; +- 视觉、声音和 [P1] 短信/通知互为补充,任一失败不让页面失去行动信息。 + +### 13.5 页面稳定性 + +- 预计等待时间、人数和当前号更新不改变主卡位置; +- 不逐秒倒计时,不做老虎机数字; +- 不因实时消息把用户滚回顶部; +- 用户展开的帮助、规则和无障碍设置在刷新后保持; +- 从后台恢复先拉取权威快照,不补播旧提示音或动画。 + +## 14. 员工 H5 规范 + +### 14.1 首页与任务结构 + +员工首页不是指标仪表盘,而是值守工作台: + +- 首次进入必须完成账号登录;不使用共享匿名账号; +- 固定顶部:当前登录账号、从已授权项目中选择当前项目、营业状态、在线/离线和当前时间; +- 主区:当前批次、下一批候选、等待队列; +- 固定底部:唯一主操作“叫下一批”; +- 一级入口只保留取号、队列、叫号;重叫、过号后重新取号、历史和设备放入“更多”; +- 告警优先按“是否阻断叫号”排序,不按技术模块排序。 + +切换项目必须清空当前勾选、批次预览和搜索条件,并再次显示目标项目名称,防止员工把 A 项目的游客叫入 B 项目。 + +### 14.2 信息密度 + +- 支持紧凑列表,但每行仍保证 48px 触控目标; +- 票号、等待时长、特殊标记位置稳定; +- 当前操作对象用边框、背景、勾选和文字同时标识; +- 日常叫号由系统按当前项目固定 `N` 自动选择队首连续号码,员工只核对“本批几个号码”;手工多选仅在后续授权异常处理中出现; +- 列表发生实时变化时,保留员工当前选择并显示冲突条,不静默重排; +- 高密度不等于小字,正文以 17–18px 为现场起点。 + +### 14.3 取号与查找 + +- 员工代取号和游客自助取号共享校验规则; +- 检测同一手机号已有活动排队单时,先显示脱敏提示;同一手机号允许多个活动号码,员工明确确认后继续取号,并显示“本次确认将记录到审计”; +- 搜索结果默认只显示完成任务需要的信息; +- 完整手机号在员工端和管理端的授权业务列表中直接与票号、姓氏/称谓和状态对应展示,便于现场联系与核对;跨项目、匿名和公示路由不得取得该字段; +- 搜索、取号和叫号入口在视觉上区分,防止员工把“创建”误当“查找”。 + +### 14.4 并发与恢复 + +当另一位员工先完成叫号: + +- 保留当前屏幕和选择; +- 页面内显示:“队列已被李师傅于 14:32 更新”; +- 列出候选集变化; +- 提供“检查最新批次”,不直接重复提交; +- 已失效选择用明确文字说明原因。 + +当网络中断: + +- 显示离线状态和上次同步时间; +- 中心服务是唯一写入权威,禁止新取号、叫号或假装离线成功; +- 只允许安全的本地查看/草稿动作; +- 恢复后先同步 revision,再允许新叫号。 + +### 14.5 设备区 [后续真实硬件专项] + +员工无需理解厂商协议。设备健康按任务呈现: + +- “大屏可用 / 数据延迟 / 未连接”; +- “广播可用 / 未启用 / 未确认”; +- “打印机缺纸 / 离线”; +- 每个状态给出影响和下一步; +- 设备详情、日志和高级重试在二级页面,主叫号页只保留阻断性摘要。 + +### 14.6 叫号后的现场处理 + +当前批次卡必须支持完整闭环: + +- **重叫**:沿用同一 `CallBatch`,创建新的叫号尝试;展示已重叫次数和最近时间; +- **到场/入场**:一个按钮完成核验并推进到 `SERVING`,支持整批或逐个号码处理; +- **[P1] 分开记录到场与开始服务**:只有入口拥堵分析确有需要时才拆成两个按钮; +- **完成**:仅从服务中推进到 `COMPLETED`,按钮文案写“完成本批服务”,不写“已入场”; +- **过号**:显示宽限是否到期和受影响号码;需要继续排队时创建新号进入队尾,明确展示原号已失效和新号关联; +- **[P1] 撤回/重排**:MVP 不提供;确有现场需求时再定义权限、原因和新位置; +- **异常取消**:MVP 只允许取消未叫号排队单;叫号后的复杂逆向取消放 P1。 + +[后续真实硬件专项] 扫码器、物理按钮等上行 `DeviceEvent` 到达时,页面再增加来源、处理结果和去重状态。MVP 只需保证批次部分到场时同时显示“已到场号码数”和“仍待到场号码数”。 + +## 15. 公示大屏 Web 规范 + +### 15.1 屏幕结构 + +大屏只承载现场公示,不承担管理或安全控制: + +1. 顶栏:景区/项目名、服务状态、当前时间; +2. 主区:最新叫号批次,最大视觉权重; +3. 辅区:入口/通道、号码数量、行动指令; +4. 历史区:最近 3–5 个批次,防止观众错过瞬时更新; +5. 底栏:数据更新时间、广播状态、服务公告。 + +每台公示屏默认绑定一个 `project_id`,页面不提供临时项目下拉框;需要更换项目时由管理端重新绑定,避免多个项目票号混屏。 + +当前号码必须持续可见,不能被轮播、广告、风景视频或公告覆盖。 + +### 15.2 批量号码 + +- 连续号段可显示“A120–A124”,同时显示“5 个号码”; +- 不连续票号按两至三行稳定网格显示; +- 票号数量超过安全显示容量时,调整业务批次/分屏规则,不把字号无限缩小; +- 所有号码同时出现;消失条件绑定 `CALLED` 状态、配置的到场窗口和活动批次保留规则,不以“出现下一批”作为唯一条件; +- “再次叫号”增加静态标签和时间,不让号码弹跳或闪烁。 + +### 15.3 远距阅读 + +大屏不以设计稿 CSS px 作为最终验收指标。初始采用更保守的: + +> 关键字符物理高度 ≥ 最远有效阅读距离 × 1% + +| 最远有效阅读距离 | 关键字符建议最低物理高度 | +|---:|---:| +| 8m | 80mm | +| 10m | 100mm | +| 15m | 150mm | +| 20m | 200mm | + +电子实时显示可参考“观看距离 ÷ 137.5”的字符高度基线,但当前叫号优先采用更保守的 1% 规则。该规则主要来自拉丁字符资料,中文必须现场二次验证。 + +验收环境: + +- 上午、正午、傍晚; +- 正视和主要斜视角; +- 室内灯光、窗边强光、反光和阴影; +- 实际屏幕、实际播放器/工控机、实际分辨率; +- 断网、数据陈旧和音频未授权。 + +### 15.4 安装配置 + +每台大屏保存独立配置: + +- 方向、分辨率、可视安全区、最远阅读距离; +- Dark / Daylight 色彩方案; +- 字符物理高度校准; +- 标准 / 低动效 / 无动效,默认低动效; +- 浏览器播报是否启用、备用设备通道; +- 环境光亮度策略和人工亮度上限; +- 设备配对、心跳和最后数据时间。 + +### 15.5 大屏内容禁令 + +- 手机号、姓氏和称谓不公开;系统不采集法定性别,首期不提供公开身份字段配置; +- 不用跑马灯传达关键叫号; +- 不使用红绿作为唯一对比; +- 不持续 pulse、呼吸、闪烁; +- 不滚动旧消息,不恢复前台后补播; +- 不让公告和营销内容与叫号争夺主视觉; +- 不在同一页面提供闸机、启航等安全关键控制。 + +## 16. 管理 Web 规范 + +### 16.1 导航 + +建议一级信息架构: + +- 运营看板; +- 项目设置; +- 设备; +- 账号权限; +- 日志; +- 系统与隐私。 + +侧边栏稳定,不用大量悬浮卡片作为导航。顶部始终显示当前登录账号和“全部项目/当前项目”筛选;进入设置、设备和日志详情时必须选定一个项目。 + +管理端所有路由,包括运营看板,都必须先完成账号登录和服务端角色校验;没有管理权限时不渲染看板数据或侧边栏入口。 + +### 16.2 总览 + +运营看板就是管理端首页,不是独立应用。默认展示全部项目的一张列表:项目状态、等待号码数/人数、当前叫号、预计等待时间、最后更新时间和设备模拟异常。顶部只保留 4 个汇总数字:运行项目数、总等待人数、异常项目数、模拟接口异常数。 + +选择一个项目后,再展示该项目今日叫号/完成人数、简单等待趋势和最近异常。不要把所有指标做成等宽“大数字卡”,也不提供自由拼装看板。 + +### 16.3 项目与预计时间设置 + +每个项目使用一张简单表单:项目名称、票号前缀、营业状态、固定批量人数 `N`、宽限时间,以及“连续放行/固定批次”二选一的预计时间模板和对应参数。两种模板都属于首期能力。 + +- 保存后直接生效,并记录修改人、时间和前后值; +- 页面显示上一版值,可由有权限的管理员恢复; +- 不做草稿审批、定时发布、公式编辑器或历史数据模拟; +- 校验错误定位到字段,跨字段错误在顶部摘要; +- 暂停项目使用单独的醒目开关和原因,不藏在预计时间参数里。 + +### 16.4 数据表和日志 + +- 默认左对齐文本、右对齐数字; +- 时间、票号、人员、动作、结果、设备回执列位置稳定; +- 列筛选、排序、分页和导出状态可见; +- MVP 只固定关键列;[P1] 再提供列显示配置和保存视图; +- 高风险日志显示前后值和关联业务事件 ID; +- 日志列表不做逐行 layout animation; +- 行点击区与行内操作分开,避免误触; +- 状态不能只有红绿圆点,必须有文字。 + +### 16.5 账号与权限 + +- 员工和管理员使用可追溯的个人账号;不建议多人共享一个登录账号; +- 首期使用系统内置账号密码,登录界面支持限流后的明确等待提示、管理员重置后的首次改密和会话被吊销状态;预留 OIDC/SSO,但不在首期显示不可用入口; +- 角色权限按能力分组,显示作用域; +- 高风险权限用明确说明,不用技术缩写; +- 修改前展示将获得/失去的能力; +- 禁用账号与删除账号分开; +- 最近登录、设备、修改人和审计记录可查; +- 权限不足提示说明当前缺少什么以及如何申请,不泄露敏感资源。 + +### 16.6 设备接口 + +MVP 不建设完整设备管理页面。项目设置中只保留“设备接口:未配置 / 模拟器”,以及“模拟成功 / 模拟失败”测试按钮和最近一次测试结果。 + +硬件型号确认并单独纳入范围后,才增加设备注册/配对、项目绑定、在线/心跳、最近命令、执行回执和重试页面。证书轮换、Webhook/API 密钥、主备链路和驱动版本属于后续高级设置。 + +### 16.7 隐私与数据治理 + +- 按字段展示处理目的、是否必填、保存期限、到期动作和可访问角色; +- 字段基线固定为手机号必填、姓氏/称谓选填、称谓默认“游客”,不采集法定性别; +- 留存页显示已确认期限:个人关联在完成/取消/过号后 30 天删除,原始排队业务事件 90 天后匿名化,登录/权限/网络安全日志至少 6 个月,后台操作审计 1 年,个人信息保护影响评估相关记录 3 年; +- 公开大屏投影字段使用单独白名单,默认只有票号、批次、入口和非个人统计; +- 数据导出先显示范围、敏感字段、用途和审批状态;下载链接短时有效并留审计; +- 支持按游客/排队单处理查询、更正、删除或匿名化流程,并标明法定/安全日志例外; +- 完整手机号只进入受账号、角色和项目范围保护的员工/管理读模型;批量导出、留存规则修改和匿名化任务仍记录操作人、原因和前后值; +- 受托短信、云服务或硬件厂商显示处理范围、合同状态和最近数据传输; +- 仪表盘只展示汇总结果,不让管理员通过小样本筛选重新识别个人。 + +## 17. 响应式与设备适配 + +### 17.1 断点 + +设计断点是布局决策起点,不是设备型号列表: + +| 名称 | 宽度 | 用途 | +|---|---:|---| +| Compact | 320–767 CSS px | 游客/员工手机 | +| Medium | 768–1023 | 平板、横屏员工端 | +| Desktop | 1024–1439 | 管理端、部分值守台 | +| Wide | ≥1440 | 宽表格和大屏配置台 | +| Display | 按物理屏幕配置 | 公示大屏,不套普通响应式 | + +### 17.2 H5 + +- 固定参考画布宽度为 390px;桌面打开员工端/游客端时也保持该宽度并居中,不扩展成桌面双列; +- 320、375、390、414px 必测;320/375px 使用 100% 宽度,414px 及更宽视口仍保持 390px; +- 只固定宽度,不固定页面高度;长内容和员工队列表正常纵向滚动,宽表格只在自身容器横向滚动; +- 关键操作在安全区内,兼容浏览器底栏和刘海; +- 键盘弹起不遮挡当前字段和提交按钮; +- 横屏不把控件横向拉得过宽; +- 200% 文本放大后仍可完成取号和叫号; +- 避免非必要双向滚动。 + +### 17.3 管理端 + +- 1024px 起可完成核心任务; +- 小于推荐宽度时,表格可受控横向滚动,但操作列和关键标识保持可见; +- 管理端手机视图可提供告警查看和有限操作,不强行压缩完整规则编辑器; +- 不用浏览器缩放代替响应式设计。 + +### 17.4 大屏 + +- 每种硬件建立独立 viewport/profile; +- 不把 1080p 设计简单等比放到 4K; +- 用实物毫米、观看距离和安全区验收; +- 对 LED 点距、电视 overscan、浏览器缩放、系统 DPI 和屏幕方向分别记录。 + +## 18. 动效与 GSAP 规范 + +### 18.1 总体原则 + +动效采用“克制、快速、状态优先”: + +- 只用于确认操作、解释状态变化和帮助视线定位; +- 不让用户等待,不作为理解状态的唯一方式; +- `CallBatch` 内成员始终作为整体出现; +- 动画、业务提交、设备命令和语音并行响应同一业务事件,互不作为成功条件; +- 项目目标是零主动闪烁。 + +### 18.2 工具边界 + +| 工具 | 允许 | 禁止 | +|---|---|---| +| CSS | hover、focus、pressed、颜色、透明度、短距离 transform | 复杂跨区编排 | +| React 原生/CSS 轻量过渡方案 | 少量条件面板、dialog/drawer、短列表进入/退出;确有需要时可评估 Motion | 实时队列整表 reorder、持续 layout animation | +| GSAP | 仅在验证有必要时处理大屏可中断的多区域换批 | 按钮、后台表格、游客预计时间、ScrollTrigger、ScrollSmoother、Draggable、SplitText | +| GSAP Flip | 少量非关键布局连续性 | 票号、批次成员和业务顺序 | + +MVP 优先 CSS;如果 CSS 已能清晰完成大屏 150ms 交叉淡化,则不引入 GSAP。同一 DOM 节点和同一 CSS 属性只能由一种动画引擎控制。 + +### 18.3 动效 token + +| Token | 时长 | 用途 | +|---|---:|---| +| `motion.instant` | 0ms | 队列顺序、数据表、无动效模式 | +| `motion.press` | 100ms | 按钮 pressed | +| `motion.fast` | 150ms | 状态、设备回执、错误文字 | +| `motion.standard` | 200ms | 轻量浮层、游客状态卡 | +| `motion.state` | 300ms | 员工批次卡整体更新 | +| `motion.large` | 400ms 上限 | 大屏批次整体换场 | + +缓动: + +```text +standard cubic-bezier(.2, 0, 0, 1) +enter cubic-bezier(0, 0, 0, 1) +exit cubic-bezier(.3, 0, 1, 1) +linear 只用于真实、确定的进度 +``` + +禁止 elastic、bounce、back、过冲 spring。H5 位移不超过 8px,大屏标准模式不超过 12px。 + +### 18.4 批量叫号序列 + +员工端: + +1. t=0:按钮 100ms pressed,同时提交请求并显示静态“正在提交叫号”; +2. 等服务端时不逐号播放、不使用循环数字; +3. 成功后旧批次卡 150ms 淡出,新批次卡在 250–300ms 内整体淡入,最多上移 8px; +4. 所有票号同时出现; +5. 响应到达即恢复允许的操作,不等动画结束; +6. 设备回执以 150ms 交叉淡化独立更新,不重播批次动画; +7. 失败用图标、文字和边框出现,不 shake、不闪红。 + +大屏: + +1. 收到 `CallBatchActivated` 后立即更新数据和时间戳; +2. 旧主区 0–150ms 降低透明度; +3. 新批次 50–350ms 整体淡入; +4. 400ms 内完成,之后长期静态; +5. 新事件打断旧时间线时,取消旧过渡并直接呈现最新快照; +6. 再次叫号仅做一次 150–200ms 色彩强调和静态标签。 + +### 18.5 Reduced Motion + +交互端提供“跟随系统 / 减少动效 / 关闭动效”,大屏提供设备级模式,默认低动效。 + +减少动效: + +- 移除位移、缩放、视差、spring、stagger 和布局移动; +- 只保留不超过 100ms 的透明度/高亮,或直接切换; +- React 动效方案必须跟随 `prefers-reduced-motion`;若使用 Motion,显式设置 `MotionConfig reducedMotion="user"`; +- GSAP 使用 `gsap.matchMedia()`,条件变化和卸载时 kill/revert; +- 代码不得依赖 `transitionend` 或 `onComplete`; +- 关闭动效时所有 duration 为 0ms。 + +### 18.6 后台标签页 + +- `document.hidden` 时停止装饰动画、浏览器音频和视频; +- 不积压 timeline; +- 回到前台先拉服务端快照和 revision,再订阅事件; +- 不补播隐藏期间的动画、提示音或语音; +- 预计等待时间、过号和宽限期只使用服务端时间; +- [后续真实硬件专项] 本地设备网关不受浏览器页面可见性控制。 + +## 19. 可访问性规范 + +### 19.1 标准基线 + +- 全端达到 WCAG 2.2 AA; +- 中国场景同时以 GB/T 37668-2019 为测试基线; +- 对交互动效主动采用 WCAG 2.3.3“可关闭”的更高要求; +- 关键大屏文字尽量达到 7:1; +- 设计评审不能只看自动化扫描,必须结合真实内容、设备和上下文。 + +### 19.2 可感知 + +- 普通文本 ≥4.5:1,大字 ≥3:1,必要控件边界/图形 ≥3:1; +- 状态不只依赖颜色; +- 视觉、语音和文字至少两种通道互补; +- 200% 文本缩放不丢内容或功能; +- 320 CSS px 宽度无非必要双向滚动; +- 图标有可访问名称,装饰图标对辅助技术隐藏; +- 视频/音频若未来加入,必须有可访问替代和控制。 + +### 19.3 可操作 + +- H5 触控目标 ≥48×48px; +- 焦点顺序符合视觉流程,焦点始终可见; +- dialog 打开后管理焦点,关闭后返回触发点; +- 无键盘陷阱; +- 自动更新、滚动或移动内容可停;本项目关键页不使用持续运动; +- 时间限制显示剩余和延期方式;业务时间不由客户端倒计时决定。 + +### 19.4 可理解与健壮 + +- label、错误摘要和字段错误完整关联; +- live region 在更新前已存在于 DOM; +- 普通更新使用 `role="status"` / polite / atomic; +- 游客自己的号码被叫到可一次性使用 assertive; +- 大列表默认 `aria-live="off"`,主动打开日志时才使用 `role="log"`; +- 后台更新不抢焦点; +- 页面 `lang="zh-CN"`,混合语言片段按需要标记; +- 业务状态、错误和设备回执有稳定可测试的语义名称。 + +### 19.5 屏幕阅读器摘要 + +不要逐行朗读批次成员。推荐摘要: + +- 员工:“已呼叫第 18 批,共 5 个号码。模拟设备执行成功。” +- 游客:“您的号码 A123 已叫到,请前往 2 号入口。” +- 普通更新:“预计等待时间调整为 25 至 35 分钟,更新于 14 点 32 分。” +- 数据延迟:“当前显示 14 点 32 分的上次成功数据,正在重连。” + +## 20. 数据可视化 + +MVP 运营看板只使用三种信息: + +1. 跨项目状态表:项目、状态、等待号码数/人数、当前叫号、预计等待时间、设备模拟异常; +2. 单项目今日趋势:等待人数和完成人数两条折线; +3. 异常列表:暂停、数据陈旧、设备离线和近期高风险操作。 + +所有图表提供同数据表,轴、单位、时间范围和更新时间始终可见。不用 3D、仪表盘 speedometer、彩虹渐变、实时流动动画或自由拼装组件。[P1] 项目对比、预测误差带、热力图和高级报表在有明确运营问题时再增加。 + +## 21. 硬件扩展、音频与弱网体验 + +### 21.1 硬件界面边界 [后续真实硬件专项] + +以下是预留的后续集成契约,不属于 MVP 验收。首期只实现统一接口、成功/失败模拟器和测试日志;浏览器页面不暴露厂商协议: + +```mermaid +flowchart LR + S["队列核心"] --> B["业务事件
CallBatchActivated"] + B --> U["页面状态更新"] + B --> C["DeviceCommand"] + C --> G["本地设备网关"] + G --> D1["大屏 / LED"] + G --> D2["广播 / TTS"] + G --> D3["打印 / 扫码 / 物理按钮"] + D1 --> E["DeviceEvent
ACK / 心跳"] + D2 --> E + D3 --> E2["DeviceEvent
扫码 / 按钮 / ACK / 心跳"] + E --> G + E2 --> G + G --> S + S --> U +``` + +设计要求: + +- 员工看到业务与设备两级结果; +- 大屏显示自己的连接、数据时间和播报是否启用; +- 设备失败有“影响、当前替代通道、下一步”; +- 设备重试不重复改变业务状态; +- 上行扫码、按钮和心跳显示来源、接收时间与去重结果;重复或乱序事件不得重复推进业务; +- 同一物理大屏只能有一个主渲染链路,备用链路明确切换。 + +### 21.2 音频 + +- 员工点击叫号可触发不超过 1 秒的确认音,必须可静音并有等价视觉反馈; +- 大屏浏览器音频需值守人员主动启用,并检测 `play()` 失败; +- 自动音频超过 3 秒需独立暂停/停止/音量控制; +- [后续真实硬件专项] 生产设备语音可优先本地网关,浏览器语音为明确备用; +- 按 `business_event_id` 去重,单通道串行,显式“重叫”才再次播放; +- 语音和画面从同一业务事件生成,但不通过动画 callback 串联。 + +### 21.3 弱网 + +四种数据新鲜度: + +| 状态 | 表现 | 操作 | +|---|---|---| +| 实时 | 不特别强调 | 正常 | +| 正在重连 | 保留上次数据 + 更新时间 | 停止新取号/叫号,恢复并同步 revision 后再开放 | +| 数据陈旧 | 持久警告,弱化预计时间可信度 | 停止写入并切换备用流程 | +| 离线 | 明确无法验证最新队列,展示纸质号码/人工广播指引 | 禁止全部数字化写入和伪成功 | + +任何页面都不能只显示“网络错误”。必须告诉用户: + +- 当前看到的是否是旧数据; +- 已提交操作是否已被服务端接收; +- 是否可以安全重试; +- 现场备用流程是什么。 + +## 22. 内容、隐私与称谓 + +### 22.1 内容风格 + +- 用短句、主动语态、常用词; +- 标题表达用户任务,不用内部模块名; +- 按钮用动作结果; +- 避免“操作失败”“未知异常”“请稍后”等空泛文案; +- 首次出现技术/业务词时解释; +- 时间统一为本地时区和 24 小时制,更新时间精确到分钟,审计日志精确到秒; +- 预计时间使用“预计”“约”“区间”,不写“保证”。 + +### 22.2 隐私 + +- 游客个人页使用不可猜测链接/token,页面不把手机号放入 URL; +- 游客个人页只显示联系手机号尾四位用于本人核对,不返回或展示完整手机号; +- 普通日志、埋点、错误报告和设备命令默认不含完整手机号; +- 员工界面在已授权项目的业务列表中直接显示完整手机号,且不得通过日志、埋点、错误报告或跨项目接口扩散; +- 手机号必填,姓氏/称谓选填,称谓默认“游客”;不收集、推断或展示法定性别; +- 同一手机号可以关联多个活动号码;重复提示只用于防误操作,员工确认后继续并留审计; +- 大屏、广播只使用每日可读票号;该票号不能单独查询个人状态; +- 姓氏/称谓不公开,首期不提供项目级公开例外; +- 未成年人仍使用独立的一人一号排队单,可复用监护人手机号;不在大屏建立儿童身份特征。 + +### 22.3 建议术语 + +| 推荐 | 避免 | +|---|---| +| 排队号码 / 等待号码 | 客户组、同行组 | +| 排队号码 / 票号 | 身份号码 | +| 预计 25–35 分钟 | 还剩 29 分 13 秒 | +| 已叫到 | 已服务(尚未入场时) | +| 设备尚未确认 | 播报成功(无 ACK 时) | +| 数据更新于 14:32 | 实时数据(已经陈旧时) | +| 已过号,请联系工作人员重新取号 | 恢复原号 / 恢复原位置 | + +## 23. 明确禁止的设计模式 + +- 玻璃拟态、霓虹描边、渐变字和低对比透明层; +- “三张等宽大卡 + 大数字”套满所有后台首页; +- 风景图、宣传视频或动态背景压在号码、预计等待时间和状态文字下; +- 所有元素都做圆角胶囊; +- 票号老虎机、计数滚动、文字 scramble、逐号飞入; +- 号码弹跳、循环缩放、红色闪烁、无限 pulse、confetti; +- 跑马灯、自动轮播和整屏切换承载关键叫号; +- 错误 shake; +- ScrollTrigger/滚动劫持进入业务流程; +- 队列更新时整表自动移动; +- 把“业务已生效”和“设备执行成功”合并成一个绿色成功; +- 用 placeholder 代替 label; +- toast 作为唯一成功/失败反馈; +- 为追求密度缩小 H5 触控区和中文字; +- 为追求品牌感改变语义色或降低对比度; +- 把大屏当普通网页等比放大; +- 隐藏标签页恢复后补播错过的动画或声音; +- 在 GSAP `onComplete` 中提交业务、解锁按钮或发硬件命令。 + +## 24. 设计验收门槛 + +### 24.1 全端 + +- [ ] 共享语义 token、状态词典和隐私规则; +- [ ] WCAG 2.2 AA 与 GB/T 37668-2019 基线; +- [ ] 状态在灰度截图中仍可辨认; +- [ ] 所有错误说明发生了什么、当前是否有效、下一步; +- [ ] 弱网、离线、旧数据和权限不足均有完整状态; +- [ ] 无主动闪烁、持续运动、老虎机数字或无意义动画; +- [ ] 业务逻辑不依赖动画、前端 timer 或浏览器音频; +- [ ] 景区 VI 替换后重新跑对比度和品牌/语义分离评审。 + +### 24.2 H5 + +- [ ] 320/375/414/768px 测试; +- [ ] 触控目标 ≥48×48px; +- [ ] 200% 文本放大仍可完成核心任务; +- [ ] 键盘、刘海、底部安全区和横屏正常; +- [ ] 低端 Android、强光、单手、湿手/薄手套测试; +- [ ] 取号失败保留已填内容; +- [ ] 游客实时更新不抢焦点、不强制滚动。 + +### 24.3 批量叫号 + +- [ ] 主按钮显示本批实际号码数量,批次预览同时显示项目固定 `N`; +- [ ] 批次预览显示队首连续成员、固定 `N` 和规则来源; +- [ ] 并发冲突不静默覆盖; +- [ ] 所有批次成员原子出现,无逐号动效; +- [ ] MVP 业务成功与模拟适配器成功/失败相互独立; +- [ ] [后续真实硬件专项] ACK 能区分已接收、执行中、执行成功、可重试失败、最终失败和过期; +- [ ] [后续真实硬件专项] 设备部分失败仍能清楚判断业务是否已生效; +- [ ] [后续真实硬件专项] 扫码/按钮/心跳等上行事件重复或乱序时不重复推进业务; +- [ ] 防重依赖服务端幂等/锁,不只依赖按钮置灰; +- [ ] MVP 没有跳序、临时修改 `N`、手选和撤回入口;[P1] 若启用则另做权限与审计验收。 + +### 24.4 大屏 + +- [ ] 按最远阅读距离和物理字符高度验收; +- [ ] 上午/正午/傍晚及斜视角测试; +- [ ] 实际分辨率、overscan、DPI 和安全区校准; +- [ ] 当前号码持续可见,不被公告/营销覆盖; +- [ ] 公共内容只显示票号,绝不含手机号、姓氏或称谓;系统不采集法定性别,首期没有身份公开例外配置; +- [ ] 数据更新时间、离线/陈旧和播报未启用可见; +- [ ] 标准/低动效/无动效配置有效,默认低动效; +- [ ] 断线十分钟恢复只显示最新快照,不补播旧声音。 + +### 24.5 管理端 + +- [ ] 项目预计时间设置显示当前值、上一版、作用项目、修改人和修改时间; +- [ ] 表格键盘可用,列/筛选/单位/时区明确; +- [ ] 图表有等价数据表; +- [ ] 日志可定位业务事件和设备回执; +- [ ] 权限修改显示前后影响; +- [ ] MVP 只提供统一设备接口、模拟成功/失败和测试记录;注册、配对、心跳与完整回执只在后续真实硬件专项验收; +- [ ] 数据字段目的、必填性、留存/到期动作、公开投影白名单和受托方范围可查; +- [ ] 手机号必填,姓氏/称谓选填且称谓默认“游客”;同手机号多活动号码的提示、确认与审计可验收; +- [ ] 30 天个人关联删除、90 天业务事件匿名化、6 个月安全日志、1 年后台审计和 3 年影响评估相关记录均有可验证任务与配置; +- [ ] 员工/管理业务列表中的完整手机号严格受账号、角色和项目范围保护;游客页只有尾四位,公示屏没有手机号;敏感导出和留存规则变更有权限、原因、审批与审计; +- [ ] 1024px 可完成核心任务,大表横向滚动受控。 + +### 24.6 性能 + +- [ ] 动画只用 transform/opacity 为主; +- [ ] 4K 大屏不对整张画布交叉淡化; +- [ ] 新批次作为一个合成单元; +- [ ] `will-change` 只在需要时短暂使用; +- [ ] 新事件会取消旧 timeline; +- [ ] 4× CPU throttle 和低端 Android 下无阻断性卡顿; +- [ ] 大屏换批期间无超过 50ms 的主线程 long task; +- [ ] 表格使用分页/虚拟化,不动画数百行。 + +## 25. 设计到开发的交付顺序 + +在写业务 UI 前,按以下顺序交付并评审: + +1. **现场与 VI 输入**:Logo、品牌色、设备尺寸/亮度、最远阅读距离、员工设备、硬件清单; +2. **任务流与低保真**:[MVP] 游客状态、员工代取/批量叫号、公示屏换批、管理项目设置;[P1] 游客自助取号; +3. **状态矩阵**:正常、空、加载、弱网、离线、陈旧、冲突、部分失败、权限不足; +4. **设计 token**:颜色、字体、间距、焦点、动效、断点; +5. **核心组件**:表单、状态条、批次卡、设备回执、项目表格和修改记录; +6. **关键屏高保真**:四端各核心屏及紧急/异常状态; +7. **可交互原型**:批量叫号、游客被叫到、公示屏换批、项目设置保存; +8. **现场与无障碍验证**:真机、真屏、阅读距离、强光、噪声、弱网、屏幕阅读器; +9. **设计签字**:产品、运营、开发、QA、硬件/现场负责人; +10. **进入实现并持续回归**。 + +开发阶段的设计治理: + +- 语义 token 是唯一颜色/间距来源,不在页面散落硬编码; +- 每个新组件先说明复用失败原因; +- 设计评审同时检查正常状态和异常状态; +- 每次硬件型号或景区 VI 变化都触发相关回归; +- 设计规范和产品规划的状态名、事件名保持一致。 + +## 26. v1.0 现场校准输入 + +产品、业务规则、技术栈、断网行为、身份字段、留存和首期硬件范围已经冻结。以下事项不阻止开发,只需在高保真品牌定稿或现场 UAT 前补齐: + +1. 是否已有景区 VI:Logo、标准色、禁用色、字体和图片规范; +2. 首批员工设备:手机/平板型号、浏览器、是否戴手套; +3. 首批大屏:尺寸、分辨率、屏幕类型、点距、亮度、朝向和最远阅读距离; +4. 大屏环境:室内/半室外/室外、反光和遮阳; +5. 人工广播与纸质号码预案的负责人、话术和恢复联网切换步骤; +6. 后续真实硬件专项的型号、协议、驱动、网络拓扑、ACK、错误码和心跳能力。 + +在未提供 VI 时,本文件的蓝/暖橙方案可用于低保真之后的临时视觉探索,但不能在未评审的情况下被解释为景区正式品牌。 + +## 27. 官方参考资料 + +所有链接于 2026-07-10 核验: + +### 公共服务与布局 + +- [GOV.UK Government Design Principles](https://www.gov.uk/guidance/government-design-principles) +- [GOV.UK Layout](https://design-system.service.gov.uk/styles/layout/) +- [GOV.UK Error Message](https://design-system.service.gov.uk/components/error-message/) +- [GOV.UK Error Summary](https://design-system.service.gov.uk/components/error-summary/) +- [GOV.UK Focus States](https://design-system.service.gov.uk/get-started/focus-states/) +- [NHS Colour](https://service-manual.nhs.uk/design-system/styles/colour) +- [NHS Buttons](https://service-manual.nhs.uk/design-system/components/buttons) +- [NHS Error Message](https://service-manual.nhs.uk/design-system/components/error-message) +- [USWDS Theme Color Tokens](https://designsystem.digital.gov/design-tokens/color/theme-tokens/) +- [USWDS Accessibility](https://designsystem.digital.gov/documentation/accessibility/) +- [USWDS Layout Grid](https://designsystem.digital.gov/utilities/layout-grid/) +- [USWDS Spacing Units](https://designsystem.digital.gov/design-tokens/spacing-units/) + +### 导视、大屏与中文 + +- [TfL Design Standards](https://tfl.gov.uk/info-for/business-and-advertisers/design-standards?intcmp=4530) +- [TfL Text Legibility Standard](https://content.tfl.gov.uk/tfl-text-legibility-standard-issue03.pdf) +- [TfL Standard for Products](https://content.tfl.gov.uk/tfl-standard-for-tfl-products.pdf) +- [FAA Airport Signing and Graphics](https://www.faa.gov/documentLibrary/media/Advisory_Circular/150_5360_12f.pdf) +- [UK DfT Inclusive Mobility](https://assets.publishing.service.gov.uk/media/61d32bb7d3bf7f1f72b5ffd2/inclusive-mobility-a-guide-to-best-practice-on-access-to-pedestrian-and-transport-infrastructure.pdf) +- [GB/T 37668-2019 信息技术 互联网内容无障碍可访问性技术要求与测试方法](https://openstd.samr.gov.cn/bzgk/std/newGbInfo?hcno=35ECC696805C1A67C93B74FB6D0D8EFB) +- [工信部:互联网应用适老化及无障碍改造专项行动](https://www.miit.gov.cn/jgsj/xgj/gzdt/art/2021/art_eddf498ded1b44829644bf20d28c9f6f.html) +- [W3C 中文排版需求](https://www.w3.org/TR/clreq/) + +### 可访问性 + +- [W3C WCAG 2.2](https://www.w3.org/TR/WCAG22/) +- [Understanding Use of Color](https://www.w3.org/WAI/WCAG22/Understanding/use-of-color) +- [Understanding Non-text Contrast](https://www.w3.org/WAI/WCAG22/Understanding/non-text-contrast) +- [Understanding Target Size](https://www.w3.org/WAI/WCAG22/Understanding/target-size-minimum) +- [Understanding Status Messages](https://www.w3.org/WAI/WCAG22/Understanding/status-messages) +- [Understanding Pause, Stop, Hide](https://www.w3.org/WAI/WCAG22/Understanding/pause-stop-hide) +- [Understanding Animation from Interactions](https://www.w3.org/WAI/WCAG22/Understanding/animation-from-interactions) +- [Understanding Three Flashes](https://www.w3.org/WAI/WCAG22/Understanding/three-flashes) +- [Understanding Audio Control](https://www.w3.org/WAI/WCAG22/Understanding/audio-control.html) + +### 动效与运行时 + +- [Material 3 Easing and Duration](https://m3.material.io/styles/motion/easing-and-duration/applying-easing-and-duration) +- [Apple Human Interface Guidelines: Motion](https://developer.apple.com/design/human-interface-guidelines/motion) +- [Apple Reduced Motion Evaluation Criteria](https://developer.apple.com/help/app-store-connect/manage-app-accessibility/reduced-motion-evaluation-criteria) +- [Fluent 2 Motion](https://fluent2.microsoft.design/motion) +- [Microsoft Timing and Easing](https://learn.microsoft.com/en-us/windows/apps/design/motion/timing-and-easing) +- [MotionConfig and reducedMotion](https://motion.dev/docs/react-motion-config) +- [GSAP matchMedia](https://gsap.com/docs/v3/GSAP/gsap.matchMedia%28%29/) +- [GSAP Timeline](https://gsap.com/docs/v3/GSAP/Timeline/) +- [GSAP Context](https://gsap.com/docs/v3/GSAP/gsap.context%28%29/) +- [MDN prefers-reduced-motion](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/At-rules/%40media/prefers-reduced-motion) +- [MDN Page Visibility API](https://developer.mozilla.org/en-US/docs/Web/API/Page_Visibility_API) +- [MDN Autoplay Guide](https://developer.mozilla.org/en-US/docs/Web/Media/Guides/Autoplay) +- [MDN ARIA Live Regions](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Guides/Live_regions) + +### 排队产品模式 + +- [Qmatic Mobile Ticket](https://www.qmatic.com/products/mobile-ticket) +- [Qmatic Mobile Ticket ETA](https://www.qmatic.com/product-updates/introducing-estimated-wait-time-in-mobile-ticket) +- [Qmatic Digital Signage](https://www.qmatic.com/products/display-signage/) +- [Waitwhile Visits Page and Bulk Actions](https://help.waitwhile.com/en/articles/8061037-navigating-the-visits-page) +- [Waitwhile Customer Status Page](https://help.waitwhile.com/en/articles/9949637-the-customer-status-page-real-time-updates-for-your-customers) +- [Waitwhile Serving Display](https://help.waitwhile.com/en/articles/11370712-how-to-use-the-waitlist-display-to-direct-customers-to-a-counter) +- [Waitwhile Managing the Waitlist](https://help.waitwhile.com/en/articles/8016100-managing-the-waitlist) +- [Waitwhile Analytics](https://help.waitwhile.com/en/articles/9250541-analytics-overview) +- [Qmatic Serve View](https://docs.qmatic.io/en/staff-user-guides/user-guide--serve-view.html) +- [Wavetec Low-connectivity Kiosk Guidance](https://www.wavetec.com/blog/best-practices-of-kiosk-installations-in-low-connectivity-zones/) diff --git a/docs/scenic-queue-system-plan.md b/docs/scenic-queue-system-plan.md new file mode 100644 index 0000000..dcf401a --- /dev/null +++ b/docs/scenic-queue-system-plan.md @@ -0,0 +1,749 @@ +# 景区项目排队叫号系统:案例调研与完整规划 + +> 文档状态:v1.0 已确认开发基线 +> 调研日期:2026-07-10 +> 当前阶段:关键产品与技术决策已确认,已获授权进入开发。 +> 适用前提:绿地项目;一个景区内存在多个游玩项目,每个项目可能有多个通道、设备或服务批次。 + +术语说明:`ETA` 是 Estimated Time of Arrival 的缩写。本系统技术文档用它指预计时间,但游客和员工界面统一写“预计等待时间”或“预计叫号时间”,不展示英文缩写。 + +## 1. 结论先行 + +系统定义为一个“以排队单和叫号批次为核心、四类终端共享同一实时事件流、首期硬件接口由模拟器验证”的现场运营系统,而不是四套相互同步的页面。真实硬件和本地设备网关属于后续专项。 + +核心方案是: + +1. 取号采用“一人一号”:每位游客对应一张 `QueueTicket`,不建立同行组或 `party_size` 规则。手机号必填,姓氏和称谓选填;称谓未填写时默认为“游客”,系统不采集法定性别。每次取号生成不可猜测的排队单 ID、可读票号和私密查询令牌。 +2. 同一手机号在同一项目可同时持有多个活动号码,以支持家人共用联系人手机号。发现重复时员工端必须展示脱敏的活动号码提示,经员工明确确认后方可继续,并记录审计;数据库不得设置“项目 + 手机号”的活动唯一约束。 +3. “批量叫号”建模为独立 `CallBatch`。每个项目配置固定人数 `N`,`CALL_NEXT` 严格按 FIFO 原子呼叫队首连续 `N` 个有效等待号码;不足 `N` 时呼叫全部剩余号码,不手选、不跳号。 +4. 预计等待时间只配置“单个号码预计间隔时间”,每个号码按该间隔累加计算;界面显示易懂的预计时间和更新时间,不建设通用预测平台。 +5. PostgreSQL 是唯一事实源,中心服务是唯一写入权威。景区完全断外网或客户端无法确认中心权威时,停止数字化取号与叫号,只展示最后快照并切换纸质号码、人工广播等现场预案。 +6. 前端采用 React,后端采用 Go + GORM,数据库采用 PostgreSQL;保持模块化单体和单仓库,不拆分微服务。 +7. 首期硬件范围只包含统一设备接口、成功/失败模拟器和测试日志,不接真实打印机、广播、LED 或扫码设备;真实硬件取得型号、协议、驱动、网络拓扑及回执能力后另行立项。 +8. 推荐先以一个项目做现场试点,跑通取号—固定 `N` 叫号—游客状态页/公示屏—到场—完成—预计时间校准的完整闭环,再扩展全景区。 +9. 一个系统支持多个项目:所有队列、规则、票号、权限、设备和统计都带 `project_id`,但共用一套代码、数据库和部署,不为每个项目复制系统。 + +## 2. 已知需求、目标与边界 + +### 2.1 已确认需求 + +- 员工端 H5:员工使用账号登录后只进行叫号和取号;每次叫下一批自动结束上一批,并按 FIFO 叫出固定数量的连续号码。 +- 游客端 H5:查看自身排队情况、预计等待时间和当前进度。 +- 大屏 Web:实体公示屏公开展示最新叫号与队列概况;其公开投影继续使用绑定的只读地址。 +- 管理端 Web:统一承载运营概览、项目规则和大屏中心;大屏中心只展示公开票号与运行状态,并可进入管理员全屏监控。账号、权限、日志和真实硬件配置仍按后续范围扩展。 +- 一人一号;手机号必填,姓氏和称谓选填,称谓默认“游客”,不采集法定性别。 +- 员工端、游客状态页和内部登录卡采用 390px 手机设计宽度;更窄设备缩到 100%,页面高度保持自适应。管理端和公示屏不套用此宽度。 +- 同一手机号允许创建多个活动号码;重复时先脱敏提示、再由员工确认并审计。 +- 每项目配置固定批量人数 `N`,严格 FIFO 呼叫队首连续号码,队列不足时呼叫全部剩余号码。 +- 过号后原号码保持 `MISSED` 并失效;仍需排队时创建新号码进入队尾,原号码保留关联审计。 +- 预计等待时间统一按项目配置的单个号码间隔累加计算。 +- 员工端和大屏端预留稳定硬件接口;首期只交付接口、模拟器和测试日志。 +- 中心服务是唯一写入权威,完全断网时停止数字化写入并执行人工预案。 +- 员工与管理员首期使用系统内置账号密码登录,预留后续 OIDC/SSO 接口。 +- 前端采用 React,后端采用 Go + GORM,数据库采用 PostgreSQL。 +- 操作和业务规则尽量简单,系统采用轻量架构,不引入没有实际需求支撑的复杂逻辑。 +- 运营数据看板作为管理端首页模块开发,不单独建设看板应用。 +- 员工端、管理端及运营看板不提供匿名访问;游客状态页和公示屏使用各自受控的无账号访问方式。 +- 同一系统支持多个景区项目,各项目的数据、规则、人员授权和设备绑定相互区分。 + +### 2.2 产品目标 + +- 减少游客必须停留在实体队伍中的时间,同时不牺牲安全容量控制和公平性。 +- 让员工在高峰、多人并发和设备故障时仍能明确知道“谁被叫了、为什么被叫、设备是否收到”。 +- 让游客看到可理解但不过度承诺的实时进度。 +- 让管理人员能解释一次异常、查看一次叫号记录、调整项目的预计时间参数并审计人工干预。 +- 让员工只用少量高频按钮完成日常工作,高级异常操作收进二级入口。 + +### 2.3 MVP 明确不做 + +- 不自行建设售票、支付、闸机安全控制和车辆启航控制;只预留接口并接收/发送受控事件。 +- 不使用机器学习、复杂滚动权重或预测平台;先用项目级简单参数计算预计等待时间。 +- 不承诺“精确到某一分钟”的等待时间。 +- 不把浏览器直连硬件作为唯一生产方案。 +- MVP 不开放游客远程自助取号;首期只做员工代取,游客自助取号属于 P1 独立范围。 +- 不拆微服务,不引入通用工作流引擎、规则 DSL、事件溯源或独立消息中间件。 +- 不单独部署运营数据看板;它是管理端的一部分。 +- 不做跨项目自动调度、跨项目队列迁移或多租户计费;MVP 只做一个景区内的多项目隔离。 + +### 2.4 简单优先原则 + +- 员工主流程固定为:选择项目 → 取号 → 叫下一批 → 到场/完成;重叫、过号和“重新取号”放入“更多”,手选/重排/撤回放 P1。 +- 每个项目只配置当前实际需要的少量参数,不提供可编程规则编辑器。 +- 管理端只保留一个运营看板、项目管理、账号权限、日志和设备配置入口。 +- 底层仍使用数据库事务、幂等和审计保证不重号、不重复叫号;“简单”指界面与部署简单,不牺牲核心正确性。 + +## 3. 成熟案例调研 + +调研优先使用景区官方、政府部门、厂商官方文档和开发者文档。案例用于验证产品模式,不代表其内部技术实现已公开;厂商宣传性指标不视为独立审计结果。 + +| 案例 | 已核验做法 | 对本项目的启示 | 不能据此推断 | +|---|---|---|---| +| 梵净山红云金顶 | 游客扫码进入预约排队,小程序显示预约号和当前叫号;接近时提醒;现场广播和电子屏按连续号段批量叫号 | 最贴近本项目的国内闭环;一个叫号批次要同时驱动 H5、提醒、广播、大屏和入口核验 | 公开资料没有披露员工 UI、并发算法和具体硬件协议 | +| 长江索道 | 售票窗口、自助机、微信公众号、OTA 等多入口取号;按号段、分时段乘坐;游客可线上查看进度并离开现场等待 | 所有入口应进入统一取号 API;排队单可关联外部票务凭证,但核心状态机不应依赖某一票务渠道 | 不能假定所有景区都需要实名身份证核销 | +| 陶然亭公园游船 | 云排队、叫号登船、智能广播、运营统计,并与游船定位、启航、收银等场景结合 | 排队最终可能联动运营控制;安全关键命令必须独立授权、审计并与普通显示命令隔离 | 大屏页面不应直接获得启航等安全控制权 | +| Disney World Virtual Queue | 通过 App 加入 boarding group;同行组统一操作;被召回后在 return window 内核销;名额有限且项目可动态启停 | 可借鉴项目开关、回场窗口和容量配额;同行组模型不采用 | 其官方页面当前可能没有正在运行的虚拟队列,不能照搬具体发放时点 | +| Universal Hollywood/Orlando、Europa-Park、USJ | 以同行组/已登记门票加入;选择或分配 return time;迟到、取消、再次取号规则因项目而异 | 可借鉴迟到规则的明确告知;本项目已冻结为过号后新号排队尾 | 不存在适用于所有项目的一套迟到规则,且外部同行组规则不适用于本项目 | +| Universal Volcano Bay TapuTapu | 腕带通过园区 reader 加入虚拟队列,倒计时并振动提醒;设备可挂起/解绑,腕带本身不存个人信息 | 核销凭证和提醒终端应抽象化;硬件丢失、重绑、禁用和回执必须可管理 | 首期 H5 无需复制专用射频腕带体系 | +| Waitwhile | Customer 与 Visit 分离;Visit 有 WAITING/SERVING/COMPLETE 等状态、位置和 ETA;提供个人状态页、公共屏、Webhooks 和审计条目 | 访客与一次排队访问分离、个人页与公共屏分离、事件订阅和全量审计是成熟通用模式 | 通用服务场景的“窗口叫一个人”不能直接覆盖景区按容量装载的批次逻辑 | + +主要来源: + +- [梵净山扫码预约、实时进度与号段广播](https://www.gzrd.gov.cn/gzwh/202505/t20250530_87945751.html) +- [长江索道智能分流与多入口取号](https://www.cq.gov.cn/ywdt/bmts/201809/t20180921_8655058.html) +- [长江索道在线查看排队进程](https://www.cq.gov.cn/zwgk/zfxxgkml/zdlyxxgk/ggwh/ly/zxdt/202503/t20250311_14394287.html) +- [陶然亭公园云排队、广播与游船运营联动](https://gygl.beijing.gov.cn/xxgk/xxgk_gyxx/202203/t20220321_2635717.html) +- [Disney World Virtual Queue](https://disneyworld.disney.go.com/en_CA/guest-services/virtual-queue/) +- [Universal Studios Hollywood Virtual Line](https://www.universalstudioshollywood.com/web/en/us/plan-your-visit/virtual-line) +- [Universal Orlando Virtual Line](https://www.universalorlando.com/web/en/us/plan-your-visit/virtual-line) +- [Europa-Park VirtualLine](https://www.europapark.de/en/theme-park/info/plan-your-visit/virtualline-europa-park) +- [Universal Studios Japan 预约乘坐](https://www.usj.co.jp/web/zh/cn/enjoy/numbered-ticket/yoyakunori) +- [Universal Volcano Bay TapuTapu FAQ](https://www.universalorlando.com/web/en/us/plan-your-visit/taputapu-faq) +- [Waitwhile Visit API](https://developers.waitwhile.com/reference/listvisits)、[Webhooks](https://developers.waitwhile.com/reference/webhooks-2)、[游客状态页](https://help.waitwhile.com/en/articles/9949637-the-customer-status-page-real-time-updates-for-your-customers) + +### 3.1 通用排队厂商能力对照 + +本次公开样本显示,大屏、游客状态页、ETA、通知和单号叫号已有多家成熟实现;但在所核验资料中,“将多张独立排队单作为一次业务批次原子叫出”并不是普遍公开的能力。 + +| 产品 | 批量/分组证据 | 硬件与边缘 | API 透明度 | 规划判断 | +|---|---|---|---|---| +| Waitwhile | 公开帮助文档明确支持多选 Visit/活动参与者后批量 Alert、Serve,并有 `partySize` | 浏览器电视/语音;官方明确无正式离线模式 | 最高:公开 REST、批量 Visit、Webhooks、认证、限流和重试 | 最适合借鉴游客体验、批量操作和开放事件,但真实硬件仍需自建设备网关 | +| Qmatic | 公开资料核验到 Call next、指定号码、Recall、Recycle、No-show,未核验到多人原子批叫 | 本地 Orchestra/Queue Agent、打印、候客屏、柜台屏、语音体系成熟 | Data Connect 只读且非实时;操作接口资料有限 | 最适合借鉴“中央配置 + 分支 Agent + 设备配置” | +| Wavetec | 核验到 Next、Random/Priority Call、Recall,未核验到多人批叫 | 本地 Controller、打印、屏幕、语音、恢复后云同步案例较完整 | 案例有双向 REST;完整公开接口有限 | 最适合借鉴本地现场控制、设备冗余和云边同步 | +| Qtrac | 官网有 customer or group 措辞,但不能证明 group 等于多张独立票;Auto Call 仍是逐个 | 浏览器大屏、语音、票据机/扫码可选;偏云端 | 有 API Library/Integration Hub 概述,无完整公开端点 | 采购前必须演示批量原子语义和断网行为 | +| QLess | 有群组群发消息,未核验到批量改变多张票的叫号状态 | 通用 kiosk、monitor、短信/语音;偏云端 | 宣称 API 覆盖广,缺少公开端点/Webhook 细节 | 群发通知不能被当作批量叫号 | + +值得关注的景区类厂商案例是 [Waitwhile 的 SUMMIT One Vanderbilt](https://waitwhile.com/case-studies/summit-one-vanderbilt/):游客扫码或由员工平板登记,填写同行人数,系统按设施吞吐召集下一组。案例数据属于厂商自报,其“设施吞吐 + 运营批次”思路可用于对照,但本项目已经确认采用“一人一号”,不采用该案例的同行组模型。 + +参考资料:[Waitwhile 多选与批量操作](https://help.waitwhile.com/en/articles/8061037-navigating-the-visits-page)、[Waitwhile 离线说明](https://help.waitwhile.com/en/articles/11603596-does-waitwhile-work-without-internet)、[Qmatic Serve View](https://docs.qmatic.io/en/staff-user-guides/user-guide--serve-view.html)、[Qmatic Queue Agent](https://docs.qmatic.io/en/service-and-branch-configuration/branch-configuration/about-branches.html)、[Qmatic Data Connect 限制](https://data-connect.docs.qmatic.io/developerguide.html)、[Wavetec 本地分支与云同步案例](https://www.wavetec.com/es/case-studies/wasl-properties-customer-experience-transformation/)、[Qtrac](https://qtrac.com/virtual-queuing/)、[QLess](https://www.qless.com/products/)。 + +### 3.2 本次公开样本观察与规划推导 + +公开样本反复出现以下事实(其中同行组是外部案例特征,不是本项目规则): + +- 身份、排队单、公开票号和核销凭证是不同概念。 +- 同行组通常由一人操作,但核销和容量按全组人数计算。 +- 设施容量决定发号和批量放行,不只是队伍长度决定。 +- 系统可以暂停发号、补放名额、切换实体队列或停止某个项目。 +- ETA/return window 是动态引导,不是服务承诺。 + +基于这些事实,本项目作出以下规划推导: + +- 通知失败不应改变已提交的排队事实;游客主动查询页仍需可用。 +- 扫码器、NFC、腕带、打印机、广播和屏幕都只是同一业务事件的不同执行端。 +- 员工 H5、游客 H5、大屏和硬件必须共享同一服务端事实源,不能各自维护队列。 + +## 4. 核心领域模型 + +| 概念 | 定义 | 关键字段/约束 | +|---|---|---| +| 景区 `Site` | 运营与权限边界 | 名称、时区、营业日、隐私/留存策略 | +| 项目 `Attraction` | 一个游玩/乘坐项目 | 代码、名称、队列类型、容量模型、状态 | +| 队列场次 `QueueSession` | 某项目在某营业日/时段的一条运行队列 | 营业日、开闭时间、状态、单调修订号、预计时间设置 | +| 服务资源 `ServiceResource` | 通道、入口、船、车辆、窗口或操作位 | 类型、容量、在线状态、所属项目 | +| 游客 `Visitor` | 受保护的最小访客资料,不以手机号唯一 | 内部 ID、加密手机号、手机号 HMAC 索引、选填姓氏、选填称谓、称谓默认“游客”、留存时间 | +| 排队单 `QueueTicket` | 一位游客一次加入某队列的业务记录;一人一号 | 内部 ID、公开票号、私密查询令牌、顺序键、状态、来源、版本、可选原过号票关联 | +| 叫号批次 `CallBatch` | 一次原子批量放行 | 批次序号、资源、固定目标人数 `N`、成员票 ID、操作人、项目设置快照、状态 | +| 叫号尝试 `CallAttempt` | 初叫、重叫及其渠道结果 | 次数、时间、到期时间、语音/屏幕/通知回执 | +| 预计时间设置 `WaitEstimateConfig` | 每个项目当前使用的简单参数 | 模板、平均速度或批容量/间隔、缓冲分钟、上次修改人、上一版值 | +| 预计时间记录 `WaitEstimateRecord` | 只记录关键业务节点的预计值 | 排队单、取号/叫号节点、预计区间、计算时间、项目设置摘要 | +| [后续真实硬件专项] 设备与网关 | 物理设备的注册、能力与连接 | 设备类型、能力、驱动、在线状态、凭证、最后心跳 | +| [后续真实硬件专项] 设备命令 `DeviceCommand` | 可幂等执行的一次硬件动作 | 命令 ID、业务事件/叫号尝试 ID、过期时间、载荷、状态、错误和回执 | +| [后续真实硬件专项] 设备事件 `DeviceEvent` | 扫码、按钮、心跳等设备上行事实 | 设备事件 ID、设备 ID、业务关联、服务端接收时间、验签/去重状态 | +| 审计日志 `AuditEntry` | 谁在何时因何改变了什么 | 操作人/设备、前后值摘要、理由、关联票/批次、IP/终端 | + +多项目采用同库同表方案:`QueueSession`、`ServiceResource`、`QueueTicket`、`CallBatch`、设备绑定和审计记录都直接包含 `project_id`。票号唯一约束使用“项目 + 营业日 + 票号”,账号通过项目授权表访问一个或多个项目。管理端可以跨项目汇总,员工端和公示屏默认只进入一个当前项目。MVP 不建立复杂的组织/租户继承,也不为每个项目单独建库或部署。 + +`project_id` 是强制安全边界:所有项目级记录(包括 `CallAttempt`、预计时间设置/记录、设备命令/事件、审计和统计)都必须携带它;唯一键、查询、更新、报表和事件频道均按它约束。项目范围由服务端根据登录账号授权、私密查询令牌或设备绑定确定,不能信任客户端自行提交的 `project_id`。 + +`Visitor` 与 `QueueTicket` 分开:同一手机号可能多次来访,也可同时作为多位游客的联系人;一次排队的状态、预计等待时间、叫号和审计不写回身份资料。同一手机号的活动排队单不设唯一约束。删除/匿名化访客资料后,仍可保留不含个人信息的运营统计。 + +## 5. 状态机与运营规则 + +### 5.1 排队单状态 + +```mermaid +stateDiagram-v2 + [*] --> WAITING: "取号成功" + WAITING --> CALLED: "进入叫号批次" + CALLED --> ARRIVED: "到场/核验" + ARRIVED --> SERVING: "进入通道/开始服务" + SERVING --> COMPLETED: "完成" + CALLED --> MISSED: "宽限到期/员工判定" + WAITING --> CANCELLED: "游客或员工取消" + CALLED --> CANCELLED: "异常取消" + ARRIVED --> CANCELLED: "授权异常处理" + COMPLETED --> [*] + MISSED --> [*] + CANCELLED --> [*] +``` + +说明: + +- MVP 员工界面把 `ARRIVED` 与 `SERVING` 合成一个“已到场/入场”按钮;数据层可保留两个时间点,首期可在同一次操作中写入。 +- `MISSED` 不能恢复为 `WAITING`,也不能直接删除。游客仍需排队时,由员工创建一张新排队单和新票号进入队尾,并记录原票、新票、原因和操作人关联。 +- 任何逆向状态变更都需更高权限、原因和审计。 + +### 5.2 项目/队列状态 + +| 状态 | 新取号 | 新叫号 | 已叫号核验 | 预计等待时间展示 | +|---|---:|---:|---:|---| +| `CLOSED` 未开放 | 否 | 否 | 否 | 未开放 | +| `OPEN` 运行中 | 是 | 是 | 是 | 正常 | +| `PAUSED` 暂停 | 否 | 否 | 是 | 隐藏分钟,展示暂停原因 | +| `FINISHED` 当日结束 | 否 | 否 | 否 | 已结束 | + +MVP 只保留这四种状态。暂停、恢复和结束均记录操作人、原因,并更新游客页和公示屏。[P1] 只有现场确实需要时,再拆分“仅暂停取号”“仅暂停叫号”和“清队中”。 + +### 5.3 MVP 已确认规则 + +- 每个项目每天生成独立票号序列,例如 `A-0238`;内部 ID 使用随机 UUID/ULID,不从票号推断记录数。 +- 同一手机号在同一项目存在活动排队单时,员工端先展示脱敏重复提示;员工确认后仍可创建新号码,确认动作和关联活动号码进入审计。 +- [P1] 优先队列、人工插队和配额规则不进入 MVP。 +- 当日结束不批量物理删除;按状态关场并保留审计。 +- [P1] 手工修改位置不进入 MVP。 +- 一人一号使每张有效等待票都计为一人;不提供人数修改、同行组合并或拆分。 +- MVP 固定严格 FIFO:每次读取项目配置的 `N`,呼叫队首连续 `N` 张有效等待票;不足 `N` 时呼叫全部剩余票,不跳号、不手选。 +- 闭园前由管理员将项目切为 `PAUSED` 停止新取号/叫号,处理完现场后切为 `FINISHED`;复杂自动清队规则后移。 + +## 6. 批量叫号:完整业务定义 + +### 6.1 MVP 只有一种模式 + +`CALL_NEXT`:读取当前项目配置的固定批量人数 `N`,从队首按严格 FIFO 选择连续 `N` 张有效等待票;队列不足 `N` 时选择全部剩余票。员工界面只有“叫下一批”,不输入票数、不手选、不跳号。 + +[P1] 手选、跳号、按班次和预叫只有在试点证明必要后再单独设计。 + +### 6.2 叫号批次生命周期 + +`CallBatch` 本身有明确状态,不能只从成员列表临时猜测: + +```mermaid +stateDiagram-v2 + [*] --> ACTIVE: "叫号事务提交" + ACTIVE --> PARTIALLY_RESOLVED: "部分成员到场/过号/取消" + ACTIVE --> RESOLVED: "所有成员已有结果" + PARTIALLY_RESOLVED --> RESOLVED: "最后一名待到场成员有结果" + RESOLVED --> [*] +``` + +- `ACTIVE`:至少一名成员仍为 `CALLED`,到场窗口尚未全部结束。 +- `PARTIALLY_RESOLVED`:部分成员已到场/过号/取消,仍有成员等待响应。 +- `RESOLVED`:没有成员仍处于 `CALLED`;记录汇总结果和结束原因,不可删除。 +- 入口的“待到场批次名额”在批次 `RESOLVED` 时释放。 +- MVP 不做预叫和批次撤回;叫错号由主管记录异常并通过人工现场处理,不设计复杂逆向状态。[P1] 若试点证明撤回必需,再增加 `WITHDRAWN`。 + +### 6.3 原子执行规则 + +员工点击“确认叫号”后,服务端在一个事务内: + +1. 校验员工权限、资源归属和请求格式;若幂等键已有成功结果,直接返回原批次。 +2. 先锁定 `QueueSession`/队列修订行(或用 `UPDATE ... WHERE revision = expected` 做 CAS),再校验项目状态和客户端看到的修订号;修订不符立即返回冲突。 +3. 在同一串行化边界内,从队首按原始顺序读取等待票。 +4. 读取项目固定批量人数 `N`,选择队首连续 `N` 张有效等待票;不足时选择全部剩余票,并生成不可变成员快照。 +5. 创建 `CallBatch`、成员记录和首次 `CallAttempt`,把成员从 `WAITING` 更新为 `CALLED`。 +6. 增加队列修订号,同时写入审计和同库的“待发送事件”记录,然后提交。 +7. 提交成功后再发布实时事件、通知和硬件命令。 + +多个员工同时叫号时,只能有一个事务在预期修订号上成功;后到请求在获得锁后会发现修订已变化,收到新的队列快照并要求重试,绝不继续使用旧候选集。 + +API 语义固定为 `callNext(project_id)`;服务端读取该项目的固定批量人数 `N`。队列人数不足时叫出全部剩余号码,界面在提交前显示“将叫 X 个号码”。每个项目同一时间只保留一个尚未处理完的活动批次。 + +### 6.4 固定 N 与连续号码 + +- 每张有效等待票代表一名游客,所以批次“票数”与“人数”相等。 +- “连续”指队列顺序连续:只跳过已取消、已过号等非 `WAITING` 记录,不因票号数值存在自然缺口而回填或重排。 +- 队列剩余人数小于 `N` 时,本批呼叫全部剩余号码,不把这视为异常或欠载规则。 +- 大屏仅在公开票号数值确实连续时压缩为号段;否则完整列出本批票号,业务成员始终以 ID 列表为准。 +- [P1] 手选、跳号、轮椅位/舱位等多维容量约束在有明确项目需求时另行扩展。 + +### 6.5 重叫、到场、过号与设备失败 + +- 重叫沿用同一 `CallBatch`,增加 `CallAttempt`,不新建一批并再次改变队列位置。 +- 到场/过号可逐张处理;批次不要求全员同一结果。 +- 叫号提交后,即使语音、短信或大屏失败,队列状态也不回滚。系统重试设备命令并醒目标注“播报未确认”。 +- 初叫与每次人工重叫都生成新的 `call_attempt_id`;同一次尝试向每台设备生成独立 `command_id`,网络重试沿用原 `command_id`。因此可以避免传输重试造成重复播报,同时又允许员工有意发起一次新的重叫。 + +## 7. 预计等待时间(技术简称 ETA) + +### 7.1 展示口径 + +游客默认看到: + +- 当前状态与公开票号; +- 前方约多少个号码; +- 预计等待区间,例如 25–35 分钟; +- 估算状态(正常、暂停、暂不可估); +- 最近更新时间; +- 免责声明:现场运营、天气、设备和安全检查可能导致变化。 + +一人一号后“前方号码数”就是前方人数;仍不显示“准确 29 分钟”,因为它会被游客理解为承诺。 + +### 7.2 MVP 简单计算模板 + +每个项目只选择一种模板,员工和管理员不需要理解公式: + +| 模板 | 适用项目 | 简单计算 | +|---|---|---| +| 单号间隔 | 所有项目 | (前方有效号码数 + 1)× 单个号码预计间隔时间 | + +两个模板均为首期能力,并由每个项目二选一。固定批次只按 FIFO 顺序和固定 `N` 计算,不建设装箱、排班或约束求解引擎;班次、车辆、多资源联合调度放到确有项目需要时再扩展。 + +预计值统一四舍五入到 5 分钟并显示为区间。项目暂停、权威队列数据陈旧或参数缺失时直接写“暂无法估算”,不叠加复杂修正系数;设备模拟失败不改变预计时间结果。 + +### 7.3 项目级设置 + +管理端每个项目只保留以下字段: + +- 计算模板:连续放行或固定批次; +- 单个号码预计间隔时间; +- 展示区间的固定缓冲分钟数; +- 暂停时是否隐藏预计时间; +- 最后修改人、修改时间和上一版值。 + +MVP 保存修改日志并允许恢复上一版,不做草稿审批流、定时发布、复杂权重、异常值规则和可编程公式。 + +### 7.4 异常与校准 + +- 新项目直接使用管理员填写的项目参数,不要求先积累历史样本。 +- 项目暂停、权威队列数据缺失或参数缺失时隐藏分钟数,显示明确原因;设备模拟失败独立展示,不影响预计时间。 +- MVP 只在取号、叫号两个关键节点记录预计值,不保存每次页面刷新的快照。 +- 管理看板只显示“近期平均误差”和“偏早/偏晚次数”,让管理员据此调整项目参数;高级统计放到后续版本。 +- 首期只预测“预计叫到时间”。若未来确有二次候场,再单独增加“预计开始体验时间”。 + +## 8. 四端产品规划 + +### 8.1 员工端 H5 + +| 模块 | MVP 能力 | +|---|---| +| 登录与工作台 | 必须账号登录;从已授权项目中选择当前项目,查看项目状态、网络和设备摘要 | +| 取号 | 一人一号;手机号必填,姓氏/称谓选填,称谓默认“游客”,不采集法定性别;重复活动号码脱敏提示,员工确认后可继续并留审计;生成票号/二维码 | +| 队列 | 等待/已叫/过号/到场列表,在账号项目授权范围内直接对应展示票号、完整手机号、姓氏/称谓、状态和取号时间;支持按手机号/票号搜索 | +| 叫号控制台 | 主按钮“叫下一批”;按项目固定 `N` 自动选择队首连续号码,提交前显示号码数量和成员预览;叫号后可到场、完成 | +| 更多操作 | 重叫、过号、取消、过号后重新取新号、暂停/恢复;默认折叠,不占主流程;授权手选/重排放 P1 | +| 设备 | 首期显示接口未启用/模拟成功/模拟失败、测试日志和降级指引 | + +关键体验:危险操作二次确认;批次预览展示固定 `N`、实际号码数量和成员票号;按钮响应后显示服务端批次号,不用乐观假成功掩盖并发冲突。 + +### 8.2 游客端 H5 + +MVP 推荐使用取号后生成的随机查询链接/二维码进入个人状态页: + +- 票号、项目、取号时间; +- 当前状态、前方号码数、预计等待时间区间、更新时间; +- 被叫后明显的到场窗口、位置导航和核验码; +- 暂停、停运、闭园等运营公告; +- 取消排队(若业务允许)与隐私说明。 + +链接丢失后的恢复建议使用“手机号 + 短信验证码”或“票号 + 手机号验证”。仅凭姓氏、称谓或手机号后四位不得查看个人队列详情。 + +### 8.3 大屏 Web + +- 项目名称、运行/暂停状态、当前批次、最近若干批次、入口/通道; +- 等待号码数/总人数、当前预计等待时间区间、更新时间、运营公告; +- 只显示票号,不显示手机号、姓氏或称谓;首期不提供公开身份字段配置; +- 16:9 和超宽屏响应式、全屏/开机自启、保活、断线状态和最近快照; +- 浏览器声音自动播放需首次人工授权/受控终端策略;真实语音硬件和设备网关属于后续专项; +- 设备配对码换取只读、限项目、可吊销的大屏令牌。 + +### 8.4 管理端 Web + +| 模块 | 能力 | +|---|---| +| 运营看板 | 管理端首页;项目筛选、等待号码数/人数、正在叫号、近期吞吐、设备模拟异常和活动号码明细;活动号码在管理员权限内对应展示项目、票号、完整手机号、姓氏/称谓与状态 | +| 景区与项目 | 项目、营业时间、票号前缀、固定 `N`、批次间隔、项目开关 | +| 叫号设置 | 固定批量人数 `N`、宽限时间、允许重叫次数;高级重排策略不进入 MVP | +| 预计等待设置 | 每项目只填写单个号码预计间隔时间;显示上一版值和修改日志 | +| 账号与权限 | 账号、角色、项目范围、禁用、会话、重置、MFA/SSO 预留 | +| 日志与报表 | 队列流转、员工操作、配置、登录、通知、设备日志与导出 | +| 设备接口 | MVP 只配置“未启用/模拟器”、测试成功/失败和记录;注册、心跳、回执与驱动管理属于后续真实硬件专项 | +| 隐私治理 | 字段展示策略、数据留存、匿名化任务、访问审计、导出审批 | + +管理看板是管理端的首页模块,与项目配置、账号和日志共用一个应用。游客公示大屏仍是只读展示路由,用于现场公开叫号;它不是另一套运营看板系统。 + +管理端和运营看板必须经过账号登录与路由权限校验后才能加载;员工端同样必须登录,且每次请求由服务端校验账号角色和项目范围。首期采用系统内置账号密码,不提供共享或匿名员工/管理员入口;密码使用安全哈希,登录有速率限制,支持会话吊销和管理员重置,并预留 OIDC/SSO 接口但首期不接企业身份源。 + +多项目交互约束:管理端顶部保留当前项目筛选,可查看“全部项目”汇总;进入配置和日志详情时必须选定一个项目。员工登录后只看到被授权项目,切换项目会清空当前选择。每台公示屏默认绑定一个项目,避免把不同项目号码混在一起。 + +## 9. 权限模型 + +采用“内置账号密码登录 + RBAC + 项目范围”: + +员工、项目主管、运营管理员、审计员和景区超级管理员均使用可追溯的个人账号登录;管理端首页与运营看板至少要求运营管理员、项目主管(限本项目)或更高角色。公示屏设备令牌与游客私密查询令牌不是员工/管理员账号,不能进入后台或员工功能。 + +| 角色 | 典型权限 | +|---|---| +| 景区超级管理员 | 全景区配置、角色、关键集成;不默认查看明文个人信息 | +| 运营管理员 | 项目/规则、暂停恢复、报表、异常审批 | +| 项目主管 | 本项目批量叫号、异常重排、人工覆盖、设备重试 | +| 一线员工 | 取号、正常批量叫号、到场/完成、受限查询 | +| 审计员 | 只读日志和脱敏导出 | +| 大屏设备 | 只读某项目公开投影 | +| 设备网关 | 只收其绑定设备的命令并回传状态 | + +MVP 敏感动作采用 step-up 验证或主管批准:批量取消、导出、项目异常停运和安全控制集成。[P1] 若启用手工插队/选号或批次撤回,同样适用。员工端与管理端的授权业务列表按本次确认直接显示完整手机号,用于现场联系和号码核对;页面请求仍受账号、角色、项目范围和访问日志保护。普通项目参数修改直接保存并留审计,不增加审批流。 + +## 10. 推荐技术架构 + +首期采用“一个模块化单体应用 + 一个 PostgreSQL”。前端使用 React,后端使用 Go + GORM,员工 H5、游客 H5、公示屏和管理端放在同一代码仓库,可按路由或构建入口区分;认证、项目管理、排队、预计时间、实时推送和管理看板都在同一个后端部署中。生产数据库结构通过版本化 SQL 迁移管理,不依赖 GORM 启动时自动建表。真实本地硬件与现场设备网关不在首期范围。 + +MVP 不设置独立 API 网关、认证服务、预计时间服务、实时服务、报表服务或消息中间件;这些只是代码模块,不是部署单元。 + +```mermaid +flowchart LR + Staff["员工 H5 / React"] --> App["Go + GORM 单体 API"] + Visitor["游客 H5 / React"] --> App + Screen["React 公示屏只读路由"] --> App + Admin["React 管理端 + 运营看板"] --> App + App --> DB[("PostgreSQL")] + App --> Push["内置 SSE;失败时轮询"] + Push --> Staff + Push --> Visitor + Push --> Screen + App --> Jobs["同应用后台任务"] + Jobs --> Adapter["设备适配接口 / 模拟器"] + Adapter -. 后续真实硬件专项 .-> LocalGW["可选本地设备网关"] + LocalGW --> Devices["语音、LED、打印、扫码"] +``` + +### 10.1 组件职责 + +- React 前端:员工 H5、游客 H5、公示屏和管理端共享业务契约,但按终端任务拆分路由与界面密度。 +- Go + GORM 单体 API:内置账号密码登录与项目授权、取号、叫号、预计时间、管理配置、运营看板和各端最小数据接口;核心 `CALL_NEXT` 使用显式 GORM 事务和 PostgreSQL 行锁。 +- PostgreSQL:唯一事实源;用普通事务、行锁、唯一约束和 `project_id` 组合索引保证正确性。 +- 内置实时推送:REST 写入 + SSE 单向更新;连接失败回退 5 秒轮询,不先引入 WebSocket 集群。 +- 同应用后台任务:处理数据库中的待发送记录、简单统计和过期状态;不依赖独立消息队列。 +- 设备适配接口:首期只提供统一命令结构、成功/失败模拟器和测试日志;厂商适配器或本地网关属于后续真实硬件专项。 +- MVP 不要求 Redis。只有压测证明会话、限流或多实例推送需要时再加入。 + +### 10.2 实时一致性 + +- 每条队列有单调递增 `queue_revision`,每个事件有唯一 ID 与序号。 +- SSE 订阅按服务端确认的项目权限过滤;普通员工和项目设备不能订阅其他项目事件。 +- 客户端检测到事件缺口时拉取最新快照,而不是猜测中间状态。 +- 大屏重连时先读快照,再订阅新事件;旧叫号只恢复画面,不自动重播声音。 +- 所有写 API 支持 `Idempotency-Key`;网络重试不会重复取号或重复叫号。 +- 公共读模型允许最终一致(目标 2 秒内),叫号写和成员选择必须强一致。 + +## 11. 硬件集成规划 + +### 11.1 为什么不能只做浏览器直连 + +截至 2026-07,WebUSB 与 Web Serial 仍被 MDN 标注为有限可用,并要求安全上下文;设备选择通常需要用户手势。Chrome 对公网页面访问本地网络又增加了 Local Network Access 权限提示。这些能力可用于受控终端试点,但不足以覆盖任意手机、浏览器和厂商驱动。 + +来源:[MDN WebUSB](https://developer.mozilla.org/en-US/docs/Web/API/WebUSB_API)、[MDN Web Serial](https://developer.mozilla.org/en-US/docs/Web/API/Web_Serial_API)、[Chrome Local Network Access](https://developer.chrome.com/blog/local-network-access)。 + +### 11.2 可选设备网关 + +MVP 先定义设备适配接口和模拟器。只有确认的真实硬件无法通过普通网络接口稳定接入时,才在景区本地运行轻量网关(Windows/Linux/工控机/边缘盒子均可评估): + +- 通过 mTLS MQTT、WSS 或 HTTPS 长连接接收标准命令; +- 适配串口、USB、TCP/UDP、厂商 SDK、ESC/POS、HTTP 等协议; +- 本地持久化未完成命令,按过期时间和重试策略执行; +- 上报心跳、驱动版本、设备状态、执行回执和诊断信息; +- 断线后不自行生成新叫号,只执行已确认且未过期的命令; +- 支持模拟设备驱动,开发阶段无需真实硬件即可做端到端测试。 + +### 11.3 标准命令、设备事件与回执(后续真实硬件专项) + +下行 `DeviceCommand`(系统要求设备执行)建议: + +- `DISPLAY_RENDER`:大屏/LED 更新; +- `VOICE_ANNOUNCE`:语音播报; +- `PRINT_TICKET`:打印排队凭条; +- `INDICATOR_SET`:指示灯/蜂鸣器; +- `DEVICE_HEALTH_POLL`:主动健康检查。 + +上行 `DeviceEvent`(设备向系统报告)建议: + +- `SCAN_VERIFIED`:扫码/NFC 核验结果; +- `BUTTON_PRESSED`:物理按钮输入; +- `DEVICE_HEARTBEAT`:设备心跳和能力状态; +- `COMMAND_ACK`:命令接收、执行或失败回执。 + +三个标识不能混用: + +- `business_event_id`:一次已提交的业务事实,例如某叫号批次进入 `ACTIVE`,供四端关联; +- `call_attempt_id`:初叫或某次有意重叫,每次播报意图一个新值; +- `command_id`:某次尝试下发给某台设备的一条命令;传输重试沿用它并由设备去重。 + +命令信封至少包含:`command_id`、`business_event_id`、`call_attempt_id`(适用时)、`site_id`、`project_id`、`device_id`、`type`、`schema_version`、`issued_at`、`expires_at`、`payload`。回执状态:`RECEIVED`、`EXECUTING`、`SUCCEEDED`、`FAILED_RETRYABLE`、`FAILED_FINAL`、`EXPIRED`。 + +浏览器公示屏从单体应用的内置实时接口读取公开数据;LED/串口屏在启用设备网关后接收命令。同一物理终端必须选定唯一主传输,禁止 Web 与设备命令同时渲染造成重复。 + +安全关键的闸机、车辆启航或安全联锁不复用普通大屏/语音权限;需要独立命令域、双向确认、白名单和现场安全评审。 + +## 12. 核心接口草案 + +以下是边界,不是最终 URL: + +- `POST /staff/tickets`:员工取号;含幂等键。 +- `GET /staff/queues/{id}/snapshot`:员工队列快照和修订号。 +- `POST /staff/queues/{id}/call-batches`:执行“叫下一批”,固定 `N` 读取项目设置。 +- `POST /staff/call-batches/{id}/recall`:重叫。 +- `POST /staff/tickets/{id}/arrive|serve|complete|miss|cancel`:明确状态动作。 +- `POST /staff/tickets/{id}/replacement`:为已过号票创建新票号并排到队尾,记录新旧票关联;不修改原票状态和位置。 +- `GET /public/status/{opaqueToken}`:游客个人最小投影。 +- `GET /display/{pairedDevice}/snapshot`:大屏公开投影。 +- `GET /events?...`:SSE 订阅,带最后事件序号恢复。 +- `PUT /admin/projects/{id}/wait-estimate`:保存该项目的简单预计时间设置,并记录上一版。 +- [P1] `POST /integrations/webhooks`:确有外部集成时注册签名 Webhook。 +- [后续真实硬件专项] 设备连接:命令下发、回执、心跳、能力和日志接口。 + +所有员工/管理接口先由服务端校验目标资源所属项目和账号的项目授权;公示屏项目来自设备绑定,游客项目来自私密查询令牌。任何接口都不能仅凭客户端传入的 `project_id` 放行。 + +若 P1 启用对外 Webhook,必须签名、重放防护和失败重试;消费者失败不能阻塞核心叫号事务。 + +## 13. 数据隐私与安全 + +### 13.1 合规事实基线 + +- 手机号、姓氏、性别与游客关联后属于个人信息。《个人信息保护法》要求处理目的明确、范围最小、事前告知并采用实现目的所必要的最短保存时间;处理者、目的、方式、种类、期限和权利渠道均应告知。[法律原文](https://www.cac.gov.cn/2021-08/20/c_1631050028355286.htm) +- 2025 年四部门个人信息保护专项行动明确把线下消费场景强制收集非必要手机号、生日、性别列为治理重点。因此“作为取号标识”不能自动证明三项都必须收集。[专项行动公告](https://www.cac.gov.cn/2025-03/28/c_1744867353112759.htm) +- 三个字段本身不在法律第 28 条明确列举的敏感个人信息类别中,但多项信息汇聚后的风险需整体评估;不满 14 周岁未成年人的个人信息属于敏感个人信息。[国家标准 GB/T 45574-2025](https://openstd.samr.gov.cn/bzgk/std/newGbInfo?hcno=F9F3A2EBF49E9B4D73AD8C8912986D5A) +- 大屏或广播若仍能对应到具体游客,属于公开个人信息的处理场景,需评估单独同意和个人信息保护影响评估要求;手机号打星只是掩码,不自动等于匿名化。 +- 《网络数据安全管理条例》要求隐私规则醒目、易访问,并说明保存期限和到期处理;短信、云服务、硬件厂商等受托方需合同约束目的、范围和安全义务。[条例原文](https://app.www.gov.cn/govdata/gov/202409/30/520076/article.html) + +这些内容是产品/技术规划依据,不替代项目法务意见。 + +### 13.2 已确认的最小必要身份字段 + +- 手机号必填,用于联系、查重和找回;姓氏与称谓选填,称谓可选“先生、女士、游客”,未填写时默认“游客”。 +- 系统不采集法定性别;称谓只用于人工核对,不推断或记录性别。 +- 同一手机号可对应多个同时活动的号码。重复检测只触发脱敏提示和员工确认,不阻止创建;确认结果必须审计。 +- 系统身份使用内部 ID;公开身份使用票号;个人查询使用高熵令牌或验证码。 +- 未成年人仍按“一人一号”创建独立排队单,可复用监护人手机号,不额外采集儿童手机号或法定性别。 + +### 13.3 存储与展示 + +- 手机号密文存储;另存带服务端密钥的 HMAC 用于等值查找,不能用裸 SHA 哈希。 +- 完整手机号不进入 URL、埋点、普通日志、Webhook 默认载荷或大屏。 +- 大屏和广播只使用票号,不显示或播报手机号、姓氏、称谓;首期不提供公开身份字段配置。 +- 员工端和管理端在账号及项目授权范围内直接展示完整手机号与票号、姓氏/称谓、状态的对应关系;接口不得跨项目返回,普通日志不得记录响应中的完整值。 +- 游客私密状态页只显示联系手机号尾四位用于本人核对,不显示完整手机号。 +- 游客状态令牌不可枚举、可撤销、有有效期;敏感操作再次验证手机号。 + +### 13.4 最小必要与留存 + +- 取号前展示处理目的、字段、使用方式、保存期限、查询/删除渠道和运营主体。 +- 营销短信/画像不得借用排队服务的同一授权。 +- 排队记录与手机号、姓氏、称谓的个人关联在完成、取消或过号后保留 30 天,期满删除个人关联。 +- 原始排队业务事件保留 90 天,期满不可逆匿名化为统计数据;匿名运营指标可继续保存。 +- 登录、权限及网络安全日志至少保留 6 个月。 +- 后台操作审计保留 1 年。 +- 个人信息保护影响评估及相关处理记录保留 3 年。 +- 上述期限实现为受权限与审计保护的系统配置;正式上线前由运营与法务复核,任何调整都必须遵守最短必要原则并记录依据。 + +### 13.5 安全控制 + +- 全链路 TLS;首期内置账号密码使用安全哈希、登录限流、会话吊销和管理员重置。MFA/OIDC/SSO 只预留扩展接口,不作为首期能力。 +- 密钥进入 KMS/专用密钥管理,不写配置文件或日志。 +- RBAC + 项目范围 + 字段级脱敏;导出需审批、带水印和过期下载地址。 +- 速率限制、验证码/短信验证码防批量查询和撞库。 +- 管理、叫号、个人信息访问、规则和设备命令全部审计。 +- 备份加密、恢复演练、依赖与镜像扫描、漏洞响应和日志告警纳入上线门槛。 + +## 14. 日志、报表与运营指标 + +### 14.1 日志分类 + +- 业务流转:取号、位置/优先变化、批次、重叫、到场、过号、重排、取消、完成。 +- 账号安全:登录、失败、登出、重置、授权变化、查看明文、导出。 +- 配置:项目、容量、预计时间参数及上一版恢复、隐私留存。 +- 设备:注册、心跳、命令、回执、重试、驱动错误、人工降级。 +- 通知:创建、发送、送达/失败,但正文和手机号需脱敏。 + +### 14.2 核心指标 + +- 当前/峰值等待号码数和人数(一人一号,两者数值一致); +- 今日平均/最长实际等待时长; +- 每小时吞吐、批次人数、容量利用率; +- 过号率、取消率、过号后重新取号率、人工跳过率; +- 近期预计时间平均误差、偏早/偏晚次数; +- 实时推送延迟、大屏陈旧时间; +- 设备模拟命令成功率/延迟;[后续真实硬件专项] 再统计真实设备离线时长; +- 员工异常操作、权限拒绝和批次冲突。 + +## 15. 非功能目标(评审基线) + +下列是建议验收基线,最终需按实际峰值容量校准: + +| 维度 | 建议目标 | +|---|---| +| 叫号正确性 | 并发和重试下同一排队单至多进入一个有效叫号批次;状态变更 100% 可审计 | +| 操作延迟 | 设计负载下,取号/叫号 API P95 ≤ 800ms,P99 ≤ 1.5s(不含外部短信/硬件完成) | +| 实时性 | 已提交叫号到在线 H5/大屏 P95 ≤ 2s;降级轮询陈旧时间 ≤ 5s | +| 可用性 | 营业时段核心服务月可用性目标 99.9%;中心服务不可达时停止数字化取号/叫号并切换人工预案 | +| 数据恢复 | 需在部署决策后确认 RPO/RTO;数据库做时间点恢复与定期演练 | +| 容量 | 以确认的峰值“取号/秒、叫号/秒、在线游客、屏幕、项目数”的 2 倍做压力验收 | +| 隐私 | 大屏、普通日志和分析事件中 0 个完整手机号;越权访问测试全部拒绝并留痕 | +| 设备接口 | MVP 模拟成功/失败且不影响核心队列;真实硬件纳入后再验收命令幂等、过期、重试和回执 | + +上线前必须取得实际容量输入:项目数、日票量、峰值每分钟取号、最大并发游客、员工/大屏/网关数、单批容量、最长营业时长和网络质量。 + +## 16. 测试与验收计划 + +### 16.1 测试层级 + +- 状态机单元/性质测试:所有允许和禁止的转移、逆向操作、营业日边界。 +- 并发测试:多员工同时叫下一批、客户端超时重试、重复提交、队列暂停竞态。 +- 固定 `N` 选号测试:严格 FIFO、队首连续 `N` 个有效等待号码、不跳号,队列不足时叫出全部剩余号码。 +- 预计时间测试:按单个号码间隔累加、暂停、参数缺失和五分钟取整。 +- 实时测试:掉线补快照、事件缺口、乱序/重复事件、大屏旧声音不重播。 +- 设备接口测试:MVP 模拟成功/失败;[后续真实硬件专项] 再测网关超时、重复命令、坏载荷、离线、过期和部分设备失败。 +- 端到端测试:四端共享同一排队单/批次事实。 +- 隐私与安全:越权、枚举令牌、日志泄露、导出、会话吊销、密钥轮换。 +- 现场 UAT:弱网、噪声、阳光屏幕、扫码速度、员工戴手套/单手操作、节假日批量压力。 + +### 16.2 必过场景 + +1. 两名员工同时叫“下一批 20 人”,成员无重复、顺序严格 FIFO 且两端得到明确结果。 +2. 服务端已成功、员工手机超时后重试,不生成第二批。 +3. 模拟语音适配器失败但公示屏成功,业务批次保持有效;真实设备重试语义留到后续专项验收。 +4. 大屏断线十分钟后恢复,只显示最新状态,不重播十分钟前语音。 +5. 项目暂停后停止新叫号,游客显示暂停而不是继续倒计时。 +6. 队列只剩 7 人而项目固定 `N=20` 时,原子叫出剩余 7 个号码,不等待凑满、不跳号。 +7. 过号后创建的新号码位于队尾,且可以追溯原号码、新号码、原因、操作人和所有叫号尝试。 +8. 已登录员工只能获得其授权项目的完整手机号对应数据,管理员可获得管理范围内的活动号码明细;游客只获得本人私密状态页中的手机号尾四位,大屏和设备令牌无法获得任何手机号或管理数据。 +9. 修改项目预计时间参数后立即生效、记录前后值,并可由授权管理员恢复上一版。 +10. 营业日结束、跨午夜、时区、时钟漂移和夏令时测试不破坏票号与统计。 +11. A 项目员工、SSE 订阅、公示屏令牌和设备绑定均无法读取或操作 B 项目数据;客户端伪造 `project_id` 也被服务端拒绝并留痕。 + +## 17. 部署、弱网与灾备分支 + +为满足轻量化要求,MVP 已确认采用方案 A。断外网时不允许数字化写入;方案 B 仅在未来需求正式变更后单独扩展立项。 + +### 方案 A:中心服务为唯一写入权威 + +- 员工、游客和公示屏访问中心服务;首期不部署现场设备网关。 +- 优点:架构简单、无双向数据冲突、运维成本低。 +- 弱点:景区完全断外网时无法安全创建新票或叫新批次;只能展示最后快照并切换人工预案。 + +### [P1] 方案 B:景区本地边缘节点为现场写入权威 + +- 员工、大屏和硬件走本地网络;边缘节点向云端同步公开读模型、管理数据和备份。 +- 优点:断外网时现场仍可取号/叫号;硬件延迟更稳定。 +- 弱点:需要本地服务器、监控、升级、备份和云边同步;游客使用蜂窝网络时的个人页需处理云端陈旧提示。 + +### 方案 C:云、边都可独立写入 + +- 不建议首期采用。离线合并队列顺序、票号和批次会产生复杂冲突,很难证明公平且安全。 + +MVP 以方案 A 和纸质号码、人工广播等应急预案为准。断网时各端保留最后成功快照和更新时间,禁用新取号/叫号;恢复后先拉取权威快照与 `queue_revision` 再重新开放写操作。方案 B 会增加本地服务器、同步、监控和升级成本,不在首期建设。方案 C 不进入规划。 + +## 18. 分阶段交付 + +### 阶段 0:开发基线与脚手架(约 1 周) + +- 将 v1.0 已确认决策落实为领域模型、API 契约、数据库迁移和验收场景。 +- 创建 React 前端、Go + GORM 后端和 PostgreSQL 的单仓库脚手架,接通本地开发与自动化测试。 +- 跟班观察普通日和高峰日,补充实际容量、网络与设备资料;这些现场输入用于校准,不改变已确认基线。 + +### 阶段 1:可运行垂直切片(约 2 周) + +- 先跑一个试点项目,但数据库和权限从第一天包含 `project_id`。 +- 员工一人一号取号、固定 `N` 批量叫号、游客状态页、公示屏更新和管理基础配置。 +- PostgreSQL 事务、幂等、审计和设备模拟器从第一条链路就具备。 + +### 阶段 2:MVP 完整闭环(3–4 周) + +- 最小必要身份字段、重复手机号确认审计、到场/过号/新号排队尾,以及单个号码间隔的预计时间规则。 +- 多项目选择、项目权限、管理端运营看板、日志报表、实时降级、隐私与安全加固。 +- 设备适配接口、成功/失败模拟器和测试日志;不加入真实设备适配器或本地网关。 + +### 阶段 3:单项目现场试点与硬化(2–3 周) + +- 灰度运行、预计时间对照、并发和弱网演练、员工培训、应急预案。 +- 先由旧流程兜底,再逐步把新系统切为权威;每日复盘预计时间误差和异常。 + +### 阶段 4:全景区多项目扩展(1–2 周) + +- 批量增加项目、容量验证、设备绑定和员工项目授权。 +- 按项目逐个上线,可独立回退,不全景区一次性切换。 + +以上约 9–12 周,是 4–5 人小团队、单一试点项目、无复杂票务改造且硬件接口范围明确时的粗略范围,不是承诺。建议由产品/UX、前端、后端、QA 组成,设备集成按实际硬件兼职或外协。真实硬件认证、短信/微信、票务/闸机和离线边缘部署会另行增加范围。 + +## 19. MVP 与后续版本 + +### MVP + +- 员工代取号,一人一号;手机号必填,姓氏/称谓选填,称谓默认“游客”,不采集法定性别。 +- 同一手机号允许多个活动号码;员工确认重复提示后继续创建并留审计。 +- 一个系统支持多个项目;员工按授权选择项目,各项目独立队列、票号、参数、设备和统计。 +- 固定 `N` 批量叫下一批、严格 FIFO 连续号码、人数不足叫出剩余号码、重叫、到场、过号、过号后新号排队尾、取消、完成。 +- 游客私密状态页;大屏公开投影;实时事件与轮询降级。 +- 简单的预计等待时间区间、暂停/停运降级。 +- 管理端运营看板,以及项目/设置/账号/权限/日志/设备管理。 +- 统一设备接口、成功/失败模拟器和测试日志;不接真实硬件。 +- 中心服务唯一写入;断网停写并切换纸质号码和人工广播预案。 +- 系统内置账号密码、RBAC 和项目权限;预留 OIDC/SSO。 +- 已确认的个人关联 30 天、业务事件 90 天、安全日志 6 个月、后台审计 1 年和影响评估记录 3 年留存策略。 +- 基础无障碍随 MVP 交付:WCAG 2.2 AA 与 GB/T 37668-2019 基线、键盘/焦点、状态消息、对比度、200% 缩放和 reduced motion。 + +### P1 + +- 游客现场扫码自助取号、短信/微信通知、票务/闸机凭证核验。 +- 优先队列、多个资源/班次、高级报表和预计时间统计、多语言,以及 AAA 级/专项辅助设备等无障碍增强。 +- 更多硬件驱动、设备远程诊断、离线部署增强和独立设备网关。 +- 员工手选、跳号、预叫、批次撤回和多维容量约束。 + +### P2 + +- 跨项目推荐与错峰引导、预约时段和同时持有号码策略。 +- 在数据质量达标后评估更高级预测模型。 +- 多景区/多租户、开放平台和外部运营数据中台。 + +## 20. 决策台账 + +### 20.1 已确认开发基线 + +- 四类访问视图:员工 H5、游客私密状态 H5、实体只读公示大屏,以及包含运营概览、项目管理和大屏中心的 PC 管理端。 +- 一个系统支持多个景区项目,所有业务通过 `project_id` 强隔离,但不为每个项目重复部署。 +- 一人一号;手机号必填,姓氏/称谓选填,称谓默认“游客”,不采集法定性别。 +- 同一手机号允许多个活动号码;重复时脱敏提示、员工确认并审计。 +- 每项目配置固定 `N`,严格 FIFO 呼叫队首连续号码;不足 `N` 时叫出全部剩余号码,不手选、不跳号。 +- 过号后原号失效;重新排队必须创建新号进入队尾,并保留新旧票关联审计。 +- 预计等待时间只保留单个号码预计间隔时间,每个号码按该规则累加计算。 +- 中心服务是唯一写入权威;断网停写并切换纸质号码、人工广播等预案。 +- 员工和管理员使用内置账号密码、RBAC 与项目权限,预留 OIDC/SSO。 +- 前端 React,后端 Go + GORM,数据库 PostgreSQL;模块化单体、单仓库、版本化 SQL 迁移。 +- 首期硬件只做统一接口、成功/失败模拟器和测试日志。 +- 留存采用已确认的 30 天个人关联、90 天原始业务事件、至少 6 个月安全日志、1 年后台审计和 3 年影响评估相关记录。 +- MVP 由员工代取,游客自助取号放 P1;大屏只显示票号,不公开身份字段。 +- 界面显示预计等待时间区间和更新时间,不承诺精确分钟,也不展示 ETA 缩写。 + +### 20.2 开发期间需要补齐的现场输入 + +下列输入用于参数校准、现场验收和后续集成立项,不是开始开发的阻塞项,也不得被解释为上述基线仍未确认: + +- 试点项目的峰值容量、营业时间、默认 `N`、宽限时间、批次间隔和连续放行速度; +- 景区 VI、员工设备、大屏尺寸/阅读距离与网络质量; +- 纸质号码、人工广播、现场负责人和恢复联网后的应急切换流程; +- 未来真实硬件的型号、协议、驱动、网络拓扑、回执能力和安全边界; +- 票务、闸机、微信、短信等后续集成的明确范围。 + +### 20.3 主要风险与验证 + +| 风险 | 应对与验证 | +|---|---| +| 批量叫号重复/跳号 | 服务端原子事务、幂等、队列修订号、并发压测和审计回放 | +| 固定 `N` 与并发队列变更 | 锁定队列修订行后读取队首连续号码;覆盖人数不足、取消穿插和并发叫号测试 | +| 预计时间被理解成承诺 | 区间、更新时间、停运降级;管理看板观察近期平均误差 | +| 大屏泄露个人信息 | 独立公开读模型、默认仅票号、自动化隐私测试 | +| 浏览器/硬件不兼容 | 首期用适配器契约和模拟器隔离;后续真实硬件专项再评估本地网关与指定设备认证清单 | +| 外网中断 | 已选择中心唯一写入;断网停写、展示最后快照并演练纸质号码/人工广播预案 | +| 规则被员工绕过 | 权限分层、主管审批、异常比例告警和不可删除审计 | +| 项目停运引发客诉/聚集 | 一键暂停/停运、全端公告、停止显示预计分钟、批量通知和现场预案 | + +## 21. 开发启动状态 + +用户已经确认本文件的关键产品与技术决策,并明确授权开始执行。开发不再等待同行组、身份字段、认证方式、断网写入、真实硬件或留存期限等旧决策分支。 + +开发启动后按以下门槛推进: + +- 首个里程碑先完成单试点项目垂直切片,再扩展完整管理看板与多项目体验; +- 状态机、固定 `N` 叫号、过号新号排队尾、项目隔离和留存任务必须进入自动化验收; +- 首批项目容量、峰值、网络、VI 和设备资料在现场 UAT 前核实并用于校准; +- 隐私告知、展示、导出规则与已确认留存配置在正式上线前由责任人复核; +- 中心服务断网停写、RPO/RTO、纸质号码与人工广播预案在试点切换前演练; +- 真实硬件不属于首期验收,任何接入必须以单独确认的型号和协议范围立项。 diff --git a/docs/staff-h5-gsap-ux-plan.md b/docs/staff-h5-gsap-ux-plan.md new file mode 100644 index 0000000..cd02708 --- /dev/null +++ b/docs/staff-h5-gsap-ux-plan.md @@ -0,0 +1,139 @@ +# 员工端 H5:GSAP UI/UX 交互提升规划 + +## 1. 目标 + +员工端动效的目标不是“更炫”,而是让现场员工快速确认:操作是否提交、结果是否生效、接下来做什么。 + +原则:业务状态先于动画;一次操作只突出一个结果;动画可中断、可清理、可降级;CSS 负责简单微反馈,GSAP 负责多元素状态编排;任何请求、刷新和导航都不能依赖动画回调。 + +## 2. 当前体验与机会 + +| 场景 | 当前基础 | 主要缺口 | 提升目标 | +|---|---|---|---| +| 叫号 | busy、结果 Banner、队列刷新 | 新号段、指标和列表同时变化,视觉关系弱 | 一眼确认叫了哪些号、队列已推进 | +| 取号 | 校验、重复手机号确认、上下个号码 | 成功后表单和指标瞬时变化 | 明确号码已生成,减少重复提交 | +| 我的 | 明确确认后切换项目 | 弹层和项目上下文变化不连续 | 降低误切项目风险 | +| 主导航 | 固定三入口 | 内容瞬时替换 | 建立轻量方向感 | +| 轮询/异常 | 5 秒刷新、离线与陈旧提示 | 后台变化可能与本地操作混淆 | 只强调与当前操作相关的新变化 | + +## 3. Motion tokens + +| Token | 时长 | 用途 | +|---|---:|---| +| press | 80–100ms | 按钮按压,CSS | +| fast | 140–180ms | 图标、提示条、小状态 | +| standard | 220–260ms | 页面内容、指标变化 | +| emphasis | 280–320ms | 叫号结果、项目切换 | +| exit | 140–180ms | 元素退出 | + +推荐 `power2.out` / `power3.out`,位移控制在 6–16px,缩放控制在 `0.98–1.02`。 + +## 4. 六个重点方案 + +### A. 叫号提交与结果(P0) + +1. 点击瞬间:按钮 CSS 下压,操作区立即锁定,文案切换为“正在叫号”。 +2. 成功后:旧号段轻淡出,新号段作为整体从 `y: 10, opacity: 0` 进入,总时长不超过 300ms。 +3. “下一个号、剩余未叫号”同步做一次背景高亮,不滚动数字。 +4. 本次消费的队列行淡出并收起,后续可见行使用 GSAP Flip 平滑补位。 +5. 失败 Banner 以 6px 位移淡入,不抖动;设备部分失败只扫过一次琥珀色边框。 + +以 API 返回的 batch/revision 标识动画事件;轮询看到相同 revision 不得重播。批量号码必须整体进入,禁止逐号 stagger、老虎机、弹跳或闪烁。 + +### B. 队列列表变化(P0) + +- 使用 Flip 处理可见行删除和重排,220–260ms。 +- 前台新增号码可使用 8px 淡入;纯轮询只做 600ms 静态背景高亮。 +- 只动画首屏可见行,不为完整长队列创建时间线。 +- 标签页从后台恢复时直接显示最新快照,不补播期间变化。 + +### C. 取号确认(P0) + +- 提交时按钮保持尺寸稳定。 +- 成功后“上个取号”在 240ms 内切换为新号码,“下个取号”同步低强度高亮。 +- 号码确认出现后再清空表单,形成清晰的事务完成顺序。 +- 重复手机号提示在字段附近展开,不造成大幅页面跳动。 +- 增加 `aria-live="polite"` 成功播报;视觉提示可淡出,辅助技术反馈不依赖动画。 + +### D. 主导航与场景切换(P1) + +- active 背景和按压继续用 CSS。 +- 内容使用 180–220ms 的淡入和 8px 水平位移,方向跟随“叫号 → 取号 → 我的”。 +- 路由先切换,进入动画随后播放,禁止等待退出动画。 +- 首次加载骨架与内容交叉淡化,避免白屏。 + +### E. 项目切换弹层(P1) + +- 遮罩 180ms 淡入;移动端 sheet 从 `y: 24` 进入 260ms;桌面对话框使用轻微缩放和 8px 位移。 +- Escape、取消、确认走统一清理路径。 +- 切换后旧项目淡出,新项目名称和首份队列数据一起进入,禁止出现“新标题 + 旧队列”。 +- 同步实现焦点圈定、关闭后焦点返回和背景滚动锁定;这些不能由动画替代。 + +### F. 离线、陈旧与恢复(P1) + +- 状态 Banner 从顶部 8px 淡入,不自动消失。 +- 恢复后 Banner 淡出,数据区只做一次柔和绿色高亮。 +- 必须先拉取服务端权威快照;动画只表达界面已同步,不代表硬件必然成功。 + +## 5. 配套 UI 调整 + +1. 叫号页固定“本次结果”为第一层,“快速叫号”为唯一主按钮,批量叫号为次级操作。 +2. 指标按任务优先级排序:下一个号、剩余未叫号、最新叫到、最末号数、最末号预计时长。 +3. 不恢复大型成功卡片,但提供短暂明确的取号号码确认。 +4. disabled 按钮附近显示不可操作原因:离线、数据陈旧、项目停运或队列为空。 +5. 动画不得改变至少 44×44px 的可点击区域。 + +## 6. 推荐代码结构 + +```text +web/src/ +├── motion/ +│ ├── tokens.ts +│ ├── useReducedMotion.ts +│ └── useRevisionMotion.ts +├── components/staff/ +│ ├── CallResult.tsx +│ ├── QueueList.tsx +│ ├── TicketMetrics.tsx +│ ├── StaffScene.tsx +│ └── ProjectSwitcher.tsx +└── pages/StaffPage.tsx +``` + +`StaffPage.tsx` 保留业务数据和动作编排,视觉组件管理各自作用域内的时间线,避免轮询、表单、幂等与动画状态互相污染。 + +## 7. 实施路线 + +### Sprint 1:核心事务(P0,2–3 天) + +- 建立 motion tokens、`useGSAP` scope、reduced motion。 +- 实现叫号结果、revision 去重、可见队列 Flip。 +- 实现取号号码确认与无障碍播报。 + +### Sprint 2:场景与恢复(P1,约 2 天) + +- 实现场景进入和项目切换弹层。 +- 补齐焦点管理、Escape、背景滚动锁定。 +- 实现离线、陈旧与恢复反馈。 + +### Sprint 3:现场调优(1–2 天) + +- 验证 320/375/390/414px 和低端 Android。 +- 根据现场单手操作、网络延迟、强光环境调整时长和对比度。 +- 小范围灰度并记录重复点击率、完成时长和错误恢复率。 + +## 8. 验收标准 + +- 动画开启、关闭或中途打断,业务结果完全一致。 +- 相同 revision/batch 不重复播放。 +- 后台恢复不补播旧叫号和队列变化。 +- `prefers-reduced-motion: reduce` 下取消位移/缩放,0–100ms 内直接到终态。 +- 焦点不被动画遮挡;弹层关闭后正确返回触发按钮。 +- 成功、警告、失败通过 `aria-live` 或 `role=status|alert` 播报。 +- 主要只动画 `transform`、`opacity`,清除列表收起产生的内联样式。 +- 页面卸载后无存活 timeline、listener 或 matchMedia context。 +- 测试覆盖 revision 去重、reduced motion、清理、焦点返回、连续点击、网络失败与后台恢复。 + +## 9. 效果指标 + +关注重复点击率、一次叫号完成时间、失败后的重试成功率、重复手机号确认放弃率和项目误切换反馈。动效成功的标准是减少犹豫与误操作,而不是增加动画播放次数。 diff --git a/findings.md b/findings.md new file mode 100644 index 0000000..036f11d --- /dev/null +++ b/findings.md @@ -0,0 +1,404 @@ +# 景区排队叫号系统:调研发现与决策台账 + +## 员工端末号预计时长与闪屏(2026-07-12) +- `queueSnapshot` 当前把 `metrics.estimated_wait` 固定写成 `nil`,等待票的 `staffTicketView` 也未见按位置注入 ETA;员工前端却只读取最后一张等待票的 `estimated_wait`,因此稳定落入“暂不可估算”。 +- 后台、公屏和游客接口已经统一调用 `domain.CalculateETA`;员工快照应按 `waiting_count - 1` 作为末号前方人数,并传入项目级 `ETAIntervalSeconds` 与当前运行状态。 +- 员工页场景入场动画依赖 `[scene, Boolean(queue)]`。初始渲染 `queue=false`,首个轮询响应后变为 `true`,整块页面会再次淡入/位移,视觉上表现为刷新闪屏;该动画应只随场景切换,不随轮询数据挂载重播。 +- 修复后首次队列数据挂载只设置稳定终态;只有 `scene` 真正变化时才播放场景切换动画。五秒轮询仍保留,且沿用旧数据刷新,不卸载叫号主内容。 +- `formatEstimatedWait` 现在优先识别服务端标准字段 `estimate_minutes`,单点预测展示为“约 N 分钟”,避免同值上下界显示成“N-N 分钟”。 +- 用户刷新后仍见“暂不可估算”的原因是开发 API 于 14:23 启动,早于后端修复且不支持热更新;Vite 前端已热更新,形成新前端配旧接口的状态,并非算法再次失效。 +- 重启 API 后真实员工接口确认:云栖观光车项目/会话均为 `RUNNING`,配置每号 96 秒,等待 279 号,末号估算返回 `available=true, estimate_minutes=450`。 +- 继续排查周期性闪屏发现:`FreshnessBanner` 在每次五秒轮询开始时插入“更新中”段落,完成时删除,导致员工页主体随 DOM 高度变化上下跳动。健康的后台刷新不应占据布局;游客端已有顶部实时状态,管理端已有刷新按钮状态,删除该临时段落不损失必要反馈。 +- 游客端另有两个直接消费 `resource.refreshing` 的可见状态:页头每三秒在“实时更新/更新中”之间切换,刷新按钮同步启用/禁用并改变样式。它们虽然不卸载主卡片,但会造成周期性色彩和透明度闪动;健康自动轮询应保持“实时更新”和按钮外观稳定,只在离线或陈旧时改变界面。 + +## 大屏全屏纵向布局(2026-07-12) +- 全屏信息顺序调整为:日期时间与项目名标题栏 -> 当前叫号 -> 自动轮播列表。 +- 现有轮播分页和六秒切换逻辑可直接保留,只调整全屏结构与标题信息。 +- “总计等待号数”与“最新取号码”同置于列表标题栏,删除底部重复等待统计。 + +## 大屏顶部信息优化(2026-07-12) +- 用户截图对应管理端 `ProjectScreenTile` 的单项目全屏状态,不是独立公示屏 `DisplayPage`。 +- 当前全屏顶部只有超大项目名和状态徽标,日期时间缺失,项目名称与主叫号数字竞争视觉注意力。 +- 调整边界:日期时间仅在单项目全屏时显示在左上角;项目名称作为右侧上下文;项目状态从该大屏组件删除。 + +## 项目管理/规则配置拆分(2026-07-11) +- 当前 `/admin/projects` 只渲染 `ProjectSettingsForm`,内容是项目状态、叫号估算数、预计等待方式与参数,本质上是规则配置。 +- 后端只有 `PUT /api/admin/projects/{id}/settings`,没有创建项目或维护项目基础信息的管理 API。 +- `projects` 已包含可作为项目档案的 `name`/`code`/`timezone`/`ticket_prefix`,规则参数也已天然按项目行隔离,无需新建规则表。 +- 最终交互边界:只保留“项目管理”导航;列表负责创建和进入维护,单个项目维护页同时包含基础信息与当前项目的规则配置。 +- 时区不向管理员暴露,底层继续使用现有默认值保持数据兼容;票号不再手填前缀,界面仅提供 `00000` 格式选项。 +- 项目名称/编码与运行状态/预计等待参数在管理交互中都是“项目维护字段”,不再使用基础信息和规则配置的视觉分区。 + +## 员工端 H5 GSAP 交互审计(2026-07-11) +- 员工端当前以 `/staff`、`/staff/tickets`、`/staff/me` 三个场景组织,核心动作是快速叫号、批量叫号、创建排队号码和切换项目。 +- 当前已具备 busy、成功/警告/失败、轮询、离线/陈旧提示、幂等键等业务反馈;GSAP 必须消费这些状态,不建立第二套状态。 +- 最适合 GSAP 的位置是组合状态时间线:叫号结果与队列重排、取号指标更新、项目切换弹层、场景内容切换。按钮 hover/focus/press 继续使用 CSS。 +- 批量叫号必须作为原子事件整体进入;禁止逐号 stagger、老虎机数字、弹跳、闪烁以及依赖动画回调执行业务。 +- 轮询每 5 秒更新,不能每次都播放动画;只强调当前前台操作导致且 revision 前进的变化。页面恢复前台后直接展示权威快照,不补播旧动画。 +- 所有动画应通过 `useGSAP` 限定作用域并清理,使用 `gsap.matchMedia()` 为 reduced motion 提供直接终态。 +- 详细方案见 `docs/staff-h5-gsap-ux-plan.md`。 +- 实施采用单一 `motion/gsap.ts` 入口注册 `useGSAP` 与 Flip,避免各组件重复注册插件。 +- 场景动画依赖 `Boolean(queue)`,确保初始加载态结束、真实场景挂载后才播放;无授权项目时不再对空 selector 调用 GSAP。 +- 测试环境固定匹配 reduced-motion 分支,使可访问性查询不会在动画中间态把内容判定为隐藏,同时保留业务和焦点行为测试。 + +## Phase 16 跨端对齐审计(2026-07-11) +- 员工端已以“单号快速叫号 + 1-100 手动数量叫号”为权威交互,后台仍暴露 `call_batch_size` 配置,会让运营人员误以为叫号数量由项目固定。 +- 管理端仍有 `第 N 批` 兼容显示分支;新业务界面应始终展示实际号码/号段,内部 call batch 仅作事务和审计容器。 +- 游客端存在硬编码“第 33 位 / 共 65 位”、`10:30` 和“每 30 秒”,与 API 真实 `people_ahead` 及 5 秒轮询逻辑冲突,必须改为真实投影。 +- 大屏号段预测已从当前叫号尾号推导,可兼容每日五位数字号;用户语义应统一为“本次叫号/最近叫号”。 + +## 用户已确认的需求 +- 员工端:H5,可取号、叫号。 +- 游客端:H5,可查看排队情况和预估等待时间。 +- 大屏端:Web,公示最新排队情况。 +- 管理端:Web,管理预估规则、数据日志、账号等。 +- 取号标识包含手机号、姓氏、性别。 +- 支持批量叫号。 +- 员工端与大屏可能外接硬件,必须预留接口。 +- 先调研成熟案例并形成完整规划,确认后再实施。 +- 一个系统需要支持多个景区游玩项目,排队、规则、员工权限、设备和数据均按项目区分。 +- 系统应优先简单操作和轻量实现,避免复杂工作流、过度配置与笨重架构。 +- 运营数据看板并入管理端开发,不单独建设一套看板系统。 +- 面向用户的文案使用“预计等待时间/预计叫号时间”,不直接显示英文缩写 ETA。 +- 员工端与管理端(含运营数据看板)均必须通过账号登录后访问;不提供匿名入口。 + +## 当前假设(尚未确认) +- 这是从零开始的绿地项目;仓库当前无业务代码或既有架构约束。 +- 当前按“一个景区/运营主体下包含多个游玩项目”规划,不在 MVP 引入多租户或多景区集团层级。 +- 游客 H5 的“查看”入口可能通过号码查询链接、二维码或手机号验证访问。 +- 首期不直接承诺与特定品牌硬件绑定,而以设备适配层隔离。 + +## 未决决策 +- 取号粒度:个人或同行团体。 +- 游客能否自助取号、是否需要短信验证码、是否允许远程取号。 +- 批量叫号的精确定义与操作上限。 +- 过号、回呼、取消、迟到、优先队列、闭园和故障降级规则。 +- 硬件类型、品牌、协议、部署网络及现场控制要求。 +- 短信/公众号/小程序通知是否属于范围。 +- 多景区/多运营主体、多租户、票务/闸机/CRM 集成是否属于后续范围;MVP 只确认同一系统内的多项目。 +- “看板并入管理端”当前解释为运营数据看板并入管理端,原需求中的游客公示大屏仍保留为只读页面;若用户所说“看板”即指公示大屏,需重新确认页面归属。 + +## Design Brief +- 模式:绿地产品,不是现有界面重设计。 +- 设计目标:降低游客等待焦虑,提高员工高压操作正确率,让大屏在远距/强光下快速扫读,让管理端可审计且信息密度可控。 +- 共同语言:可信、清晰、耐候、低焦虑;克制的景区品牌感建立在公共服务可读性之上。 +- 四端差异:员工端高密度操作;游客端单任务引导;大屏远距信息;管理端数据/审计。 +- 反方向:AI 紫渐变、全面玻璃拟态、娱乐化霓虹、三等分通用卡片、无意义循环动画、用颜色作为唯一状态信号。 +- Product Design 未发现已保存的用户设计上下文或品牌资产;本轮从当前需求与公开参考出发,品牌色/Logo 作为后续可替换层。 + +## Design Research Findings +- UI/UX 设计库的首轮综合检索返回了 `Accessible & Ethical` 风格和高对比深蓝/蓝色板,这与公共服务场景吻合;但同时误匹配了 App Store 落地页、Cinzel/Josefin Sans 等奢华地产表达,已明确排除。数据库推荐只能作为候选,不可替代场景判断。 +- 针对性风格检索最符合的是 `Swiss Modernism 2.0 + Accessible & Ethical + Inclusive Design`:严格网格、数学间距、非装饰性层级、单一品牌强调色、高对比、清晰焦点与 reduced motion。 +- 产品类型检索把本项目识别为 `Government/Public Service` 与 `Real-Time Monitoring/Analytics Dashboard` 的组合。推荐底座是 accessible minimalism;实时监控只用于员工/管理端的信息组织,不采用 IoT 常见的全暗色玻璃拟态。 +- 色彩候选中,公共服务深蓝方案和公共交通“蓝 + 橙”方案最相关。设计范式应把品牌主色与语义状态色分开:蓝色可承担品牌/交互,橙色只承担提醒或景区品牌点缀,红/绿/黄不可作为唯一状态编码。 +- 简体中文字体检索明确支持 Noto Sans SC 单字体体系;首轮综合检索推荐的 Cinzel/Josefin Sans 缺少中文与公共服务适配性,排除。最终字体应优先系统中文字体栈或可自托管的 Noto Sans SC/思源黑体,数字可使用 tabular numerals。 +- UX 检索强调:44×44px 以上触控目标、3-4px 可见焦点、动画只作用于每屏 1-2 个关键元素、完整 loading/success/error 反馈、320/375/414/768/1024/1440 多断点验证。 +- 图表检索建议:等待趋势用折线图;ETA 预测与实际用实线/虚线加预测带;项目比较用条形图;目标达成用 bullet chart;时段拥挤用热力图但必须提供数值/表格替代。实时流图只在 ≥1Hz 且确有监控价值时使用,并提供暂停。 +- GOV.UK 的官方设计原则与本项目高度一致:从用户需要开始、把复杂工作留给系统、为所有人设计、理解真实使用环境、建设完整服务而非孤立网页、保持一致但不强求四端完全相同。这支持“共享 token/语言,但按终端任务差异化”的设计范式。 +- 中国侧可访问性底线可锚定现行 `GB/T 37668-2019`《信息技术 互联网内容无障碍可访问性技术要求与测试方法》。工信部适老化与无障碍改造通知进一步强调界面元素简化、信息扁平化、功能标识统一和操作流程一致;这些原则适用于游客 H5 的低认知负担设计,也适用于员工端在高压现场减少误操作。 +- W3C《中文排版需求》最新工作草案(2026-05-03)提醒:中文并非把西文字号规则机械替换即可,设计需验证汉字字面、字重、行距、断行以及汉字与拉丁字母/数字/票号混排。项目因此统一采用清晰的中文无衬线字体,并将号码、ETA、时间戳的等宽数字和中西文混排列入真机测试。 +- 成熟公共服务/交通系统共同指向“公共服务导视式界面(Public-service Wayfinding UI)”:事务层使用 GOV.UK/NHS 的清晰、可恢复和低负担模式,状态与组件采用 USWDS 一类语义 token,远距信息借鉴机场/公共交通导视。景区品牌只作为可替换的识别层,不侵入号码、ETA、状态和关键操作区。 +- 官方导视标准支持以下可落地约束:无衬线、开放字腔、稳定笔画;正文左对齐、不两端对齐、不压在复杂图片上;长行控制在约 60–70 个西文字符等效范围。大屏字号不能只按 CSS px 验收,应按最远观看距离和屏幕实物字符高度做上午/正午/傍晚与斜视角测试。 +- 动效原则收敛为“克制、快速、状态优先”:普通交互首选 CSS,React 条件面板才使用 Motion,GSAP 只允许进入大屏多区域同步换批等真正需要可中断时间线的少量场景。批量叫号必须作为一个原子批次同时出现,禁止逐号 stagger、老虎机数字、弹跳、闪烁、跑马灯和恢复前台后补播旧动画。 +- 建议动效 token:按压 100ms、快速状态 150ms、标准面板 200ms、员工批次更新 300ms、大屏整体换批不超过 400ms;低动效仅保留不超过 100–150ms 的透明度/高亮,无动效为 0ms。业务提交、硬件命令、语音与动画并行响应同一事件,任何业务逻辑不得依赖动画完成回调。 +- Page Visibility 与浏览器节流意味着 ETA、过号、叫号宽限和硬件执行都不能依赖前端计时器。标签页恢复时应先拉取服务端权威快照和 revision,直接显示最新状态,不重放隐藏期间的动画、提示音或语音。 +- 排队产品 UI 专项核验收敛出 7 个成熟模式:Visitor Status Ticket、Operational Call Card、Batch Action Bar、Call Takeover、Live Ops Strip、Offline Trust Banner、Hardware Receipt Tray。前四项在 Qmatic/Waitwhile 公开资料中有直接界面或帮助文档证据;离线/硬件闭环可从 Wavetec/Qmatic 核验能力,但“多通道 ACK + 局部重试”并无成熟公开 UI,仍需本项目自定义。 +- 厂商公开证据边界:Waitwhile 能证明批量 Serve/Alert/No Show 等操作,不能证明容量自动选组、原子事务、并发冲突和硬件 ACK;Qmatic 能证明单票叫号/转移/过号和多硬件,未证明多人原子批量叫号;因此不能从营销截图推断这些关键交互已经成熟。 +- USWDS 使用语义颜色 token,而非在组件里硬编码;其官方可访问性指南强调颜色不能成为唯一含义。对本项目意味着品牌色、交互色、状态色和图表色必须分层,状态还需文字、图标和形状。 +- WCAG 2.2 官方标准确认:普通文本至少 4.5:1、大字 3:1;支持 200% 文本缩放;焦点不可被遮挡;状态消息应能被辅助技术读取而不强制抢焦点;动画与闪烁需有暂停/停止/隐藏或 reduced motion 方案。 +- TfL 的官方标准把一致的品牌、字体、颜色与安全/信心直接关联,并要求根据交通模式与环境选择正确标准。其导视强调清晰视线、图文结合、环境适配,而不是把一套 Web 卡片放大到屏幕。 +- GOV.UK 错误消息规范要求错误具体、可修复、与字段标签语言一致,避免“发生错误”“必填”等泛化文案。这应作为员工取号表单和管理配置表单的文案标准。 + +## Research Findings +- 浏览器硬件 API 不能作为唯一的生产级硬件方案:截至 2026-07,WebUSB、Web Serial、WebHID 仍是“Limited availability”,且通常要求 HTTPS、安全上下文、用户授权/手势及特定浏览器支持。 +- Web Serial 可连接物理串口以及通过 USB/Bluetooth 模拟串口的设备;首次授权需用户主动选择端口。它适合受控 Chromium 终端上的可选直连模式,不适合作为任意游客手机/任意浏览器的必备能力。 +- 公网页面访问本地网络设备正在受到更严格的权限控制。Chrome 142 起推出 Local Network Access 权限提示;本地设备的 HTTP/HTTPS、CORS、证书及浏览器策略都会影响直连可靠性。 +- 初步架构推论:硬件集成应有独立的“设备网关/本地代理”作为主路径,以云端下发设备命令、本地驱动适配、回传执行回执;WebUSB/Web Serial/WebHID 仅作为受控终端的可选适配器。所有设备命令都需幂等键、超时、重试、状态回执和手动降级。 +- Waitwhile 的公开 API/帮助资料提供了一套可借鉴的领域拆分:Location/Queue、Service、Resource(窗口/通道/员工)、Customer、Visit。Visit 的主状态包含 WAITING、SERVING、COMPLETE,另有取消、到达、优先等标签/状态;事件以 visit.created/updated/removed、location-status.updated、message.created/updated 等 webhook 对外分发。 +- Waitwhile 的 Visit 示例同时包含 `partySize`、票号、队列位置、原始/当前预估等待时长等字段,说明成熟产品通常把“访客资料”和“一次排队访问”分开,并显式支持同行人数。 +- 成熟产品把游客个人状态页与公共大屏分开:个人页可展示实时位置、ETA,并允许到达/延迟/退出;公共屏可只显示票号/脱敏标识、正在服务及窗口/资源,且可播放声音。 +- Waitwhile 的 API 允许一次创建或更新多个 Visit,但其“批量消息”不等于“批量叫号”。这进一步说明本项目必须定义批量叫号的原子语义,而不能把多选 UI 当作完整业务规则。 +- 成熟系统记录账号、访客、队列访问、消息和配置变更等审计条目;并公开 409 并发冲突与 429 限流行为。对本项目的启示是叫号写操作需要乐观并发/串行化、幂等、重试边界和完整审计。 +- 国内景区已有与本项目高度相似且可核验的成熟实操: + - 梵净山红云金顶采用扫码预约叫号,小程序展示预约号/当前叫号并在接近时提醒;现场广播和电子屏按连续号段(示例为 1736—1835)批量播报,引导游客在等待时游览其他点位。这直接证明“批量叫号”应支持按容量叫连续号段,并把 H5、提醒、广播、大屏统一在同一批次事件下。 + - 长江索道通过售票窗口、自助机、微信公众号、OTA 等多入口实名购票取号,按号段、分时段乘坐;游客可在线查看排队进程并离开现场等候。其经验表明取号来源要统一归一化,队列票应可关联外部票务凭证,但不应把票务系统直接耦合进核心状态机。 + - 陶然亭公园游船把云上排队、叫号登船、游船启航控制、广播、收银、定位和统计结合。其经验表明叫号系统可能最终进入“运营控制”边界,硬件命令与安全关键的启航控制必须分级授权、独立审计,不能由普通大屏页面直接触发。 +- 国内案例还显示两种常见容量模型:红云金顶/长江索道属于按号段或批次放行,游船属于资源/船次容量调度。因此 ETA 不能只有“每人平均服务分钟数”一种公式,至少需支持连续服务与批次/班次两类规则。 +- 国际主题乐园的官方资料进一步验证了几个共性: + - Disney、Universal、Europa-Park、USJ 普遍将门票/账户/同行组作为核销与排队主体,一人可为全组操作;成熟系统不会把姓名等弱标识当数据库主键。 + - 主流体验通常使用 boarding group、return window 或系统分配时段,并明确名额有限、可能停发/暂停,且不承诺静态精确分钟数。 + - 过号策略差异很大:Disney 表述为迟到可能不予接待;Europa-Park 明确迟到失去位置并需重新排队;这证明宽限、重排和重叫必须是项目级可配置规则。 + - Volcano Bay 的 TapuTapu 用射频腕带、reader、振动提醒并支持遗失挂起/解绑;Disney/Universal 也使用 QR/NFC/实体票。这些案例支持“身份/核销凭证抽象 + 设备适配器”,而非绑定手机号或某一硬件协议。 + - 多数官方游客材料不披露员工批量叫号 UI 和内部算法。可以确认 boarding group/号段/容量配额具备批量放行语义,但不能声称已核验某家具体的员工端按钮设计。 +- 同行组应当成为一等业务对象:一个排队单需记录 `party_size` 及成员/外部票凭证(可选);按容量批量叫号默认不得无提示拆组,拆组或跳过大组必须授权并留痕。 +- 通用厂商对照补充: + - Waitwhile 是公开资料中对批量操作证据最明确的产品,可多选 Visit/活动参与者执行 Alert、Serve 等操作,并公开 REST、Webhook、认证、限流和重试文档;但官方明确核心产品没有正式离线模式。 + - Qmatic 公开能力主要是 Call next、指定号码、Recall/Recycle/No-show;优势是本地 Orchestra/Queue Agent 与打印、屏幕、语音硬件体系。其 Data Connect 是只读且非实时,不能被误用为叫号接口。 + - Wavetec 公开案例展示本地 Controller、打印/语音/屏幕、双向 REST 与恢复后云同步,硬件/边缘模式成熟;但没有核验到一次叫多条独立票的公开证据。 + - Qtrac、QLess 偏云端/浏览器和多渠道通知;公开材料不足以证明真正的批量多人叫号或断网自治。 + - 行业结论是“批量叫号并非通用标配”。本项目的按人数/设备容量批量放行是核心差异,必须自建明确原子语义,而不是假定现成产品天然具备。 +- Waitwhile 的 SUMMIT One Vanderbilt 案例按设施吞吐管理游客组:游客扫码/员工平板登记、填写同行人数,在场馆内活动,到时系统召集下一组。这是“同行组 + 设施容量 + 运营批次”的直接参考;页面所列效果与流量数字属于厂商案例自报。 +- 合规核验结论: + - 手机号、姓氏、性别与游客关联后均属于个人信息;处理需目的明确、最小必要、事前告知并采用最短必要保存期限。 + - 2025 年专项治理明确点名线下消费场景强制收集非必要手机号、生日、性别;仅写“用于取号标识”不足以自动证明三项均必要。 + - 上述字段本身不在《个人信息保护法》第28条明确列举的敏感个人信息类别中,但聚合风险可能改变保护等级;不满14周岁未成年人的个人信息属于敏感个人信息。 + - 大屏/广播若仍能对应具体游客,属于公开个人信息,应单独评估合法性/同意与保护影响;仅做手机号星号掩码并不自动等于匿名化。 + - 网络运行/安全日志依法至少保留6个月;普通排队身份映射没有全国统一天数,应按最短必要;个人信息保护影响评估和委托/提供处理记录至少保留3年。 +- 合规产品建议:公开屏和广播默认只使用随机票号;提供不依赖手机号的现场二维码/纸质号替代;儿童随监护人同行组处理;手机号只在短信/OTP/找回确有需要时收集;性别改为可选称谓或取消。 +- ETA 研究结论:Little's Law `L=λW` 适合长期/稳定窗口的平均校验,不等于实时个人 ETA;景区午高峰、停机和增减资源是非平稳场景,直接用“人数÷到达率”会误导。 +- ETA 应使用完成/放行吞吐而非游客到达率。批次项目基础公式可写为:`距下一批时间 + (ceil((前方有效人数 + 本组人数) / 预计实际批容量) - 1) × 预计批次间隔`;实际容量应来自近期真实入场而非名义容量。 +- 游客展示应称“预测区间”而不是统计学上的置信区间;建议四舍五入到5分钟,显示高/中/低可信度、更新时间,并分别定义“预计叫到号”和“预计开始体验”两个可能的终点。 + +## 技术/产品决策 +| Decision | Rationale | +|----------|-----------| +| 硬件采用抽象设备命令 + 本地设备网关,浏览器直连只作为可选方案 | 浏览器硬件 API 兼容性、授权和本地网络策略无法满足多硬件/多终端的稳定生产要求 | +| 大屏终端需支持保活、断线缓存和连接健康上报 | 大屏属于长时间运行终端;浏览器虽有 Screen Wake Lock,但旧设备兼容和权限仍需降级策略 | +| 客户(Visitor)与一次排队单(Queue Ticket/Visit)分表 | 同一游客可多次排队;排队状态、ETA 与审计属于一次访问而非永久身份资料 | +| 将批量叫号建模为独立 Call Batch,而非循环调用单号接口 | 需要原子选择、部分失败规则、统一回执、撤销边界和硬件播报一致性 | +| 游客个人状态页与公共大屏使用不同数据投影 | 权限、隐私、交互和刷新粒度不同,不能复用同一返回模型 | +| 大屏/广播默认只使用随机票号 | 手机号/姓氏/性别的公开必要性不足,且现场组合信息可能重新识别游客 | +| ETA 使用完成吞吐、批次容量和区间预测,不用到达率直接相除 | Little's Law 的稳定平均前提不适合非平稳实时队列;批次设施还受周期和实际载客量影响 | +| 先锁定队列修订行/CAS,再比较客户端修订号 | 避免两个并发叫号请求先同时通过版本校验,后到者等锁后仍沿用旧候选集 | +| 严格 FIFO 且不拆组时,ETA 复用逐组装箱算法 | `总人数÷容量` 忽略批次容量碎片,只能作为允许拆组时的近似下界 | +| MVP 的 `CallBatch` 只保留 ACTIVE/PARTIALLY_RESOLVED/RESOLVED | 覆盖叫号、部分到场/过号和结束;WITHDRAWN 撤回属于 P1,避免首期复杂逆向状态 | + +## Phase 10:端结构重构发现 + +- 当前管理端只有 `/admin` 概览,后端已经提供项目参数更新接口;本轮可补齐项目管理表单,而不需要扩展服务端。 +- 公示屏的公开令牌只保存哈希,管理概览不能重建现有 `/display/:token` 地址。因此大屏中心应以内置、受管理员保护的实时监控墙为主,物理公示屏继续沿用原有绑定 URL。 +- 当前员工页把取号、批次核验、队首预览和叫号堆在一屏。重构为 `/staff`、`/staff/tickets`、`/staff/verify` 三个 H5 场景入口,可复用全部既有 API、幂等和项目授权逻辑。 +- 设计采用公共服务导视式运营界面:桌面端左侧任务导航与高密度表格,H5 仅在当前场景呈现必要信息;不采用营销页面式视觉、装饰动效或新组件依赖。 +| 硬件确认后再拆分下行 DeviceCommand 与上行 DeviceEvent | MVP 只保留统一接口和模拟成功/失败;扫码/按钮/完整 ACK 在具体硬件型号确认后实现 | +| taste 的建议只作用于品牌表达和反模板化检查 | 该 skill 明确不适合仪表盘、数据表与多步骤产品 UI;后台基础应选择成熟可访问组件模式 | +| GSAP 不作为全局默认动画库 | 简单状态反馈用 CSS/框架原生轻量过渡;只有大屏确有可中断多区域时间线需求时才评估 GSAP,并强制 reduced motion/清理 | +| 设计范式采用“公共服务导视型 Calm Utility” | 公共服务事务清晰度、交通导视和现场运营稳定性比旅游营销或科技大屏风格更适合四端场景 | +| 四端共享语义 token,但采用不同密度/信息层级 | 游客需要单主状态,员工需要高压操作,大屏需要远距扫读,管理端需要高密度审计;一致不等于相同 | +| 临时使用高对比蓝 + 暖橙品牌层,VI 到位后通过 alias 替换 | 当前没有既有品牌资产;语义状态、对比度、焦点和关键布局不能被品牌替换改变 | +| MVP 动效以 CSS/框架原生轻量过渡为主 | GSAP 只有大屏出现确切的可中断多区域时间线需求时才评估,且不能进入业务提交或设备控制链路 | +| 公共使用每日可读票号,私密查询使用高熵 token | 兼顾远距可读性与抗枚举;公开票号本身不能成为个人状态查询凭证 | +| WCAG 2.2 AA 与 GB/T 37668-2019 基线随 MVP 交付 | 无障碍不是后补功能;P1 只承载多语言、AAA 或专项辅助设备增强 | +| MVP 采用轻量模块化单体,不引入微服务、工作流引擎、规则 DSL 或事件溯源 | 用户明确要求系统不笨重;队列正确性保留在清晰的数据库事务中,其余能力按真实需求逐步增加 | +| 运营数据看板作为管理端模块 | 共用账号、权限、项目筛选和数据接口,避免多建一个前端应用与部署单元 | +| 以 `project_id` 作为核心业务边界 | 一个系统支持多个项目,但共用同一数据库和代码;项目分别拥有队列、规则、号段、设备、人员权限与统计 | +| 用户界面不用 ETA 缩写 | 对游客和现场员工统一写“预计等待时间”或“预计叫号时间”,降低理解成本 | +| 员工端与管理端看板均采用账号登录 + 项目授权 | 员工和管理员身份、可见项目与操作范围需要可追溯;游客状态页和公示屏仍使用受控无账号访问 | + +## 风险与边界 +- 手机号、姓氏、性别均涉及个人信息处理,大屏必须脱敏,日志需最小化留存与受控访问。 +- 等待时间是动态估计,不宜呈现为确定承诺;需要区间、更新时间与异常降级。 +- 叫号是高并发实时操作,必须防止重复叫号、跳号和多员工竞争导致的不一致。 +- 浏览器直连本地硬件能力有限且兼容性不一,硬件接口不能只定义成前端 JavaScript API。 + +## 轻量化复核(2026-07-10 用户补充后) +- 原规划中的三类预计时间模型、完整规则版本审批、独立实时/设备工作进程等内容适合作为能力上限,但若整体进入 MVP 会违背“操作简单、系统不笨重”的新约束。 +- MVP 应只实现首批项目实际需要的一种预计等待计算方式;同一项目只保存容量、批次间隔/平均处理时长、人工暂停等少量参数,高级回放、权重融合、复杂规则版本和机器学习后移。 +- 管理端的运营数据看板与规则管理共用一个 Web 应用;游客公示大屏保留只读路由,不另建管理看板应用。 +- 技术实现进一步收敛为一个后端应用、一个 PostgreSQL、同一代码库中的 H5/管理/公示页面;Redis、独立消息中间件、微服务和通用工作流引擎均不是 MVP 前提。 +- 多项目采用同库同表 `project_id` 隔离,不做每项目一套部署/数据库,也不先建设组织—租户—景区—项目的多级继承。账号通过项目授权表访问一个或多个项目。 +- 多项目下必须独立的最小范围:队列状态、票号前缀/序列、容量/预计时间参数、员工权限、设备绑定、营业状态和报表筛选;跨项目总览只放在管理端。 +- 文档一致性复核发现旧版高级内容仍散落在领域对象、指标、测试和默认架构中(预计规则版本/逐次快照、复杂误差指标、定时发布回放、事务 Outbox/独立网关默认值);这些需要同步降级为简单配置、关键节点记录、基础误差统计和数据库待发送表/可选设备网关。 +- 二次复核还发现硬件章节、管理接口和设计验收仍默认完整设备网关/规则发布流程。轻量版应把网关标为“真实硬件需要时选配”,管理接口改为项目预计时间设置,设计验收改为当前值/上一版/审计,而不是版本 diff 与发布流程。 +- 最终轻量 MVP 已冻结为:四种项目状态(未开放/运行/暂停/结束)、唯一 `CALL_NEXT`、严格 FIFO、不拆组、不跳组、装不下即欠载;员工主界面没有手选、预叫、撤回或复杂规则。 +- 硬件 MVP 只保留统一适配接口、模拟成功/失败和测试记录;注册配对、心跳、完整 ACK、扫码/按钮上行与本地网关均以具体硬件型号确认为前提。 +- `project_id` 已形成闭环契约:覆盖所有项目级记录、唯一键、查询更新、统计审计、SSE 频道、设备命令和接口授权;项目范围由服务端从账号、查询令牌或设备绑定确定。 +- 独立复核确认上述轻量化、多项目、看板归属和中文预计时间术语均无剩余 P1 问题。 + +## Resources +- MDN WebUSB API: https://developer.mozilla.org/en-US/docs/Web/API/WebUSB_API +- MDN Web Serial API: https://developer.mozilla.org/en-US/docs/Web/API/Web_Serial_API +- web.dev Accessing hardware devices on the web: https://web.dev/articles/devices-introduction +- Chrome Web Serial guide: https://developer.chrome.com/docs/capabilities/serial +- Chrome Local Network Access update: https://developer.chrome.com/blog/local-network-access +- MDN Screen Wake Lock API: https://developer.mozilla.org/en-US/docs/Web/API/Screen_Wake_Lock_API +- Waitwhile API - List visits: https://developers.waitwhile.com/reference/listvisits +- Waitwhile API - Webhooks: https://developers.waitwhile.com/reference/webhooks-2 +- Waitwhile webhook examples: https://developers.waitwhile.com/docs/webhook-response-examples +- Waitwhile audit entries: https://developers.waitwhile.com/docs/tracking-changes-with-audit-entries +- Waitwhile customer status page: https://help.waitwhile.com/en/articles/9949637-the-customer-status-page-real-time-updates-for-your-customers +- Waitwhile public/serving display: https://help.waitwhile.com/en/articles/11370712-how-to-use-the-waitlist-display-to-direct-customers-to-a-counter +- 梵净山扫码预约、当前叫号、号段广播(贵州省人大转载贵州日报): https://www.gzrd.gov.cn/gzwh/202505/t20250530_87945751.html +- 长江索道智能分流、按号段分时乘坐(重庆市政府): https://www.cq.gov.cn/ywdt/bmts/201809/t20180921_8655058.html +- 长江索道在线查看排队进程(重庆市政府,2025): https://www.cq.gov.cn/zwgk/zfxxgkml/zdlyxxgk/ggwh/ly/zxdt/202503/t20250311_14394287.html +- 陶然亭公园云排队、叫号、广播与启航控制(北京市公园管理中心): https://gygl.beijing.gov.cn/xxgk/xxgk_gyxx/202203/t20220321_2635717.html +- Disney World Virtual Queue: https://disneyworld.disney.go.com/en_CA/guest-services/virtual-queue/ +- Universal Studios Hollywood Virtual Line: https://www.universalstudioshollywood.com/web/en/us/plan-your-visit/virtual-line +- Universal Orlando Virtual Line: https://www.universalorlando.com/web/en/us/plan-your-visit/virtual-line +- Europa-Park VirtualLine: https://www.europapark.de/en/theme-park/info/plan-your-visit/virtualline-europa-park +- Universal Studios Japan 预约乘坐: https://www.usj.co.jp/web/zh/cn/enjoy/numbered-ticket/yoyakunori +- Universal Volcano Bay TapuTapu FAQ: https://www.universalorlando.com/web/en/us/plan-your-visit/taputapu-faq +- Waitwhile 批量操作: https://help.waitwhile.com/en/articles/8061037-navigating-the-visits-page +- Waitwhile SUMMIT One Vanderbilt: https://waitwhile.com/case-studies/summit-one-vanderbilt/ +- Waitwhile 离线能力说明: https://help.waitwhile.com/en/articles/11603596-does-waitwhile-work-without-internet +- Qmatic Serve View: https://docs.qmatic.io/en/staff-user-guides/user-guide--serve-view.html +- Qmatic Queue Agent: https://docs.qmatic.io/en/service-and-branch-configuration/branch-configuration/about-branches.html +- Qmatic Data Connect 限制: https://data-connect.docs.qmatic.io/developerguide.html +- Wavetec Wasl 本地分支与云同步案例: https://www.wavetec.com/es/case-studies/wasl-properties-customer-experience-transformation/ +- Qtrac Virtual Queuing: https://qtrac.com/virtual-queuing/ +- QLess Products: https://www.qless.com/products/ +- 《个人信息保护法》: https://www.cac.gov.cn/2021-08/20/c_1631050028355286.htm +- 2025 年个人信息保护专项行动: https://www.cac.gov.cn/2025-03/28/c_1744867353112759.htm +- 《网络数据安全管理条例》: https://app.www.gov.cn/govdata/gov/202409/30/520076/article.html +- GB/T 45574-2025 敏感个人信息处理安全要求: https://openstd.samr.gov.cn/bzgk/std/newGbInfo?hcno=F9F3A2EBF49E9B4D73AD8C8912986D5A +- Little's Law 原始论文: https://pubsonline.informs.org/doi/10.1287/opre.9.3.383 +- Time-Varying Little's Law: https://www.cambridge.org/core/journals/probability-in-the-engineering-and-informational-sciences/article/abs/estimating-waiting-times-with-the-timevarying-littles-law/18BF94EF65D06A5E52B72D1F81F3BFE0 + +## Browser Findings +- MDN 将 WebUSB(页面更新 2025-07-12)和 Web Serial(页面更新 2026-05-26)标注为非 Baseline/有限可用,并说明仅安全上下文可用。 +- Chrome 文档说明 Web Serial/WebUSB 的设备选择必须由用户手势触发,且单设备权限由用户显式授予。 +- Chrome Local Network Access 文档(更新说明覆盖至 2025-09-29)显示 Chrome 142 推出访问局域网设备的权限提示;这会影响公网页面直连本地打印机、控制器或网关。 +- Waitwhile API 状态与 webhook 示例显示,成熟排队产品使用明确 Visit 状态、位置/ETA 字段和前后快照事件;其帮助中心将个人状态页面与 TV/Serving Display 拆分配置。 +- 梵净山资料给出了完整现场链路:手机实时队列 → 接近提醒 → 广播/电子屏按号段叫号 → 闸机入场;这是当前最贴近用户需求的国内参考案例。 +- 国际案例中同行组、门票凭证、return window、容量限额、迟到处理和多通道核销是反复出现的成熟模式;应吸收其领域模型,不照搬依赖原生 App 或自有腕带的交互前提。 +- 五家通用厂商的公开资料证明游客状态页、ETA、大屏、短信、硬件代理和审计已成熟,但真正的多人批量叫号与断网自治不能靠营销词推断,均需明确合同/POC。 + +## 实施冻结决策(2026-07-10) +- 用户已在最新完整摘要后明确回复“确认,可以执行”,需求确认门槛已打开。 +- MVP 采用中心服务唯一写入;完全断网时停止数字化取号/叫号并切换人工预案。 +- 取号为一人一号;同一手机号允许在同一项目持有多个活动号码,员工确认重复提示后继续并留审计。 +- 取号字段为手机号必填、姓氏和称谓选填,称谓默认“游客”,不采集法定性别;大屏只显示票号。 +- 批量叫号为每项目固定 N、严格 FIFO 的连续号码;不足 N 时叫剩余号码,不手选、不跳号。 +- 过号后原号码失效,若继续排队则由员工重新取新号进入队尾。 +- 预计等待时间首期支持固定批次与连续放行两种简单模板,每项目二选一。 +- 认证首期为内置账号密码、RBAC 和项目范围,预留 SSO/OIDC。 +- 技术栈冻结为 React + TypeScript + Vite 前端,Go + GORM + PostgreSQL 后端;核心叫号事务显式使用锁、幂等和约束。 +- 硬件首期只交付统一接口、成功/失败模拟器和测试日志。 +- 留存默认值:个人关联 30 天、原始业务事件 90 天后匿名化、安全日志至少 6 个月、后台审计 1 年、影响评估记录 3 年。 +- 当前本机已发现 Node.js 22.22.3、pnpm 10.33.2、npm 10.9.8;未发现 Go、Docker 或 psql,需要在实现阶段建立可复现运行环境。 + +## Phase 7 实施发现 +- 已安装 Go 1.26.5 与 PostgreSQL 17.10;本地开发数据库使用角色/库 `queue`。 +- 初版迁移从数据库层落实项目级外键、项目/营业日票号唯一、公开 token 哈希唯一、手机号 HMAC 活动索引、审计留存时间、幂等键唯一和设备模拟结果约束。 +- GORM 仅承担模型与常规查询;固定 N 叫号仍需在同一显式事务中锁定 `queue_sessions`、读取 FIFO 票号、创建批次、推进票状态、增加 revision 并写审计/幂等响应。 +- 独立契约审查证明“可编译/可构建”不足以替代端到端响应核验:公示投影必须使用独立白名单 DTO,管理项目行必须返回真实指标,客户端同一提交意图必须复用幂等键。 +- “过号后重新取号”需要一等业务动作而非仅靠普通取号表单:原票与新票应有唯一关联、服务端锁/revision/幂等保护和显式审计。 +- 连接新鲜度必须按最近一次成功请求判断,不能用最后一次业务状态变化时间代替;空闲但健康的队列不会持续产生 `updated_at`。 +- 公示屏的公开 DTO 刻意不暴露内部主键,因此前端列表稳定键应由公开且会话内唯一的批次号/叫号时间组成,不能假定内部 DTO 字段存在。 +- 最终纵向切片已通过真实 PostgreSQL、增强 API 烟测、Go 竞态测试、13 项前端测试、生产构建和四端浏览器验收;当前可作为真实硬件接入与现场试点的开发基线。 + +## Phase 8 变更边界(2026-07-10) +- 员工端与管理端需要在已登录且通过 `project_id` 授权的上下文中,显示票号、完整手机号、姓氏/称谓和状态之间的对应关系。 +- 游客状态页由高熵私密 token 授权,只增加联系手机号尾四位用于自我核对,不返回完整手机号。 +- 公示屏的公开白名单契约保持不变,任何序列化结果均不得出现 `phone` 字段。 +- “真实测试”定义为:数据写入本机 PostgreSQL,经过正在运行的 Go API 读写,并由 React 页面实际渲染和浏览器操作验证;测试手机号是明确的受控测试值,不使用组件 mock 或静态假响应冒充联调结果。 +- 现有手机号和姓氏已经使用 AES-GCM 加密存储,附加认证数据分别是 `phone:{project_id}` 与 `last_name:{project_id}`;无需也不应新增数据库明文字段。 +- 当前 `ticketView` 是不持有 cipher 的包级函数,且只返回 honorific;内部 DTO 若要返回完整手机号/姓氏,需要改为经过 `Server` 解密的授权投影,解密失败必须让请求失败而不是静默返回错误对应关系。 +- 管理概览路由已有 ADMIN 门槛;员工队列路由已有账号项目授权。新增完整手机号只能落在这两类现有鉴权路由,不能复用到公示 DTO。 +- 当前真实 PostgreSQL 中已有多条 AES-GCM 密文记录:手机号密文 27 字节、nonce 12 字节、HMAC 64 字符,证明页面此前缺少对应关系是读模型/展示问题,不是数据未落库。 +- 员工端需要同时覆盖“等待队列表”和“当前批次卡片”;只在等待表加手机号会导致叫到后立刻失去号码与联系方式对应关系。 +- 管理端目前只有项目汇总,新增活动号码表应限制为 WAITING/CALLED/ARRIVED 等仍需现场处理的状态,避免运营首页默认展开历史个人信息。 +- 后端已将员工票号投影改为经 `Server` cipher 解密的授权 DTO,覆盖等待队列、当前批次、创建、叫号、状态迁移和重新取号响应;任一密文认证失败会让请求失败,不会产生错配的空手机号。 +- 管理端 `active_tickets` 只选择 WAITING/CALLED/ARRIVED,游客响应只增加 `phone_last4`;公示 DTO 仍是独立结构,未复用内部票号视图。 +- 前端员工端同时覆盖队首预览、等待表和当前批次,管理端新增活动号码表;公屏 `BatchCard` 默认 `showContactDetails=false`,只有员工端显式开启。 +- 真实浏览器/实库记录 A0009 验证通过:员工端将 A0009、受控完整手机号与“实测女士”对应;游客私密页只显示尾号 8042;管理端活动号码表显示同一完整对应关系;公屏 DOM 不含完整测试手机号或“手机号”标签。 +- 独立审查发现事务边界细节:状态迁移若在提交后才解密并组装响应,密文损坏会让数据库已变更但客户端收到 500。个人字段授权投影属于事务成功条件,必须在 commit 前构造。 +- 真实烟测不能只验证静态 waiting/admin 投影;创建、叫号/current batch、状态动作和重新取号都各自序列化票号,必须逐路径验证手机号/姓氏连续性,并在结束时清理活动批次以支持重复执行。 +- 最终桌面视觉复核中,员工端把手机号作为队首预览的次级信息且保留固定底部叫号动作;管理端活动号码表在首屏内清晰对应项目、票号、完整手机号、姓氏/称谓、状态和时间,没有挤压原项目总览。 +- 四个新鲜标签页的浏览器控制台均无 error/warn。 +- 390×844 真实员工页面包含 A0009 和完整测试手机号,document scrollWidth 375 小于 innerWidth 390;新增手机号列仍被表格自己的横向滚动容器约束,没有造成整页横向溢出。 +- `go run` 作为后台烟测进程时,父进程退出与编译后二进制释放数据库连接之间存在竞态;临时数据库清理应运行可直接控制 PID 的构建产物,并用 PostgreSQL `dropdb --force` 收口残余连接。 +- 最终 `make smoke-real` 改为构建单一临时 API 二进制;连续两次运行均完成真实迁移/写入/全路径断言,且每次结束后临时数据库计数都回到 0。 + +## Phase 9 尺寸边界(2026-07-10) +- 员工端与游客端按手机 H5 处理,固定参考宽度为 390px;实际设备视口小于 390px 时必须使用 100% 宽度,不能产生整页横向滚动。 +- “固定尺寸”只固定设计宽度,不固定页面高度;员工表格和长内容仍在页面/局部容器内正常滚动。 +- 管理端和公示屏分别面向桌面运营与远距大屏,不能被手机端 max-width 规则误伤。 +- 当前员工与管理端共用无变体的 `AppShell`,因此不能直接给 `.app-shell` 限宽;需要增加仅 StaffPage 使用的 mobile variant,避免管理端一起变成 390px。 +- 现有员工单列布局主要由 viewport `@media (max-width: 767px)` 触发;固定 390px 容器在桌面 viewport 下仍会命中桌面双列规则,因此 mobile variant 还必须显式覆盖 header、main、staff-layout、project picker 和固定底栏布局。 +- 实现采用 `AppShell variant="mobile"`,只由 StaffPage 启用;选择器以 `.app-shell--mobile ...` 提高作用域,因此后续通用 media query 不会把管理端限宽。 +- 员工固定操作栏使用 `left: 50% + translateX(-50%) + width: min(100%, 390px)` 与手机画布同宽;游客页和登录卡同样收敛到 390px,宽表格仍由 `.table-scroll` 局部滚动。 +- 1280px 桌面窗口实测:员工 shell=390px、底部叫号栏=390px,游客页=390px;管理端和公屏均为 1265px,证明手机规则未污染桌面路由。 +- 桌面截图确认员工 H5 以居中手机画布呈现,header、单列内容和固定叫号栏对齐同一宽度。 +- 390×844 实际视口复验:员工 shell=390px、固定叫号栏=390px、document scrollWidth=390px;游客页=390px、scrollWidth=390px,并继续显示真实尾号 8042。 +- 独立尺寸审查指出 `viewport-fit=cover` 下顶部还需 safe-area inset;固定栏的主内容预留不能小于文本放大后的栏高;登录页父级 16px padding 会让 414px 视口中的 390px 卡片缩水到 382px。 +- 上述三项已修复:员工、游客与登录页覆盖四向安全区;员工主内容和固定栏共用 `180px + safe-bottom` 预留,项目/状态长文本单行省略;登录页用动态左右留白确保 414px 视口中的卡片为 390px。 +- 四档真实浏览器回归通过:员工 shell/底栏在 320、375、390、414px 视口分别为 320、375、390、390px;登录卡同样分别为 320、375、390、390px,均无整页横向溢出。 +- 游客页四档回归均无横向溢出且继续显示真实手机号尾号;1280px 桌面复核中员工/游客保持 390px,管理端/公屏保持约 1265px,四端控制台无 error/warn。 +- 为避免只在空列表上验收,通过真实员工页面向开发 PostgreSQL 创建 A0010:员工端与管理端显示 `A0010 / 13800138042 / 尺寸实测女士`,游客私密页只显示 A0010 与尾号 8042。 +- A0010 存在时的 390px 员工页 document scrollWidth=390px;760px 宽的号码表被 322px 的局部 `.table-scroll` 正确约束,没有把整页撑宽。 + +## Phase 11 全平台功能文案极简化(2026-07-11) +- 用户要求所有平台删除非必要标题、说明、备注、副标题、设计思考和约束表达,只保留直接完成任务所需的按钮、字段、数据与状态。 +- 采用公共服务产品界面的保守边界:不删除表单标签、表头、按钮名称、票号/手机号/人数/时间、成功/错误反馈、必要空态、隐私最小披露和辅助技术名称。 +- 本轮只改前端内容层级与相应间距,不改变路由、业务接口、权限、字段顺序、状态机或隐私投影。 +- 设计方向读取为:面向现场员工、管理员和游客的高密度公共服务产品界面,低变化、低动效、高信息密度;`DESIGN_VARIANCE=3`、`MOTION_INTENSITY=2`、`VISUAL_DENSITY=8`,沿用现有原生 CSS 设计系统。 +- 初步字符串扫描显示冗余主要集中在 `page-heading` 的 eyebrow+H1+说明三层、panel 的 eyebrow+标题、项目上下文备注、Freshness 常态时间、EmptyState 描述和卡片总结句;按钮、表单标签、表头、票号、手机号和关键状态无需改名。 +- 员工 H5 当前每个场景同时出现顶级场景标题、场景导航、面板标题,语义重复。应保留“叫号/取号/核验”导航与面板内直接操作,删除“员工工作台”eyebrow、顶级 H1 说明和面板 eyebrow。 +- 员工叫号页的“当前叫号/当前批次/队首预览/固定容量/底栏本次将叫”存在多次重复;保留批次本体、等待数、队首号码和唯一底部叫号按钮即可。 +- 法定/隐私留存说明虽然属于必要合规信息,但不必常驻占据主流程;本轮保留字段用途的最小辅助技术描述与错误反馈,删除长留存备注的可见常驻行,不改变数据处理规则。 +- 管理端冗余集中在侧栏三行介绍与隐私注释、各页“eyebrow + H1 + 说明”、运行规则说明和大屏中心底部注释;导航、汇总指标、表单字段、表头、票号与全屏按钮是硬功能,应保留。 +- 游客端可删除品牌副标题、私密链接标签、重复状态徽标、通用 guidance 与“需要帮助”长说明;必须保留项目、文字状态、票号、尾号、预计等待、前方人数、叫号入口/时间、服务端消息和刷新按钮。 +- 公示屏可删除“现场叫号”眉题、重复批次元信息、健康状态下的数据更新时间、设备文案和“以工作人员指引为准”;项目、运行状态、当前票号、最近叫号、等待量、预计等待及异常连接提示必须保留。 +- 登录页只需品牌名、账号、密码、登录按钮和错误反馈;宣传副标题、内部账号 eyebrow、重复登录标题、权限说明与账号约束帮助文字均可删除。 +- 删除可见标题时必须把 `aria-labelledby` 改成稳定 `aria-label` 或保留屏幕阅读器标题;重点包括员工四个区块、BatchCard、Visitor help、Login、Display current 和 Admin 两张表。删除手机号留存提示时必须同步清理 `aria-describedby`,避免悬空引用。 +- 测试应从页标题断言转向硬功能断言:按钮、表单控件、表头、票号、手机号、文字状态、操作结果、离线/错误/空态和隐私不泄露;另加反回归断言确保被点名的解释性文案不再出现。 +- CSS 需要同步收口:员工底栏删去两行状态后,`--staff-primary-bar-reserve` 可由 180px 下调到约 100px;删去页标题后以紧凑工具栏承载项目选择/刷新/全屏,不保留空的 `.page-heading` 间距。 +- 侧栏只剩三项导航后应删除 intro/note 样式并缩窄栏宽与内间距;登录卡删除介绍区后降低纵向 gap;游客帮助区改成单按钮操作区;公示屏删除 footer 后同步调整 grid rows,避免底部空行。 +- 设计技能的营销布局规则不适用于本项目的高密度后台/数据表;本轮只采用复制审计、无障碍、对比度、按钮不换行、主题/颜色/圆角一致性和空/错/加载状态等通用预检项,不引入新设计系统依赖。 +- 页面层精简已落地:员工三场景删除页级标题/说明和面板眉题,底栏只剩主按钮;管理三页删除介绍层与备注;游客删除重复状态/帮助说明;登录只剩品牌与控件;公示屏删除眉题、设备说明与重复元信息。 +- 第一轮字符串复扫仍发现少量需总线收口的内容:游客 `privacy-label`、项目名仍使用 eyebrow 样式、公示屏健康 footer、UI 中的 `—/–` 占位或范围符号,以及员工项目状态被单独包装成一张过重的 context 卡片。 +- 管理端只剩导航后,1023px 媒体规则仍会把单一 nav 放入双列侧栏;需要改为单列。单按钮/单选择器的 `.admin-page-heading` 需要右对齐并缩小底部间距,避免删除标题后留下大片空白。 +- 首个真实浏览器页面确认登录卡只剩品牌、账号、密码和登录按钮,已删除内部账号、登录工作台、宣传说明与帮助备注;访问员工端时未登录会正确回到该精简登录页。 +- 管理员登录实际落到 `/admin`;精简后 DOM 只保留三项导航、刷新按钮、四项汇总、两张业务表及真实 A0010/完整手机号/姓名状态对应,已无页级 eyebrow、H1 说明或侧栏注释。 +- 管理概览截图确认左侧导航已缩为紧凑卡片,刷新按钮右对齐,汇总与表格连续排列;删除标题后没有空白容器、断裂边框或按钮漂浮问题。 +- 员工叫号页真实 DOM 已压缩为项目选择/运行状态、叫号/取号/核验导航、等待数、下一批真实号码及唯一叫号按钮;用户点名的“员工工作台”“当前批次与下一批”“严格按队首自动选号”等说明均不存在。 +- 390×844 实测宽度与固定栏正确:shell=390px、底栏=390px/86px、document scrollWidth=390px,A0010 仍可见且旧说明为零。 +- 视觉截图发现“暂无当前批次”仍继承通用 180px 空态高度,虽然文字已删但留下过量空白;需对叫号面板空态压缩到约 88px 后复验。 +- 叫号空态已压缩并实测为 88px,390px 页面仍无横向溢出。 +- 员工取号页真实 DOM 只保留项目/状态、三任务导航、手机号/姓氏/称谓、创建按钮、等待队列/刷新和业务表;手机号用途、留存说明、可选备注与“代游客创建/实时列表”均已删除,A0010 对应关系未丢失。 +- 390px 取号页截图确认表单直接进入字段/按钮,等待队列紧接其后,无空标题块;宽表仍仅在局部容器内滚动。 +- 核验页空状态只保留项目、任务导航、刷新和“暂无需要核验的批次”,未保留“当前批次/到场与完成核验/叫号后会显示”等说明。 +- 游客 A0010 页只保留品牌、项目、文字状态、票号、尾号 8042、预计等待、前方号码和刷新按钮;“个人状态页/私密链接/需要帮助/通用 guidance”均已删除,完整手机号仍未泄露。 +- 390px 游客截图确认内容在单张票卡内完成,刷新按钮紧随其后,无帮助说明卡、空白页脚或横向溢出。 +- 项目管理页真实 DOM 只剩项目选择器、项目状态/批量数/宽限/预计方式/间隔/缓冲字段和保存按钮;页级标题、说明和“运行规则”说明均不存在。 +- 管理端大屏中心只保留全屏按钮、项目名/状态、当前票号、等待量、预计等待和设备状态;已无“全项目实时监控”说明、公开投影备注或 tile 眉题。 +- 旧公示屏 token 当前返回“尚未绑定项目”,需要从本地种子/配置定位当前可用只读地址后再验公示内容;错误态本身已精简为错误、原因和重连按钮。 +- 使用项目幂等 seed 生成新公示 token 后,真实公示页正常返回;页面只保留项目/时钟/运行状态、当前叫号、最近叫号和队列概况,健康 footer、设备文案和现场说明均已删除。 +- 公示屏桌面截图确认四个硬信息区占满画布且无底部空行;当前新营业日暂无叫号,正确显示“暂无待到场号码/暂无最近叫号/等待 0”。 +- 最终静态扫描中用户点名的旧文案、纸号/广播预案、帮助说明和可见长破折号均为零;所有页面控制台 error/warn 为零。 +- 最终前端基线为 11 个 Vitest 文件、26 项测试;TypeScript、Vite 生产构建和 Go API 健康检查全部通过。 + +## Phase 12 移除核验流程(2026-07-11) +- 用户确认不需要核验逻辑,并批准推荐方案:员工端只保留叫号/取号;每次叫下一批时自动结束上一批,不再提供到场、完成、过号或重新取号操作。 +- 不能只删除前端入口:当前 Go 事务会在活动批次存在时拒绝再次叫号;必须在同一显式事务内结束上一批与其未结束号码,再锁定 FIFO 下一批。 +- 保留边界:固定 N、严格 FIFO、幂等键、`expected_revision`、项目授权、审计记录、设备模拟和公开隐私投影均不改变。 +- 当前 `callNext` 在 revision 校验后统计项目级 `CALLED` 批次并返回 `ACTIVE_BATCH_EXISTS`;替代实现应在同一事务中锁定所有活动批次,将其仍为 `CALLED/ARRIVED` 的票标为 `COMPLETED`,写入 `completed_at`,再选取本营业日队首 WAITING 号码。 +- 自动轮换只在新批次确实创建成功时提交;若 revision 冲突、队列为空、设备记录或响应投影失败,事务回滚,上一批不能被单独结束。 +- revision 仍只增加 1 次,幂等重放仍在轮换之前返回保存响应;`CALL_NEXT` 审计应补充自动结束的批次/票数,便于追溯系统而非人工完成。 +- 旧状态列和迁移需保留以兼容历史数据,但应移除 HTTP 路由与前端 API 入口,使到场/完成/过号/重新取号在产品运行面不可访问。 +- 代码核查确认:核验能力由两个 Go 路由(状态迁移、重新取号)、React `/staff/verify` 页面及 `BatchCard` 行操作共同暴露;三处必须一起移除,避免残留入口。 +- 当前员工端在存在 `current_batch` 时同时拦截函数和禁用按钮;改造后只按等待人数、项目状态和数据新鲜度决定能否叫下一批。 +- 工具记录:首次追加本节时匹配了错误的标题上下文,补读文件尾部后按现有 Phase 12 段落重新定位。 +# 2026-07-11 新增确认:无批次叫号与账号归属 + +- 后台必须提供账号管理,并能维护每个账号所属项目。 +- 员工叫号包含两个入口:快速叫下一个号;批量叫号可手动输入本次数量。 +- “批次”只可作为后端事务分组实现细节,不再作为用户可见业务概念或编号。 +- 票号按项目、按营业日独立递增,展示固定五位纯数字,从 `00001` 开始,不含 A/B/C/D 等前缀。 +# Phase 17 登录隔离发现(2026-07-11) + +- 当前 React 只有 `/login`,`LoginPage.destinationFor` 按角色把 `ADMIN` 导向 `/admin`,其他导向 `/staff`。 +- 当前 Go API 只有 `/api/auth/login|me|logout`,全站共用 `SessionCookieName` 一枚 Cookie,员工 H5 与管理 Web 无法在同一浏览器保持独立会话。 +- 员工 API 当前只要登录即可访问,管理员也会被当作员工使用;需要将入口、Cookie 和后端角色校验三层同时隔离。 +# 2026-07-11 维护账号 UI 审计 + +- 截图中“所属项目”直接使用浏览器原生 `fieldset/legend`,在宽屏容器中产生突兀的灰色边框、过多空白与松散纵向排列。 +- 维护页当前是单项即时保存;本次仅调整视觉结构,不改变角色、状态和项目归属的 API 行为。 +- 改造方向:使用无边框语义分组、紧凑的说明文案、自适应项目卡片网格和整卡可点击状态,与现有公共服务导视风格保持一致。 +# 2026-07-11 页头 Logo 审计 + +- `xiaoqikong-logo.jpg` 是 640×640 方形完整品牌图,内含绿色山水图形、“小七孔文旅集团”与英文名,不是可直接作为横向页头图标的素材。 +- 截图中的重复文字由“图片内嵌公司名 + `AppShell` 默认公司名”叠加造成;多段后置 CSS 覆盖还使原有裁切几何失效。 +- 不修改原始品牌资产,通过固定视窗和绝对定位只展示图片上半部的绿色图形,避免各端继续显示内嵌文字。 +# 2026-07-11 管理端列表与账号维护 UI 审计 + +- 项目列表当前使用 5 个弹性列,“操作”列占用宽度后内容仍左对齐,视觉上像表格只使用了左侧区域。 +- 角色字段使用原生 ` setStatusFilter(event.target.value)}> + + + {projects.length ? ( + <> +
+ + {projects.map((project) => ( +
+ {project.name}{deviceStatusLabel(project.device_status)} + {project.waiting_count ?? 0}个号码 + {currentBatchLabel(project.current_batch)} + + +
+ ))} +
+
共 {projects.length} 项上一页 1 下一页
+ + ) : } + + + + ); +} + +type SettingsDraft = { + status: string; + callBatchSize: string; + gracePeriodMinutes: string; + etaIntervalSeconds: string; + visitorNotice: string; +}; + +function draftFor(project: AdminProjectDto): SettingsDraft { + return { + status: project.status, + callBatchSize: String(project.call_batch_size ?? project.batch_size ?? 1), + gracePeriodMinutes: String(project.grace_period_minutes ?? 0), + etaIntervalSeconds: String(project.eta?.interval_per_number_seconds ?? 60), + visitorNotice: project.visitor_notice ?? "", + }; +} + +function ProjectSettingsForm({ project, onSaved }: { project: AdminProjectDto; onSaved: () => void | Promise }) { + const [draft, setDraft] = useState(() => draftFor(project)); + const [name, setName] = useState(project.name); + const [code, setCode] = useState(project.code ?? ""); + const [saving, setSaving] = useState(false); + const [notice, setNotice] = useState<{ tone: "success" | "danger"; message?: string } | null>(null); + + const update = (patch: Partial) => { + setDraft((current) => ({ ...current, ...patch })); + setNotice(null); + }; + + const submit = async (event: FormEvent) => { + event.preventDefault(); + setSaving(true); + setNotice(null); + try { + await api.updateProject(project.id, { + name, + code: code.toUpperCase(), + timezone: project.timezone ?? "Asia/Shanghai", + ticket_prefix: project.ticket_prefix ?? "A", + }); + await api.updateProjectSettings(project.id, { + status: draft.status, + call_batch_size: Number(draft.callBatchSize), + grace_period_minutes: Number(draft.gracePeriodMinutes), + eta_interval_seconds: Number(draft.etaIntervalSeconds), + visitor_notice: draft.visitorNotice, + }); + setNotice({ tone: "success", message: "项目维护内容已更新。" }); + await onSaved(); + } catch (caught) { + setNotice({ tone: "danger", message: caught instanceof ApiError ? caught.message : "项目未保存,请检查后重试。" }); + } finally { + setSaving(false); + } + }; + + return ( +
+
+ + + + + + +