diff --git a/.cloud-dev/Dockerfile b/.cloud-dev/Dockerfile index ff44178..59c1c41 100644 --- a/.cloud-dev/Dockerfile +++ b/.cloud-dev/Dockerfile @@ -96,7 +96,8 @@ RUN curl -fsSL https://code-server.dev/install.sh | sh -s -- --version=${CODE_SE # 安装 npm 全局包 RUN npm install -g \ @anthropic-ai/claude-code \ - @musistudio/claude-code-router + @musistudio/claude-code-router \ + pm2 # 创建工作目录 RUN mkdir -p /workspace /root/.local/share/code-server/User diff --git a/.gitignore b/.gitignore index ac730c5..7938b82 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,7 @@ # next.js /.next/ +/.next-prod/ /out/ # production diff --git a/CLAUDE.md b/CLAUDE.md index f073e3a..3f88c47 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -50,43 +50,43 @@ Hair Keeper是个诙谐有趣的名称,和项目内容毫无关系。 ## 重要目录和文件 ### 前端 -- `components/common/`:进一步封装的高级通用控件,例如下拉菜单、对话框表单、响应式tabs等控件 -- `components/features/`:进一步封装的控件,通常更重或者与业务关联强需要在不同的页面中复用 -- `components/ai-elements/`:ai对话相关的组件 -- `components/data-details/`:专注于数据展示的可复用控件,例如detail-badge-list、detail-copyable、detail-list、detail-timeline等控件 -- `components/data-table/`:专注于数据表格的可复用控件,本项目模板自带了基础的data-table、过滤器、排序、分页、列可见性切换等功能 -- `components/icons/`:项目的自定义图标可以写在这个文件夹 -- `components/layout/`:应用的完整布局框架和导航系统以及可复用的布局容器 -- `components/ui/`:高度可定制可复用的基础UI组件,通常源自第三方库 -- `app/(main)/`:开发者在这里自行实现的所有业务的页面 -- `app/(main)/dev/`:辅助开发的页面,本项目模板在其中实现了许多功能,代码在实现业务时也可以借鉴参考 -- `app/(main)/settings/`:全局设置,由开发者根据业务需求进行补充和实现 -- `app/(main)/users/`:用户管理模块,提供用户CRUD、角色管理、批量授权等完整的用户管理功能的页面和组件实现 -- `hooks/`:可复用React Hooks库,部分复杂的组件也通过hook实现Headless UI逻辑与样式分离,组件中可复用的逻辑都可以放在这 -- `lib/trpc.ts`:创建并导出tRPC React客户端实例,用于前端与后端API通信 -- `lib/stores/`:通过zustand管理的全局的状态 +- `src/components/common/`:进一步封装的高级通用控件,例如下拉菜单、对话框表单、响应式tabs等控件 +- `src/components/features/`:进一步封装的控件,通常更重或者与业务关联强需要在不同的页面中复用 +- `src/components/ai-elements/`:ai对话相关的组件 +- `src/components/data-details/`:专注于数据展示的可复用控件,例如detail-badge-list、detail-copyable、detail-list、detail-timeline等控件 +- `src/components/data-table/`:专注于数据表格的可复用控件,本项目模板自带了基础的data-table、过滤器、排序、分页、列可见性切换等功能 +- `src/components/icons/`:项目的自定义图标可以写在这个文件夹 +- `src/components/layout/`:应用的完整布局框架和导航系统以及可复用的布局容器 +- `src/components/ui/`:高度可定制可复用的基础UI组件,通常源自第三方库 +- `src/app/(main)/`:开发者在这里自行实现的所有业务的页面 +- `src/app/(main)/dev/`:辅助开发的页面,本项目模板在其中实现了许多功能,代码在实现业务时也可以借鉴参考 +- `src/app/(main)/settings/`:全局设置,由开发者根据业务需求进行补充和实现 +- `src/app/(main)/users/`:用户管理模块,提供用户CRUD、角色管理、批量授权等完整的用户管理功能的页面和组件实现 +- `src/hooks/`:可复用React Hooks库,部分复杂的组件也通过hook实现Headless UI逻辑与样式分离,组件中可复用的逻辑都可以放在这 +- `src/lib/trpc.ts`:创建并导出tRPC React客户端实例,用于前端与后端API通信 +- `src/lib/stores/`:通过zustand管理的全局的状态 ### 后端 -- `server/routers/`:项目trpc api定义文件,开发者主要在这里定义和实现业务的后端API -- `server/routers/_app.ts`:`appRouter`根路由定义,需要添加子路由时在此处注册 -- `server/routers/common.ts`:定义需要在多个模块中复用的通用业务接口路由 -- `server/routers/jobs.ts`:tRPC任务进度订阅路由 -- `server/routers/selection.ts`:用于记录用户选择的选项或者输入的内容,优化用户的输入体验 -- `server/routers/global.ts`:系统全局和特定业务关联不大的一些api -- `server/routers/dev/`:开发模式下的辅助功能需要的trpc api -- `server/queues/`:消息队列和worker,通过其中的index.ts统一导出,任务状态更新采用trpc SSE subscription,接口定义在`server/routers/jobs.ts`中 -- `server/agents`:LLM的对接和使用 -- `server/service/`:服务层模块集合,封装后端业务逻辑和系统服务 -- `server/service/dev/`:开发模式下的辅助功能需要的后台服务 -- `server/utils/`:服务端专用工具函数库,为后端业务逻辑提供基础设施支持 -- `api/dev/`:开发模式下的辅助功能需要的api +- `src/server/routers/`:项目trpc api定义文件,开发者主要在这里定义和实现业务的后端API +- `src/server/routers/_app.ts`:`appRouter`根路由定义,需要添加子路由时在此处注册 +- `src/server/routers/common.ts`:定义需要在多个模块中复用的通用业务接口路由 +- `src/server/routers/jobs.ts`:tRPC任务进度订阅路由 +- `src/server/routers/selection.ts`:用于记录用户选择的选项或者输入的内容,优化用户的输入体验 +- `src/server/routers/global.ts`:系统全局和特定业务关联不大的一些api +- `src/server/routers/dev/`:开发模式下的辅助功能需要的trpc api +- `src/server/queues/`:消息队列和worker,通过其中的index.ts统一导出,任务状态更新采用trpc SSE subscription,接口定义在`server/routers/jobs.ts`中 +- `src/server/agents`:LLM的对接和使用 +- `src/server/service/`:服务层模块集合,封装后端业务逻辑和系统服务 +- `src/server/service/dev/`:开发模式下的辅助功能需要的后台服务 +- `src/server/utils/`:服务端专用工具函数库,为后端业务逻辑提供基础设施支持 +- `src/api/dev/`:开发模式下的辅助功能需要的api ### 其他 -- `constants/`:项目全局常量管理 -- `constants/permissions.ts`:权限定义,支持前后端一致的权限控制,支持解析复杂的权限表达式(如"A&B|(C&D)") -- `lib/schema/`:集中管理数据验证schema,定义前后端统一的数据结构和验证规则,前端对默认值等其他要求写在表单组件中,后端对默认值等其他要求写在接口文件中,使用z.input而不是z.infer来获取Schema的输入类型 -- `lib/algorithom.ts`:通用计算机算法实现,例如拓扑排序 -- `lib/format.ts`:数据格式化工具函数库 +- `src/constants/`:项目全局常量管理 +- `src/constants/permissions.ts`:权限定义,支持前后端一致的权限控制,支持解析复杂的权限表达式(如"A&B|(C&D)") +- `src/lib/schema/`:集中管理数据验证schema,定义前后端统一的数据结构和验证规则,前端对默认值等其他要求写在表单组件中,后端对默认值等其他要求写在接口文件中,使用z.input而不是z.infer来获取Schema的输入类型 +- `src/lib/algorithom.ts`:通用计算机算法实现,例如拓扑排序 +- `src/lib/format.ts`:数据格式化工具函数库 ## 非标准命令 - `pnpm run dev:attach`:这会使用tmux在名为nextdev的session中启动pnpm run dev,便于在开发页面或其他地方与开发服务器交互 diff --git a/next.config.ts b/next.config.ts index 07f18a0..4d19a69 100644 --- a/next.config.ts +++ b/next.config.ts @@ -1,7 +1,7 @@ import type { NextConfig } from "next"; const nextConfig: NextConfig = { - + distDir: process.env.NODE_ENV === "production" ? ".next-prod" : '.next', // 开发和生产环境输出到不同的目录,这样可以同时运行开发服务器和生产服务器 }; diff --git a/package.json b/package.json index 894fcdd..01608c6 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "dev": "next dev -p 3000 --turbo", "dev:attach": "DEV_TERMINAL=nextdev;tmux new-session -A -s $DEV_TERMINAL\\; send-keys \"pnpm run dev\" ^M", "build": "next build", - "start": "next start -p 3000", + "start": "next start", "lint": "next lint && tsc --noEmit", "db:seed": "tsx prisma/seed.ts", "build:analyze": "ANALYZE=true next build" @@ -74,7 +74,7 @@ "minio": "^8.0.6", "motion": "^12.23.22", "nanoid": "^5.1.6", - "next": "~15.4.8", + "next": "~15.4.10", "next-auth": "^4.24.11", "next-themes": "^0.4.6", "nuqs": "^2.6.0", diff --git a/quickstart.md b/quickstart.md new file mode 100644 index 0000000..9225334 --- /dev/null +++ b/quickstart.md @@ -0,0 +1,4 @@ +## Claude Code编程工具常见用法 +常见指令和操作: +- `/ide`:与当前终端所在的ide集成,比如说与code server集成,那么claude code会在code server中展示要修改的文件内容,你在code server中选中的代码也会自动发送给claude code +- 按`Tab`键来切换思考模式,默认是打开的,但是一般复杂任务才打开思考模式,所以请关闭思考模式,关闭的时候终端右下角会有提示`Thinking off (tab to toggle)` \ No newline at end of file diff --git a/simple_deploy.sh b/simple_deploy.sh old mode 100644 new mode 100755 index a253ff0..63f667b --- a/simple_deploy.sh +++ b/simple_deploy.sh @@ -1,6 +1,6 @@ #!/bin/bash # 此脚本用来一键部署到生产服务器 -# 需事先配置好ssh免密登录,目标服务器需要安装好node、pnpm、tsx、pm2来运行容器 +# 需事先配置好ssh免密登录,目标服务器需要安装好node、pnpm、tsx、pm2来运行程序 set -e # 配置 diff --git a/simple_deploy_local.sh b/simple_deploy_local.sh new file mode 100755 index 0000000..d06a11d --- /dev/null +++ b/simple_deploy_local.sh @@ -0,0 +1,28 @@ +#!/bin/bash +# 此脚本用来直接在当前目录下一键部署生产服务器 +# 需要安装好node、pnpm、pm2来运行程序 +set -e + +# 配置 +PROJECT_NAME="hair-keeper" # 可自由修改,默认使用本项目模板的名称 +PORT="8000" + +pnpm run lint +echo "🔨 开始构建项目..." +pnpm run build + +echo "🗄️ 运行数据库迁移..." +npx prisma migrate deploy +npx prisma generate + +echo "🔄 使用PM2重启服务..." +pm2 describe hair-keeper > /dev/null 2>&1 && pm2 delete hair-keeper +pm2 start pnpm --name $PROJECT_NAME -- start -p ${PORT} +# 保存当前 PM2 进程列表的快照,使其在系统重启后能自动恢复 +pm2 save + +echo "✅ 部署完成!服务运行在端口 ${PORT}" +echo "📊 查看服务状态: pm2 status" +echo "📝 查看日志: pm2 logs $PROJECT_NAME" +echo "❌ 关闭服务: pm2 delete $PROJECT_NAME" +echo "🎉 部署成功!" diff --git a/src/components/common/form-dialog.tsx b/src/components/common/form-dialog.tsx index 34444a2..9733e25 100644 --- a/src/components/common/form-dialog.tsx +++ b/src/components/common/form-dialog.tsx @@ -267,7 +267,7 @@ export function FormDialog({ if (formRef.current) { // 查找第一个可聚焦的输入元素 const firstInput = formRef.current.querySelector( - 'input:not([type="hidden"]):not([disabled]), textarea:not([disabled]), select:not([disabled])' + 'input:not([type="hidden"]):not([disabled]), textarea:not([disabled]), select:not([disabled]), button[role="combobox"]:not([disabled])' ) if (firstInput) { firstInput.focus() diff --git a/src/components/common/multi-step-form-dialog.tsx b/src/components/common/multi-step-form-dialog.tsx index 90d9fe0..bd25cac 100644 --- a/src/components/common/multi-step-form-dialog.tsx +++ b/src/components/common/multi-step-form-dialog.tsx @@ -160,7 +160,7 @@ export function MultiStepFormDialog({ if (formRef.current) { // 查找第一个可聚焦的输入元素 const firstInput = formRef.current.querySelector( - 'input:not([type="hidden"]):not([disabled]), textarea:not([disabled]), select:not([disabled])' + 'input:not([type="hidden"]):not([disabled]), textarea:not([disabled]), select:not([disabled]), button[role="combobox"]:not([disabled])' ) if (firstInput) { firstInput.focus()