Files
weather-data/weather-data-ui/CLAUDE.md
T
2026-06-26 17:39:57 +08:00

6.6 KiB

CLAUDE.md — weather-data-ui

Frontend module: Vue 3 / Vite 5 / TypeScript SPA.


Commands

npm install                    # install dependencies
npm run dev                    # Vite dev server (port 8001, host 0.0.0.0)
npm run build / npm run build:prod  # production build
npm run serve                  # preview production build
npm run lint                   # lint with autofix (ESLint)
npx vue-tsc --noEmit           # type-check (not in pre-commit)

Pre-commit: lint-staged runs eslint --fix on *.ts/*.vue via yorkie git hooks (not husky). No test runner configured.

Stack

  • Vite 5 + Vue 3 + TypeScript SPA
  • Element Plus + Element Plus Icons (all icons registered globally)
  • vue-router with hash history (createWebHashHistory)
  • Pinia for state management
  • Axios via src/utils/http.ts + src/service/baseService.ts
  • API base URL: VITE_APP_API env var, overridable at runtime by window.SITE_CONFIG.apiURL

Environment config

  • Dev: VITE_APP_API=http://192.168.2.186:8080/system-admin (hardcoded IP — new devs must change)
  • Prod: VITE_APP_API=/system-admin (relative, proxied via Nginx)
  • Runtime override takes priority: window.SITE_CONFIG.apiURL

Vite config

  • base: "./" (relative paths), chunkSizeWarningLimit: 1024
  • Manual chunks: lodash and vlib (vue/vue-router/element-plus)
  • Dev: HMR overlay disabled, host: "0.0.0.0", port 8001

Axios HTTP pattern

  • Success check: response.data.code === 0 (not === 200)
  • Request interceptor: adds token header, X-Requested-With, request timing, cache-busting _t on GET
  • On code === 401: auto-redirects to /login
  • Response unwrapped: callers receive response.data
  • File exports: bypass Axios, use window.location.href with token as query param
  • Uploads: no Content-Type set (browser auto-sets for FormData)

Routing & state

  • src/router/base.ts: 7 base routes (/, /home, /login, /user/password, /iframe/:id?, /error, 404 catch-all)
  • src/router/index.ts: beforeEach guard — auth check, dynamic route registration from backend menus, tab management. Routes are dynamically added via addRoute with flattened nested routes (keep-alive limitation). View components resolved via import.meta.glob("/src/views/**/*.vue").
  • src/store/index.ts (useAppStore): monolithic store — all state nested in state.state (double nesting, e.g. store.state.appIsLogin). initApp fetches menus/permissions/user/dicts in 4 parallel requests.
  • src/store/importTasks.ts: separate store for import task tracking (computed getters: activeTasks, hasActiveTasks, recentTasks).
  • src/utils/router.ts: converts backend menu records → Vue router records, supports iframe/external links with openStyle flags.

Layout is event-driven: src/layout/ shell + mitt event bus (src/utils/emits.ts). The EMitt enum defines 13 events for sidebar, theme, tabs, layout changes. Trace both the Pinia store and mitt events when changing navigation/sidebar/tabs/theme.

Common page pattern: useView hook

Admin CRUD pages use src/hooks/useView.ts for shared list-page workflow.

Key behaviors to know before refactoring:

  • closeCurrentTab(): if tabs enabled, emits OnCloseCurrTab mitt event; otherwise navigates to /home.
  • exportHandle(): uses window.location.href with token as query param (NOT Axios).
  • dataListSortChangeHandle(): converts camelCase → snake_case for backend (e.g. stationIdstation_id).
  • createdIsNeed: true / activatedIsNeed: false by default. Pages needing refresh on tab activation must set activatedIsNeed: true.
  • Includes workflow helpers (handleFlowRoute, flowDetailRoute) hardcoded to /flow/task-form.

Cache utility

All cache keys prefixed with v1@ to avoid collisions. Supports localStorage and sessionStorage (token uses sessionStorage). JSON serialization is automatic. getCache supports auto-delete-after-read (isDelete flag).

Weather frontend module

The home dashboard (src/views/home.vue) uses a composable-based architecture:

Composable Responsibility
useWeatherConstants.ts Rain levels, temperature thresholds, filter field definitions, fmtVal(), level/class helpers
useWeatherFilter.ts Filter state, toggle/reset/match logic, matchOp()
useWeatherStats.ts computeStats(), buildStatCards(), buildSummary(), rainLevelDistribution, WeatherDataRow type
useWeatherChart.ts ECharts dynamic import, buildChartOption(), ResizeObserver, precise trigger key (not deep watch)
useWeatherExport.ts PNG/PDF export with dynamic html2canvas/jspdf imports, loading indicator

Supporting utils: src/utils/chartBuilder.ts, src/utils/exportReport.ts.

Critical rules

1. Null ≠ zero — missing data MUST be preserved as null

When mapping backend API responses to frontend models, never default missing numeric values to 0. Rainfall of 0mm means "no rain that day" (valid measurement); null means "no data available" (missing record). Use : null not : 0 in data mapping, and display "—" for null values via fmtVal().

// ✅ Correct
rainfall: row.rain2020 != null ? +row.rain2020 : null,

// ❌ Wrong — confuses "no data" with "measured zero"
rainfall: row.rain2020 != null ? +row.rain2020 : 0,

All helper functions must accept number | null and return "—" or "" for null. Stats computations must skip null values.

2. Heavy libraries must use dynamic imports

html2canvas, jspdf, and echarts are NOT imported at module level. Load them via await import() only when triggered by user action. This saves ~600KB from the initial bundle.

3. Google Fonts go in index.html, not scoped styles

Never use @import url("https://fonts.googleapis.com/...") inside Vue scoped styles. Use <link rel="preconnect"> + <link rel="stylesheet"> in index.html.

4. Export must show user feedback

Always show ElLoading.service fullscreen and ElMessage success/failure. Disable the export button during rendering.

5. Deep watchers on filter objects are banned

Never use watch(filters, callback, { deep: true }). Derive a precise computed trigger key (e.g. dataHash, filteredHash, extremesVersion) and watch that instead.

Import progress polling pattern

  1. Upload via baseService.upload() (FormData, no explicit Content-Type).
  2. On success, poll GET .../import/progress/{backendTaskId} every 3 seconds.
  3. Update Pinia store (useImportTaskStore) with percentage/state.
  4. Emit refreshDataList on completion. Header indicator (import-task-indicator.vue) shows active tasks with spinning badge + popover.