feat(frontend): Phase 1 UX foundation — react-router v6 + responsive AppShell

Atomic foundation for the consensus-approved UX modernization (makes the
platform URL-addressable, refresh-safe, and mobile-usable for hospital demos).

- Migrate hand-rolled useState page switching → react-router v6 (routes.tsx,
  thin App.tsx auth gate, NavLink SideNav, lazy+Suspense per route)
- Add responsive AppShell: persistent rail (lg:) ⇄ off-canvas drawer + hamburger
  (<lg); kills hardcoded ml-[200px]; usable at 375px
- Add ui primitive kit (Skeleton/LoadingState/EmptyState/Card/Panel/Segmented)
- Sweep all raw "加载中..." text loaders → skeleton primitives (G4)
- Centralize test ids (utils/testids.ts); rewrite e2e for URL nav (17/17 pass:
  deep-link, refresh-preserves-page, back, 375px drawer/no-scroll)
- Harden DemographicAnalysis against API shape mismatch (defensive normalize)
- gitignore playwright-report/ and test-results/

Gates: tsc --noEmit 0 · pnpm build ok · e2e 17/17 · grep 加载中 zero outside ui/

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-21 20:12:13 +08:00
parent e95e2f1338
commit 3db3b12480
29 changed files with 796 additions and 357 deletions

View File

@@ -26,6 +26,9 @@ importers:
react-leaflet:
specifier: ^4.2.1
version: 4.2.1(leaflet@1.9.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
react-router-dom:
specifier: ^6.30.4
version: 6.30.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
recharts:
specifier: ^2.12.0
version: 2.15.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
@@ -385,6 +388,10 @@ packages:
react: ^18.0.0
react-dom: ^18.0.0
'@remix-run/router@1.23.3':
resolution: {integrity: sha512-4An71tdz9X8+3sI4Qqqd2LWd9vS39J7sqd9EU4Scw7TJE/qB10Flv/UuqbPVgfQV9XoK8Np6jNquZitnZq5i+Q==}
engines: {node: '>=14.0.0'}
'@rolldown/pluginutils@1.0.0-beta.27':
resolution: {integrity: sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==}
@@ -1504,6 +1511,19 @@ packages:
resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==}
engines: {node: '>=0.10.0'}
react-router-dom@6.30.4:
resolution: {integrity: sha512-q4HvNl+mmDdkS0g+MqiBZNteQJCuimWoOyHMy4T/RQLAn9Z29+E91QXRaxOujeMl2HTzRSS0KFPd7lxX3PjV0Q==}
engines: {node: '>=14.0.0'}
peerDependencies:
react: '>=16.8'
react-dom: '>=16.8'
react-router@6.30.4:
resolution: {integrity: sha512-SVUsDe+DybHM/WmYKIVYhZh1o5Dcuf16yM6WjG02Q9XVFMZIJyHYhwrr6bFBXZkVP6z69kNkMyBCujt8FaFLJA==}
engines: {node: '>=14.0.0'}
peerDependencies:
react: '>=16.8'
react-smooth@4.0.4:
resolution: {integrity: sha512-gnGKTpYwqL0Iii09gHobNolvX4Kiq4PKx6eWBCYYix+8cdw+cGo3do906l1NBPKkSWx1DghC1dlWG9L2uGd61Q==}
peerDependencies:
@@ -2157,6 +2177,8 @@ snapshots:
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
'@remix-run/router@1.23.3': {}
'@rolldown/pluginutils@1.0.0-beta.27': {}
'@rollup/rollup-android-arm-eabi@4.60.2':
@@ -3233,6 +3255,18 @@ snapshots:
react-refresh@0.17.0: {}
react-router-dom@6.30.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
dependencies:
'@remix-run/router': 1.23.3
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
react-router: 6.30.4(react@18.3.1)
react-router@6.30.4(react@18.3.1):
dependencies:
'@remix-run/router': 1.23.3
react: 18.3.1
react-smooth@4.0.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
dependencies:
fast-equals: 5.4.0