+ );
+}
+```
+
+Create the remaining page files with the exact title and description below. Each file imports `PageHeader`, exports a function named after the file, wraps content in `
`, and renders one `
` with the listed loading text.
+
+| File | Export | Title | Description | Loading text |
+| --- | --- | --- | --- | --- |
+| `Vehicles.tsx` | `Vehicles` | `车辆台账` | `车辆身份、协议绑定、车牌、手机号和 OEM 的运营台账` | `加载车辆台账...` |
+| `Realtime.tsx` | `Realtime` | `实时状态` | `按协议和车辆查看最新实时位置、在线状态和核心数据` | `加载实时状态...` |
+| `VehicleDetail.tsx` | `VehicleDetail` | `车辆详情` | `单车身份、实时、历史、RAW、里程和质量的综合视图` | `加载车辆详情...` |
+| `History.tsx` | `History` | `历史查询` | `位置历史和 RAW 帧历史的分页查询工作台` | `加载历史查询...` |
+| `Mileage.tsx` | `Mileage` | `里程分析` | `每日里程、区间里程和异常差值分析` | `加载里程分析...` |
+| `Quality.tsx` | `Quality` | `数据质量` | `断链、VIN 缺失、字段缺失和链路健康的排查入口` | `加载数据质量...` |
+
+- [ ] **Step 6: Run tests and build**
+
+Run:
+
+```bash
+cd vehicle-data-platform/apps/web
+npm run test
+npm run build
+```
+
+Expected: pass.
+
+- [ ] **Step 7: Commit**
+
+```bash
+git add vehicle-data-platform/apps/web/src
+git commit -m "feat(platform-web): add Semi UI app shell"
+```
+
+## Task 7: Frontend API Client And Dashboard
+
+**Files:**
+- Create: `vehicle-data-platform/apps/web/src/api/types.ts`
+- Create: `vehicle-data-platform/apps/web/src/api/client.ts`
+- Modify: `vehicle-data-platform/apps/web/src/pages/Dashboard.tsx`
+
+- [ ] **Step 1: Create API types**
+
+Create `vehicle-data-platform/apps/web/src/api/types.ts`:
+
+```ts
+export interface ApiEnvelope {
+ data: T;
+ traceId: string;
+ timestamp: number;
+}
+
+export interface ProtocolStat {
+ protocol: string;
+ online: number;
+ total: number;
+}
+
+export interface LinkHealth {
+ name: string;
+ status: string;
+ detail?: string;
+}
+
+export interface DashboardSummary {
+ onlineVehicles: number;
+ activeToday: number;
+ frameToday: number;
+ issueVehicles: number;
+ kafkaLag: number;
+ protocols: ProtocolStat[];
+ linkHealth: LinkHealth[];
+}
+
+export interface VehicleRow {
+ vin: string;
+ plate: string;
+ phone: string;
+ oem: string;
+ protocol: string;
+ online: boolean;
+ lastSeen: string;
+ locationText: string;
+ bindingScore: number;
+}
+
+export interface Page {
+ items: T[];
+ total: number;
+ limit: number;
+ offset: number;
+}
+```
+
+- [ ] **Step 2: Create API client**
+
+Create `vehicle-data-platform/apps/web/src/api/client.ts`:
+
+```ts
+import type { ApiEnvelope, DashboardSummary, Page, VehicleRow } from './types';
+
+async function request(path: string): Promise {
+ const response = await fetch(path);
+ if (!response.ok) {
+ throw new Error(`request failed ${response.status}`);
+ }
+ const envelope = (await response.json()) as ApiEnvelope;
+ return envelope.data;
+}
+
+export const api = {
+ dashboardSummary: () => request('/api/dashboard/summary'),
+ vehicles: (params: URLSearchParams) => request>(`/api/vehicles?${params.toString()}`)
+};
+```
+
+- [ ] **Step 3: Update dashboard**
+
+Replace `Dashboard.tsx` with a complete data-bound page. It must import `useEffect`, `useState`, Semi UI `Card`, `Col`, `Row`, `Spin`, `Table`, `Tag`, and `Toast`, plus `api`, `DashboardSummary`, and `PageHeader`. The component state and loading lifecycle must be:
+
+```tsx
+const [summary, setSummary] = useState(null);
+const [loading, setLoading] = useState(true);
+
+useEffect(() => {
+ api.dashboardSummary()
+ .then(setSummary)
+ .catch(error => Toast.error(error.message))
+ .finally(() => setLoading(false));
+}, []);
+```
+
+Render inside ``:
+
+- Four KPI cards: online vehicles, active today, frames today, issue vehicles.
+- A protocol table with columns `protocol`, `online`, `total`.
+- A link-health table with columns `name`, `status`, `detail`; status renders `Tag` with green for `ok`, orange for `warning`, red for `error`, and grey for other values.
+- A compact Kafka lag card showing `summary.kafkaLag`.
+
+- [ ] **Step 4: Run frontend build**
+
+Run:
+
+```bash
+cd vehicle-data-platform/apps/web
+npm run build
+```
+
+Expected: pass.
+
+- [ ] **Step 5: Commit**
+
+```bash
+git add vehicle-data-platform/apps/web/src/api vehicle-data-platform/apps/web/src/pages/Dashboard.tsx
+git commit -m "feat(platform-web): connect dashboard summary"
+```
+
+## Task 8: Vehicles And Realtime Pages
+
+**Files:**
+- Modify: `vehicle-data-platform/apps/web/src/pages/Vehicles.tsx`
+- Modify: `vehicle-data-platform/apps/web/src/pages/Realtime.tsx`
+- Modify: `vehicle-data-platform/apps/api/internal/platform/model.go`
+- Modify: `vehicle-data-platform/apps/api/internal/platform/handler.go`
+
+- [ ] **Step 1: Write backend test for realtime location route**
+
+Add to `handler_test.go`:
+
+```go
+func TestHandlerRealtimeLocations(t *testing.T) {
+ handler := NewHandler(NewService(NewMockStore()))
+ rec := httptest.NewRecorder()
+ req := httptest.NewRequest(http.MethodGet, "/api/realtime/locations?limit=10", nil)
+ handler.ServeHTTP(rec, req)
+ if rec.Code != http.StatusOK {
+ t.Fatalf("status = %d body=%s", rec.Code, rec.Body.String())
+ }
+ if !strings.Contains(rec.Body.String(), "LB9A32A24R0LS1426") {
+ t.Fatalf("response missing vehicle: %s", rec.Body.String())
+ }
+}
+```
+
+- [ ] **Step 2: Run backend test and see failure**
+
+Run:
+
+```bash
+cd vehicle-data-platform/apps/api
+go test ./internal/platform -run RealtimeLocations
+```
+
+Expected: fail with 404.
+
+- [ ] **Step 3: Implement realtime location models and route**
+
+Add `RealtimeLocationRow` to `model.go` and expose `GET /api/realtime/locations` using mock data derived from vehicles.
+
+Expected JSON fields:
+
+```json
+{
+ "vin": "LB9A32A24R0LS1426",
+ "plate": "粤AG18312",
+ "protocol": "JT808",
+ "longitude": 113.2644,
+ "latitude": 23.1291,
+ "speedKmh": 42.5,
+ "socPercent": 78.4,
+ "totalMileageKm": 119925,
+ "lastSeen": "2026-07-03 20:00:00"
+}
+```
+
+- [ ] **Step 4: Implement Vehicles page**
+
+Use Semi UI `Form`, `Input`, `Select`, `Button`, `Table`, `Drawer`, and `Tag`. The table must show VIN, plate, phone, OEM, protocol, online status, lastSeen, locationText, and bindingScore.
+
+- [ ] **Step 5: Implement Realtime page**
+
+Use two tabs:
+
+- `表格视图` with realtime location table.
+- `地图视图` with a professional schematic map panel that plots rows as simple positioned dots inside a bounded panel.
+
+Do not use a full-screen decorative map.
+
+- [ ] **Step 6: Verify**
+
+Run:
+
+```bash
+cd vehicle-data-platform/apps/api && go test ./...
+cd ../web && npm run build
+```
+
+- [ ] **Step 7: Commit**
+
+```bash
+git add vehicle-data-platform/apps/api vehicle-data-platform/apps/web/src/pages/Vehicles.tsx vehicle-data-platform/apps/web/src/pages/Realtime.tsx
+git commit -m "feat(platform): add vehicles and realtime pages"
+```
+
+## Task 9: History, Mileage, Quality, And Vehicle Detail Pages
+
+**Files:**
+- Modify: `vehicle-data-platform/apps/api/internal/platform/model.go`
+- Modify: `vehicle-data-platform/apps/api/internal/platform/mock_store.go`
+- Modify: `vehicle-data-platform/apps/api/internal/platform/service.go`
+- Modify: `vehicle-data-platform/apps/api/internal/platform/handler.go`
+- Modify: `vehicle-data-platform/apps/web/src/pages/VehicleDetail.tsx`
+- Modify: `vehicle-data-platform/apps/web/src/pages/History.tsx`
+- Modify: `vehicle-data-platform/apps/web/src/pages/Mileage.tsx`
+- Modify: `vehicle-data-platform/apps/web/src/pages/Quality.tsx`
+
+- [ ] **Step 1: Add backend tests**
+
+Add handler tests for:
+
+- `GET /api/history/locations`
+- `POST /api/history/raw-frames/query`
+- `GET /api/mileage/daily`
+- `GET /api/quality/issues`
+- `GET /api/ops/health`
+
+Each test should assert HTTP 200 and a domain-specific field, such as `rawSizeBytes`, `dailyMileageKm`, `issueType`, or `linkHealth`.
+
+- [ ] **Step 2: Run backend tests and see failure**
+
+Run:
+
+```bash
+cd vehicle-data-platform/apps/api
+go test ./internal/platform -run 'History|Mileage|Quality|Ops'
+```
+
+Expected: fail because routes are missing.
+
+- [ ] **Step 3: Implement mock-backed routes**
+
+Implement mock-backed service methods and routes for all five endpoints with these concrete response models:
+
+- `HistoryLocationRow`: `vin`, `plate`, `protocol`, `longitude`, `latitude`, `speedKmh`, `totalMileageKm`, `deviceTime`, `serverTime`.
+- `RawFrameRow`: `id`, `vin`, `protocol`, `frameType`, `deviceTime`, `serverTime`, `rawSizeBytes`, `parsedFields`.
+- `DailyMileageRow`: `vin`, `plate`, `date`, `startMileageKm`, `endMileageKm`, `dailyMileageKm`, `source`.
+- `QualityIssueRow`: `vin`, `plate`, `protocol`, `issueType`, `severity`, `lastSeen`, `detail`.
+- `OpsHealth`: `linkHealth`, `kafkaLag`, `redisOnlineKeys`, `tdengineWritable`, `mysqlWritable`.
+
+The mock store returns at least two rows per table so frontend empty and non-empty table states can both be verified by changing query filters.
+
+- [ ] **Step 4: Implement pages**
+
+Use Semi UI:
+
+- `VehicleDetail`: `Tabs`, `Descriptions`, `Table`, `CodeHighlight` style JSON block.
+- `History`: filters, `Tabs`, table, raw detail drawer.
+- `Mileage`: date range controls, daily mileage table, anomaly tags.
+- `Quality`: issue summary cards, issue table, ops health table.
+
+Each page must call its matching API endpoint through `src/api/client.ts`, show a loading state, show `DataEmpty` when no rows are returned, and render `Toast.error(error.message)` on request failure.
+
+- [ ] **Step 5: Verify**
+
+Run:
+
+```bash
+cd vehicle-data-platform/apps/api && go test ./...
+cd ../web && npm run build
+```
+
+- [ ] **Step 6: Commit**
+
+```bash
+git add vehicle-data-platform/apps/api vehicle-data-platform/apps/web/src/pages
+git commit -m "feat(platform): add history mileage quality workflows"
+```
+
+## Task 10: Replace Mock Store With Production Repositories
+
+**Files:**
+- Create: `vehicle-data-platform/apps/api/internal/platform/mysql_store.go`
+- Create: `vehicle-data-platform/apps/api/internal/platform/redis_store.go`
+- Create: `vehicle-data-platform/apps/api/internal/platform/tdengine_store.go`
+- Modify: `vehicle-data-platform/apps/api/cmd/platform-api/main.go`
+
+- [ ] **Step 1: Write repository query-builder tests**
+
+Do not add a SQL mocking dependency. Create pure query-builder functions and unit test SQL strings plus argument order.
+
+Create tests for these query builders:
+
+- Vehicle list from `vehicle_identity_binding` plus realtime location.
+- Mileage daily from `vehicle_daily_mileage`.
+- Raw frame query from TDengine `raw_frames`.
+
+- [ ] **Step 2: Implement production store**
+
+Create a `ProductionStore` that satisfies `platform.Store`. It should:
+
+- Use MySQL for vehicle list, realtime snapshot, realtime location, mileage, identity.
+- Use TDengine for raw frames and history locations.
+- Use Redis for realtime raw and online state.
+- Use capacity-check executable or local metrics endpoints for ops health.
+
+- [ ] **Step 3: Keep mock fallback**
+
+In `main.go`, if production DSNs are empty, use `NewMockStore()`. If DSNs are set, use `NewProductionStore(...)`. This makes local UI development possible without production credentials.
+
+- [ ] **Step 4: Verify**
+
+Run:
+
+```bash
+cd vehicle-data-platform/apps/api
+go test ./...
+```
+
+- [ ] **Step 5: Commit**
+
+```bash
+git add vehicle-data-platform/apps/api
+git commit -m "feat(platform-api): connect production data stores"
+```
+
+## Task 11: Static Serving, Deployment, And Systemd
+
+**Files:**
+- Create: `vehicle-data-platform/deploy/systemd/lingniu-vehicle-platform.service`
+- Create: `vehicle-data-platform/docs/deployment.md`
+- Modify: `vehicle-data-platform/package.json`
+- Modify: `vehicle-data-platform/apps/api/cmd/platform-api/main.go`
+
+- [ ] **Step 1: Create systemd unit**
+
+Create `vehicle-data-platform/deploy/systemd/lingniu-vehicle-platform.service`:
+
+```ini
+[Unit]
+Description=Lingniu Vehicle Data Platform
+After=network-online.target
+Wants=network-online.target
+
+[Service]
+Type=simple
+WorkingDirectory=/opt/lingniu-vehicle-platform/current
+EnvironmentFile=/opt/lingniu-vehicle-platform/env/platform.env
+ExecStart=/opt/lingniu-vehicle-platform/current/platform-api
+Restart=always
+RestartSec=3
+LimitNOFILE=1048576
+KillSignal=SIGTERM
+TimeoutStopSec=30
+
+[Install]
+WantedBy=multi-user.target
+```
+
+- [ ] **Step 2: Write deployment doc**
+
+Create `vehicle-data-platform/docs/deployment.md` with:
+
+~~~markdown
+# Deployment
+
+## Build
+
+Run from repository root:
+
+```bash
+cd vehicle-data-platform
+npm install
+npm --prefix apps/web install
+npm run web:build
+cd apps/api
+GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -trimpath -ldflags='-s -w' -o ../../dist/platform-api ./cmd/platform-api
+```
+
+## ECS Paths
+
+```text
+/opt/lingniu-vehicle-platform/current
+/opt/lingniu-vehicle-platform/releases
+/opt/lingniu-vehicle-platform/env/platform.env
+```
+
+## Health
+
+```bash
+curl -fsS http://127.0.0.1:20300/api/ops/health
+```
+~~~
+
+- [ ] **Step 3: Verify build**
+
+Run:
+
+```bash
+cd vehicle-data-platform
+npm run web:build
+cd apps/api
+go test ./...
+go build -o ../../dist/platform-api ./cmd/platform-api
+```
+
+- [ ] **Step 4: Commit**
+
+```bash
+git add vehicle-data-platform/deploy vehicle-data-platform/docs vehicle-data-platform/package.json vehicle-data-platform/apps/api/cmd/platform-api/main.go
+git commit -m "ops(platform): add deployment unit and docs"
+```
+
+## Task 12: ECS Deploy And Browser Verification
+
+**Files:**
+- Modify only if deployment reveals a concrete defect.
+
+- [ ] **Step 1: Build release locally**
+
+Run:
+
+```bash
+cd vehicle-data-platform
+npm install
+npm --prefix apps/web install
+npm run web:build
+cd apps/api
+GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -trimpath -ldflags='-s -w' -o ../../dist/platform-api ./cmd/platform-api
+```
+
+- [ ] **Step 2: Upload release**
+
+Run:
+
+```bash
+release="platform-$(date +%Y%m%d%H%M%S)"
+ssh root@115.29.187.205 "mkdir -p /opt/lingniu-vehicle-platform/releases/$release /opt/lingniu-vehicle-platform/env"
+scp vehicle-data-platform/dist/platform-api root@115.29.187.205:/opt/lingniu-vehicle-platform/releases/$release/
+scp -r vehicle-data-platform/apps/web/dist root@115.29.187.205:/opt/lingniu-vehicle-platform/releases/$release/web
+scp vehicle-data-platform/deploy/systemd/lingniu-vehicle-platform.service root@115.29.187.205:/etc/systemd/system/
+ssh root@115.29.187.205 "chmod 755 /opt/lingniu-vehicle-platform/releases/$release/platform-api && ln -sfn /opt/lingniu-vehicle-platform/releases/$release /opt/lingniu-vehicle-platform/current"
+```
+
+- [ ] **Step 3: Create environment file**
+
+Create `/opt/lingniu-vehicle-platform/env/platform.env` on ECS with production DSNs. Do not commit secrets.
+
+Required keys:
+
+```text
+HTTP_ADDR=:20300
+STATIC_DIR=/opt/lingniu-vehicle-platform/current/web
+MYSQL_DSN=...
+REDIS_ADDR=...
+REDIS_USERNAME=...
+REDIS_PASSWORD=...
+REDIS_DB=50
+TDENGINE_DSN=...
+TDENGINE_DATABASE=lingniu_vehicle_ts
+CAPACITY_CHECK_BIN=/opt/lingniu-go-native/current/capacity-check
+AUTH_TOKEN=...
+```
+
+- [ ] **Step 4: Start service**
+
+Run:
+
+```bash
+ssh root@115.29.187.205 "systemctl daemon-reload && systemctl enable --now lingniu-vehicle-platform.service && systemctl status lingniu-vehicle-platform.service --no-pager"
+```
+
+- [ ] **Step 5: Verify APIs**
+
+Run:
+
+```bash
+curl -fsS http://127.0.0.1:20300/api/dashboard/summary
+curl -fsS 'http://127.0.0.1:20300/api/vehicles?limit=5'
+curl -fsS http://127.0.0.1:20300/api/ops/health
+```
+
+Expected:
+
+- All return JSON envelopes.
+- Dashboard has `onlineVehicles`.
+- Vehicles has `items`.
+- Ops health has link health state.
+
+- [ ] **Step 6: Browser verification**
+
+Open:
+
+```text
+http://115.29.187.205:20300
+```
+
+Verify:
+
+- Dashboard loads.
+- Vehicles page table loads.
+- Realtime page loads.
+- History page renders query controls.
+- Mileage page renders tables.
+- Quality page renders issue and health sections.
+- Visual style is Semi UI console style: clean, dense, professional, no marketing hero.
+
+- [ ] **Step 7: Commit deployment fixes**
+
+If deployment required code changes:
+
+```bash
+git add vehicle-data-platform
+git commit -m "fix(platform): stabilize ecs deployment"
+```
+
+## Self Review
+
+Spec coverage:
+
+- New project folder: Task 2 creates `vehicle-data-platform`.
+- Semi UI frontend: Tasks 1, 2, 6, 7, 8, 9.
+- Go backend: Tasks 2, 3, 4, 5, 10.
+- Dashboard, vehicles, realtime, detail, history, mileage, quality: Tasks 6 through 9.
+- RAW query with POST and field filtering: Tasks 9 and 10.
+- Production data sources: Task 10.
+- ECS deployment and browser testing: Tasks 11 and 12.
+
+Completeness scan:
+
+- This plan contains concrete files, routes, models, verification commands, and commit points.
+- Production secrets are intentionally described as environment values and must not be committed.
+
+Type consistency:
+
+- `DashboardSummary`, `VehicleRow`, `Page`, and API envelope names match between backend and frontend tasks.
+- API route names match the design spec.