mirror of
https://github.com/zuiidea/antd-admin.git
synced 2026-10-19 16:14:37 +00:00
Feature/upgrade (#1170)
* chore: align workspace tooling and refresh docs Unify pnpm workspace configuration and CI versioning, upgrade create package dependencies, and update README guidance for basic and with-lingui templates. Made-with: Cursor * chore: regenerate route tree files Update generated route tree outputs after recent formatting and script changes. Made-with: Cursor * ci: fix with-lingui e2e script name Use the existing test:e2e script in CI to avoid missing-script failures. Made-with: Cursor
This commit is contained in:
@@ -40,7 +40,7 @@ jobs:
|
|||||||
- name: Setup pnpm
|
- name: Setup pnpm
|
||||||
uses: pnpm/action-setup@v4
|
uses: pnpm/action-setup@v4
|
||||||
with:
|
with:
|
||||||
version: 8.15.6
|
version: 10.33.2
|
||||||
|
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ jobs:
|
|||||||
|
|
||||||
- uses: pnpm/action-setup@v4
|
- uses: pnpm/action-setup@v4
|
||||||
with:
|
with:
|
||||||
version: 10
|
version: 10.33.2
|
||||||
|
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
@@ -44,7 +44,7 @@ jobs:
|
|||||||
|
|
||||||
- name: E2E (core)
|
- name: E2E (core)
|
||||||
working-directory: apps/with-lingui
|
working-directory: apps/with-lingui
|
||||||
run: pnpm run test:e2e:core
|
run: pnpm run test:e2e
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
working-directory: apps/with-lingui
|
working-directory: apps/with-lingui
|
||||||
|
|||||||
@@ -5,10 +5,12 @@
|
|||||||
Antd Admin provides production-style admin templates with modern frontend tooling, mock-first development, and practical testing coverage.
|
Antd Admin provides production-style admin templates with modern frontend tooling, mock-first development, and practical testing coverage.
|
||||||
|
|
||||||
Available templates:
|
Available templates:
|
||||||
|
|
||||||
- `basic`: English-only setup
|
- `basic`: English-only setup
|
||||||
- `with-lingui`: Bilingual setup (`en` + `zh`) with Lingui
|
- `with-lingui`: Bilingual setup (`en` + `zh`) with Lingui
|
||||||
|
|
||||||
Scaffolding CLI:
|
Scaffolding CLI:
|
||||||
|
|
||||||
- `init-antd-admin` (from `packages/create`)
|
- `init-antd-admin` (from `packages/create`)
|
||||||
|
|
||||||
## Key Features
|
## Key Features
|
||||||
@@ -45,17 +47,19 @@ pnpm dlx init-antd-admin@latest my-app --example with-lingui --skip-install
|
|||||||
|
|
||||||
### Common options
|
### Common options
|
||||||
|
|
||||||
| Option | Description |
|
|
||||||
| --- | --- |
|
|
||||||
| `[project-directory]` | Target folder |
|
|
||||||
| `-e, --example <name|url>` | Template source |
|
|
||||||
| `--example-path <path>` | Subpath inside remote repository |
|
|
||||||
| `-m, --package-manager <pm>` | `npm` / `pnpm` / `yarn` / `bun` |
|
|
||||||
| `--skip-install` | Skip dependency install |
|
|
||||||
| `--skip-transforms` | Skip rewrite transforms |
|
|
||||||
| `--no-git` | Skip git initialization |
|
|
||||||
|
|
||||||
For detailed CLI behavior, see [`packages/create/README.md`](./packages/create/README.md).
|
| Option | Description |
|
||||||
|
| ---------------------------- | -------------------------------- |
|
||||||
|
| `[project-directory]` | Target folder |
|
||||||
|
| `-e, --example <name | url>` |
|
||||||
|
| `--example-path <path>` | Subpath inside remote repository |
|
||||||
|
| `-m, --package-manager <pm>` | `npm` / `pnpm` / `yarn` / `bun` |
|
||||||
|
| `--skip-install` | Skip dependency install |
|
||||||
|
| `--skip-transforms` | Skip rewrite transforms |
|
||||||
|
| `--no-git` | Skip git initialization |
|
||||||
|
|
||||||
|
|
||||||
|
For detailed CLI behavior, see `[packages/create/README.md](./packages/create/README.md)`.
|
||||||
|
|
||||||
Production docs: [https://antd-admin-doc.zuiidea.top](https://antd-admin-doc.zuiidea.top)
|
Production docs: [https://antd-admin-doc.zuiidea.top](https://antd-admin-doc.zuiidea.top)
|
||||||
|
|
||||||
|
|||||||
+68
-54
@@ -56,12 +56,9 @@ English-only admin scaffold (no Lingui): MSW mocks, minimal RBAC, full CRUD, and
|
|||||||
|
|
||||||
### Install & Run
|
### Install & Run
|
||||||
|
|
||||||
From the monorepo root, or after `cd apps/basic`:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd apps/basic
|
pnpm install
|
||||||
vp install
|
pnpm run dev
|
||||||
vp dev
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Open [http://localhost:5173](http://localhost:5173) — you'll be redirected to the login page.
|
Open [http://localhost:5173](http://localhost:5173) — you'll be redirected to the login page.
|
||||||
@@ -71,75 +68,92 @@ Open [http://localhost:5173](http://localhost:5173) — you'll be redirected to
|
|||||||
### Build
|
### Build
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
vp build
|
pnpm run build
|
||||||
vp preview
|
pnpm run preview
|
||||||
|
```
|
||||||
|
|
||||||
|
### Project Setup
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm run prepare
|
||||||
|
```
|
||||||
|
|
||||||
|
### Code Quality
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm run fmt
|
||||||
|
pnpm run lint
|
||||||
|
pnpm run check
|
||||||
|
```
|
||||||
|
|
||||||
|
### Unit Tests
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm run test:unit
|
||||||
```
|
```
|
||||||
|
|
||||||
### E2E Tests
|
### E2E Tests
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd apps/basic
|
|
||||||
pnpm run test:e2e
|
pnpm run test:e2e
|
||||||
pnpm run test:e2e:core
|
pnpm run test:e2e:core
|
||||||
pnpm run test:e2e:ui # interactive UI mode
|
pnpm run test:e2e:ui # interactive UI mode
|
||||||
```
|
```
|
||||||
|
|
||||||
`test:e2e:core` runs the scaffold's highest-value flows only: login and users CRUD.
|
|
||||||
|
|
||||||
## Project Structure
|
## Project Structure
|
||||||
|
|
||||||
```
|
```text
|
||||||
src/
|
src/
|
||||||
├── api/ # Zod schemas, endpoint constants, type exports
|
├── api/ # Zod models + endpoint contracts
|
||||||
│ ├── schemas.ts # Domain models (User, AuthTokens, MenuItem, etc.)
|
├── components/ # Reusable UI primitives and layout shells
|
||||||
│ ├── auth.ts # Auth endpoint constants
|
│ ├── Layout/ # MainLayout, Header, Sidebar, UserMenu, AppFooter
|
||||||
│ └── user.ts # User CRUD endpoint constants
|
│ ├── DataTable/ # Shared table frame/skeleton/empty state
|
||||||
├── components/
|
│ ├── FormModal/ # Generic modal form wrapper
|
||||||
│ ├── Aurora/ # Login background effect
|
│ ├── FilterToolbar/ # Reusable list page toolbar
|
||||||
│ ├── Auth/ # Auth (permission gate): index.tsx
|
│ ├── Auth/ # Permission gate component
|
||||||
│ ├── DataTable/ # Shared table shell, skeleton, empty state
|
│ ├── Aurora/ # Login page background effect
|
||||||
│ ├── FilterToolbar/ # Shared filter/action toolbar
|
│ ├── Icon/ # Shared icons (GitHub, Theme, etc.)
|
||||||
│ ├── FormModal/ # Reusable modal + form shell
|
│ ├── NotFound/ # Not-found visual component
|
||||||
│ ├── Icon/ # Shared icons and theme toggle icon
|
│ └── RouteError.tsx # Route-level error boundary UI
|
||||||
│ └── Layout/ # Admin shell (sidebar, header, main)
|
├── hooks/ # Shared hooks (theme, CRUD, URL state, permissions)
|
||||||
│ ├── MainLayout/ # Main layout shell: index.tsx
|
│ ├── useAppTheme.ts
|
||||||
│ ├── AppFooter/ # Login footer: Powered by + GitHub → zuiidea/antd-admin
|
│ ├── usePermission.ts
|
||||||
│ ├── Sidebar/ # Dynamic menu sidebar: index.tsx
|
│ ├── useResourceCRUD.ts
|
||||||
│ ├── UserMenu/ # User dropdown in sidebar: index.tsx + index.css
|
│ ├── useCrudToasts.ts
|
||||||
│ └── Header/ # Top bar: index.tsx
|
│ ├── useUrlSearchState.ts
|
||||||
├── hooks/
|
│ ├── useTableFitHeight.ts
|
||||||
│ ├── tokenBuilders.ts # Shared Ant Design token/config builders
|
│ └── tokenBuilders.ts
|
||||||
│ ├── useAppTheme.ts # Theme selection hook (ConfigProvider)
|
├── mocks/ # MSW bootstrap, handlers, seed data, test helpers
|
||||||
│ ├── usePermission.ts # Permission check hook
|
│ ├── browser.ts
|
||||||
│ └── useResourceCRUD.ts # Shared CRUD query/mutation wiring
|
│ ├── createHandler.ts
|
||||||
├── utils/
|
│ ├── createHandler.test.ts
|
||||||
│ ├── constants.ts # API base URL, static assets
|
│ ├── data.ts
|
||||||
│ └── http.ts # HTTP client with JWT injection & error handling
|
│ ├── utils.ts
|
||||||
├── mocks/
|
│ └── handlers/
|
||||||
│ ├── browser.ts # MSW worker setup
|
├── routes/ # TanStack Router file routes
|
||||||
│ ├── createHandler.ts # Shared MSW success/error/delay helpers
|
│ ├── __root.tsx # QueryClient + ConfigProvider (en_US) wiring
|
||||||
│ ├── data.ts # Mock seed data (users, menus)
|
│ ├── _auth.tsx # Protected route layout
|
||||||
│ ├── utils.ts # Mock-only helpers (filters, pagination, demo avatar URLs)
|
|
||||||
│ └── handlers/ # Request handlers (auth, user CRUD)
|
|
||||||
├── routes/ # TanStack Router file-based routes
|
|
||||||
│ ├── __root.tsx # Root layout (QueryClient, ConfigProvider, en_US)
|
|
||||||
│ ├── _auth.tsx # Auth guard layout (redirects to /login)
|
|
||||||
│ ├── _auth/dashboard/index.tsx
|
│ ├── _auth/dashboard/index.tsx
|
||||||
│ ├── _auth/users/index.tsx # Full CRUD with URL-synced search params
|
│ ├── _auth/users/index.tsx
|
||||||
|
│ ├── _auth/users/-Toolbar.tsx
|
||||||
|
│ ├── _auth/users/-FormModal.tsx
|
||||||
│ ├── _auth/403/index.tsx
|
│ ├── _auth/403/index.tsx
|
||||||
│ ├── login/index.tsx
|
│ ├── login/index.tsx
|
||||||
|
│ ├── register/index.tsx
|
||||||
│ ├── 404/index.tsx
|
│ ├── 404/index.tsx
|
||||||
│ └── index.tsx # Redirects / → /login
|
│ └── index.tsx
|
||||||
├── stores/
|
├── stores/ # Persisted auth/settings stores
|
||||||
│ ├── auth.ts # Auth store (tokens, user, menus, permissions)
|
├── utils/ # HTTP client, constants, session/menu helpers
|
||||||
│ ├── createPersistentStore.ts # Shared persisted-store factory
|
├── main.tsx # App bootstrap (MSW init + React render)
|
||||||
│ └── settings.ts # Settings store (darkMode, sidebar)
|
└── routeTree.gen.ts # Generated TanStack route tree
|
||||||
└── main.tsx # Entry point (MSW init → React render)
|
|
||||||
|
|
||||||
e2e/ # Playwright E2E tests
|
e2e/ # Playwright E2E suites
|
||||||
├── helpers.ts
|
├── helpers.ts
|
||||||
├── login.spec.ts
|
├── login.spec.ts
|
||||||
└── users.spec.ts
|
├── users.spec.ts
|
||||||
|
├── auth-refresh.spec.ts
|
||||||
|
├── rbac.spec.ts
|
||||||
|
└── url-state.spec.ts
|
||||||
```
|
```
|
||||||
|
|
||||||
## Language & i18n
|
## Language & i18n
|
||||||
|
|||||||
@@ -13,7 +13,6 @@
|
|||||||
"check": "vp check --no-fmt",
|
"check": "vp check --no-fmt",
|
||||||
"test:unit": "vitest run",
|
"test:unit": "vitest run",
|
||||||
"test:e2e": "playwright test",
|
"test:e2e": "playwright test",
|
||||||
"test:e2e:core": "playwright test e2e/login.spec.ts e2e/users.spec.ts e2e/auth-refresh.spec.ts e2e/rbac.spec.ts e2e/url-state.spec.ts",
|
|
||||||
"test:e2e:ui": "playwright test --ui"
|
"test:e2e:ui": "playwright test --ui"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -39,16 +38,6 @@
|
|||||||
"vitest": "3.0.5"
|
"vitest": "3.0.5"
|
||||||
},
|
},
|
||||||
"packageManager": "pnpm@10.32.1",
|
"packageManager": "pnpm@10.32.1",
|
||||||
"pnpm": {
|
|
||||||
"overrides": {
|
|
||||||
"vite": "npm:@voidzero-dev/vite-plus-core@latest",
|
|
||||||
"vitest": "npm:@voidzero-dev/vite-plus-test@latest"
|
|
||||||
},
|
|
||||||
"onlyBuiltDependencies": [
|
|
||||||
"esbuild",
|
|
||||||
"msw"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"msw": {
|
"msw": {
|
||||||
"workerDirectory": [
|
"workerDirectory": [
|
||||||
"public"
|
"public"
|
||||||
|
|||||||
+145
-138
@@ -8,184 +8,191 @@
|
|||||||
// You should NOT make any changes in this file as it will be overwritten.
|
// You should NOT make any changes in this file as it will be overwritten.
|
||||||
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
|
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
|
||||||
|
|
||||||
import { Route as rootRouteImport } from "./routes/__root";
|
import { Route as rootRouteImport } from './routes/__root'
|
||||||
import { Route as AuthRouteImport } from "./routes/_auth";
|
import { Route as AuthRouteImport } from './routes/_auth'
|
||||||
import { Route as IndexRouteImport } from "./routes/index";
|
import { Route as IndexRouteImport } from './routes/index'
|
||||||
import { Route as RegisterIndexRouteImport } from "./routes/register/index";
|
import { Route as RegisterIndexRouteImport } from './routes/register/index'
|
||||||
import { Route as LoginIndexRouteImport } from "./routes/login/index";
|
import { Route as LoginIndexRouteImport } from './routes/login/index'
|
||||||
import { Route as R404IndexRouteImport } from "./routes/404/index";
|
import { Route as R404IndexRouteImport } from './routes/404/index'
|
||||||
import { Route as AuthUsersIndexRouteImport } from "./routes/_auth/users/index";
|
import { Route as AuthUsersIndexRouteImport } from './routes/_auth/users/index'
|
||||||
import { Route as AuthDashboardIndexRouteImport } from "./routes/_auth/dashboard/index";
|
import { Route as AuthDashboardIndexRouteImport } from './routes/_auth/dashboard/index'
|
||||||
import { Route as Auth403IndexRouteImport } from "./routes/_auth/403/index";
|
import { Route as Auth403IndexRouteImport } from './routes/_auth/403/index'
|
||||||
|
|
||||||
const AuthRoute = AuthRouteImport.update({
|
const AuthRoute = AuthRouteImport.update({
|
||||||
id: "/_auth",
|
id: '/_auth',
|
||||||
getParentRoute: () => rootRouteImport,
|
getParentRoute: () => rootRouteImport,
|
||||||
} as any);
|
} as any)
|
||||||
const IndexRoute = IndexRouteImport.update({
|
const IndexRoute = IndexRouteImport.update({
|
||||||
id: "/",
|
id: '/',
|
||||||
path: "/",
|
path: '/',
|
||||||
getParentRoute: () => rootRouteImport,
|
getParentRoute: () => rootRouteImport,
|
||||||
} as any);
|
} as any)
|
||||||
const RegisterIndexRoute = RegisterIndexRouteImport.update({
|
const RegisterIndexRoute = RegisterIndexRouteImport.update({
|
||||||
id: "/register/",
|
id: '/register/',
|
||||||
path: "/register/",
|
path: '/register/',
|
||||||
getParentRoute: () => rootRouteImport,
|
getParentRoute: () => rootRouteImport,
|
||||||
} as any);
|
} as any)
|
||||||
const LoginIndexRoute = LoginIndexRouteImport.update({
|
const LoginIndexRoute = LoginIndexRouteImport.update({
|
||||||
id: "/login/",
|
id: '/login/',
|
||||||
path: "/login/",
|
path: '/login/',
|
||||||
getParentRoute: () => rootRouteImport,
|
getParentRoute: () => rootRouteImport,
|
||||||
} as any);
|
} as any)
|
||||||
const R404IndexRoute = R404IndexRouteImport.update({
|
const R404IndexRoute = R404IndexRouteImport.update({
|
||||||
id: "/404/",
|
id: '/404/',
|
||||||
path: "/404/",
|
path: '/404/',
|
||||||
getParentRoute: () => rootRouteImport,
|
getParentRoute: () => rootRouteImport,
|
||||||
} as any);
|
} as any)
|
||||||
const AuthUsersIndexRoute = AuthUsersIndexRouteImport.update({
|
const AuthUsersIndexRoute = AuthUsersIndexRouteImport.update({
|
||||||
id: "/users/",
|
id: '/users/',
|
||||||
path: "/users/",
|
path: '/users/',
|
||||||
getParentRoute: () => AuthRoute,
|
getParentRoute: () => AuthRoute,
|
||||||
} as any);
|
} as any)
|
||||||
const AuthDashboardIndexRoute = AuthDashboardIndexRouteImport.update({
|
const AuthDashboardIndexRoute = AuthDashboardIndexRouteImport.update({
|
||||||
id: "/dashboard/",
|
id: '/dashboard/',
|
||||||
path: "/dashboard/",
|
path: '/dashboard/',
|
||||||
getParentRoute: () => AuthRoute,
|
getParentRoute: () => AuthRoute,
|
||||||
} as any);
|
} as any)
|
||||||
const Auth403IndexRoute = Auth403IndexRouteImport.update({
|
const Auth403IndexRoute = Auth403IndexRouteImport.update({
|
||||||
id: "/403/",
|
id: '/403/',
|
||||||
path: "/403/",
|
path: '/403/',
|
||||||
getParentRoute: () => AuthRoute,
|
getParentRoute: () => AuthRoute,
|
||||||
} as any);
|
} as any)
|
||||||
|
|
||||||
export interface FileRoutesByFullPath {
|
export interface FileRoutesByFullPath {
|
||||||
"/": typeof IndexRoute;
|
'/': typeof IndexRoute
|
||||||
"/404/": typeof R404IndexRoute;
|
'/404/': typeof R404IndexRoute
|
||||||
"/login/": typeof LoginIndexRoute;
|
'/login/': typeof LoginIndexRoute
|
||||||
"/register/": typeof RegisterIndexRoute;
|
'/register/': typeof RegisterIndexRoute
|
||||||
"/403/": typeof Auth403IndexRoute;
|
'/403/': typeof Auth403IndexRoute
|
||||||
"/dashboard/": typeof AuthDashboardIndexRoute;
|
'/dashboard/': typeof AuthDashboardIndexRoute
|
||||||
"/users/": typeof AuthUsersIndexRoute;
|
'/users/': typeof AuthUsersIndexRoute
|
||||||
}
|
}
|
||||||
export interface FileRoutesByTo {
|
export interface FileRoutesByTo {
|
||||||
"/": typeof IndexRoute;
|
'/': typeof IndexRoute
|
||||||
"/404": typeof R404IndexRoute;
|
'/404': typeof R404IndexRoute
|
||||||
"/login": typeof LoginIndexRoute;
|
'/login': typeof LoginIndexRoute
|
||||||
"/register": typeof RegisterIndexRoute;
|
'/register': typeof RegisterIndexRoute
|
||||||
"/403": typeof Auth403IndexRoute;
|
'/403': typeof Auth403IndexRoute
|
||||||
"/dashboard": typeof AuthDashboardIndexRoute;
|
'/dashboard': typeof AuthDashboardIndexRoute
|
||||||
"/users": typeof AuthUsersIndexRoute;
|
'/users': typeof AuthUsersIndexRoute
|
||||||
}
|
}
|
||||||
export interface FileRoutesById {
|
export interface FileRoutesById {
|
||||||
__root__: typeof rootRouteImport;
|
__root__: typeof rootRouteImport
|
||||||
"/": typeof IndexRoute;
|
'/': typeof IndexRoute
|
||||||
"/_auth": typeof AuthRouteWithChildren;
|
'/_auth': typeof AuthRouteWithChildren
|
||||||
"/404/": typeof R404IndexRoute;
|
'/404/': typeof R404IndexRoute
|
||||||
"/login/": typeof LoginIndexRoute;
|
'/login/': typeof LoginIndexRoute
|
||||||
"/register/": typeof RegisterIndexRoute;
|
'/register/': typeof RegisterIndexRoute
|
||||||
"/_auth/403/": typeof Auth403IndexRoute;
|
'/_auth/403/': typeof Auth403IndexRoute
|
||||||
"/_auth/dashboard/": typeof AuthDashboardIndexRoute;
|
'/_auth/dashboard/': typeof AuthDashboardIndexRoute
|
||||||
"/_auth/users/": typeof AuthUsersIndexRoute;
|
'/_auth/users/': typeof AuthUsersIndexRoute
|
||||||
}
|
}
|
||||||
export interface FileRouteTypes {
|
export interface FileRouteTypes {
|
||||||
fileRoutesByFullPath: FileRoutesByFullPath;
|
fileRoutesByFullPath: FileRoutesByFullPath
|
||||||
fullPaths: "/" | "/404/" | "/login/" | "/register/" | "/403/" | "/dashboard/" | "/users/";
|
fullPaths:
|
||||||
fileRoutesByTo: FileRoutesByTo;
|
| '/'
|
||||||
to: "/" | "/404" | "/login" | "/register" | "/403" | "/dashboard" | "/users";
|
| '/404/'
|
||||||
|
| '/login/'
|
||||||
|
| '/register/'
|
||||||
|
| '/403/'
|
||||||
|
| '/dashboard/'
|
||||||
|
| '/users/'
|
||||||
|
fileRoutesByTo: FileRoutesByTo
|
||||||
|
to: '/' | '/404' | '/login' | '/register' | '/403' | '/dashboard' | '/users'
|
||||||
id:
|
id:
|
||||||
| "__root__"
|
| '__root__'
|
||||||
| "/"
|
| '/'
|
||||||
| "/_auth"
|
| '/_auth'
|
||||||
| "/404/"
|
| '/404/'
|
||||||
| "/login/"
|
| '/login/'
|
||||||
| "/register/"
|
| '/register/'
|
||||||
| "/_auth/403/"
|
| '/_auth/403/'
|
||||||
| "/_auth/dashboard/"
|
| '/_auth/dashboard/'
|
||||||
| "/_auth/users/";
|
| '/_auth/users/'
|
||||||
fileRoutesById: FileRoutesById;
|
fileRoutesById: FileRoutesById
|
||||||
}
|
}
|
||||||
export interface RootRouteChildren {
|
export interface RootRouteChildren {
|
||||||
IndexRoute: typeof IndexRoute;
|
IndexRoute: typeof IndexRoute
|
||||||
AuthRoute: typeof AuthRouteWithChildren;
|
AuthRoute: typeof AuthRouteWithChildren
|
||||||
R404IndexRoute: typeof R404IndexRoute;
|
R404IndexRoute: typeof R404IndexRoute
|
||||||
LoginIndexRoute: typeof LoginIndexRoute;
|
LoginIndexRoute: typeof LoginIndexRoute
|
||||||
RegisterIndexRoute: typeof RegisterIndexRoute;
|
RegisterIndexRoute: typeof RegisterIndexRoute
|
||||||
}
|
}
|
||||||
|
|
||||||
declare module "@tanstack/react-router" {
|
declare module '@tanstack/react-router' {
|
||||||
interface FileRoutesByPath {
|
interface FileRoutesByPath {
|
||||||
"/_auth": {
|
'/_auth': {
|
||||||
id: "/_auth";
|
id: '/_auth'
|
||||||
path: "";
|
path: ''
|
||||||
fullPath: "/";
|
fullPath: '/'
|
||||||
preLoaderRoute: typeof AuthRouteImport;
|
preLoaderRoute: typeof AuthRouteImport
|
||||||
parentRoute: typeof rootRouteImport;
|
parentRoute: typeof rootRouteImport
|
||||||
};
|
}
|
||||||
"/": {
|
'/': {
|
||||||
id: "/";
|
id: '/'
|
||||||
path: "/";
|
path: '/'
|
||||||
fullPath: "/";
|
fullPath: '/'
|
||||||
preLoaderRoute: typeof IndexRouteImport;
|
preLoaderRoute: typeof IndexRouteImport
|
||||||
parentRoute: typeof rootRouteImport;
|
parentRoute: typeof rootRouteImport
|
||||||
};
|
}
|
||||||
"/register/": {
|
'/register/': {
|
||||||
id: "/register/";
|
id: '/register/'
|
||||||
path: "/register";
|
path: '/register'
|
||||||
fullPath: "/register/";
|
fullPath: '/register/'
|
||||||
preLoaderRoute: typeof RegisterIndexRouteImport;
|
preLoaderRoute: typeof RegisterIndexRouteImport
|
||||||
parentRoute: typeof rootRouteImport;
|
parentRoute: typeof rootRouteImport
|
||||||
};
|
}
|
||||||
"/login/": {
|
'/login/': {
|
||||||
id: "/login/";
|
id: '/login/'
|
||||||
path: "/login";
|
path: '/login'
|
||||||
fullPath: "/login/";
|
fullPath: '/login/'
|
||||||
preLoaderRoute: typeof LoginIndexRouteImport;
|
preLoaderRoute: typeof LoginIndexRouteImport
|
||||||
parentRoute: typeof rootRouteImport;
|
parentRoute: typeof rootRouteImport
|
||||||
};
|
}
|
||||||
"/404/": {
|
'/404/': {
|
||||||
id: "/404/";
|
id: '/404/'
|
||||||
path: "/404";
|
path: '/404'
|
||||||
fullPath: "/404/";
|
fullPath: '/404/'
|
||||||
preLoaderRoute: typeof R404IndexRouteImport;
|
preLoaderRoute: typeof R404IndexRouteImport
|
||||||
parentRoute: typeof rootRouteImport;
|
parentRoute: typeof rootRouteImport
|
||||||
};
|
}
|
||||||
"/_auth/users/": {
|
'/_auth/users/': {
|
||||||
id: "/_auth/users/";
|
id: '/_auth/users/'
|
||||||
path: "/users";
|
path: '/users'
|
||||||
fullPath: "/users/";
|
fullPath: '/users/'
|
||||||
preLoaderRoute: typeof AuthUsersIndexRouteImport;
|
preLoaderRoute: typeof AuthUsersIndexRouteImport
|
||||||
parentRoute: typeof AuthRoute;
|
parentRoute: typeof AuthRoute
|
||||||
};
|
}
|
||||||
"/_auth/dashboard/": {
|
'/_auth/dashboard/': {
|
||||||
id: "/_auth/dashboard/";
|
id: '/_auth/dashboard/'
|
||||||
path: "/dashboard";
|
path: '/dashboard'
|
||||||
fullPath: "/dashboard/";
|
fullPath: '/dashboard/'
|
||||||
preLoaderRoute: typeof AuthDashboardIndexRouteImport;
|
preLoaderRoute: typeof AuthDashboardIndexRouteImport
|
||||||
parentRoute: typeof AuthRoute;
|
parentRoute: typeof AuthRoute
|
||||||
};
|
}
|
||||||
"/_auth/403/": {
|
'/_auth/403/': {
|
||||||
id: "/_auth/403/";
|
id: '/_auth/403/'
|
||||||
path: "/403";
|
path: '/403'
|
||||||
fullPath: "/403/";
|
fullPath: '/403/'
|
||||||
preLoaderRoute: typeof Auth403IndexRouteImport;
|
preLoaderRoute: typeof Auth403IndexRouteImport
|
||||||
parentRoute: typeof AuthRoute;
|
parentRoute: typeof AuthRoute
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface AuthRouteChildren {
|
interface AuthRouteChildren {
|
||||||
Auth403IndexRoute: typeof Auth403IndexRoute;
|
Auth403IndexRoute: typeof Auth403IndexRoute
|
||||||
AuthDashboardIndexRoute: typeof AuthDashboardIndexRoute;
|
AuthDashboardIndexRoute: typeof AuthDashboardIndexRoute
|
||||||
AuthUsersIndexRoute: typeof AuthUsersIndexRoute;
|
AuthUsersIndexRoute: typeof AuthUsersIndexRoute
|
||||||
}
|
}
|
||||||
|
|
||||||
const AuthRouteChildren: AuthRouteChildren = {
|
const AuthRouteChildren: AuthRouteChildren = {
|
||||||
Auth403IndexRoute: Auth403IndexRoute,
|
Auth403IndexRoute: Auth403IndexRoute,
|
||||||
AuthDashboardIndexRoute: AuthDashboardIndexRoute,
|
AuthDashboardIndexRoute: AuthDashboardIndexRoute,
|
||||||
AuthUsersIndexRoute: AuthUsersIndexRoute,
|
AuthUsersIndexRoute: AuthUsersIndexRoute,
|
||||||
};
|
}
|
||||||
|
|
||||||
const AuthRouteWithChildren = AuthRoute._addFileChildren(AuthRouteChildren);
|
const AuthRouteWithChildren = AuthRoute._addFileChildren(AuthRouteChildren)
|
||||||
|
|
||||||
const rootRouteChildren: RootRouteChildren = {
|
const rootRouteChildren: RootRouteChildren = {
|
||||||
IndexRoute: IndexRoute,
|
IndexRoute: IndexRoute,
|
||||||
@@ -193,7 +200,7 @@ const rootRouteChildren: RootRouteChildren = {
|
|||||||
R404IndexRoute: R404IndexRoute,
|
R404IndexRoute: R404IndexRoute,
|
||||||
LoginIndexRoute: LoginIndexRoute,
|
LoginIndexRoute: LoginIndexRoute,
|
||||||
RegisterIndexRoute: RegisterIndexRoute,
|
RegisterIndexRoute: RegisterIndexRoute,
|
||||||
};
|
}
|
||||||
export const routeTree = rootRouteImport
|
export const routeTree = rootRouteImport
|
||||||
._addFileChildren(rootRouteChildren)
|
._addFileChildren(rootRouteChildren)
|
||||||
._addFileTypes<FileRouteTypes>();
|
._addFileTypes<FileRouteTypes>()
|
||||||
|
|||||||
+86
-54
@@ -49,8 +49,8 @@ Balanced admin scaffold: i18n (Lingui), MSW mocks, minimal RBAC, full CRUD, and
|
|||||||
### Install & Run
|
### Install & Run
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
vp install
|
pnpm install
|
||||||
vp dev
|
pnpm run dev
|
||||||
```
|
```
|
||||||
|
|
||||||
Open [http://localhost:5173](http://localhost:5173) — you'll be redirected to the login page.
|
Open [http://localhost:5173](http://localhost:5173) — you'll be redirected to the login page.
|
||||||
@@ -60,78 +60,110 @@ Open [http://localhost:5173](http://localhost:5173) — you'll be redirected to
|
|||||||
### Build
|
### Build
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
vp build
|
pnpm run build
|
||||||
vp preview
|
pnpm run preview
|
||||||
|
```
|
||||||
|
|
||||||
|
### Project Setup
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm run prepare
|
||||||
|
```
|
||||||
|
|
||||||
|
### Code Quality
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm run fmt
|
||||||
|
pnpm run lint
|
||||||
|
pnpm run check
|
||||||
|
```
|
||||||
|
|
||||||
|
### Unit Tests
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm run test:unit
|
||||||
```
|
```
|
||||||
|
|
||||||
### E2E Tests
|
### E2E Tests
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
vp run test:e2e
|
pnpm run test:e2e
|
||||||
vp run test:e2e:core
|
pnpm run test:e2e:core
|
||||||
vp run test:e2e:ui # interactive UI mode
|
pnpm run test:e2e:ui # interactive UI mode
|
||||||
```
|
```
|
||||||
|
|
||||||
`test:e2e:core` runs the scaffold's highest-value flows: login, users CRUD, auth refresh, RBAC, URL state (see `package.json` script list).
|
### Lingui Scripts
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# extract messages from source to .po catalogs
|
||||||
|
vp exec lingui extract -- --clean
|
||||||
|
|
||||||
|
# compile catalogs to runtime messages
|
||||||
|
vp exec lingui compile
|
||||||
|
|
||||||
|
# CI-oriented check: extract + ensure no pending locale diff
|
||||||
|
pnpm run i18n:extract && git diff --exit-code -- src/locales
|
||||||
|
```
|
||||||
|
|
||||||
## Project Structure
|
## Project Structure
|
||||||
|
|
||||||
```
|
```
|
||||||
.github/instructions/ # Scoped AI recipes (mirrored in ../../.cursor/rules/with-lingui-*.mdc)
|
.github/instructions/ # Scoped AI guidance (frontend/testing/api/refactor/add-resource)
|
||||||
src/
|
src/
|
||||||
├── api/ # Zod schemas, endpoint constants, type exports
|
├── api/ # Zod models + endpoint contracts
|
||||||
│ ├── schemas.ts # Domain models (User, AuthTokens, MenuItem, etc.)
|
├── components/ # Reusable UI primitives and layout shells
|
||||||
│ ├── auth.ts # Auth endpoint constants
|
│ ├── Layout/ # MainLayout, Header, Sidebar, UserMenu, AppFooter
|
||||||
│ └── user.ts # User CRUD endpoint constants
|
│ ├── DataTable/ # Shared table frame/skeleton/empty state
|
||||||
├── components/
|
│ ├── FormModal/ # Generic modal form wrapper
|
||||||
│ ├── Aurora/ # Login background effect
|
│ ├── FilterToolbar/ # Reusable list page toolbar
|
||||||
│ ├── Auth/ # Auth (permission gate): index.tsx
|
│ ├── Auth/ # Permission gate component
|
||||||
│ ├── DataTable/ # Shared table shell, skeleton, empty state
|
│ ├── Aurora/ # Login page background effect
|
||||||
│ ├── FilterToolbar/ # Shared filter/action toolbar
|
│ ├── Icon/ # Shared icons (GitHub, Theme, etc.)
|
||||||
│ ├── FormModal/ # Reusable modal + form shell
|
│ ├── NotFound/ # Not-found visual component
|
||||||
│ ├── Icon/ # Shared icons and theme toggle icon
|
│ └── RouteError.tsx # Route-level error boundary UI
|
||||||
│ └── Layout/ # Admin shell (sidebar, header, main)
|
├── hooks/ # Shared hooks (theme, CRUD, URL state, permissions)
|
||||||
│ ├── MainLayout/ # Main layout shell: index.tsx
|
│ ├── useAppTheme.ts
|
||||||
│ ├── AppFooter/ # Login footer: Powered by + GitHub → zuiidea/antd-admin
|
│ ├── usePermission.ts
|
||||||
│ ├── Sidebar/ # Dynamic menu sidebar: index.tsx
|
│ ├── useResourceCRUD.ts
|
||||||
│ ├── UserMenu/ # User dropdown in sidebar: index.tsx + index.css
|
│ ├── useCrudToasts.ts
|
||||||
│ └── Header/ # Top bar: index.tsx
|
│ ├── useUrlSearchState.ts
|
||||||
├── hooks/
|
│ ├── useTableFitHeight.ts
|
||||||
│ ├── tokenBuilders.ts # Shared Ant Design token/config builders
|
│ └── tokenBuilders.ts
|
||||||
│ ├── useAppTheme.ts # Theme selection hook (ConfigProvider)
|
├── locales/ # Lingui catalogs + locale loader
|
||||||
│ ├── usePermission.ts # Permission check hook
|
|
||||||
│ └── useResourceCRUD.ts # Shared CRUD query/mutation wiring
|
|
||||||
├── utils/
|
|
||||||
│ ├── constants.ts # API base URL, static assets
|
|
||||||
│ └── http.ts # HTTP client with JWT injection & error handling
|
|
||||||
├── locales/ # LinguiJS catalogs (.po + compiled .js)
|
|
||||||
│ ├── en/messages.po
|
│ ├── en/messages.po
|
||||||
│ └── zh/messages.po
|
│ ├── zh/messages.po
|
||||||
├── mocks/
|
│ └── loadLocaleCatalog.ts
|
||||||
│ ├── browser.ts # MSW worker setup
|
├── mocks/ # MSW bootstrap, handlers, seed data, test helpers
|
||||||
│ ├── createHandler.ts # Shared MSW success/error/delay helpers
|
│ ├── browser.ts
|
||||||
│ ├── data.ts # Mock seed data (users, menus)
|
│ ├── createHandler.ts
|
||||||
│ ├── utils.ts # Mock-only helpers (filters, pagination, demo avatar URLs)
|
│ ├── createHandler.test.ts
|
||||||
│ └── handlers/ # Request handlers (auth, users, …)
|
│ ├── data.ts
|
||||||
├── routes/ # TanStack Router file-based routes
|
│ ├── utils.ts
|
||||||
│ ├── __root.tsx # Root layout (QueryClient, ConfigProvider, I18n)
|
│ └── handlers/
|
||||||
│ ├── _auth.tsx # Auth guard layout (redirects to /login)
|
├── routes/ # TanStack Router file routes
|
||||||
|
│ ├── __root.tsx # QueryClient + ConfigProvider + I18n wiring
|
||||||
|
│ ├── _auth.tsx # Protected route layout
|
||||||
│ ├── _auth/dashboard/index.tsx
|
│ ├── _auth/dashboard/index.tsx
|
||||||
│ ├── _auth/users/index.tsx # Full CRUD with URL-synced search params
|
│ ├── _auth/users/index.tsx
|
||||||
|
│ ├── _auth/users/-Toolbar.tsx
|
||||||
|
│ ├── _auth/users/-FormModal.tsx
|
||||||
│ ├── _auth/403/index.tsx
|
│ ├── _auth/403/index.tsx
|
||||||
│ ├── login/index.tsx
|
│ ├── login/index.tsx
|
||||||
|
│ ├── register/index.tsx
|
||||||
│ ├── 404/index.tsx
|
│ ├── 404/index.tsx
|
||||||
│ └── index.tsx # Redirects / → /login
|
│ └── index.tsx
|
||||||
├── stores/
|
├── stores/ # Persisted auth/settings stores
|
||||||
│ ├── auth.ts # Auth store (tokens, user, menus, permissions)
|
├── utils/ # HTTP client, constants, session/menu helpers
|
||||||
│ ├── createPersistentStore.ts # Shared persisted-store factory
|
├── main.tsx # App bootstrap (MSW init + React render)
|
||||||
│ └── settings.ts # Settings store (darkMode, locale, sidebar)
|
└── routeTree.gen.ts # Generated TanStack route tree
|
||||||
└── main.tsx # Entry point (MSW init → React render)
|
|
||||||
|
|
||||||
e2e/ # Playwright E2E tests
|
e2e/ # Playwright E2E suites
|
||||||
├── helpers.ts
|
├── helpers.ts
|
||||||
├── login.spec.ts
|
├── login.spec.ts
|
||||||
└── users.spec.ts
|
├── users.spec.ts
|
||||||
|
├── auth-refresh.spec.ts
|
||||||
|
├── rbac.spec.ts
|
||||||
|
└── url-state.spec.ts
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,6 @@
|
|||||||
"check": "vp check --no-fmt",
|
"check": "vp check --no-fmt",
|
||||||
"test:unit": "vitest run",
|
"test:unit": "vitest run",
|
||||||
"test:e2e": "playwright test",
|
"test:e2e": "playwright test",
|
||||||
"test:e2e:core": "playwright test e2e/login.spec.ts e2e/users.spec.ts e2e/auth-refresh.spec.ts e2e/rbac.spec.ts e2e/url-state.spec.ts",
|
|
||||||
"test:e2e:ui": "playwright test --ui",
|
"test:e2e:ui": "playwright test --ui",
|
||||||
"i18n:extract": "vp exec lingui extract -- --clean",
|
"i18n:extract": "vp exec lingui extract -- --clean",
|
||||||
"i18n:compile": "vp exec lingui compile",
|
"i18n:compile": "vp exec lingui compile",
|
||||||
@@ -48,16 +47,6 @@
|
|||||||
"vitest": "4.1.5"
|
"vitest": "4.1.5"
|
||||||
},
|
},
|
||||||
"packageManager": "pnpm@10.32.1",
|
"packageManager": "pnpm@10.32.1",
|
||||||
"pnpm": {
|
|
||||||
"overrides": {
|
|
||||||
"vite": "npm:@voidzero-dev/vite-plus-core@latest",
|
|
||||||
"vitest": "npm:@voidzero-dev/vite-plus-test@latest"
|
|
||||||
},
|
|
||||||
"onlyBuiltDependencies": [
|
|
||||||
"esbuild",
|
|
||||||
"msw"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"msw": {
|
"msw": {
|
||||||
"workerDirectory": [
|
"workerDirectory": [
|
||||||
"public"
|
"public"
|
||||||
|
|||||||
+27
-17
@@ -1,19 +1,29 @@
|
|||||||
{
|
{
|
||||||
"name": "antd-admin",
|
"name": "antd-admin",
|
||||||
"version": "6.0.0",
|
"version": "6.0.0",
|
||||||
"description": "AI-friendly enterprise front-end best practices",
|
"description": "AI-friendly enterprise front-end best practices",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "turbo run build",
|
"build": "turbo run build",
|
||||||
"dev": "turbo run dev",
|
"dev": "turbo run dev",
|
||||||
"lint": "turbo run lint",
|
"lint": "turbo run lint",
|
||||||
"format": "prettier --write \"**/*.{ts,tsx,md}\"",
|
"format": "prettier --write \"**/*.{ts,tsx,md}\"",
|
||||||
"setup:git-hooks": "git config core.hooksPath .githooks && git config core.hooksPath"
|
"setup:git-hooks": "git config core.hooksPath .githooks && git config core.hooksPath"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"eslint": "^8.57.0",
|
||||||
|
"prettier": "^3.2.5",
|
||||||
|
"turbo": "^2.9.6"
|
||||||
|
},
|
||||||
|
"packageManager": "pnpm@10.33.2",
|
||||||
|
"pnpm": {
|
||||||
|
"overrides": {
|
||||||
|
"vite": "npm:@voidzero-dev/vite-plus-core@latest",
|
||||||
|
"vitest": "npm:@voidzero-dev/vite-plus-test@latest"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"onlyBuiltDependencies": [
|
||||||
"eslint": "^8.57.0",
|
"esbuild",
|
||||||
"prettier": "^3.2.5",
|
"msw"
|
||||||
"turbo": "^2.9.6"
|
]
|
||||||
},
|
}
|
||||||
"packageManager": "pnpm@8.15.6"
|
}
|
||||||
}
|
|
||||||
|
|||||||
@@ -26,15 +26,15 @@
|
|||||||
"node": ">=20"
|
"node": ">=20"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@inquirer/prompts": "^7.10.1",
|
"@inquirer/prompts": "^8.4.2",
|
||||||
"commander": "^14.0.0",
|
"commander": "^14.0.0",
|
||||||
"giget": "^3.2.0",
|
"giget": "^3.2.0",
|
||||||
"picocolors": "^1.0.1"
|
"picocolors": "^1.0.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^22.10.0",
|
"@types/node": "^25.6.0",
|
||||||
"tsup": "^8.3.5",
|
"tsup": "^8.3.5",
|
||||||
"typescript": "~5.9.3",
|
"typescript": "~6.0.3",
|
||||||
"vitest": "^3.0.5"
|
"vitest": "^3.0.5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Generated
+8051
-7669
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user