# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Build & Development

```bash
# WeChat mini-program
npm run dev:weapp          # Watch/dev build → weapp/
npm run build:weapp        # Production build → weapp/

# H5 (web)
npm run dev:h5             # Watch/dev build → build/
npm run build:h5           # Production build → build/
```

Output directories are determined by `TARO_ENV`: for H5 it's `build/`, for all other platforms it matches the platform name (e.g., `weapp/`).

No test or lint scripts are configured. ESLint (`eslint-config-taro`) and Stylelint are installed but must be run manually.

## Architecture

This is a **Taro 3.6** cross-platform app using **React 18** (concurrent mode), **TypeScript**, and **Webpack 5**. It targets WeChat, Alipay, DingTalk, JD, Kwai, QQ, Baidu, Toutiao mini-programs, and H5 web.

### UI Library: `@antmjs/vantui`

The UI library is `@antmjs/vantui` (v3), a Taro-compatible port of Vant. Components are imported from `@antmjs/vantui` directly (e.g., `import { Button } from '@antmjs/vantui'`). The `babel-plugin-import` in `babel.config.js` handles on-demand style imports.

### Page Structure

Each page lives in `src/pages/<name>/` and consists of three files:

- `index.tsx` — page component (functional or class)
- `index.config.ts` — page-level config (title, navigation style, etc.)
- `index.less` — page styles

Pages must be registered in `src/app.config.ts` under the `pages` array.

### Path Alias

`@` maps to `src/` (configured in `config/index.js`).

### Theme & Global Styles

- `src/app.less` — global app styles (imported in `app.ts`)
- `src/styles/index.less` — theme variable overrides for Vant UI. It imports `@antmjs/vantui/es/style/var.less` and uncomments/redefines Less variables to customize the theme. This file is injected into every `.less` file via Less `modifyVars` (configured in `config/index.js` under both `mini.lessLoaderOption` and `h5.lessLoaderOption`).

### Webpack Customizations (`config/webpack/`)

- **`commonChain.js`** — Overrides Taro's default `script` rule `exclude` so that Babel also processes these `node_modules` packages: `taro`, `inversify`, `@antmjs`, `react-spring`, `recoil`, `buffer`, `qrcode`. Do NOT include "taro" in the project name or all node_modules will be recompiled.
- **`miniChain.js`** — Applied to mini-program builds. Adds:
  - `MiniFixPlugin` — fixes path param encoding differences between WeChat/Douyin and Alipay/DingTalk
  - `GlobalFixPlugin` — polyfills the `global` variable for Alipay, DingTalk, and Baidu mini-programs
- **`h5Chain.js`** — Applied to H5 builds. Adds `H5FixPlugin` for H5 compatibility fixes.

### Build Config Layers

`config/index.js` exports a function that merges the base config with `config/development.js` or `config/production.js` based on `NODE_ENV`. Environment variables:

| Variable | Purpose | Default |
|---|---|---|
| `TARO_ENV` | Target platform (`weapp`, `h5`, `alipay`, etc.) | `weapp` |
| `NODE_ENV` | Build mode | `production` |
| `API_ENV` | API environment selector | `real` |

- `designWidth: 750` — all px values are converted to rpx at this ratio
- `@tarojs/plugin-framework-react` is configured with `reactMode: "concurrent"` (React 18 concurrent features)
- `browserslist` targets iOS 9+ for production

### App Entry

- `src/app.ts` — class-based App component, renders `this.props.children` (the current page)
- `src/app.config.ts` — defines routes (`pages` array), window config, and app-level settings
- `src/index.html` — H5 entry HTML template

## VantUI Component API Reference (v3.7)

This project uses `@antmjs/vantui` v3.7. The official docs: https://antmjs.github.io/vantui/main/

**Always check the actual type definitions** in `node_modules/@antmjs/vantui/types/` when unsure about props. Key API notes for commonly used components:

