From 612a567f2fe91b0dfa708159cbf0c414383e0bf7 Mon Sep 17 00:00:00 2001 From: alex_q <914269@qq.com> Date: Fri, 12 Jun 2026 13:54:20 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0AGENTS.md=EF=BC=8C=E9=87=8D?= =?UTF-8?q?=E5=86=99README.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AGENTS.md | 246 ++++++++++++++++++++++++++++++++++ README.md | 394 ++++++++++++++++++++++++++++++++---------------------- 2 files changed, 477 insertions(+), 163 deletions(-) create mode 100644 AGENTS.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..02639f0 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,246 @@ +# AGENTS.md — 唱作网 (ichangzuo) + +> This file tells AI agents what they would likely get wrong without help. +> Every line answers: "Would an agent miss this on first contact?" If the answer is no, it doesn't belong here. + +## Project Identity + +- **Name**: `ichangzuo` (唱作网) — a fork of vue3-element-admin, heavily customized +- **Type**: Vue 3 admin panel / SPA (single-page app, no SSR/SSG) +- **Package manager**: **pnpm only** — `preinstall` script enforces this (`npx only-allow pnpm`); npm/yarn will fail + +## Tech Stack (versions from package.json, not README badges) + +| Layer | Package | Version | +|---|---|---| +| Framework | vue | ^3.5.13 | +| Build | vite | ^6.0.2 | +| Language | typescript | ^5.7.2 | +| UI | element-plus | ^2.9.0 | +| State | pinia | ^2.2.8 | +| Routing | vue-router | ^4.5.0 | +| i18n | vue-i18n | 10.0.5 | +| CSS | UnoCSS + SCSS | ^0.65.0 / sass ^1.82.0 | +| HTTP | axios | ^1.7.8 | +| Node | >=18 (20.6.0 broken) | — | + +## Commands + +| Purpose | Command | Notes | +|---|---|---| +| Dev server | `pnpm dev` | Reads `VITE_APP_PORT` from `.env.development` (default 3000) | +| Type check | `pnpm type-check` | `vue-tsc --noEmit` — **use this before committing** | +| Build (prod) | `pnpm build` | Runs `vue-tsc --noEmit & vite build` in parallel (Windows `&` = background) | +| Build only | `pnpm build-only` | `vite build` without type check | +| Lint JS/TS/Vue | `pnpm lint:eslint` | `eslint --fix` | +| Format | `pnpm lint:prettier` | `prettier --write` | +| Lint CSS/Vue | `pnpm lint:stylelint` | `stylelint --fix` | +| Commit | `pnpm commit` | Interactive cz-git; do NOT `git commit` directly | + +**No test runner is configured.** There is no `test` script, no test framework, and no test files in this repo. + +## Path Alias + +- **`@` → `src/`** — configured in both `vite.config.ts` (`resolve.alias`) and `tsconfig.json` (`paths`). +- Import as `@/api/auth`, `@/store/modules/user`, etc. + +## Environment Variables + +Defined in `src/types/env.d.ts` and `.env.*` files: + +| Variable | Dev Default | Prod | Purpose | +|---|---|---|---| +| `VITE_APP_PORT` | `3000` | — | Dev server port | +| `VITE_APP_BASE_API` | `/dev-api` | `/prod-api` | Proxy prefix — Vite rewrites requests under this prefix to `VITE_APP_API_URL` | +| `VITE_APP_API_URL` | `http://localhost:8989` | — | Backend API target | +| `VITE_MOCK_DEV_SERVER` | `false` | — | Enable mock server (currently disabled in code) | + +**Agent trap**: The proxy prefix is NOT a real URL path on the backend. The Vite dev server strips it before forwarding. So a request to `/dev-api/api/v1/auth/login` is proxied to `http://localhost:8989/api/v1/auth/login`. + +## Architecture & Wiring + +### App Bootstrap Order (src/plugins/index.ts) + +``` +main.ts → createApp(App) → app.use(setupPlugins) → mount("#app") + setupPlugins.install(app): + 1. setupDirective(app) — custom directives (e.g., v-permission) + 2. setupRouter(app) — vue-router + 3. setupStore(app) — Pinia + 4. setupI18n(app) — vue-i18n + 5. setupElIcons(app) — Element Plus icons + 6. setupPermission() — route guards (NProgress + auth check) +``` + +### Route System + +- **Static routes** defined in `src/router/index.ts` (`constantRoutes`) — includes `/software`, `/helper`, `/dashboard`, `/autoLogin`, `/resetPassword`, error pages (401, 404) +- **Dynamic routes** injected at runtime by `permissionStore.generateRoutes()` after login — fetched from backend API +- **Default redirect**: `/` → `/software` +- **History mode**: `createWebHistory()` (HTML5 history, NOT hash mode) + +### Permission / Auth Flow + +1. Route guard (`src/plugins/permission.ts`) runs `beforeEach` +2. Checks `localStorage.getItem("accessToken")` for token +3. **White-listed paths** (no auth required): `/software`, `/helper`, `/autoLogin`, `/resetPassword` +4. If token exists but no roles loaded → calls `userStore.getUserInfo()` → `permissionStore.generateRoutes()` → `router.addRoute()` +5. Token invalid → `A0230` code from backend → `resetToken()` + redirect to `/software` +6. **Button-level auth** via `hasAuth()` — checks `perms` array; `ROOT` role bypasses all button checks +7. **Directives**: `v-permission` in `src/directive/` for template-level auth + +### API Layer Pattern + +- All API modules live in `src/api/` — one file per domain (auth, user, role, menu, dict, etc.) +- Each exports a **static class** with methods that call `request()` from `src/utils/request.ts` +- The `request.ts` axios instance: + - `baseURL` = `import.meta.env.VITE_APP_BASE_API` (the proxy prefix) + - Request interceptor: attaches `Authorization` header from `localStorage.accessToken` + - Response interceptor: unwraps `{ code, data, msg }` — returns `data` on success (`00000`), rejects otherwise + - Token invalid (`A0230`): auto-redirect to login +- **Request/response types exported alongside the class** in the same file (e.g., `AuthAPI`, `LoginData`, `LoginResult` in `src/api/auth.ts`) + +### State Management (Pinia) + +- Store modules in `src/store/modules/`: `app`, `permission`, `settings`, `tagsView`, `user` +- **Composition API style** — uses `defineStore("name", () => { ... })` +- **Outside-component usage**: `useUserStoreHook()`, `useAppStoreHook()` etc. — these wrap `useXxxStore(piniaInstance)` for use in non-component code (interceptors, guards) +- **Token stored in localStorage** under key `"accessToken"` (from `CacheEnum.TOKEN_KEY`), NOT in Pinia state directly + +### Global Types (src/types/global.d.ts) + +These are **ambient global** types (no import needed): +- `ResponseData` — `{ code, data, msg }` +- `PageQuery` — `{ pageNum, pageSize }` +- `PageResult` — `{ list, total }` +- `TagView` — tab/nav view metadata +- `AppSettings` — app configuration interface +- `OptionType` — select/dropdown datasource (`{ value, label, children? }`) + +### Components + +- Auto-registered: `unplugin-vue-components` scans `src/components/` and `src/**/components/` +- **DTS generation is OFF** (`dts: false` in vite.config.ts) — types for auto-imported components are in `src/types/components.d.ts` (checked in, not auto-generated) +- **Element Plus components**: auto-imported via `ElementPlusResolver` — no manual imports needed +- **Icons**: use `` (e.g., ``) via unplugin-icons with `@iconify-json/ep` +- **SVG icons**: place SVGs in `src/assets/icons/`, use `` + +## Auto-Import: Critical Trap + +`unplugin-auto-import` is configured in `vite.config.ts`: + +```js +AutoImport({ + imports: ["vue", "@vueuse/core", "pinia", "vue-router", "vue-i18n"], + resolvers: [ElementPlusResolver(), IconsResolver({})], + eslintrc: { enabled: false }, // NOT generating eslint rules + dts: false, // NOT generating .d.ts +}) +``` + +**This means**: +- `ref`, `reactive`, `computed`, `watch`, `onMounted`, `defineStore`, `useRouter`, `useRoute`, `createI18n`, etc. are **auto-imported** — do NOT add explicit `import { ref } from "vue"` in new files +- The existing `src/types/auto-imports.d.ts` and `.eslintrc-auto-import.json` are **checked-in snapshots** — they are NOT auto-updated +- **When adding new auto-imported APIs** (e.g., from @vueuse/core): enable `dts` and `eslintrc.enabled` temporarily, run dev server once, then disable both again and commit the updated files +- **ElMessage, ElNotification, ElMessageBox** etc. are auto-imported — no need to import them manually + +## UnoCSS Shortcuts + +Defined in `uno.config.ts` — use these instead of writing full utility classes: + +| Shortcut | Expands To | +|---|---| +| `flex-center` | `flex justify-center items-center` | +| `flex-x-center` | `flex justify-center` | +| `flex-y-center` | `flex items-center` | +| `wh-full` | `w-full h-full` | +| `flex-x-between` | `flex items-center justify-between` | +| `flex-x-end` | `flex items-center justify-end` | +| `absolute-lt` | `absolute left-0 top-0` | +| `absolute-rt` | `absolute right-0 top-0` | +| `fixed-lt` | `fixed left-0 top-0` | + +**Theme colors**: `primary` → `var(--el-color-primary)`, `primary_dark` → `var(--el-color-primary-light-5)` — these reference Element Plus CSS variables, not hardcoded values. + +## SCSS + +- Global SCSS variables injected via `vite.config.ts` `css.preprocessorOptions.scss.additionalData`: + ```scss + @use "@/styles/variables.scss" as *; + ``` + This means every `.vue` and `.scss` file can use variables from `src/styles/variables.scss` without explicit `@use`. + +## Build Details + +- **Minifier**: `terser` (not esbuild) — configured with `drop_console: true` and `drop_debugger: true` for production +- **Manual chunks**: `vue` (vue+router+pinia), `vue-i18n`, `element-plus` — split into separate bundles +- **Asset naming**: `js/[name].[hash].js`, `img/[name].[hash].[ext]`, `fonts/[name].[hash].[ext]`, `media/[name].[hash].[ext]` +- **`__APP_INFO__`**: build-time global constant (name, version, engines, dependencies, buildTimestamp) — do NOT try to modify at runtime + +## Git Commit Convention + +Enforced by `husky` + `commitlint` + `lint-staged`: + +- **Always use `pnpm commit`** — launches cz-git interactive prompt +- **Allowed types**: `feat`, `fix`, `docs`, `style`, `refactor`, `perf`, `test`, `build`, `ci`, `revert`, `chore`, `wip` +- **Subject case**: not enforced (rule level 0) +- **Pre-commit**: `lint-staged` runs ESLint + Prettier + Stylelint on staged files automatically + +## ESLint Specifics + +- **Config**: `.eslintrc.cjs` (legacy format, NOT flat config) +- **Parser**: `vue-eslint-parser` → `@typescript-eslint/parser` for `