);
}
```
Create `vehicle-data-platform/apps/web/src/components/StatusTag.tsx`:
```tsx
import { Tag } from '@douyinfe/semi-ui';
export function StatusTag({ status }: { status: 'ok' | 'warning' | 'error' | 'offline' }) {
const map = {
ok: { color: 'green' as const, text: '正常' },
warning: { color: 'orange' as const, text: '关注' },
error: { color: 'red' as const, text: '异常' },
offline: { color: 'grey' as const, text: '离线' }
};
return {map[status].text};
}
```
Create `vehicle-data-platform/apps/web/src/components/DataEmpty.tsx`:
```tsx
import { Empty } from '@douyinfe/semi-ui';
export function DataEmpty({ text = '暂无数据' }: { text?: string }) {
return ;
}
```
- [ ] **Step 5: Create first-pass page components**
Create `vehicle-data-platform/apps/web/src/pages/Dashboard.tsx`:
```tsx
import { PageHeader } from '../components/PageHeader';
export function Dashboard() {
return (
加载总览数据...
);
}
```
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.