### Search
- `shape`: `'square'` (default) | `'round'`
- `background`: background color string (default `#FFFFFF`)
- `onFocus`, `onBlur`, `onChange`, `onSearch`, `onClear`, `onCancel` — event callbacks
- `leftIcon`, `rightIcon` — icon name or image URL
- `showAction` + `actionText` — right-side action button

### Swiper (NOT the same as Taro's native Swiper)
- `autoPlay` — auto-play interval in ms (0 = disabled). **Not** `autoplay`
- `loop` — infinite loop mode. **Not** `circular`
- `paginationColor` — indicator dot color. **Not** `indicatorColor`
- `paginationVisible` — show/hide pagination dots
- `duration` — animation duration (default 500)
- `width` / `height` — card dimensions
- **No** `indicatorActiveColor` prop — only one `paginationColor`
- `onChange` callback: `(currPage: number) => void` (not an event object)

### SwiperItem
- Minimal component, only `className` and `children`. No special props.

### Grid / GridItem
- `Grid`: `columnNum`, `border`, `gutter`, `square`, `clickable`, `center`, `iconSize`
- `GridItem`: `icon` (string), `text` (ReactNode), `iconColor`, `dot`, `info`/`badge`, `url` + `linkType`

### Tabs / Tab (v3.7)
- `Tabs`:
  - `active` — current tab index (number | string)
  - `sticky` — enable sticky mode
  - `offsetTop` — sticky offset in px. **Not** `stickyOffsetTop`
  - `color`, `titleActiveColor`, `titleInactiveColor` — color props
  - `type`: `'line'` | `'card'`
  - `swipeable` — enable swipe gesture switching
  - `animated` — enable tab switch animation
  - `ellipsis` — text overflow ellipsis (default true)
  - **No** `lineWidth` or `lineHeight` props
  - `onChange`: `(e: { detail: { index: number; name?: string; title?: string } }) => void`
- `Tab`: `title` (ReactNode), `name` (identifier), `dot`, `info`, `disabled`, `titleStyle`

### Image (VantUI Image, not Taro's native Image)
- `src`, `fit` (`'contain' | 'cover' | 'fill' | 'widthFix' | 'heightFix' | 'none'`), `width`, `height`, `radius`
- `round` — circular image (boolean)
- `lazyLoad`, `showError`, `showLoading`
- `renderLoading`, `renderError` — custom render slots

### Common VantUI pattern: Taro's native components vs VantUI components
- Use `@tarojs/components` for: `View`, `Text`, `ScrollView`, `Image` (native, simpler)
- Use `@antmjs/vantui` for: styled UI components (Search, Swiper, Tabs, Grid, Button, etc.)
- VantUI component props extend Taro's `ViewProps`, so `onClick`/`onTap` and style props are always available

## Custom TabBar (`src/custom-tab-bar/`)

This project uses Taro's **custom tabBar** mechanism (`tabBar.custom: true` in `app.config.ts`). Key rules:

### Component location
- The custom tabBar component MUST be at `src/custom-tab-bar/index.tsx` — this is a Taro convention, not configurable
- Taro automatically renders this component on every tab page; do NOT import `<CustomTabBar />` in individual pages

### Do NOT use CoverView
- `CoverView` / `CoverImage` have known bugs on real devices: invisible, click events don't bubble
- Use regular `<View>` with `position: fixed; bottom: 0; z-index: 999` instead

### Active tab state
- Taro creates a **new instance** of the tabBar component on each page, so `useState` resets on every tab switch
- Derive the active tab from `Taro.getCurrentPages()` at render time — NOT from component state
- Pattern: `const route = Taro.getCurrentPages()[last]?.route; const activeKey = TAB_LIST.find(t => route.includes(t.key))`

### Tab switching
- Use `Taro.switchTab({ url: pagePath })` — this matches native tabBar behavior
- The `pagePath` format must match the `tabBar.list[].pagePath` in `app.config.ts`

### Center "publish" button
- The publish button is a UI-only element not in `tabBar.list`
- It triggers a VantUI `<Popup>` rendered as a sibling of the tabBar, NOT nested inside it
- The popup state can use `useState` since it's ephemeral and not shared across tabs
