# Protein_synthesis **Repository Path**: JLLAYUN/protein_synthesis ## Basic Information - **Project Name**: Protein_synthesis - **Description**: 关于蛋白质某一合成过程,使用微信小程序 - **Primary Language**: CSS - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2025-10-29 - **Last Updated**: 2026-02-28 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Coze Mini Program 这是一个基于 [Taro 4](https://docs.taro.zone/docs/) + [Nest.js](https://nestjs.com/) 的前后端分离项目,由扣子编程 CLI 创建。 ## 技术栈 - **整体框架**: Taro 4.1.9 - **语言**: TypeScript 5.4.5 - **渲染**: React 18.0.0 - **样式**: TailwindCSS 4.1.18 - **Tailwind 适配层**: weapp-tailwindcss 4.9.2 - **状态管理**: Zustand 5.0.9 - **图标库**: lucide-react-taro latest - **工程化**: Vite 4.2.0 - **包管理**: pnpm - **运行时**: Node.js >= 18 - **服务端**: NestJS 10.4.15 - **数据库 ORM**: Drizzle ORM 0.45.1 - **类型校验**: Zod 4.3.5 ## 项目结构 ``` ├── .cozeproj/ # Coze 平台配置 │ └── scripts/ # 构建和运行脚本 ├── config/ # Taro 构建配置 │ ├── index.ts # 主配置文件 │ ├── dev.ts # 开发环境配置 │ └── prod.ts # 生产环境配置 ├── server/ # NestJS 后端服务 │ └── src/ │ ├── main.ts # 服务入口 │ ├── app.module.ts # 根模块 │ ├── app.controller.ts # 应用控制器 │ └── app.service.ts # 应用服务 ├── src/ # 前端源码 │ ├── pages/ # 页面组件 │ ├── presets/ # 框架预置逻辑(无需读取,如无必要不改动) │ ├── utils/ # 工具函数 │ ├── network.ts # 封装好的网络请求工具 │ ├── app.ts # 应用入口 │ ├── app.config.ts # 应用配置 │ └── app.css # 全局样式 ├── types/ # TypeScript 类型定义 ├── key/ # 小程序密钥(CI 上传用) ├── .env.local # 环境变量 └── project.config.json # 微信小程序项目配置 ``` ## 快速开始 ### 安装依赖 ```bash pnpm install ``` ### 本地开发 同时启动 H5 前端和 NestJS 后端: ```bash pnpm dev ``` - 前端地址:http://localhost:5000 - 后端地址:http://localhost:3000 单独启动: ```bash pnpm dev:web # 仅 H5 前端 pnpm dev:weapp # 仅微信小程序 pnpm dev:server # 仅后端服务 ``` ### 构建 ```bash pnpm build # 构建所有(H5 + 小程序 + 后端) pnpm build:web # 仅构建 H5,输出到 dist-web pnpm build:weapp # 仅构建微信小程序,输出到 dist pnpm build:server # 仅构建后端 ``` ### 预览小程序 ```bash pnpm preview:weapp # 构建并生成预览小程序二维码 ``` ## 前端核心开发规范 ### 新建页面流程 1. 在 \`src/pages/\` 下创建页面目录 2. 创建 \`index.tsx\`(页面组件) 3. 创建 \`index.config.ts\`(页面配置) 4. 创建 \`index.css\`(页面样式,可选) 5. 在 \`src/app.config.ts\` 的 \`pages\` 数组中注册页面路径 或使用 Taro 脚手架命令: ```bash pnpm new # 交互式创建页面/组件 ``` ### 常用 Taro 组件 引入方式 ```typescript import { Text } from '@tarojs/components' ``` - 基础组件 - Text - Icon - Progress - RichText - 表单组件 - Button - Checkbox - CheckboxGroup - Editor - Form - Input - Label - Picker - PickerView - PickerViewColumn - Radio - RadioGroup - Slider - Switch - Textarea - 导航组件 - FunctionalPageNavigator - NavigationBar - Navigator - TabItem - Tabs - 媒体组件 - Camera - Image - Video - 视图容器 - ScrollView - Swiper - SwiperItem - View ### 路径别名 项目配置了 `@/*` 路径别名指向 `src/*`: ```typescript import { SomeComponent } from '@/components/SomeComponent' import { useUserStore } from '@/stores/user' ``` ### 代码模板 #### 页面组件 (TypeScript + React) ```tsx // src/pages/example/index.tsx import { View, Text } from '@tarojs/components' import { useLoad, useDidShow } from '@tarojs/taro' import type { FC } from 'react' import './index.css' const ExamplePage: FC = () => { useLoad(() => { console.log('Page loaded.') }) useDidShow(() => { console.log('Page showed.') }) return ( Hello Taro! ) } export default ExamplePage ``` #### 页面配置 ```typescript // src/pages/example/index.config.ts import { definePageConfig } from '@tarojs/taro' export default definePageConfig({ navigationBarTitleText: '示例页面', enablePullDownRefresh: true, backgroundTextStyle: 'dark', }) ``` #### 应用配置 ```typescript // src/app.config.ts import { defineAppConfig } from '@tarojs/taro' export default defineAppConfig({ pages: [ 'pages/index/index', 'pages/example/index', ], window: { backgroundTextStyle: 'light', navigationBarBackgroundColor: '#fff', navigationBarTitleText: 'App', navigationBarTextStyle: 'black', }, // TabBar 配置 (可选) // tabBar: { // list: [ // { pagePath: 'pages/index/index', text: '首页' }, // ], // }, }) ``` ### 发送请求 **IMPORTANT: 禁止直接使用 Taro.request、Taro.uploadFile、Taro.downloadFile,使用 Network.request、Network.uploadFile、Network.downloadFile 替代。** Network 是对 Taro.request、Taro.uploadFile、Taro.downloadFile 的封装,自动添加项目域名前缀,参数与 Taro 一致。 ✅ 正确使用方式 ```typescript import { Network } from '@/network' // GET 请求 const data = await Network.request({ url: '/api/hello' }) // POST 请求 const result = await Network.request({ url: '/api/user/login', method: 'POST', data: { username, password } }) // 文件上传 await Network.uploadFile({ url: '/api/upload', filePath: tempFilePath, name: 'file' }) // 文件下载 await Network.downloadFile({ url: '/api/download/file.pdf' }) ``` ❌ 错误用法 ```typescript import Taro from '@tarojs/taro' // ❌ 会导致自动域名拼接无法生效,除非是特殊指定域名 const data = await Network.request({ url: 'http://localhost/api/hello' }) // ❌ 不要直接使用 Taro.request await Taro.request({ url: '/api/hello' }) // ❌ 不要直接使用 Taro.uploadFile await Taro.uploadFile({ url: '/api/upload', filePath, name: 'file' }) ``` ### Zustand 状态管理 ```typescript // src/stores/user.ts import { create } from 'zustand' interface UserState { userInfo: UserInfo | null token: string setUserInfo: (info: UserInfo) => void setToken: (token: string) => void logout: () => void } interface UserInfo { id: string name: string avatar: string } export const useUserStore = create((set) => ({ userInfo: null, token: '', setUserInfo: (info) => set({ userInfo: info }), setToken: (token) => set({ token }), logout: () => set({ userInfo: null, token: '' }), })) ``` ### Taro 生命周期 Hooks ```typescript import { useLoad, // 页面加载 (onLoad) useReady, // 页面初次渲染完成 (onReady) useDidShow, // 页面显示 (onShow) useDidHide, // 页面隐藏 (onHide) usePullDownRefresh, // 下拉刷新 (onPullDownRefresh) useReachBottom, // 触底加载 (onReachBottom) useShareAppMessage, // 分享 (onShareAppMessage) useRouter, // 获取路由参数 } from '@tarojs/taro' ``` ### 路由导航 ```typescript import Taro from '@tarojs/taro' // 保留当前页面,跳转到新页面 Taro.navigateTo({ url: '/pages/detail/index?id=1' }) // 关闭当前页面,跳转到新页面 Taro.redirectTo({ url: '/pages/detail/index' }) // 跳转到 tabBar 页面 Taro.switchTab({ url: '/pages/index/index' }) // 返回上一页 Taro.navigateBack({ delta: 1 }) // 获取路由参数 const router = useRouter() const { id } = router.params ``` ### 图标使用 (lucide-react-taro) **IMPORTANT: 禁止使用 lucide-react,必须使用 lucide-react-taro 替代。** lucide-react-taro 是 Lucide 图标库的 Taro 适配版本,专为小程序环境优化,API 与 lucide-react 一致: ```tsx import { View } from '@tarojs/components' import { House, Settings, User, Search, Camera, Zap } from 'lucide-react-taro' const IconDemo = () => { return ( {/* 基本用法 */} {/* 自定义尺寸和颜色 */} {/* 自定义描边宽度 */} {/* 绝对描边宽度(描边不随 size 缩放) */} {/* 组合使用 */} ) } ``` 常用属性: - `size` - 图标大小(默认 24) - `color` - 图标颜色(默认 currentColor,小程序中建议显式设置) - `strokeWidth` - 线条粗细(默认 2) - `absoluteStrokeWidth` - 绝对描边宽度,启用后描边不随 size 缩放 - `className` / `style` - 自定义样式 更多图标请访问:https://lucide.dev/icons ### TabBar 图标生成 (CLI 工具) **IMPORTANT: 微信小程序的 TabBar 不支持 base64 或 SVG 图片,必须使用本地 PNG 文件。** lucide-react-taro 提供了 CLI 工具来生成 TabBar 所需的 PNG 图标: ```bash # 生成带选中状态的图标 npx taro-lucide-tabbar House Settings User -c "#999999" -a "#1890ff" # 指定输出目录和尺寸 npx taro-lucide-tabbar House Settings User -c "#999999" -a "#1890ff" -o ./src/assets/tabbar -s 81 ``` CLI 参数: - `--color, -c` (默认 #000000): 图标颜色 - `--active-color, -a`: 选中状态颜色 - `--size, -s` (默认 81): 图标尺寸 - `--output, -o` (默认 ./tabbar-icons): 输出目录 - `--stroke-width` (默认 2): 描边宽度 在 `app.config.ts` 中使用生成的图标: ```typescript export default defineAppConfig({ tabBar: { color: '#999999', selectedColor: '#1890ff', backgroundColor: '#ffffff', borderStyle: 'black', list: [ { pagePath: 'pages/index/index', text: '首页', iconPath: './assets/tabbar/house.png', selectedIconPath: './assets/tabbar/house-active.png', }, { pagePath: 'pages/settings/index', text: '设置', iconPath: './assets/tabbar/settings.png', selectedIconPath: './assets/tabbar/settings-active.png', }, { pagePath: 'pages/user/index', text: '用户', iconPath: './assets/tabbar/user.png', selectedIconPath: './assets/tabbar/user-active.png', }, ], }, }) ### Tailwind CSS 样式开发 IMPORTANT:必须使用 tailwindcss 实现样式,只有在必要情况下才能 fallback 到 css / less > 项目已集成 Tailwind CSS 4.x + weapp-tailwindcss,支持跨端原子化样式: ```tsx 标题 ``` ### 性能优化 #### 图片懒加载 ```tsx import { Image } from '@tarojs/components' ``` #### 虚拟列表 ```tsx import { VirtualList } from '@tarojs/components' ( {data[index].name} )} /> ``` #### 分包加载 ```typescript // src/app.config.ts export default defineAppConfig({ pages: ['pages/index/index'], subPackages: [ { root: 'packageA', pages: ['pages/detail/index'], }, ], }) ``` ### 小程序限制 | 限制项 | 说明 | | -------- | ---------------------------------------- | | 主包体积 | ≤ 2MB | | 总包体积 | ≤ 20MB | | 域名配置 | 生产环境需在小程序后台配置合法域名 | | 本地开发 | 需在微信开发者工具开启「不校验合法域名」 | ### 权限配置 ```typescript // src/app.config.ts export default defineAppConfig({ // ...其他配置 permission: { 'scope.userLocation': { desc: '你的位置信息将用于小程序位置接口的效果展示' } }, requiredPrivateInfos: ['getLocation', 'chooseAddress'] }) ``` ### 位置服务 ```typescript // 需先在 app.config.ts 中配置 permission async function getLocation(): Promise { return await Taro.getLocation({ type: 'gcj02' }) } ``` ## 后端核心开发规范 本项目后端基于 NestJS + TypeScript 构建,提供高效、可扩展的服务端能力。 ### 项目结构 ```sh . ├── server/ # NestJS 后端服务 │ └── src/ │ ├── main.ts # 服务入口 │ ├── app.module.ts # 根模块 │ ├── app.controller.ts # 根控制器 │ └── app.service.ts # 根服务 ``` ### 开发命令 ```sh pnpm dev:server // 启动开发服务 (热重载, 默认端口 3000) pnpm build:server // 构建生产版本 ``` ### 新建模块流程 (CLI) 快速生成样板代码: ```bash cd server # 生成完整的 CRUD 资源 (包含 Module, Controller, Service, DTO, Entity) npx nest g resource modules/product # 仅生成特定部分 npx nest g module modules/order npx nest g controller modules/order npx nest g service modules/order ``` ### 环境变量配置 在 server/ 根目录创建 .env 文件: ```sh ## 服务端口 PORT=3000 ## 微信小程序配置 WX_APP_ID=你的AppID WX_APP_SECRET=你的AppSecret ## JWT 密钥 JWT_SECRET=your-super-secret-key ``` 在代码中使用 @nestjs/config 读取环境变量: ```typescript import { ConfigService } from '@nestjs/config'; // 在 Service 中注入 constructor(private configService: ConfigService) {} getWxConfig() { return { appId: this.configService.get('WX_APP_ID'), secret: this.configService.get('WX_APP_SECRET'), }; } ``` ### 标准响应封装 建议使用拦截器 (Interceptor) 统一 API 响应格式: ```typeScript // src/common/interceptors/transform.interceptor.ts import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common'; import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; export interface Response { code: number; data: T; message: string; } @Injectable() export class TransformInterceptor implements NestInterceptor> { intercept(context: ExecutionContext, next: CallHandler): Observable> { return next.handle().pipe( map((data) => ({ code: 200, data, message: 'success', })), ); } } ``` 在 main.ts 中全局注册: ```typescript app.useGlobalInterceptors(new TransformInterceptor()); ``` ### 微信登录后端实现 ```typescript // src/modules/auth/auth.service.ts import { Injectable, UnauthorizedException } from '@nestjs/common'; import { HttpService } from '@nestjs/axios'; import { ConfigService } from '@nestjs/config'; import { lastValueFrom } from 'rxjs'; @Injectable() export class AuthService { constructor( private httpService: HttpService, private configService: ConfigService, ) {} async code2Session(code: string) { const appId = this.configService.get('WX_APP_ID'); const secret = this.configService.get('WX_APP_SECRET'); const url = `https://api.weixin.qq.com/sns/jscode2session?appid=${appId}&secret=${secret}&js_code=${code}&grant_type=authorization_code`; const { data } = await lastValueFrom(this.httpService.get(url)); if (data.errcode) { throw new UnauthorizedException(`微信登录失败: ${data.errmsg}`); } return data; // 包含 openid, session_key } } ``` ### 异常处理 使用全局异常过滤器 (Filter) 统一错误响应: ```typescript // src/common/filters/http-exception.filter.ts import { ExceptionFilter, Catch, ArgumentsHost, HttpException } from '@nestjs/common'; import { Response } from 'express'; @Catch(HttpException) export class HttpExceptionFilter implements ExceptionFilter { catch(exception: HttpException, host: ArgumentsHost) { const ctx = host.switchToHttp(); const response = ctx.getResponse(); const status = exception.getStatus(); const exceptionResponse = exception.getResponse(); response.status(status).json({ code: status, message: typeof exceptionResponse === 'string' ? exceptionResponse : (exceptionResponse as any).message, data: null, }); } } ``` 在 main.ts 中注册: ``` app.useGlobalFilters(new HttpExceptionFilter()); ``` ### 数据库 (Drizzle ORM) 推荐使用 [Drizzle ORM](https://orm.drizzle.team/),已预安装。 ### 类型校验 (Zod) 项目集成了 [Zod](https://zod.dev/) 用于运行时类型校验。 #### 定义 Schema ```typescript import { z } from 'zod'; // 基础类型 const userSchema = z.object({ id: z.number(), name: z.string().min(1).max(50), email: z.string().email(), age: z.number().int().positive().optional(), }); // 从 schema 推导 TypeScript 类型 type User = z.infer; ``` #### 请求校验 ```typescript // src/modules/user/dto/create-user.dto.ts import { z } from 'zod'; export const createUserSchema = z.object({ nickname: z.string().min(1, '昵称不能为空').max(20, '昵称最多20个字符'), avatar: z.string().url('头像必须是有效的URL').optional(), phone: z.string().regex(/^1[3-9]\d{9}$/, '手机号格式不正确').optional(), }); export type CreateUserDto = z.infer; // 在 Controller 中使用 @Post() create(@Body() body: unknown) { const result = createUserSchema.safeParse(body); if (!result.success) { throw new BadRequestException(result.error.errors); } return this.userService.create(result.data); } ```