diff --git a/.gitignore b/.gitignore index 3399f39c0..df1f37a8a 100644 --- a/.gitignore +++ b/.gitignore @@ -50,3 +50,10 @@ vite.config.ts.* *.sw? .history .cursor + +# AI +.agent +.agents +.claude +.codex +skills-lock.json diff --git a/.npmrc b/.npmrc index aeac1ae91..356b660d8 100644 --- a/.npmrc +++ b/.npmrc @@ -2,7 +2,6 @@ registry=https://registry.npmmirror.com public-hoist-pattern[]=lefthook public-hoist-pattern[]=eslint public-hoist-pattern[]=prettier -public-hoist-pattern[]=prettier-plugin-tailwindcss public-hoist-pattern[]=stylelint public-hoist-pattern[]=*postcss* public-hoist-pattern[]=@commitlint/* diff --git a/.prettierignore b/.prettierignore index d0b0ca133..d0cf12365 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,6 +1,10 @@ dist dev-dist .local +.claude +.agent +.agents +.codex .output.js node_modules .nvmrc @@ -16,3 +20,4 @@ CODEOWNERS public .npmrc *-lock.yaml +skills-lock.json diff --git a/.stylelintignore b/.stylelintignore index f4b2db2c1..3adb33b22 100644 --- a/.stylelintignore +++ b/.stylelintignore @@ -2,3 +2,7 @@ dist public __tests__ coverage +.codex +.claude +.agent +.agents diff --git a/.vscode/settings.json b/.vscode/settings.json index 8da37dc96..984acf37b 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,6 @@ { - "tailwindCSS.experimental.configFile": "internal/tailwind-config/src/index.ts", + "tailwindCSS.experimental.configFile": "packages/@core/base/design/src/css/global.css", + "tailwindCSS.lint.suggestCanonicalClasses": "ignore", // workbench "workbench.list.smoothScrolling": true, "workbench.startupEditor": "newUntitledFile", @@ -31,6 +32,9 @@ "editor.autoClosingOvertype": "always", "editor.autoClosingQuotes": "beforeWhitespace", "editor.wordSeparators": "`~!@#%^&*()=+[{]}\\|;:'\",.<>/?", + "editor.quickSuggestions": { + "strings": "on" + }, "editor.codeActionsOnSave": { "source.fixAll.eslint": "explicit", "source.fixAll.stylelint": "explicit", @@ -79,6 +83,7 @@ "files.insertFinalNewline": true, "files.simpleDialog.enable": true, "files.associations": { + "*.css": "tailwindcss", "*.ejs": "html", "*.art": "html", "**/tsconfig.json": "jsonc", @@ -220,8 +225,7 @@ "*.env": "$(capture).env.*", "README.md": "README*,CHANGELOG*,LICENSE,CNAME", "package.json": "pnpm-lock.yaml,pnpm-workspace.yaml,.gitattributes,.gitignore,.gitpod.yml,.npmrc,.browserslistrc,.node-version,.git*,.tazerc.json", - "eslint.config.mjs": ".eslintignore,.prettierignore,.stylelintignore,.commitlintrc.*,.prettierrc.*,stylelint.config.*,.lintstagedrc.mjs,cspell.json,lefthook.yml", - "tailwind.config.mjs": "postcss.*" + "eslint.config.mjs": ".eslintignore,.prettierignore,.stylelintignore,.commitlintrc.*,.prettierrc.*,stylelint.config.*,.lintstagedrc.mjs,cspell.json,lefthook.yml" }, "commentTranslate.hover.enabled": false, "commentTranslate.multiLineMerge": true, diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 000000000..5d677b0bd --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,148 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## 技术栈 + +1. 基于 **pnpm workspaces** + **Turborepo** 的 Vue 3 + TypeScript + Vite monorepo 项目。 +2. 提供多个 UI 组件库版本(Ant Design Vue、Element Plus、Naive UI、TDesign),共享同一套使用tailwindcss+shadcn-vue的UI组件库核心框架。 +3. 要求 Node ≥ 20.19.0,pnpm ≥ 10。 +4. 使用 **prettier** + **eslint** + **stylelint** 进行代码检查和格式化。 +5. 使用 **vitest** 进行单元测试。 +6. 使用 **commitlint** 进行提交规范。 +7. 使用 **czg** 进行提交规范。 +8. 使用 **lefthook** 进行提交规范。 +9. 使用 **vsh** 进行代码检查和格式化。 +10. 使用 **turbo** 进行构建。 +11. 使用 **vite** 进行开发。 +12. 使用 **vue-tsc** 进行类型检查。 + +```bash +# 其他检查 +pnpm check:circular # 循环依赖扫描 +pnpm check:dep # depcheck 依赖检查 +pnpm check:cspell # 拼写检查 + +# 清理 +pnpm clean # 删除 dist、node_modules 等产物 +pnpm reinstall # clean + 重新安装 + +# 交互式规范提交 +pnpm commit # czg 提交向导 +``` + +Turbo 任务通过 `dependsOn: ["^build"]` 级联,构建某个应用时会自动先构建其所有依赖包。 + +## Monorepo 目录结构 + +```text +apps/ + backend-mock/ # 基于 Nitro 的 mock API 服务(h3 路由 + faker.js 数据) + web-antd/ # Ant Design Vue 应用 + web-ele/ # Element Plus 应用 + web-naive/ # Naive UI 应用 + web-tdesign/ # TDesign Vue 应用 + +packages/ + @core/ # 框架核心(不依赖具体 UI 库) + base/ # 共享工具、缓存、颜色处理、类型定义 + composables/ # 核心 Vue composable + preferences/ # PreferenceManager 类(响应式、持久化配置) + ui-kit/ # UI 组件片段:form-ui、layout-ui、menu-ui、popup-ui、shadcn-ui、tabs-ui + effects/ # 高层模块,可依赖 @core 和 UI 库 + access/ # 路由/菜单生成与权限指令 + common-ui/ # 通用 UI 组件(ApiComponent、IconPicker、VCropper、Tippy 等) + hooks/ # useAppConfig 等 + layouts/ # BasicLayout、登录页、各类 widgets + plugins/ # Motion 等插件 + request/ # RequestClient(axios 封装 + 拦截器体系) + constants/ # 全局常量(LOGIN_PATH 等) + icons/ # Iconify 图标封装 + locales/ # vue-i18n 初始化、loadLocalesMap 工具 + preferences/ # 对外暴露 @core/preferences 的公共 API + stores/ # Pinia 全局 store:useAccessStore、useUserStore、useTabbarStore + styles/ # 全局 CSS / TailwindCSS 基础样式 + types/ # 共享 TypeScript 类型 + utils/ # 共享工具函数(mergeRouteModules、mapTree 等) + +internal/ + lint-configs/ # ESLint、Prettier、Stylelint、commitlint 配置包 + node-utils/ # 构建时 Node 工具 + tailwind-config/ # 共享 Tailwind 配置 + tsconfig/ # 基础 tsconfig + vite-config/ # 共享 Vite 配置工厂 + 插件集合 + +scripts/ + vsh/ # CLI 工具(lint、check-dep、check-circular、publint) + turbo-run/ # 交互式 turbo 运行器 + +playground/ # 组件演示场 +docs/ # VitePress 文档 +``` + +## 核心架构说明 + +### 应用启动流程 + +每个应用的 `src/main.ts` 调用 `bootstrap(namespace)`(位于 `src/bootstrap.ts`),依次执行: + +1. 初始化**组件适配器**(`src/adapter/component/index.ts`)——将通用表单组件名映射到具体 UI 库的组件。 +2. 调用 `initSetupVbenForm()`(`src/adapter/form.ts`)配置通用表单系统。 +3. 依次初始化 i18n、Pinia stores、权限指令、Tippy、路由、MotionPlugin,最后挂载到 `#app`。 + +### 偏好设置系统 + +`@vben/preferences` 导出单例 `preferences`(`PreferenceManager`)。它是响应式的,自动持久化到 localStorage(以应用 namespace 为前缀),并驱动主题 CSS 变量的更新。各应用在 `src/preferences.ts` 中调用 `defineOverridesPreferences()` 覆盖默认值,无需修改核心代码。 + +### 权限/访问系统 + +`@vben/access`(`packages/effects/access`)支持三种访问模式: + +- **frontend**:根据用户角色过滤静态路由。 +- **backend**:从接口(`getAllMenusApi`)获取菜单并动态注册路由。 +- **mixed**:同时使用以上两种方式。 + +路由守卫(`src/router/guard.ts`)在登录后首次导航时调用 `generateAccess()`,将结果存入 `useAccessStore`,再重定向到目标页。`v-access` 指令和 `` 组件用于按权限码或角色控制 UI 元素显示。 + +### 请求客户端 + +`@vben/request` 将 Axios 封装为 `RequestClient`。每个应用在 `src/api/request.ts` 中创建自己的实例,挂载以下拦截器: + +- **请求拦截**:自动附加 Bearer Token 和 Accept-Language 头。 +- **`defaultResponseInterceptor`**:解包 `{ code, data, message }` 响应格式。 +- **`authenticateResponseInterceptor`**:处理 401,自动刷新 token 或跳转登录。 +- **`errorMessageResponseInterceptor`**:调用 `message.error()` 显示错误。 + +在 API 文件中从 `#/api/request` 引入 `requestClient`(自动解包响应)或 `baseRequestClient`(原始响应)。 + +### 路由组织 + +- `src/router/routes/modules/*.ts`:需要权限验证的动态路由。 +- `src/router/routes/core/`:始终可访问的路由(登录页、404 等)。 +- `mergeRouteModules(import.meta.glob(...))` 用于聚合路由模块文件。 +- 动态路由在运行时由权限系统注册。 + +### 适配器模式 + +每个 UI 库应用在 `src/adapter/` 下提供适配器,将 `@vben/common-ui` 的通用 form/modal/drawer 组件桥接到具体组件库。这是 `web-antd`、`web-ele` 等应用之间的主要差异所在。 + +### 全局 Pinia Store + +- `useAccessStore`:token、路由、菜单、锁屏、登录过期状态。 +- `useUserStore`:用户信息、角色、homePath。 +- `useTabbarStore`:已打开标签页管理。 + +所有 store 通过 `@vben/stores` 的 `initStores(app, { namespace })` 统一初始化。 + +### Mock 后端 + +`apps/backend-mock` 是一个 Nitro 服务器,可单独启动:`pnpm -F @vben/backend-mock start`。Vite 开发服务器通过 `vite.config.ts`(位于 `internal/vite-config`)将 API 请求代理到该服务。 + +## 开发约定 + +- **路径别名**:`#/*` 指向各应用的 `./src/*`(在 `package.json#imports` 中定义)。 +- **依赖版本管理**:内部包使用 `workspace:*`,第三方包使用 `catalog:`(版本集中在 `pnpm-workspace.yaml#catalog` 中管理)。 +- **提交规范**:遵循 Conventional Commits(`feat`、`fix`、`chore`、`docs`、`refactor`、`perf`、`test`、`ci`、`style`、`types`、`revert`),由 lefthook + commitlint 强制执行。 +- **pre-commit 钩子**(lefthook):自动对暂存文件执行 prettier + eslint + stylelint,推荐使用 `pnpm commit`(czg)提交。 +- **新增页面**:在 `src/views/` 下创建 `.vue` 文件,在 `src/router/routes/modules/` 下添加路由模块;若使用 backend 模式,还需确保后端接口返回对应菜单数据。 +- **国际化**:统一使用 `$t('key')`,locale 文件位于 `packages/locales/`,项目级国际化文件位于 `src/locales/langs`。 diff --git a/apps/backend-mock/api/table/list.ts b/apps/backend-mock/api/table/list.ts index 6664b583e..f81571e3b 100644 --- a/apps/backend-mock/api/table/list.ts +++ b/apps/backend-mock/api/table/list.ts @@ -79,7 +79,7 @@ export default eventHandler(async (event) => { const aValue = a[sortKey] as unknown; const bValue = b[sortKey] as unknown; - let result = 0; + let result: number; if (typeof aValue === 'number' && typeof bValue === 'number') { result = aValue - bValue; diff --git a/apps/web-antd/postcss.config.mjs b/apps/web-antd/postcss.config.mjs deleted file mode 100644 index 3d8070455..000000000 --- a/apps/web-antd/postcss.config.mjs +++ /dev/null @@ -1 +0,0 @@ -export { default } from '@vben/tailwind-config/postcss'; diff --git a/apps/web-antd/src/store/auth.ts b/apps/web-antd/src/store/auth.ts index bd496d1ee..4b93784c8 100644 --- a/apps/web-antd/src/store/auth.ts +++ b/apps/web-antd/src/store/auth.ts @@ -98,8 +98,7 @@ export const useAuthStore = defineStore('auth', () => { } async function fetchUserInfo() { - let userInfo: null | UserInfo = null; - userInfo = await getUserInfoApi(); + const userInfo = await getUserInfoApi(); userStore.setUserInfo(userInfo); return userInfo; } diff --git a/apps/web-antd/src/views/dashboard/analytics/index.vue b/apps/web-antd/src/views/dashboard/analytics/index.vue index 5e3d6d285..e794c99a9 100644 --- a/apps/web-antd/src/views/dashboard/analytics/index.vue +++ b/apps/web-antd/src/views/dashboard/analytics/index.vue @@ -76,10 +76,10 @@ const chartTabs: TabOption[] = [
- + - + diff --git a/apps/web-antd/tailwind.config.mjs b/apps/web-antd/tailwind.config.mjs deleted file mode 100644 index f17f556fa..000000000 --- a/apps/web-antd/tailwind.config.mjs +++ /dev/null @@ -1 +0,0 @@ -export { default } from '@vben/tailwind-config'; diff --git a/apps/web-antdv-next/postcss.config.mjs b/apps/web-antdv-next/postcss.config.mjs deleted file mode 100644 index 3d8070455..000000000 --- a/apps/web-antdv-next/postcss.config.mjs +++ /dev/null @@ -1 +0,0 @@ -export { default } from '@vben/tailwind-config/postcss'; diff --git a/apps/web-antdv-next/src/store/auth.ts b/apps/web-antdv-next/src/store/auth.ts index 6f7a3750c..90af35c8d 100644 --- a/apps/web-antdv-next/src/store/auth.ts +++ b/apps/web-antdv-next/src/store/auth.ts @@ -98,8 +98,7 @@ export const useAuthStore = defineStore('auth', () => { } async function fetchUserInfo() { - let userInfo: null | UserInfo = null; - userInfo = await getUserInfoApi(); + const userInfo = await getUserInfoApi(); userStore.setUserInfo(userInfo); return userInfo; } diff --git a/apps/web-antdv-next/src/views/dashboard/analytics/index.vue b/apps/web-antdv-next/src/views/dashboard/analytics/index.vue index 5e3d6d285..e794c99a9 100644 --- a/apps/web-antdv-next/src/views/dashboard/analytics/index.vue +++ b/apps/web-antdv-next/src/views/dashboard/analytics/index.vue @@ -76,10 +76,10 @@ const chartTabs: TabOption[] = [
- + - + diff --git a/apps/web-antdv-next/tailwind.config.mjs b/apps/web-antdv-next/tailwind.config.mjs deleted file mode 100644 index f17f556fa..000000000 --- a/apps/web-antdv-next/tailwind.config.mjs +++ /dev/null @@ -1 +0,0 @@ -export { default } from '@vben/tailwind-config'; diff --git a/apps/web-ele/postcss.config.mjs b/apps/web-ele/postcss.config.mjs deleted file mode 100644 index 3d8070455..000000000 --- a/apps/web-ele/postcss.config.mjs +++ /dev/null @@ -1 +0,0 @@ -export { default } from '@vben/tailwind-config/postcss'; diff --git a/apps/web-ele/src/store/auth.ts b/apps/web-ele/src/store/auth.ts index 74fadfe24..bb37051ad 100644 --- a/apps/web-ele/src/store/auth.ts +++ b/apps/web-ele/src/store/auth.ts @@ -99,8 +99,7 @@ export const useAuthStore = defineStore('auth', () => { } async function fetchUserInfo() { - let userInfo: null | UserInfo = null; - userInfo = await getUserInfoApi(); + const userInfo = await getUserInfoApi(); userStore.setUserInfo(userInfo); return userInfo; } diff --git a/apps/web-ele/src/views/dashboard/analytics/index.vue b/apps/web-ele/src/views/dashboard/analytics/index.vue index 5e3d6d285..e794c99a9 100644 --- a/apps/web-ele/src/views/dashboard/analytics/index.vue +++ b/apps/web-ele/src/views/dashboard/analytics/index.vue @@ -76,10 +76,10 @@ const chartTabs: TabOption[] = [
- + - + diff --git a/apps/web-ele/src/views/demos/element/index.vue b/apps/web-ele/src/views/demos/element/index.vue index 0a7012d63..8d391700f 100644 --- a/apps/web-ele/src/views/demos/element/index.vue +++ b/apps/web-ele/src/views/demos/element/index.vue @@ -102,9 +102,7 @@ const segmentedOptions = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']; -
- 一些演示的内容 -
+
一些演示的内容
diff --git a/apps/web-ele/tailwind.config.mjs b/apps/web-ele/tailwind.config.mjs deleted file mode 100644 index f17f556fa..000000000 --- a/apps/web-ele/tailwind.config.mjs +++ /dev/null @@ -1 +0,0 @@ -export { default } from '@vben/tailwind-config'; diff --git a/apps/web-naive/postcss.config.mjs b/apps/web-naive/postcss.config.mjs deleted file mode 100644 index 3d8070455..000000000 --- a/apps/web-naive/postcss.config.mjs +++ /dev/null @@ -1 +0,0 @@ -export { default } from '@vben/tailwind-config/postcss'; diff --git a/apps/web-naive/src/store/auth.ts b/apps/web-naive/src/store/auth.ts index 0ff050b3b..5e5cf655b 100644 --- a/apps/web-naive/src/store/auth.ts +++ b/apps/web-naive/src/store/auth.ts @@ -99,8 +99,7 @@ export const useAuthStore = defineStore('auth', () => { } async function fetchUserInfo() { - let userInfo: null | UserInfo = null; - userInfo = await getUserInfoApi(); + const userInfo = await getUserInfoApi(); userStore.setUserInfo(userInfo); return userInfo; } diff --git a/apps/web-naive/src/views/dashboard/analytics/index.vue b/apps/web-naive/src/views/dashboard/analytics/index.vue index 5e3d6d285..e794c99a9 100644 --- a/apps/web-naive/src/views/dashboard/analytics/index.vue +++ b/apps/web-naive/src/views/dashboard/analytics/index.vue @@ -76,10 +76,10 @@ const chartTabs: TabOption[] = [
- + - + diff --git a/apps/web-naive/tailwind.config.mjs b/apps/web-naive/tailwind.config.mjs deleted file mode 100644 index f17f556fa..000000000 --- a/apps/web-naive/tailwind.config.mjs +++ /dev/null @@ -1 +0,0 @@ -export { default } from '@vben/tailwind-config'; diff --git a/apps/web-tdesign/postcss.config.mjs b/apps/web-tdesign/postcss.config.mjs deleted file mode 100644 index 3d8070455..000000000 --- a/apps/web-tdesign/postcss.config.mjs +++ /dev/null @@ -1 +0,0 @@ -export { default } from '@vben/tailwind-config/postcss'; diff --git a/apps/web-tdesign/src/store/auth.ts b/apps/web-tdesign/src/store/auth.ts index b3b4b7494..7f8d8f9b2 100644 --- a/apps/web-tdesign/src/store/auth.ts +++ b/apps/web-tdesign/src/store/auth.ts @@ -97,8 +97,7 @@ export const useAuthStore = defineStore('auth', () => { } async function fetchUserInfo() { - let userInfo: null | UserInfo = null; - userInfo = await getUserInfoApi(); + const userInfo = await getUserInfoApi(); userStore.setUserInfo(userInfo); return userInfo; } diff --git a/apps/web-tdesign/src/views/dashboard/analytics/index.vue b/apps/web-tdesign/src/views/dashboard/analytics/index.vue index 5e3d6d285..e794c99a9 100644 --- a/apps/web-tdesign/src/views/dashboard/analytics/index.vue +++ b/apps/web-tdesign/src/views/dashboard/analytics/index.vue @@ -76,10 +76,10 @@ const chartTabs: TabOption[] = [
- + - + diff --git a/apps/web-tdesign/src/views/demos/tdesign/index.vue b/apps/web-tdesign/src/views/demos/tdesign/index.vue index 27299dea5..f6342ae32 100644 --- a/apps/web-tdesign/src/views/demos/tdesign/index.vue +++ b/apps/web-tdesign/src/views/demos/tdesign/index.vue @@ -38,7 +38,7 @@ function notify(type: NotificationType) { description="支持多语言,主题功能集成切换等" title="TDesign Vue组件使用演示" > - + @@ -46,7 +46,7 @@ function notify(type: NotificationType) { - + @@ -55,7 +55,7 @@ function notify(type: NotificationType) { - + diff --git a/apps/web-tdesign/tailwind.config.mjs b/apps/web-tdesign/tailwind.config.mjs deleted file mode 100644 index f17f556fa..000000000 --- a/apps/web-tdesign/tailwind.config.mjs +++ /dev/null @@ -1 +0,0 @@ -export { default } from '@vben/tailwind-config'; diff --git a/cspell.json b/cspell.json index 0c05c512d..08b165070 100644 --- a/cspell.json +++ b/cspell.json @@ -13,6 +13,7 @@ "brotli", "cascader", "clsx", + "dedup", "defu", "demi", "dotenv", @@ -41,6 +42,7 @@ "noreferrer", "nprogress", "nuxt", + "organisation", "pinia", "prefixs", "publint", @@ -51,6 +53,7 @@ "sonner", "sortablejs", "styl", + "tabler", "taze", "tdesign", "ui-kit", diff --git a/docs/.vitepress/components/demo-preview.vue b/docs/.vitepress/components/demo-preview.vue index 983a95062..cca9c749d 100644 --- a/docs/.vitepress/components/demo-preview.vue +++ b/docs/.vitepress/components/demo-preview.vue @@ -27,7 +27,7 @@ const parsedFiles = computed(() => {
- + ERROR: The preview directory does not exist. Please check the 'dir' diff --git a/docs/.vitepress/components/preview-group.vue b/docs/.vitepress/components/preview-group.vue index e08e921aa..ccf1f7e87 100644 --- a/docs/.vitepress/components/preview-group.vue +++ b/docs/.vitepress/components/preview-group.vue @@ -56,15 +56,15 @@ const toggleOpen = () => {