Files
weather-data/weather-data-ui/CLAUDE.md
T

10 KiB

CLAUDE.md — weather-data-ui

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


Code Style

  • No emoji in UI strings. Plain Chinese text for labels, buttons, status.
  • No emoji in code comments or docstrings. Plain text only.
  • Keep CLAUDE.md current — whenever code is modified, added, deleted, or any file change affects the module structure, build, conventions, or component patterns, update this file (and the root CLAUDE.md if cross-cutting) in the same commit to reflect the new state. Stale documentation is a bug.

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 events for sidebar, theme, tabs, layout changes. Trace both the Pinia store and mitt events when changing navigation/sidebar/tabs/theme.

Header right side: notification bell (combined badge) → expand (user menu). The old import-task-indicator.vue has been removed in favor of the notification center drawer.

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. Fonts are self-hosted — no external network dependency

Fonts (Noto Sans SC, JetBrains Mono) are bundled via @fontsource/* packages, imported in src/main.ts. Do NOT add Google Fonts <link> tags or @import back — the system runs on intranet where external network may be unavailable. To add a new font weight, import the corresponding fontsource CSS file in main.ts.

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.

Notification center

The global notification system (src/components/alert-marquee/index.vue) aggregates two feed types:

Feed Source Clickable?
System alerts useAlertMarquee.ts composable (polls /sys/alert/active/since every 30s) Yes — opens detail dialog
Import tasks useImportTaskStore Pinia store No — shows real-time progress inline

Components & composables

File Role
src/composables/useAlertMarquee.ts Module-level singleton: message queue, drawer toggle, detail dialog state, scrollbar position. Real-time delivery: SSE (/sys/alert/stream) primary + 10s polling fallback. Exports danger-specific computed: dangerMessages, latestDangerMessage, dangerCount, hasDangerMessages
src/composables/useFloatingDrag.ts Pointer Events drag logic: setPointerCapture, viewport clamping, deferred isDragging (activates only on >3px move), dragMoved flag to distinguish drag vs click
src/components/alert-marquee/index.vue Floating scrollbar (640px wide, centered top, draggable) + el-drawer notification center + el-dialog detail popup
src/layout/header/base-header.vue Bell button with combined badge (alerts + active import tasks), toggles drawer
src/store/importTasks.ts Import task CRUD: addTask, updateTask, removeTask, clearCompleted; getters: activeTasks, recentTasks

Level-based routing

  • danger (紧急): triggers the floating scrollbar + appears in notification center drawer + bell badge
  • warning / info (警告 / 提示): notification center drawer + bell badge only (no scrollbar)
  • The bell badge in base-header always shows total count (all levels + active import tasks)

Floating scrollbar behavior

  • Visible only when logged in (appStore.state.appIsLogin) and has danger-level alerts (hasDangerMessages)
  • Positioned centered at top (y: 56 below header), draggable to reposition, re-centers on window resize
  • Shows latest alert headline + count badge
  • Close button hides the bar; auto-reappears when new danger alerts arrive (watch on dangerCount)
  • 查看详情 button opens el-dialog with full alert text
  • The old import-task-indicator.vue (bell icon with popover in header) has been removed

Import progress integration

  1. Upload via baseService.upload() (FormData, no explicit Content-Type).
  2. On upload start, toast: "导入已开始,可在通知中心查看进度".
  3. Poll GET .../import/progress/{backendTaskId} every 3 seconds.
  4. Update Pinia store (useImportTaskStore) — progress bar + status tag render reactively in the drawer.
  5. On completion: green checkmark; on failure: red cross + error message. Completed/failed tasks show a dismiss button.

Alert management page

Manual alert CRUD at route sys/system-alert:

File Role
src/views/sys/system-alert.vue List page: useView-based, search by level/title/sourceType, batch delete, per-row withdraw
src/views/sys/system-alert-add-or-update.vue Add/edit dialog: level (info/warning/danger), title, content (textarea), sourceType, expireTime (datetime picker)

Backend endpoints under /sys/alert:

  • GET /page — paginated list (sys:alert:page)
  • GET /{id} — detail (sys:alert:info)
  • POST / — create (sys:alert:save)
  • PUT / — update (sys:alert:update)
  • DELETE / — batch delete (sys:alert:delete)
  • PUT /{id}/withdraw — soft-withdraw (sys:alert:update)
  • GET /active, GET /active/since — frontend polling (no permission required)