# 组件规范，必须绝对遵守此规则。

## 组件库概述

本组件库按照前缀进行分组，包含七种类型的组件：

1. **Element Plus 包装组件 (Ui 前缀)**：以 `Ui` 开头，包装 Element Plus 组件库，完全透传原组件功能
2. **Vant 包装组件 (Vt 前缀)**：以 `Vt` 开头，包装 Vant 移动端组件库，完全透传原组件功能
3. **Ant Design Vue 包装组件 (At 前缀)**：以 `At` 开头，包装 Ant Design Vue 组件库，完全透传原组件功能
4. **重型第三方整合组件 (Tp 前缀)**：以 `Tp` 开头，基于 ECharts、Monaco、Three.js 等重型第三方库进行深度封装与扩展，提供超出简单包装的自定义逻辑
5. **自研组件 (Cw 前缀)**：以 `Cw` 开头的自主研发组件，实现通用业务需求和自定义功能，具有较高的复用性
6. **业务特定组件 (Bs 前缀)**：以 `Bs` 开头的业务特定组件，面向特定业务场景，复用性较低，通常只在特定页面或业务中使用
7. **页面级业务组件 (Page 前缀)**：以 `Page` 开头，面向具体业务场景的页面级组件，可能包含多个基础组件的组合与业务逻辑

## 基本规范

> **重要说明**：除非特别注明，所有针对 `Cw` 自研组件的规范同样适用于 `Tp` 重型第三方整合组件、`Bs` 业务特定组件和 `Page` 页面级业务组件，包括但不限于：
> - Tailwind CSS 样式规范（只能使用 Tailwind CSS，禁止自定义 CSS）
> - Design Tokens 色值使用规范（必须使用 tokens 变量，禁止 hardcode 色值；`pnpm run check:tokens` 强制检查 `Cw*`）
> - 样式和文本国际化规范（通过 styles 和 texts props 传入）
> - Focus 和键盘支持规范（可交互组件必须支持聚焦和键盘操作）
> - 纯函数抽离规范（复杂逻辑必须抽离到 functions 目录）
> - 组件导出格式规范（index.ts 导出格式）
> - Stories 文件格式规范（Storybook 分类格式）

1. 第三方包装组件完全透传给对应的组件，我们只做中转
2. 第三方包装组件不要自定义任何类型，因为所有组件的类型都可以从第三方库中获取
3. 每个组件都需要stories
4. 需要自定义滚动条的容器必须使用自研组件 `CwDiv`，与原生 `div` 用法一致，可直接替换；保持滚动体验与 macOS Chrome 一致
5. 所有组件都放在src/components目录下
6. 一个组件都单独生成一个文件夹，包含一个index.ts、一个types.ts、一个stories文件、组件文件，以及styles.ts和texts.ts文件（用于样式和文本国际化）
7. 自研组件（Cw/Tp/Page 开头）必须有自定义逻辑和类型定义
8. 所有需要双向绑定的组件在 stories 中必须使用 v-model 而不是直接设置 modelValue 属性，需要在 render 函数中通过 ref 创建响应式变量并使用 v-model 绑定
9. **所有可交互的自研组件必须支持聚焦和键盘操作（强制）**
10. **图标使用规范（强制）**：所有图标必须使用 `CwIcon` 组件，禁止直接内嵌 SVG 代码、SVG 组件或其他图标组件（如 Element Plus 的图标组件、Vant 的图标组件等）

## rem / token 尺寸规范（Cw 自研组件 + 消费方，强制）

vue-ui 组件与 **Chat 业务层**（`packages/chat.zengchaowu.com/src`）的 UI 尺寸须随 **根字号** 同比缩放，禁止把视觉尺寸写死在 px。Chat 视口与移动端交互区分见 `packages/chat.zengchaowu.com/README.md`。

### 设计稿 rem 换算

- **rem 数值按 16px 设计稿定义**（如 14px → `0.875rem` / `text-sm`，20px 图标 → `size: 1.25`）
- 工具函数：`designPxToRem(n)`、`resolveDesignCssSize()`（`src/utils/cwRemSize.ts`）
- **实际渲染像素** = `rem × 当前根字号`；Chat 独立打开时根字号会变，故必须用 rem/token，勿写 px

### Chat 根字号（仅独立打开）

| 视口 | 根字号 | 配置位置 |
|------|--------|----------|
| 宽屏 ≥768px | **6px** | `chat-viewport.css` → `--chat-root-font-size` 默认值 |
| 窄屏 ≤767px | **30px** | 同上，`@media (max-width: 767px)` |

- 仅 **独立 Chat** 在 `html.chat-root-viewport-lock` 上生效；**嵌入 qiankun 不改宿主 html**
- **唯一允许写根字号 px 的文件**：`packages/chat.zengchaowu.com/src/styles/chat-viewport.css`
- 断点 / inject 键：`constants/chatLayoutViewport.ts`（`CHAT_LAYOUT_BELOW_MD_MEDIA` 等）；**勿**在 TS 再重复定义 6/30

### 字号（Tailwind / styles.ts）

| 设计稿 | 用法 |
|--------|------|
| 12px | `text-xs` |
| 14px | `text-sm` |
| 16px | `text-base` |
| 10、12、14、16、18px 等 | `[font-size:var(--font-size-*)]`（见 `tokens.css`） |

```ts
// ❌ Tailwind v3 会把 [font-size:var(--font-size-*)] 编译成 color，字号不生效
'... [font-size:var(--font-size-caption)] ...'
// ✅
'... text-sm ...'
'... [font-size:var(--font-size-caption)] ...'   // 12px @16
```

**禁止** `text-[Npx]`。新增语义字号先在 `tokens.css` 增加 `--font-size-*`。

### 图标 / 头像（CwIcon、CwAvatar、CwFileIcon）

```vue
<!-- ❌ 把设计稿 px 当 rem 直填（20px 应写 1.25，不是 20） -->
:styles="{ size: 20 }"

<!-- ✅ rem（设计稿 20px → 1.25） -->
<CwFileIcon :size="2" />
:styles="{ size: 1.25 }"
:styles="{ size: 0 }"   <!-- 尺寸交给 em / Tailwind -->
```

- **`size`**：边长（rem，相对当前根字号）；**`size: 0`** 表示不注入 inline 宽高
- 换算：设计稿 px ÷ 16（`designPxToRemNum(n)` / `designPxToRem(n)`）
- **勿把设计稿 px 整数当 rem 传入**（迁移后常见 bug）：
  - ❌ `mineAvatarSize: 32`、`:size="16"`、`avatarStyles(32)` → 窄屏会变成 32rem
  - ✅ 32px → `2` 或 `CW_CHAT_SHELL_MINE_AVATAR_SIZE`；16px → `1` 或 `CW_FILE_ICON_SIZE_COMPOSER_PENDING`
- 非 UI 语义字段（如 `UploadFile.size` 字节数、并查集 `element.size`、表单 `size="small"`）不受此限

### 布局尺寸

- 优先 Tailwind rem 刻度：`h-7`、`gap-2`、`max-w-sm`
- 语义尺寸：`min-h-[var(--size-touch-target)]` 等 `--size-*` token
- 无 token 时用 rem 字面量：`w-[6.75rem]`，勿 `w-[108px]`

### 允许保留 px

- **DOM 实测**：`getBoundingClientRect`、选区/拖拽、弹层定位、裁剪框坐标
- **Safe area**：仅用 `var(--app-safe-area-*)` / `var(--app-chrome-safe-*)`；`bootstrapAppSafeArea()` + `html[data-app-chrome]`
- **Storybook**（`*.stories.*`）、**TpHeroScene**
- **`chat-viewport.css`** 根字号与 `@media` 断点
- **`--radius-*`**：圆角 token 一般为 px，不随字号缩放

### 校验

```bash
cd packages/vue-ui && pnpm run check:no-px      # prebuild 会跑
cd packages/chat.zengchaowu.com && pnpm run check:no-px   # type-check 会跑
```

## 层叠与 z-index 规范（Cw 自研组件，强制）

目标：**默认不写 `z-index`**，视觉上下关系尽量交给 **DOM 结构**（同一父级下，**后出现的兄弟节点**后绘制，自然盖在先出现的节点之上）。

### 语义 token（`tokens.css`）

**禁止**在组件内写裸数字、`z-10`、`z-[100]`、内联 `zIndex: 120` / `Z_INDEX_TOKEN.*` 等；**必须**使用下表语义 token（Tailwind：`z-[var(--z-index-*)]`）：

| Token | 值 | 用途 |
| --- | --- | --- |
| `--z-index-base` | 0 | 同层垫底（空状态、选中 scrim 底） |
| `--z-index-above` | 1 | 局部浮于兄弟（NavBar 槽位、StickyTabs、行内关闭钮） |
| `--z-index-above-high` | 2 | 高于 `above`（如标题栏控件区） |
| `--z-index-sticky` | 10 | 吸顶表头、行内 loading 遮罩、Chrome 标签激活态 |
| `--z-index-float` | 20 | Split 分隔条、多选勾选、表格固定列 body |
| `--z-index-fixed-column-header` | 30 | 表格固定列表头 |
| `--z-index-fixed-column-shadow` | 40 | 表格固定列阴影 pseudo |
| `--z-index-scrollbar` | 45 | 滚动视口内自绘滚动条（CwDiv 等），高于表格固定列栈 |
| `--z-index-scene-overlay` | 50 | 全屏场景层（Tp 可视化，同尺度） |
| `--z-index-modal` | 100 | Dialog / Sheet / Popup / ContextMenu / 全屏预览（modal 档；同档顺序靠 DOM） |
| `--z-index-toast` | 200 | Toast / Hud（全局反馈层，高于 modal 档） |

CI：`pnpm run check:tokens` 会扫描 Cw* 组件中的违规写法。

### 浮层栈（Overlay Stack）

实现见 `src/constants/cwOverlayStack.ts`、`src/constants/overlayBodyScrollLock.ts`。

浮层有两套独立能力，**不要混为一谈**：

| 能力 | 作用 | 典型 API |
| --- | --- | --- |
| **层叠（视觉顺序）** | 谁盖在谁上面 | modal 档：`useAppendToBody({ overlayStack: true })`、`scheduleBringOverlayHostToFront`；Toast/Hud 固定 toast 档 z-index |
| **Dismiss 栈** | click-outside / Escape **一次只关栈顶一层** | `useCwOverlayLayer`（Dialog/Sheet 内）、`bindPopupOverlayLayer`（Popup 内） |

只做层叠、不进 Dismiss 栈的浮层，其 click 会「穿透」到下层已注册的 Dialog/Sheet。**凡参与 click-outside/Escape 关层的浮层，必须走三壳之一；业务勿直接散写 `useCwOverlayLayer`。**

#### 三壳 + variant（唯一入口）

| 壳 | variant / 用途 | Dismiss |
| --- | --- | --- |
| **CwDynamicDialog** | `modal`（默认）/ `fullscreen`（图库预览）/ `toast` / `hud` | modal、fullscreen：是；toast、hud：否 |
| **CwDynamicSheet** | 底滑/侧滑 Sheet | 是 |
| **CwPopup** | Select/Dropdown/Tooltip/**ContextMenu** 等锚点浮层 | 是（`bindPopupOverlayLayer`） |

- 声明式单层：`CwDynamicDialogLayer`（预览、Toast、Hud）
- 命令式：`showDynamicDialog` / `showDynamicSheet` / `createCwPopup`
- **ContextMenu** = `CwPopup` + `virtualReference` + `dismissGraceMs` + `closeOnScroll`（无箭头）
- **全屏预览** = `CwDynamicDialogLayer` + `variant: 'fullscreen'`
- **Toast / Hud** = `CwDynamicDialogLayer` + `variant: 'toast' | 'hud'`

#### 底层机制（仅三壳内部使用）

- **`useCwOverlayLayer`**：Dialog/Sheet/Layer 内 panel + traps
- **`bindPopupOverlayLayer`**：Popup 内自动注册
- **`useAppendToBody({ overlayStack: true })`**：层叠置顶

#### 谁需要注册 Dismiss？

| 类型 | 是否注册 | 接入方式 |
| --- | --- | --- |
| Dialog / Sheet | 是 | `CwDynamicDialogInner` / `CwDynamicSheetInner` 内 `useCwOverlayLayer` |
| 全屏预览 | 是 | `CwDynamicDialogLayer` + `variant: 'fullscreen'` |
| Popup 系（含 ContextMenu） | 是 | `createCwPopup` → `bindPopupOverlayLayer` |
| Toast / Hud | 否（`maskClosable` 时 Hud 临时进栈） | `CwDynamicDialogLayer` + `dismissStack: false`；Hud `maskClosable: true` 时由 Dismiss 栈关层 |

#### Popup / ContextMenu 挂载

- **modal 档浮层一律挂 `document.body`**（`createCwPopup` / `showCwContextMenu` / Dialog ephemeral mount）。
- 同档层叠靠 **DOM 顺序** + `scheduleBringOverlayHostToFront`；Dismiss 走全局栈。

#### Dismiss 规则摘要

- 层根：`data-cw-overlay-layer` + `data-cw-overlay-layer-id`；可见时 `data-cw-overlay-active`。
- **栈顶** = 文档序最后一个 `[data-cw-overlay-layer][data-cw-overlay-active]`（嵌套时跳过仍被子层占用的祖先）。
- **边界** = `panel` DOM + `traps`（如 Popup 的 trigger/bridge）。
- **禁止**在 backdrop 上单独 `@click` 关层（与栈双关）；**禁止**手写 `contains(target)` 函数式 hit test。
- click-outside 在 **capture 阶段** 判定（Dialog/Sheet 内容区 `@click.stop` 会挡住 bubble）；同一轮 click 仍会继续传到 panel 内菜单项等目标。

#### 命令式浮层（Toast / Hud / 图片预览 / Dialog）

`showCwToast`、`showHud`、`showCwImageGalleryPreview`、`showDynamicDialog` 均为 **按需 mount**：打开时创建 DOM 与 Vue 实例，关闭动画结束后 **unmount 并移除容器**。每次 `visible === true` 时须 `scheduleBringOverlayHostToFront`，保证与同档 modal 层叠顺序正确。

#### 反模式

- ❌ 仅 `overlayStack: true` 的全屏层，却不进 Dismiss 栈（多层弹窗必踩坑）
- ❌ 业务 SFC 里直接 `useCwOverlayLayer` 或自写 append/dismiss
- ❌ 为嵌套 Select 单独提高 z-index

- **modal 档**：`CW_OVERLAY_MODAL_Z_CLASS`（`z-[var(--z-index-modal)]`），同档内顺序由 DOM 末尾决定
- **toast 档**：`CW_OVERLAY_TOAST_Z_CLASS`（`z-[var(--z-index-toast)]`），仅 Toast / Hud 使用，始终盖在 modal 档之上

### 何时允许使用 z-index

仅当组件（或子树根）**脱离常规文档流、且必须压过页面其它区域**时，才在该**浮层根节点**使用语义 z-index token，例如：

- **modal 档**（`--z-index-modal`）：`CwPopup`、`CwDynamicDialog`（modal/fullscreen）、`CwDynamicSheet`、`CwContextMenu`、`CwImageGalleryPreview`
- **toast 档**（`--z-index-toast`）：`CwToast`、`CwHud`（须始终可见于任意 Dialog/Sheet 之上）
- **固定在视口的辅助浮层**：如 `CwMessageInput` 的 `@` 提及面板等

**浮层内部**（遮罩、标题、正文、按钮区等）**禁止**再为「谁在上」而叠加 z-index：用 **DOM 顺序**；装饰层用 **`pointer-events-none`**。

### 窄幅例外（非全屏弹层）

在**滚动视口内部**，仅靠 DOM 无法满足 `sticky` / 固定列 / 伪元素阴影时，使用 **`--z-index-sticky` ~ `--z-index-scrollbar`**，例如 `CwStickyTabs`、`CwDataTable`；`CwDiv` 自绘滚动条统一用 **`--z-index-scrollbar`**，须高于固定列 body/header/shadow 以便拖拽。

**动态栈深**（如 `CwStack`）：同一父级下用 **DOM 顺序** 叠层，**禁止**按层递增写内联 `zIndex` 数字。

上述例外**不得**滥用到普通布局组件。

### 右键菜单（showCwContextMenu）

- **仅命令式 API**：`showCwContextMenu({ x, y, items, onSelect, onClose })`；每次打开 **ephemeral mount**（body + 全屏透明 scrim + Popup 面板）。
- **不要在 `v-for` 里为每一行各挂一个菜单组件**；列表在 `contextmenu` 里解析行数据后调用 `showCwContextMenu`。
- 全屏预览等组件只 emit **`onContextMenu({ clientX, clientY, index, item })`**，由业务层拼菜单项并调用 `showCwContextMenu`。
- body 滚动锁：Dialog / Sheet / Hud(mask) 共用 `acquireOverlayBodyScrollLock` / `releaseOverlayBodyScrollLock`。

## Focus 和键盘支持规范（强制）

所有可交互的自研组件（Cw/Tp/Page 前缀）必须支持聚焦和键盘操作，确保无障碍性和用户体验。

### 组件分类

根据组件功能，分为三种类型：

**1. 直接交互组件**（需要完整的 focus 和键盘支持）
- 用户可以与其直接交互
- 示例：`CwButton`、`CwInput`、`CwCheckbox`、`CwRadio`、`CwSlider`、`CwSwitch`
- 必须遵循完整的 focus 和键盘支持规范

**2. 容器组件**（不需要直接 focus，但需要键盘支持）
- 包装器组件，通过插槽渲染内容
- 示例：`CwPopup`、`CwDynamicDialog`
- 特点：
  - 不需要 tabindex 和 focus 管理（由内部元素管理）
  - 必须支持键盘操作（如 ESC 关闭）
  - 焦点由 trigger 或其他内部可聚焦元素管理

**3. 展示组件**（无交互性）
- 纯展示组件，不需要键盘支持
- 示例：`CwAvatar`、`CwCard`、`CwBadge`

### 组件聚焦类型（重要）

对于"直接交互组件"，有两种聚焦方式：

**类型1：整个容器聚焦**（适用于无标签或标签为组件整体的组件）
- 示例：`CwButton`、`CwSlider`
- 聚焦样式：`rootFocused`

**类型2：内部元素聚焦**（适用于带标签文字的组件或开关）
- 示例：`CwCheckbox`、`CwRadio`、`CwSwitch`
- 聚焦样式：`checkboxFocused`、`radioFocused`、`trackFocused`
- 原因：焦点应该显示在图标/开关按钮上，而不是整个容器（包括文字），更符合用户预期

### 1. 聚焦支持（强制）

所有可聚焦的组件必须：

1. **添加 tabindex 属性（必须使用 computed 函数处理 disabled 状态）**：
   ```vue
   <!-- 类型1：整个容器可聚焦（适用于组件本身就是一个整体的情况，如按钮、开关） -->
   <div 
     :tabindex="tabindex"
     @focus="handleFocus"
     @blur="handleBlur"
   >
   
   <!-- 类型2：内部元素可聚焦，外部容器不聚焦（适用于带标签的组件，如 checkbox、radio） -->
   <div :class="styles.root">  <!-- 外层容器不聚焦 -->
     <div 
       :class="[styles.checkbox, { [styles.checkboxFocused]: isFocusVisible && !disabled }]"
       :tabindex="tabindex"
       @focus="handleFocus"
       @blur="handleBlur"
     >
   ```
   
   ```typescript
   // 必须使用 computed 函数定义 tabindex
   const tabindex = computed(() => (props.disabled ? undefined : 0))
   ```
   
   **重要**：
   - **必须使用 computed 函数**：`const tabindex = computed(() => (props.disabled ? undefined : 0))`
   - **统一使用 `undefined`**：当 `disabled` 为 `true` 时，`tabindex` 必须为 `undefined`
   - **变量名统一**：使用小写 `tabindex`（不是 `tabIndex`）
   - 对于带标签的组件（如 Checkbox、Radio），焦点应该应用在图标元素本身，而不是整个容器（包括文字）

2. **添加 outline-none 样式**（移除浏览器默认聚焦样式）：
   ```typescript
   // styles.ts
   root: 'outline-none focus:outline-none',
   ```

3. **添加自定义聚焦样式（必须从 `focusStyles.ts` 引用，禁止手写 ring 类）**：
   ```typescript
   // styles.ts
   import { INTERACTIVE_FOCUS_CLASS, FORM_CONTROL_FOCUS_CLASS } from '../../utils/focusStyles'

   // 交互控件（Button / Checkbox / Tab 等）
   rootFocused: INTERACTIVE_FOCUS_CLASS,

   // 表单控件 wrapper（Input / Select / Textarea / NumberInput 等）
   inputWrapperFocus: FORM_CONTROL_FOCUS_CLASS,
   ```

4. **添加聚焦状态管理（使用 useFocusVisible）**：
   ```typescript
   import { useFocusVisible } from '../../utils/useFocusVisible'
   
   const { isFocusVisible, handleFocus: handleFocusVisibleFocus, handleBlur: handleFocusVisibleBlur } = useFocusVisible()
   
   const handleFocus = (event: FocusEvent) => {
     if (props.disabled) return
     handleFocusVisibleFocus(event)
   }
   
   const handleBlur = () => {
     handleFocusVisibleBlur()
   }
   ```
   
   **重要**：
   - **必须使用 `useFocusVisible` composable**：这确保了只在键盘导航（Tab 键）时显示 focus 样式，鼠标点击时不显示
   - **符合成熟组件库的行为**：与 Material-UI、Ant Design 等组件库保持一致
   - **handleFocus 必须接收 FocusEvent**：用于判断是否是键盘导航导致的聚焦

5. **应用聚焦样式（必须排除 disabled 状态）**：
   ```vue
   <!-- 类型1：聚焦样式应用在根容器 -->
   <div :class="[styles.root, { [styles.rootFocused]: isFocusVisible && !disabled }]">
   
   <!-- 类型2：聚焦样式应用在内部可聚焦元素（适用于带标签的组件） -->
   <div :class="styles.root">
     <div :class="[styles.checkbox, { [styles.checkboxFocused]: isFocusVisible && !disabled }]">
   ```
   
   **重要**：
   - 聚焦样式只在 `isFocusVisible && !disabled` 时应用（配合 `useFocusVisible`，鼠标点击不显示）
   - 对于带标签的组件（如 Checkbox、Radio），聚焦样式应该应用在图标元素上（如 `checkboxFocused`），而不是整个容器（root）
   - 验证逻辑：如果组件有标签文字，但整个容器显示了聚焦环，则需要将焦点调整到内部元素

6. **handleFocus 必须检查 disabled 状态**：
   ```typescript
   const handleFocus = (event: FocusEvent) => {
     if (props.disabled) return
     handleFocusVisibleFocus(event)
   }
   ```
   
   **重要**：
   - `handleFocus` 函数必须首先检查 `disabled` 状态，如果已禁用则直接返回
   - `handleFocus` 必须接收 `FocusEvent` 参数，并传递给 `handleFocusVisibleFocus`

### 2. 键盘支持（强制）

所有需要键盘操作的组件必须：

1. **监听键盘事件**：
   ```vue
   <div @keydown="handleKeyDown">
   ```

2. **实现键盘事件处理函数（必须首先检查 disabled 状态）**：
   ```typescript
   const handleKeyDown = (event: KeyboardEvent) => {
     // 重要：disabled 状态下不允许键盘操作
     if (props.disabled) return
     
     switch (event.key) {
       case 'Enter':
       case ' ': // Space
         event.preventDefault()
         // 执行操作
         break
       // 根据组件功能实现相应的键盘支持
     }
   }
   ```
   
   **重要**：`handleKeyDown` 函数必须首先检查 `props.disabled`，如果已禁用则直接返回，不执行任何键盘操作。

3. **必须阻止默认行为**：
   ```typescript
   event.preventDefault()
   ```

### 3. ARIA 属性支持（强制）

所有组件必须提供完整的 ARIA 支持：

1. **添加 role 属性**：
   ```vue
   :role="'button'"  // 或 'slider'、'combobox' 等
   ```

2. **添加 aria 属性**：
   ```vue
   :aria-disabled="disabled"
   :aria-label="label"
   :aria-describedby="descriptionId"
   ```

### 4. 组件分类规范

不同组件类型的键盘支持要求：

**按钮类组件（Button）**：
- `Enter` 或 `Space`：触发点击
- 示例：`CwButton`、`CwIconButton`

**输入类组件（Input、Select、Slider）**：
- `ArrowLeft`/`ArrowRight`：左右导航
- `ArrowUp`/`ArrowDown`：上下导航
- `Home`/`End`：跳到开始/结束
- `Escape`：关闭/取消
- 示例：`CwSlider`、`CwSelect`、`CwInput`

**列表类组件（List、Menu）**：
- `ArrowUp`/`ArrowDown`：导航
- `Enter`：选择
- `Home`/`End`：跳到开始/结束
- 示例：`CwDropdown`、`CwMenu`

### 5. 聚焦视觉样式统一规范（强制）

**禁止**手写 `ring-2 ring-[var(--brand-color-focus)] ring-offset-1` 等旧样式；**禁止**使用已废弃的 `--brand-color-focus` 作为聚焦环颜色（该 token 为浅色品牌色，不适合聚焦）。

**统一入口**：`src/utils/focusStyles.ts`（组件 `styles.ts` 中 import 使用，勿复制字符串）

| 场景 | 常量 | 实际效果 |
|------|------|----------|
| 带 `border` 的表单控件 wrapper | `FORM_CONTROL_FOCUS_CLASS` | `!border-[var(--focus-ring)]`（须 `!` 覆盖常态 border-color，勿再加 ring） |
| 无独立边框的交互控件 | `INTERACTIVE_FOCUS_CLASS` | `ring-1 ring-[var(--focus-ring)] ring-offset-0` |
| 菜单项等原生 `focus-visible` | `FOCUS_VISIBLE_INTERACTIVE_CLASS` | 同上，带 `focus-visible:` 前缀 |
| 原生 `<input>` / `<select>` 的 `:focus` | `NATIVE_FOCUS_CLASS` | `focus:outline-none focus:!border-[var(--focus-ring)]` |
| 交互控件 + 额外圆角等 | `withInteractiveFocus('rounded-lg')` | 在统一 ring 后拼接 utility |

**Design Token**（`src/assets/tokens/tokens.css`）：

- `--focus-ring`：键盘聚焦色，默认 `var(--brand-color-6)`；暗色主题见 `theme-dark.css`
- 表单控件**不要**再加 `ring`：`inputWrapper` 等常有 `overflow-hidden`，`ring` + `ring-offset` 会与 `border` 叠成「双线」或白边

**适用组件（示例）**：

- 表单：`CwInput`、`CwNumberInput`、`CwTextarea`、`CwSearchBar`、`CwSelect`（内嵌 `CwInput`）
- 交互：`CwButton`、`CwCheckbox`、`CwRadio`、`CwSwitch`、`CwSlider`、`CwTabbar`、`CwTag`
- 浮层项：`CwDropdown` 菜单项、`CwSegment` 选项

**表格内 Checkbox**：多选列须 `fitContent: true`；`CwDataTable` 对 fitContent 列使用 `cellContentFit` / `overflow-visible`，避免外圈被 `overflow-hidden` 裁切。

**选中态 ≠ 聚焦态**：如图节点 `nodeActive`、`LowcodeEngine` 画布选中等业务高亮，不使用上述聚焦常量。

#### 状态样式选择写法（强制）

- 所有状态样式的选择必须使用统一三元嵌套写法，禁止使用 `|| ''` 作为回退：
  - 根容器：`disabled ? styles.rootDisabled : (readonly ? styles.rootReadonly : styles.rootNormal)`
  - 控件容器：`disabled ? styles.inputWrapperDisabled : (readonly ? styles.inputWrapperReadonly : styles.inputWrapperNormal)`
  - 输入/控件本体：`disabled ? styles.inputDisabled : (readonly ? styles.inputReadonly : styles.inputNormal)`
- 聚焦样式为独立类，仅在 `isFocusVisible && !disabled` 为真时叠加，例如：`{ [styles.inputWrapperFocus]: isFocusVisible && !disabled }`
- 禁止在 class 表达式中使用空字符串兜底（如 `styles.xxx || ''`），所有状态类都必须在 `styles` 中事先定义好

### 6. 示例代码

完整的聚焦和键盘支持示例：

```vue
<template>
  <div 
    :class="[
      styles.root,
      disabled ? styles.rootDisabled : styles.rootNormal,
      { [styles.rootFocused]: isFocusVisible && !disabled },
    ]"
    @keydown="handleKeyDown"
    @focus="handleFocus"
    @blur="handleBlur"
    :tabindex="tabindex"
    :role="'button'"
    :aria-disabled="disabled ? 'true' : 'false'"
    :aria-label="texts.button"
  >
    {{ texts.button }}
  </div>
</template>

<script setup lang="ts">
import { computed } from 'vue'
import type { CwButtonProps } from './types'
import { defaultStyles } from './styles'
import { useFocusVisible } from '../../utils/useFocusVisible'

const { isFocusVisible, handleFocus: handleFocusVisibleFocus, handleBlur: handleFocusVisibleBlur } =
  useFocusVisible()

const tabindex = computed(() => (props.disabled ? undefined : 0))

const handleFocus = (event: FocusEvent) => {
  if (props.disabled) return
  handleFocusVisibleFocus(event)
}

const handleBlur = () => {
  handleFocusVisibleBlur()
}

const handleKeyDown = (event: KeyboardEvent) => {
  if (props.disabled) return
  if (event.key === 'Enter' || event.key === ' ') {
    event.preventDefault()
    emit('click')
  }
}
</script>
```

```typescript
// styles.ts
import { INTERACTIVE_FOCUS_CLASS } from '../../utils/focusStyles'

export const defaultStyles: CwButtonStyles = {
  root: 'outline-none focus:outline-none',
  rootNormal: 'cursor-pointer',
  rootDisabled: 'cursor-not-allowed text-[var(--text-color-disabled)] bg-[var(--bg-color-component-disabled)] border-[var(--component-border)]',
  rootFocused: INTERACTIVE_FOCUS_CLASS,
}
```

### 7. Disabled / Readonly 实现清单（强制）

- 行为规则（所有 Data Input 必须遵守）
  - Readonly：可聚焦并显示聚焦环，但不得以任何方式改变值（鼠标、键盘、拖拽、清除、打开弹层、滚轮、步进等全部拦截）
  - Disabled：不可聚焦（tabindex 为 undefined），不响应任何交互，不显示聚焦环

- Props（统一）
  - 必须同时提供：`disabled?: boolean`、`readonly?: boolean`，默认均为 `false`

- 交互拦截（统一入口）
  - 一切可能改值的入口函数首行添加：`if (props.disabled || props.readonly) return`

- ARIA（统一）
  - `:aria-disabled="disabled ? 'true' : 'false'"`
  - `:aria-readonly="readonly ? 'true' : 'false'"`（适用于 checkbox/radio/switch/slider/combobox/input 等角色）

- TabIndex（统一）
  - **必须使用 computed 函数**：`const tabindex = computed(() => (props.disabled ? undefined : 0))`
  - Disabled：`tabindex` 置 `undefined`（统一使用 `undefined`）
  - Readonly：允许聚焦（保留键盘导航/可读性），但不得改值

- 样式类名（统一）
  - 根容器：`rootDisabled`、`rootReadonly`
  - 控件元素：`inputDisabled/inputReadonly`、`checkboxDisabled/checkboxReadonly`、`radioDisabled/radioReadonly`、`switchDisabled/switchReadonly`、`sliderDisabled/sliderReadonly`、`optionDisabled`
  - 选项元素（Select/Cascader/AutoComplete）：`option`（基础）+ `optionNormal`（正常状态）+ `optionSelected`（选中状态）+ `optionDisabled`（禁用状态），必须使用多层三元表达式确保状态互斥
- 颜色与对比度：
  - 禁用态**必须**使用 `text-[var(--text-color-disabled)]`、`border-[var(--component-border)]`、`bg-[var(--bg-color-component-disabled)]` 等 tokens
  - 禁用态**完全禁止**使用透明度（`opacity-*`），必须通过 token 实现禁用视觉效果
  - 只读态保持可读（避免误用禁用色），且禁止通过降低透明度体现只读（例如 `opacity-70`）
  - 所有颜色必须使用 token，禁止使用硬编码色值（如 `#ffffff`、`rgb()`、`rgba()` 等）
  - 状态选择表达式（强制）：`disabled ? disabledClass : (readonly ? readonlyClass : normalClass)`，禁止使用 `|| ''`

- Hover 在移动端的规范（强制）
  - **移动端默认禁止 hover 视觉效果**（包含 `hover:bg-*`、`hover:text-*`、`hover:border-*`、`hover:shadow-*` 等）
  - 仅允许在“明确支持 hover 的设备”上生效，必须使用媒体查询门控：
    - `@media (hover: hover) and (pointer: fine)`（推荐）
  - Tailwind 写法建议（任选其一）：
    - 使用任意变体：`[@media(hover:hover)_and_(pointer:fine)]:hover:bg-[var(--gray-color-1)]`
    - 或在组件中按设备能力切换 class（例如通过 `matchMedia('(hover: hover) and (pointer: fine)')`）
  - **禁止**在通用 class 中直接写裸 `hover:*` 并默认作用于移动端
  - **JS 行为与 Tooltip / 弹出层（强制）**：
    - 依赖 `mouseenter` / `mouseleave` 的 **hover 型触发**（如 `CwPopup` / `CwTooltip` 的 `trigger: 'hover'`）必须与上述门控一致：仅在 `matchMedia('(hover: hover) and (pointer: fine)').matches` 为真时绑定或响应该类事件。
    - **原因**：触摸环境第一次点击常会合成鼠标事件，易误触「悬停才出现」的浮层；统一门控后，触摸为主设备上不会因误合成 `mouseenter` 弹出说明层。
    - **实现约定**：弹出层事件在 `usePopupEvents` 中已按该媒体查询处理；若新增同类组件，须复用 `finePointerHoverMatches()`（`CwPopup/utils/finePointerHoverMedia.ts`）或等价逻辑，禁止在无门控下对通用触发区域绑定纯 hover 打开浮层。
    - **移动端需要显式展示说明**：使用 `trigger: 'click'` / `manual`、或独立文案/UI，不得依赖未门控的 hover 浮层作为唯一说明手段。

- 样式分离规范（强制）
  - **基础样式与状态样式必须分离**：每个元素必须明确区分基础样式（所有状态共享的布局/尺寸）和状态样式（根据状态切换的颜色/cursor/opacity 等）
  - **基础样式**：包含所有状态共享的样式，如尺寸（`w-4 h-4`）、布局（`flex items-center`）、边框宽度（`border-2`）、圆角（`rounded`）等，不包含任何状态相关的样式（颜色、cursor、opacity）
  - **状态样式**：根据组件状态（Normal/Checked/Disabled/Readonly）互斥切换，每个元素在同一时刻只能应用一个状态样式类，禁止在同一元素上同时存在多个互斥的状态类
  - **状态样式组合方式**：
    - 根容器：`root`（基础）+ `rootNormal/rootDisabled/rootReadonly`（状态，互斥）
    - 控件元素：`checkbox`（基础）+ `checkboxNormal/checkboxChecked/checkboxDisabled/checkboxReadonly`（状态，互斥）
    - 选项元素：`option`（基础，布局尺寸）+ `optionNormal/optionSelected/optionDisabled`（状态，互斥），使用多层三元表达式：`disabled ? optionDisabled : (selected ? optionSelected : optionNormal)`
    - 子元素：`icon`（基础，尺寸布局）+ `iconNormal/iconDisabled`（状态，颜色）；`label`（基础，字体大小）+ `labelNormal/labelDisabled`（状态，颜色）
  - **禁止在同一样式类中混用基础样式和状态样式**：如 `checkbox: 'w-4 border-2 cursor-pointer'` 是错误的（`cursor-pointer` 应放在 `checkboxNormal` 状态样式中）
  - **完整示例**（CwCheckbox）：
    ```ts
    // styles.ts - 正确示例
    import { INTERACTIVE_FOCUS_CLASS } from '../../utils/focusStyles'

    export const defaultStyles: CwCheckboxStyles = {
      // 根容器：基础样式 + 状态样式
      root: 'inline-flex items-center gap-2 select-none',  // 基础：布局、间距
      rootNormal: 'cursor-pointer',  // 状态：正常指针
      rootDisabled: 'cursor-not-allowed',  // 状态：禁用指针
      rootReadonly: 'cursor-default',  // 状态：只读指针
      
      // 控件元素：基础样式 + 状态样式
      checkbox: 'relative inline-flex items-center justify-center w-4 h-4 border-2 rounded appearance-none outline-none transition-colors',  // 基础：布局、尺寸、边框结构
      checkboxNormal: 'border-[var(--gray-color-6)] bg-[var(--white)] cursor-pointer',  // 状态：未选中
      checkboxChecked: 'border-[var(--brand-color-6)] bg-[var(--white)] cursor-pointer',  // 状态：选中
      checkboxDisabled: 'border-[var(--component-border)] bg-[var(--bg-color-component-disabled)] cursor-not-allowed',  // 状态：禁用
      checkboxReadonly: 'border-[var(--gray-color-6)] bg-[var(--white)] cursor-default',  // 状态：只读（禁止降低透明度）
      checkboxFocused: INTERACTIVE_FOCUS_CLASS,  // 独立：聚焦环（可叠加），见 focusStyles.ts
      
      // 子元素：基础样式（尺寸）+ 状态样式（颜色）
      icon: 'w-3 h-3',  // 基础：尺寸
      iconNormal: 'text-[var(--brand-color-6)]',  // 状态：正常颜色
      iconDisabled: 'text-[var(--text-color-disabled)]',  // 状态：禁用颜色
      
      label: 'text-sm select-none',  // 基础：字体大小、选择禁止
      labelNormal: 'text-[var(--text-color-primary)]',  // 状态：正常颜色
      labelDisabled: 'text-[var(--text-color-disabled)]'  // 状态：禁用颜色
    }
    ```
    ```vue
    <!-- CwCheckbox.vue - 正确示例 -->
    <template>
      <!-- 根容器：基础样式 + 互斥状态样式 -->
      <div :class="[styles.root, disabled ? styles.rootDisabled : (readonly ? styles.rootReadonly : styles.rootNormal)]">
        <!-- 控件：基础样式 + 互斥状态样式 + 独立聚焦样式 -->
        <div :class="[
          styles.checkbox,
          disabled ? styles.checkboxDisabled : (readonly ? styles.checkboxReadonly : (isChecked ? styles.checkboxChecked : styles.checkboxNormal)),
          { [styles.checkboxFocused]: isFocusVisible && !disabled }
        ]">
          <!-- 图标：基础样式（尺寸）+ 互斥状态样式（颜色） -->
          <svg :class="[styles.icon, disabled ? styles.iconDisabled : styles.iconNormal]">...</svg>
        </div>
        <!-- 标签：基础样式（字体大小）+ 互斥状态样式（颜色） -->
        <span :class="[styles.label, { [styles.labelNormal]: !disabled }, { [styles.labelDisabled]: disabled }]">...</span>
      </div>
    </template>
    ```
  - **选项元素示例**（CwSelect/CwCascader/CwAutoComplete）：
    ```ts
    // styles.ts - 正确示例
    export const defaultStyles: CwSelectStyles = {
      // 选项：基础样式（布局尺寸）+ 状态样式（颜色、cursor）
      option: 'px-3 py-2 text-sm whitespace-nowrap overflow-hidden text-ellipsis transition-colors duration-200',  // 基础：布局、尺寸、过渡
      optionNormal: 'text-[var(--text-color-primary)] cursor-pointer',  // 状态：正常
      optionSelected: 'text-[var(--brand-color-6)] bg-[var(--brand-color-1)] font-medium',  // 状态：选中
      optionDisabled: 'text-[var(--text-color-disabled)] cursor-not-allowed pointer-events-none',  // 状态：禁用（必须使用 token，禁止使用透明度）
      optionHover: 'hover:bg-[var(--gray-color-1)]',  // 独立：悬停（可叠加）
      optionFocus: 'bg-[var(--gray-color-1)]',  // 独立：聚焦（可叠加）
    }
    ```
    ```vue
    <!-- CwSelect.vue - 正确示例 -->
    <template>
      <div
        v-for="(option, index) in options"
        :key="option.value"
        :class="[
          styles.option,
          // 使用多层三元表达式确保状态互斥：disabled > selected > normal
          option.disabled 
            ? styles.optionDisabled 
            : (option.value === modelValue ? styles.optionSelected : styles.optionNormal),
          // 独立样式可叠加
          {
            [styles.optionHover]: option.value !== modelValue && !option.disabled,
            [styles.optionFocus]: (hasKeyboardInteracted && index === selectedIndex && !option.disabled)
          }
        ]"
      >
        {{ option.label }}
      </div>
    </template>
    ```

- 插槽状态传递（统一）
  - 对外暴露插槽的输入类组件，必须通过 slot props 传递 `disabled` 与 `readonly`，以便插槽内容按状态自行切换样式
  - 示例：
    - CwInput：`<slot name="icon" :disabled="disabled" :readonly="readonly" />`、`<slot name="suffix" ... />`、`<slot name="clear" ... />`
    - CwCheckbox/CwRadio：默认插槽 `<slot :disabled="disabled" :readonly="readonly" />`

- 清除按钮规范（强制）
  - **清除按钮不可聚焦**：清除按钮不得设置 `tabindex` 属性，禁止通过 Tab 键聚焦
  - **清除按钮交互方式**：
    - 只能通过鼠标点击清除
    - 清除操作通过键盘快捷键处理：通过各自的键盘 hook（如 `useSelectKeyboard`、`useAutoCompleteKeyboard`、`useCascaderKeyboard`）中的 `Backspace/Delete` 键处理
  - **清除按钮实现**：
    - 清除按钮只设置 `role="button"` 和 `@click.stop="handleClear"`
    - 不设置 `tabindex` 属性
    - 不设置 `@keydown` 事件处理
    - 设置 `aria-label` 和 `aria-disabled` 属性（根据 disabled/readonly 状态）
  - **适用组件**：所有带清除功能的组件（CwInput、CwSelect、CwAutoComplete、CwCascader 等）

- 组件要点
  - CwSelect：Readonly 不打开弹层、不清除、不改值；触发器可聚焦；内置 `CwInput` 只读
  - CwSlider：Readonly 可聚焦但拖拽与键盘增减无效
  - CwCheckbox/CwRadio/CwSwitch：Readonly 不允许切换；支持 `aria-readonly`

- 验证清单
  - Props 存在且默认 `false`
  - 改值入口均有 `if (props.disabled || props.readonly) return`
  - Disabled 不可聚焦；Readonly 可聚焦但不改值
  - ARIA：`aria-disabled`/`aria-readonly` 同步
  - 样式：`rootDisabled` 与 `rootReadonly` 以及子元素 `*Disabled/*Readonly` 已生效，禁用态不显示 focus/hover/active 高亮

## 自定义组件命名规范（Cw/Tp/Page 前缀组件）

### 平台区分规则

组件库中的包装组件按照平台类型进行了严格区分：
- **Vt 前缀（Vant）**：移动端组件库
- **Ui 前缀（Element Plus）**：PC 端组件库
- **At 前缀（Ant Design Vue）**：PC 端组件库

### Cw/Tp/Page 组件分类规范

随着自定义组件数量的增加，Cw/Tp/Page 组件必须按**核心能力**进行分类管理，便于维护和使用。所有 Cw/Tp/Page 组件必须按照以下分类规则进行组织：

#### 分类原则

1. **能力导向分类**：按组件的核心能力和功能进行分类
2. **业务场景优先**：考虑组件的典型使用场景和业务需求
3. **技术能力相关**：考虑组件提供的技术能力和实现方式
4. **平台无关**：不按平台（Mobile/PC）分类，按能力分类

#### 分类规则

**1. Form & Input（表单和输入）**
- 用户数据输入、表单处理、选择器相关
- 如：输入框、选择器、表单组件、复选框、单选框、滑块、开关等

**2. Display（展示）**
- 内容展示、媒体播放、信息呈现、文件管理、内容创作相关
- 如：头像、徽章、单元格、分割线、图标、图片、图片预览、加载中、富文本、轮播、标签、视频播放器、文件上传、图片裁剪、富文本编辑器等

**3. Layout & Navigation（布局和导航）**
- 页面布局、空间组织、导航结构相关
- 如：响应式布局、栅格系统、栈容器、虚拟滚动、瀑布流、导航栏、标签栏、标签页、树形列表等

**4. Interaction（交互）**
- 用户交互、手势操作、交互反馈相关
- 如：按钮、倒计时按钮、无限循环滚动、无限滚动等

**5. Overlay & Dialog（覆盖层和对话框）**
- 弹窗、对话框、提示框等覆盖层组件
- 如：弹出层、工具提示、确认对话框、动态对话框、动态底部弹层、选择器弹层、图片裁剪弹层等

**6. Visualization（可视化）**
- 数据图表、可视化、流程图、图形处理、动画效果、3D渲染相关
- 如：图表组件、流程图、算法可视化、数据结构可视化、3D场景、动画组件、粒子效果、GLSL着色器等

**7. Business（业务）**
- 特定业务场景、业务功能相关
- 如：聊天组件、低代码引擎、计算网格、信息流等

**8. Map & Location（地图位置）**
- 地图展示、地理位置相关
- 如：地图组件、位置服务等

#### 分类实施要求

1. **入口文件分类**：在 `src/index.ts` 中按分类组织导出，先导出所有 Cw 组件，再导出所有 Tp 组件，最后导出所有 Page 组件
2. **Storybook分类**：
   - `Cw` 组件使用 `1. Custom Components/{Category}/{ComponentName}` （数字前缀用于控制排序）
   - `Bs` 组件使用 `2. Business Specific Components/{Category}/{ComponentName}` （数字前缀用于控制排序）
   - `Page` 组件使用 `3. Pages/{ComponentName}` （数字前缀用于控制排序）
   - `Tp` 组件使用 `4. Third Party Components/{Category}/{ComponentName}` （数字前缀用于控制排序）
   - `At` 组件使用 `5. Ant Design Vue Components/{ComponentName}` （数字前缀用于控制排序）
   - `Ui` 组件使用 `6. Element Plus Components/{ComponentName}` （数字前缀用于控制排序）
   - `Vt` 组件使用 `7. Vant Components/{ComponentName}` （数字前缀用于控制排序）
3. **新增组件**：必须确定合适的分类归属
4. **分类调整**：当组件功能发生变化时，及时调整分类
5. **命名一致性**：分类名称使用英文，保持简洁明了
6. **规范一致性**：Cw、Tp、Page 组件必须遵守完全相同的规范要求（Tailwind CSS、tokens 使用、样式和文本国际化等）

### 自研组件命名规范（Cw/Tp/Page，强制）

**规则 1：Mobile 后缀规则**
- 如果自定义组件（Cw/Tp/Page 前缀）中使用了 **Vt 组件**（移动端），则该组件名称必须以 `Mobile` 结尾
- 例如：
  - ✅ `CwCountDownButtonMobile` - 使用了 VtButton
  - ✅ `CwAgreementPopupMobile` - 使用了 VtPopup、VtTabs、VtButton
  - ✅ `CwPhoneLoginMobile` - 使用了 VtField、VtButton、VtCheckbox
  - ✅ `CwImageUploaderMobile` - 使用了 VtUploader
  - ❌ `CwCountDownButton` - 使用了 VtButton 但没有 Mobile 后缀（错误）

**规则 2：组件平台隔离规则**
- **严禁在同一个组件中混用不同平台的包装组件**
- Vt 组件（移动端）不能与 Ui/At 组件（PC 端）混用
- 例如：
  - ❌ 在同一组件中同时使用 `VtButton` 和 `UiButton`（错误）
  - ❌ 在同一组件中同时使用 `VtField` 和 `AtInput`（错误）
  - ✅ 只使用 Vt 组件，组件名以 Mobile 结尾（正确）
  - ✅ 只使用 Ui/At 组件，组件名不带 Mobile 后缀（正确）

**规则 3：纯自定义组件命名**
- 如果 Cw/Bs/Tp/Page 组件不使用任何 Vt/Ui/At 包装组件，则按照业务需求正常命名
- 例如：
  - ✅ `CwChat` - 纯自定义实现的聊天组件（通用组件）
  - ✅ `CwRichText` - 纯自定义实现的富文本解析组件（通用组件）
  - ✅ `BsAccountBalanceCard` - 业务特定的账户余额卡片（复用性低）
  - ✅ `PageLogin` - 页面级业务组件

**规则 4：Tp 前缀使用规则**
- 当组件在自定义逻辑中深度集成 ECharts、Monaco Editor、Three.js、Mapbox、PDF.js 等重型第三方库并提供超出简单包装的功能时，必须使用 `Tp` 前缀
- `Tp` 组件必须遵守本文所有自研组件规范（包括 Tailwind CSS、tokens 使用等），同时在文档中明确列出所依赖的重型第三方库

**规则 5：Page 前缀使用规则**
- 当组件是面向具体业务场景的页面级组件，可能包含多个基础组件的组合与业务逻辑时，必须使用 `Page` 前缀
- `Page` 组件必须遵守本文所有自研组件规范（包括 Tailwind CSS、tokens 使用、样式和文本国际化等），与 `Cw` 和 `Tp` 组件规范完全一致

**命名规范总结：**
```typescript
// ✅ 正确示例
CwCountDownButtonMobile    // 使用了 VtButton（移动端）
CwAgreementPopupMobile     // 使用了 VtPopup、VtTabs（移动端）
CwPhoneLoginMobile         // 使用了 VtField、VtButton（移动端）
CwLoginForm                // 使用了 UiInput、UiButton（PC端），不需要 Mobile 后缀
CwChat                     // 纯自定义组件，不使用包装组件
TpChartJs                  // 深度整合 Chart.js 的可视化组件
TpMonacoEditor             // 深度整合 Monaco Editor 的代码编辑器
PageLogin                  // 页面级业务组件
PageChangePassword         // 页面级业务组件

// ❌ 错误示例
CwCountDownButton          // 使用了 VtButton 但缺少 Mobile 后缀
CwAgreementPopup           // 使用了 Vt 组件但缺少 Mobile 后缀
CwMixedFormMobile          // 混用了 VtField 和 UiInput（不允许）
```

**违规检查：**
- 在开发新的 Cw/Tp/Page 组件时，必须先确定使用的包装组件类型
- 如果使用 Vt 组件，必须添加 Mobile 后缀
- 不允许在同一组件中混用不同平台的包装组件
- 所有组件必须符合此命名规范，违规组件必须重命名
- **Tp 和 Page 组件必须遵守与 Cw 组件完全相同的规范要求**（Tailwind CSS、tokens 使用、样式和文本国际化等）

### 1. 组件代码格式（强制）

#### 1.1 第三方库包装组件格式（Ui/Vt/At开头）

所有第三方库包装组件必须严格按照以下格式编写，不允许任何偏差：

```typescript
<template>
  <component :is="h(ElComponent, { ...props, ...attrs, ref: changeRef }, slots)" />
</template>

<script setup lang="ts">
import { useSlots, useAttrs, type Slots, getCurrentInstance, h } from 'vue'
import { ElComponent } from 'element-plus'
import type { UiComponentProps } from './types'

defineOptions({
  name: 'UiComponentName'
})

const props = defineProps<UiComponentProps>()
const attrs = useAttrs() as any
const slots = useSlots() as Slots

const vm = getCurrentInstance()
function changeRef(instance: any) {
  if (vm) {
    vm.exposeProxy = vm.exposed = instance || {}
  }
}
</script>
```

**强制要求：**
- 必须使用 `<script setup lang="ts">` 进行逻辑编写
- 必须导入并使用 `useSlots`、`useAttrs`、`getCurrentInstance`、`h` 和 `Slots` 类型
- 必须为 `slots` 变量添加类型注解：`as Slots`
- 模板中必须使用 `<component :is="h(ElComponent, { ...props, ...attrs, ref: changeRef }, slots)" />` 的render函数方式
- 必须使用 `useAttrs()` 获取 attrs 而不是直接使用 `$attrs`
- **必须使用 `getCurrentInstance()` 和 `changeRef` 函数暴露组件方法：通过 `changeRef` 函数将底层Element Plus组件实例同时赋值给 `vm.exposeProxy` 和 `vm.exposed`，并使用 `|| {}` 提供默认空对象，确保外部可以访问底层组件的完整API**
- 不允许添加额外的导入、注释或代码
- 不要使用 `defineEmits`
- **必须使用 `defineOptions({ name: 'UiComponentName' })` 为组件定义名称，以便在模板中使用 `<ui-component-name>` 标签形式**
- **严格的代码格式要求：不允许在 `<script setup lang="ts">` 行后添加空行，所有代码必须紧凑排列，不允许多余的空行或空格**


#### 1.3 自研组件格式（Cw/Tp/Page 开头）

**重要：所有 Cw/Tp/Page 开头的自研组件都必须只使用 Tailwind CSS，不允许使用任何自定义 CSS 样式！**

自研组件可以根据业务需求自由编写，不受第三方库透传限制，但必须严格遵守以下样式规范：

**根容器 BEM 类名（强制，仅 `Cw` 组件）：**

1. **命名**：组件 `CwFooBar` 对应块名 `cw-foo-bar`（`Cw` 前缀改为 `cw-`，其余 PascalCase 转 kebab-case）。
2. **只加在根容器**：块名 class **仅**写在组件**最外层**原生 DOM 节点（`div` / `span` / `nav` 等）的 `class` 或 `styles.root` 中，用于在 DOM 里定位组件；**可不写任何配套 CSS**，与 Tailwind 并存。
3. **禁止子元素 BEM**：除根块名外，**不要**再写 `cw-foo-bar__toolbar`、`cw-foo-bar--active` 等 Element/Modifier；子区域样式只用 Tailwind（`styles.ts` 各字段）或 `data-*` 钩子。
4. **根节点是其他 Cw 组件时不加**：若模板根是 `<CwPopup>`、`<CwCard>`、`<CwTooltip>` 等，**不要**为当前组件再包一层只为加 BEM；由该子组件自己的根 BEM 承担定位。
5. **编辑器 / 协议钩子例外**：ProseMirror、拖拽、单元测试等必须在 DOM 上留名的 class（如 `cw-emoji`、`cw-file-ref`、`data-cw-*`）保留，但不算「块名」，也不扩展到 `__` 子元素 BEM。
6. **`CwDiv` 自绘滚动条**：根节点必须有 `cw-div`；内部 `cw-div__viewport`、`cw-div__scrollbar` 等为滚动实现与 `querySelector` 钩子，允许保留（不视为装饰性 BEM）。

**样式规范（强制）：**
1. **只能使用 Tailwind CSS 类名**：所有样式都必须通过 Tailwind CSS 类名实现
2. **禁止自定义 CSS**：不允许在 `<style>` 标签中编写任何自定义样式
3. **完全移除 style 标签**：组件中不应包含任何 `<style>` 标签，包括空的 `<style>` 标签，即使有注释说明也必须移除
   - **例外：响应式布局组件**：对于需要响应式布局功能的组件（如栅格系统的 `CwCol`、`CwRow`），允许使用功能性 CSS（容器查询 / 媒体查询和 CSS 变量）来实现响应式断点布局。此类 CSS 必须是纯功能性样式（用于布局计算），不能包含任何装饰性样式（颜色、边框、阴影等），且必须使用 `scoped` 属性
4. **HTML 元素使用限制**：为避免 Tailwind @layer 样式优先级问题，Cw/Tp/Page 组件中禁止使用以下浏览器原生元素：
   - ❌ 禁止：`<button>`、`<a>`、`<form>`、`<label>`、`<select>` 等（使用 div/span 替代）
   - ✅ 允许：`<input>`、`<textarea>` 等输入控件（无法替代的功能性元素）
   - ✅ 推荐：主要使用 `<div>` 和 `<span>` 构建组件
   - 💡 提示：需要点击交互的 div 元素必须添加 `cursor-pointer` 类名
   - 💡 **使用 div 替代 button 时的键盘支持**：当使用 `<div>` 替代 `<button>` 时，必须添加：
     - `role="button"` 属性（用于可访问性）
     - `:tabindex="tabindex"` 属性（使用 computed 函数，使元素可聚焦）
     - `@keydown.enter.prevent` 和 `@keydown.space.prevent` 事件处理器（支持键盘操作）
     - 示例：
```vue
<div
  role="button"
  :tabindex="tabindex"
  @click="handleClick"
  @keydown.enter.prevent="handleClick"
  @keydown.space.prevent="handleClick"
>
  按钮文本
</div>
```
```typescript
// 必须使用 computed 函数定义 tabindex
const tabindex = computed(() => (props.disabled ? undefined : 0))
```
5. **强制使用 Design Tokens 色值**：
   - ❌ **严禁 hardcode 色值**：禁止 `#hex`、`rgb()` / `rgba()`、`bg-blue-500`、`text-gray-600` 等
   - ❌ **严禁 `var(--token, #fallback)`**：禁止为 token 写 hex fallback，只写 `var(--token)`
   - ✅ **唯一可写 hex 的位置**：`src/assets/tokens/**`（含 `tokens.css` 与各 token 源文件）
   - ✅ **组件内只用语义 token**：`bg-[var(--brand-color-6)]`、`text-[var(--text-color-primary)]`、`border-[var(--component-border)]` 等；浅色在 `:root`、深色在 `theme-dark.css` **仅写与浅色不同的变量**（其余继承 `:root`，`pnpm run check:theme-parity` 校验子集），通过 `setAppTheme('dark'|'light')` 或 `data-theme` 在应用根切换
   - ✅ **导入 tokens**：应用入口须引入 `zcw-vue-ui/dist/tokens.css`（或包内 `src/assets/tokens/tokens.css`）
   - 🔍 **CI / 本地检查**（默认扫描 `Cw*`）：
     ```bash
     pnpm run check:tokens          # 必须通过后再提 PR（默认扫描 Cw*）
     pnpm run check:tokens -- --scope=all  # 含 Tp/Bs/Page
     pnpm run check:tokens -- --fix # 仅自动去掉 var(--x, #hex) 形式的 fallback
     ```
   - ⚠️ **Canvas / 运行时拼色**：若必须用 `rgba()` 拼 alpha，在上一行加 `// design-token-check: ignore` 并说明原因；优先从 `tokenSolidColors` 等 token 源读取 hex
   - 💡 **参考**：`src/assets/tokens/tokens.css`、脚本 `scripts/check-design-token-colors.mjs`

```typescript
<template>
  <!-- 只能使用布局相关的class -->
  <div class="flex flex-col p-4 space-y-2">
    <h3 :class="styles.title">{{ texts.title }}</h3>
    <p :class="styles.content">{{ texts.content }}</p>
    
    <!-- ✅ 正确：使用 div 替代 button，样式通过 styles 传入 -->
    <div 
      :class="styles.button"
      @click="handleAction"
    >
      {{ texts.buttonText }}
    </div>
    
    <!-- ✅ 允许：输入控件可以使用原生元素，样式通过 styles 传入 -->
    <input 
      type="text"
      :class="styles.input"
      :placeholder="texts.inputPlaceholder"
    />
  </div>
</template>

<script setup lang="ts">
import { ref, computed } from 'vue'
import type { CwComponentProps, CwComponentEmits } from './types'
import { defaultStyles } from './styles'
import { defaultTexts } from './texts'

defineOptions({
  name: 'CwComponentName'
})

const props = defineProps<CwComponentProps & {
  styles?: Record<string, string>
  texts?: Record<string, string>
}>()

const emit = defineEmits<CwComponentEmits>()

// 合并默认样式和传入的样式
const styles = computed(() => ({ ...defaultStyles, ...props.styles }))

// 合并默认文本和传入的文本
const texts = computed(() => ({ ...defaultTexts, ...props.texts }))
</script>

<!-- 注意：Cw/Tp/Page 组件不应包含 <style> 标签 -->
```

**Cw/Tp/Page 组件样式设计原则：**

1. **布局优先**：组件内部只能使用布局相关的class
2. **样式外部化**：所有外观样式通过 `styles` 对象传入
3. **文本国际化**：所有文本通过 `texts` 对象传入
4. **默认值管理**：提供默认的 `styles.ts` 和 `texts.ts` 文件
5. **简单合并**：使用 `{ ...defaultStyles, ...props.styles }` 方式合并样式和文本

**自研组件要求：**
- 必须使用 `<script setup lang="ts">` 进行逻辑编写
- 必须使用 `defineOptions({ name: 'CwComponentName' })` 定义组件名称（Cw/Tp/Page 组件都遵循此规范）
- 可以自定义 Props 和 Emits 类型
- 可以包含自定义业务逻辑
- 可以使用任何 Vue 3 Composition API
- 组件名必须以 `Cw`、`Tp` 或 `Page` 开头（Tp 需满足"重型第三方整合"规则，Page 需满足"页面级业务组件"规则）
- **必须只使用 Tailwind CSS 进行样式设计**（Cw/Tp/Page 组件统一要求）
- **禁止使用自定义 CSS 样式**（Cw/Tp/Page 组件统一要求）
- **强制使用 tokens 色值**：禁止使用 `gray-500`、`blue-600` 等 hardcode 色值，必须使用 `var(--brand-color-6)` 等 tokens 变量（Cw/Tp/Page 组件统一要求）

#### 1.4 自研组件纯函数抽离规范（Cw/Tp/Page，强制）

**适用场景：**
当组件逻辑较为复杂（超过 300 行代码或包含大量计算逻辑）时，必须将纯函数抽离到独立的 `functions` 目录中。

**纯函数定义：**
- **纯函数**：不依赖 Vue 响应式状态（`ref`、`computed`、`props` 等），只依赖传入参数的函数
- **非纯函数**：需要访问组件状态、`props`、`emit` 等的函数，必须保留在组件中

**抽离规范：**

1. **创建 functions 目录**：
   - 在组件目录下创建 `functions` 目录
   - 每个纯函数放在独立的 `.ts` 文件中
   - 使用 `functions/index.ts` 统一导出所有函数

2. **纯函数命名规范**：
   - 使用动词开头，清晰表达函数功能
   - 例如：`calculateImageBounds`、`adjustCropBoxSize`、`generateCropHandles`

3. **类型定义**：
   - 纯函数必须包含完整的 TypeScript 类型定义
   - 输入参数和返回值必须有明确的类型
   - 相关类型定义可以放在函数文件中，或统一放在 `functions/types.ts`

4. **必须抽离的纯函数类型**：
   - ✅ 计算函数：坐标计算、尺寸计算、边界检查等
   - ✅ 数据处理函数：数据转换、格式化、验证等
   - ✅ 工具函数：事件坐标提取、URL 创建/撤销等
   - ✅ 配置生成函数：生成配置对象、样式对象等
   - ❌ 事件处理函数：需要访问 `props`、`emit`、响应式状态的函数
   - ❌ 生命周期钩子：`onMounted`、`onUnmounted`、`watch` 等

5. **目录结构示例**：
   ```
   CwImageCropper/
   ├── functions/
   │   ├── eventCoordinates.ts      # 事件坐标提取
   │   ├── imageBounds.ts           # 图片边界计算
   │   ├── cropBoxBounds.ts         # 裁剪框边界限制
   │   ├── cropBoxAdjust.ts         # 裁剪框尺寸调整
   │   ├── cropBoxMove.ts           # 裁剪框移动
   │   ├── cropBoxReset.ts          # 初始裁剪框计算
   │   ├── cropCoordinates.ts       # 裁剪坐标计算
   │   ├── cropHandles.ts           # 裁剪手柄配置生成
   │   ├── canvasCrop.ts            # Canvas 裁剪逻辑
   │   ├── fileUtils.ts            # 文件工具函数
   │   └── index.ts                # 统一导出
   ├── CwImageCropper.vue
   ├── types.ts
   ├── styles.ts
   └── index.ts
   ```

6. **函数导出和使用**：
   ```typescript
   // functions/index.ts
   export * from './eventCoordinates'
   export * from './imageBounds'
   // ... 其他函数导出
   
   // CwImageCropper.vue
   import {
     getEventCoordinates,
     calculateImageBounds,
     adjustCropBoxSize,
     // ... 其他函数导入
     type CropBox,
     type ImageBounds
   } from './functions'
   ```

7. **组件代码要求**：
   - 组件代码应专注于 Vue 响应式逻辑和事件处理
   - 所有纯计算逻辑都应通过导入的纯函数实现
   - 组件代码应保持简洁，避免超过 500 行（不包括模板）

**示例：**
```typescript
// functions/cropBoxBounds.ts - 纯函数
export interface CropBox {
  x: number
  y: number
  width: number
  height: number
}

export function clampCropBoxToBounds(cropBox: CropBox, bounds: ImageBounds): CropBox {
  // 纯函数逻辑，不依赖任何 Vue 响应式状态
  const result = { ...cropBox }
  // ... 计算逻辑
  return result
}

// CwImageCropper.vue - 组件中使用
import { clampCropBoxToBounds, type CropBox } from './functions'

const cropBox = ref<CropBox>({ x: 0, y: 0, width: 200, height: 200 })

const updateCropBox = () => {
  const bounds = getImageBounds()
  if (bounds) {
    cropBox.value = clampCropBoxToBounds(cropBox.value, bounds)
  }
}
```

**好处：**
- ✅ 提高代码可测试性：纯函数易于单独测试
- ✅ 提高代码可维护性：逻辑分离，职责清晰
- ✅ 提高代码可复用性：纯函数可在其他组件中复用
- ✅ 降低组件复杂度：组件代码更简洁，易于理解

#### 1.5 自研组件样式和文本规范（Cw/Tp/Page，强制）

**样式规范：**
1. **内部样式限制**：组件内部只能有布局相关的class（如 `flex`、`grid`、`space-x-4`、`p-4`、`w-full` 等）
2. **外观样式禁止**：禁止在组件内部使用外观样式（如 `bg-blue-500`、`text-red-600`、`border-gray-300`、`rounded-lg` 等）
3. **必须使用 tokens**：**禁止使用任何 hardcode 色值**，所有颜色必须使用 `bg-[var(--brand-color-6)]`、`text-[var(--text-color-primary)]` 等 tokens 变量
4. **样式统一管理**：所有样式相关的都放在 `styles` 对象中，通过 props 传入
5. **默认样式文件**：默认styles对象应该是一个单独的文件（如 `styles.ts`），组件导入并使用
6. **样式覆盖**：外部可以通过传入自定义styles对象来覆盖默认样式
7. **Props 类型必须是 Partial（强制）**：
   - **所有组件的 `styles` 和 `texts` Props 类型必须是 `Partial<组件Styles>` 和 `Partial<组件Texts>`**
   - **原因**：组件内部必须对 `props.styles` 与 `defaultStyles` 进行浅层合并（`{ ...defaultStyles, ...props.styles }`）
   - **类型定义示例**：
     ```typescript
     export interface CwComponentProps {
       styles?: Partial<CwComponentStyles>
       texts?: Partial<CwComponentTexts>
     }
     ```
   - **组件内部实现**：
     ```typescript
     const styles = computed(() => ({ ...defaultStyles, ...props.styles }))
     const texts = computed(() => ({ ...defaultTexts, ...props.texts }))
     ```
   - **好处**：
     - 外部只需传入需要覆盖的部分样式，不需要提供所有样式
     - 类型安全，支持智能提示
     - 符合组件设计原则：默认值 + 可选覆盖

**文本国际化规范：**
1. **内部文本禁止**：组件内部不能有硬编码文本，因为所有组件都需要做国际化
2. **文本统一管理**：文本也应该有一个类似styles的对象（如 `texts` 或 `labels`），通过 props 传入
3. **默认文本文件**：默认文本对象也应该是一个单独的文件（如 `texts.ts` 或 `defaultTexts.ts`）
4. **文本覆盖**：外部可以通过传入自定义文本对象来覆盖默认文本
5. **禁止硬编码文本**：组件内不允许有任何硬编码的中文或英文文本，所有文本都必须通过 `texts` 或 `props` 传入
6. **简单合并**：使用 `{ ...defaultTexts, ...props.texts }` 方式合并，undefined 值会自动被忽略
7. **Props 类型必须是 Partial（强制）**：与样式规范相同，`texts` Props 类型也必须是 `Partial<组件Texts>`

**插槽容器规范：**
1. **禁止插槽容器内置样式**：插槽容器（包裹 slot 的 div）不应该有硬编码的样式类
2. **样式外部化**：插槽容器的样式应该通过外层容器传入，或者由插槽内容自行控制
3. **结构纯净**：插槽容器只负责提供结构框架，不添加任何装饰性样式

**子组件处理：**
1. **独立子组件**：可以提供独立的子组件供外部导入使用（如 CwCardTitle、CwCardContent 等）
2. **子组件样式完整**：这些子组件应该包含完整的样式（包括 padding、margin 等），使其可以独立使用
3. **子组件独立导出**：子组件应该在 index.ts 中独立导出，使外部可以直接使用
4. **不作为默认内容**：这些子组件不应该直接写在主组件的 slots 中作为默认内容，而是由外部用户根据需要选择是否使用
5. **Story 展示**：每个子组件都应该有独立的 Story 来展示其样式和用法

**父组件中使用子组件的样式和文本配置规范（强制）：**

⚠️ **核心原则（绝对禁止违反）：**
- ❌ **严禁在父组件的 styles 中直接定义子组件的样式**：所有对 Cw/Tp/Page 子组件样式的覆盖必须单独成对象，不能直接写在父组件的 styles 中
- ✅ **必须使用独立的子对象**：子组件的样式配置必须作为父组件 props 的独立子对象（如 `inputStyles`、`buttonStyles`）
- ✅ **必须导入子组件类型**：父组件的 `types.ts` 中必须导入子组件的 styles 和 texts 类型，并使用 `Partial<子组件Styles>` 和 `Partial<子组件Texts>` 作为类型约束
- ✅ **支持部分覆盖**：由于所有 Cw/Tp/Page 组件内部都会浅合并传入的 styles 和自带的 defaultStyles，所以覆盖时可以只覆盖需要修改的部分样式属性

**详细规范：**
1. **子组件配置分离（强制）**：
   - 当父组件内部使用了 Cw/Tp/Page 子组件时，**所有**对子组件样式的覆盖必须作为父组件 props 的独立子对象
   - **禁止**：在父组件的 styles 中直接定义子组件的样式（如 `inputWrapper`、`button` 等子组件样式）
   - **必须**：创建独立的子对象（如 `inputStyles`、`buttonStyles`）来配置子组件样式

2. **命名规范（强制）**：
   - 子组件的样式配置使用 `{子组件简写}Styles` 格式（如 `inputStyles`、`buttonStyles`、`iconStyles`）
   - 子组件的文本配置使用 `{子组件简写}Texts` 格式（如 `inputTexts`、`buttonTexts`、`iconTexts`）
   - 命名必须清晰明确，能够一眼看出是哪个子组件的配置

3. **类型定义（强制）**：
   - **必须导入子组件类型**：在父组件的 `types.ts` 中，必须从子组件导入对应的 styles 和 texts 类型
   - 使用 `Partial<子组件Styles>` 和 `Partial<子组件Texts>` 作为类型约束
   - 类型定义必须是 `Partial` 类型，因为只需要覆盖部分样式，不需要提供所有样式

4. **传递规则（强制）**：
   - 父组件通过 props 接收子组件的配置，然后传递给子组件的 `:styles` 和 `:texts` 属性
   - 传递时直接传递子对象（如 `:styles="styles.inputStyles"`），而不是整个 styles 对象

5. **默认值处理**：
   - 子对象是可选的（`?`），如果不需要自定义子组件配置，可以不传入该子对象，子组件使用其默认配置
   - 由于使用 `Partial` 类型，可以只覆盖需要修改的部分样式属性，其他样式会使用子组件的默认值

**示例：CwSelect 使用 CwInput**

✅ **正确示例：**
```typescript
// CwSelect/types.ts
// ⚠️ 必须导入子组件的类型
import type { CwInputStyles, CwInputTexts } from '../CwInput/types'

export interface CwSelectStyles {
  root: string
  popup: string
  option: string
  // ... 其他父组件自身的样式
  /** CwInput 样式配置 - 必须作为独立子对象 */
  inputStyles?: Partial<CwInputStyles>
}

export interface CwSelectTexts {
  placeholder: string
  empty: string
  // ... 其他父组件自身的文本
  /** CwInput 文本配置 - 必须作为独立子对象 */
  inputTexts?: Partial<CwInputTexts>
}
```

❌ **错误示例（禁止）：**
```typescript
// ❌ 错误：在父组件的 styles 中直接定义子组件的样式
export interface CwSelectStyles {
  root: string
  popup: string
  // ❌ 禁止：不能直接定义子组件的样式属性
  inputWrapper: string  // 这是 CwInput 的样式，不能写在这里
  input: string         // 这是 CwInput 的样式，不能写在这里
  inputFocused: string  // 这是 CwInput 的样式，不能写在这里
}
```

```vue
<!-- CwSelect.vue -->
<template>
  <CwInput
    :model-value="displayValue"
    :placeholder="placeholder"
    readonly
    :styles="styles.inputStyles"
    :texts="texts.inputTexts"
  />
</template>

<script setup lang="ts">
import CwInput from '../CwInput/CwInput.vue'
import type { CwSelectProps } from './types'

const props = defineProps<CwSelectProps>()
const styles = computed(() => props.styles || defaultStyles)
const texts = computed(() => props.texts || defaultTexts)
</script>
```

**使用方式：**
```vue
<!-- 使用默认配置 -->
<CwSelect :options="options" />

<!-- ✅ 正确：自定义子组件配置 - 使用独立的子对象 -->
<CwSelect 
  :options="options"
  :styles="{ 
    // 可以只覆盖部分样式，其他样式会使用 CwInput 的默认值
    inputStyles: { 
      inputWrapper: 'border-2 border-[var(--brand-color-6)]',
      // 只覆盖 inputWrapper，其他样式（如 input、inputFocused 等）使用默认值
    } 
  }"
  :texts="{ 
    inputTexts: { 
      placeholder: 'Custom placeholder' 
    } 
  }"
/>

<!-- ❌ 错误：禁止在父组件的 styles 中直接定义子组件样式 -->
<CwSelect 
  :options="options"
  :styles="{ 
    // ❌ 禁止：不能直接定义子组件的样式属性
    inputWrapper: 'border-2 border-[var(--brand-color-6)]',  // 错误
    input: 'custom-input-style',  // 错误
  }"
/>
```

**规范要点（强制遵守）：**
- ✅ **严禁在父组件 styles 中直接定义子组件样式**：所有对 Cw/Tp/Page 子组件样式的覆盖必须作为独立的子对象（如 `inputStyles`、`buttonStyles`）
- ✅ **必须导入子组件类型**：父组件的 `types.ts` 中必须导入子组件的 styles 和 texts 类型，确保类型安全
- ✅ **必须使用 Partial 类型**：子组件配置必须是 `Partial<子组件Styles>` 类型，因为只需要覆盖部分样式
- ✅ **支持部分覆盖**：由于所有 Cw/Tp/Page 组件内部都会浅合并传入的 styles 和自带的 defaultStyles，可以只覆盖需要修改的部分样式属性
- ✅ **父组件不直接操作子组件的样式**：父组件通过子对象的 props 传递配置，保持组件解耦
- ✅ **子组件保持独立性**：子组件不知道自己被其他组件使用，保持可复用性
- ✅ **配置类型安全**：使用 `Partial<子组件类型>` 确保类型安全和智能提示
- ✅ **向后兼容**：子对象是可选的（`?`），不传则使用子组件默认配置

**函数式 API 组件的特殊说明：**
- **Dialog 和 Sheet 类型组件**（如 `CwSelectSheet`、`CwImageCropperSheet`、`CwConfirmDialog` 等）允许将子组件样式配置作为 **props 的直接属性**，而不是必须在 `styles` 对象中
- 这是为了方便函数式 API 的使用，保持 API 的简洁性
- 示例：
  ```typescript
  // ✅ 函数式 API 组件允许这样做
  export interface CwSelectSheetProps {
    options?: CwSelectSheetOption[]
    styles?: Partial<CwSelectSheetStyles>
    // 子组件样式可以作为 props 的直接属性
    sheetStyles?: Partial<CwDynamicSheetInnerStyles>
  }
  
  export interface CwImageCropperSheetProps {
    styles?: Partial<CwImageCropperSheetStyles>
    // 子组件样式可以作为 props 的直接属性
    cropperStyles?: Partial<CwImageCropperStyles>
    sheetStyles?: Partial<CwDynamicSheetInnerStyles>
  }
  ```
- **普通组件**（非函数式 API）仍然必须将子组件样式配置放在 `styles` 对象中（如 `styles.inputStyles`、`styles.buttonStyles`）

**示例：父组件包含子组件配置的完整示例**

✅ **正确示例：CwSelect 的 styles.ts 和 texts.ts**
```typescript
// CwSelect/styles.ts
import type { CwSelectStyles } from './types'
import type { CwInputStyles } from '../CwInput/types'

export const defaultStyles: CwSelectStyles = {
  // 父组件自身的样式
  root: 'relative',
  popup: 'absolute z-10 bg-[var(--white)] border border-[var(--component-border)] rounded-md shadow-lg',
  option: 'px-3 py-2 text-sm cursor-pointer hover:bg-[var(--gray-color-1)]',
  
  // ⚠️ 子组件配置必须作为独立子对象（可选）
  // 如果需要为子组件提供默认样式覆盖，可以在这里定义
  // 如果不需要，可以不定义这个属性，子组件会使用自己的默认样式
  inputStyles: {
    // 只覆盖需要的部分样式，其他样式使用 CwInput 的默认值
    inputWrapper: 'border-2 border-[var(--component-border)]',
    // 可以只覆盖部分样式属性
  } as Partial<CwInputStyles>,
}

// CwSelect/texts.ts
import type { CwSelectTexts } from './types'
import type { CwInputTexts } from '../CwInput/types'

export const defaultTexts: CwSelectTexts = {
  // 父组件自身的文本
  placeholder: '请选择',
  empty: '暂无数据',
  
  // ⚠️ 子组件配置必须作为独立子对象（可选）
  inputTexts: {
    placeholder: '请选择',
  } as Partial<CwInputTexts>,
}
```

❌ **错误示例（禁止）：**
```typescript
// ❌ 错误：在父组件的 styles 中直接定义子组件的样式
export const defaultStyles = {
  root: 'relative',
  popup: 'absolute z-10',
  // ❌ 禁止：不能直接定义子组件的样式属性
  inputWrapper: 'border-2 border-[var(--component-border)]',  // 错误：这是 CwInput 的样式
  input: 'px-3 py-2',  // 错误：这是 CwInput 的样式
  inputWrapperFocus: 'ring-2',  // 错误：这是 CwInput 的样式，应走 focusStyles.ts
}
```

**简单组件示例（无子组件）：**
```typescript
// styles.ts - 单独的默认样式文件（无子组件时）
import { NATIVE_FOCUS_CLASS } from '../../utils/focusStyles'

export const defaultStyles = {
  title: 'text-lg font-semibold text-[var(--text-color-primary)] mb-2',
  content: 'text-sm text-[var(--text-color-secondary)]',
  button: 'mt-4 px-4 py-2 bg-[var(--brand-color-6)] text-[var(--white)] rounded-md hover:bg-[var(--brand-color-hover)] transition-colors duration-200 cursor-pointer inline-block text-center',
  input: `mt-2 w-full px-3 py-2 border border-[var(--component-border)] rounded-md ${NATIVE_FOCUS_CLASS}`  // NATIVE_FOCUS_CLASS 来自 focusStyles.ts
}

// texts.ts - 单独的默认文本文件（无子组件时）
export const defaultTexts = {
  title: '组件标题',
  content: '组件内容',
  buttonText: '操作按钮',
  inputPlaceholder: '输入内容'
}
```

```vue
<!-- CwButton组件 -->
<template>
  <div class="flex items-center justify-center"> <!-- 布局class -->
    <div 
      :class="styles.button" 
      @click="handleClick"
    >
      {{ texts.submit }}
    </div>
  </div>
</template>

<script setup>
import { defaultStyles } from './styles'
import { defaultTexts } from './texts'

const props = defineProps({
  styles: {
    type: Object,
    default: () => defaultStyles
  },
  texts: {
    type: Object,
    default: () => defaultTexts
  }
})
</script>
```

**使用方式：**
```vue
<!-- 使用默认样式和文本 -->
<CwComponent />

<!-- 自定义样式和文本（必须使用 tokens） -->
<CwComponent 
  :styles="{ 
    button: 'px-6 py-3 bg-[var(--error-color-6)] text-[var(--white)] rounded-lg hover:bg-[var(--error-color-hover)]',
    title: 'text-xl font-bold text-[var(--text-color-brand)]'
  }"
  :texts="{ 
    buttonText: 'Submit',
    title: 'Custom Title'
  }"
/>
```

**⚠️ 重要提醒：**
- 自定义样式时也必须使用 tokens 变量，禁止使用 `bg-red-500`、`text-blue-600` 等 hardcode 色值
- 所有颜色必须从 `src/assets/tokens/tokens.css` 中定义的变量中选择
- 可选的 tokens：`--brand-color-*`、`--gray-color-*`、`--text-color-*`、`--error-color-*`、`--warning-color-*`、`--success-color-*` 等

**可跟随状态旋转的图标样式规范（强制）：**

当组件需要使用可跟随状态旋转的图标（如箭头图标，根据弹层打开/关闭状态旋转）时，必须遵循以下规范：

1. **样式定义规范**：
   - **基础样式**：将所有静态样式（颜色、过渡效果等）整合到 `{icon}IconStyles.root` 的默认值中
   - **状态样式**：使用独立的字符串属性（如 `arrowOpen`）定义状态样式（如 `rotate-180`）
   - **必须包含过渡效果**：在 `{icon}IconStyles.root` 中必须包含 `transition-transform duration-200` 等过渡样式，确保旋转动画流畅

2. **类型定义规范**：
   ```typescript
   export interface CwComponentStyles {
     // ... 其他样式
     /** 状态样式（如旋转角度） */
     arrowOpen: string
     /** CwIcon 样式配置（箭头图标） */
     arrowIconStyles?: Partial<CwIconStyles>
   }
   ```

3. **默认样式定义**：
   ```typescript
   export const defaultStyles: CwComponentStyles = {
     // ... 其他样式
     // 状态样式：定义旋转角度等状态变化
     arrowOpen: 'rotate-180',
     // 图标样式：基础样式包含颜色、过渡效果等，不包含状态样式
     arrowIconStyles: {
       root: 'inline-flex items-center justify-center text-current text-[var(--text-color-secondary)] transition-transform duration-200',
       size: 16
     } as Partial<CwIconStyles>,
   }
   ```

4. **组件实现规范**：
   - 使用 `computed` 动态合并基础样式和状态样式
   - 根据组件状态（如 `popupVisible`）决定是否应用状态样式
   - 只合并状态样式，不重复合并基础样式（因为基础样式已经在 `arrowIconStyles.root` 中）
   
   ```typescript
   // 箭头图标样式
   const arrowIconStyle = computed(() => {
     const baseStyles = styles.value
     const iconStyle = baseStyles.arrowIconStyles || {}
     return {
       ...iconStyle,
       root: [
         iconStyle.root || '',
         popupVisible.value ? baseStyles.arrowOpen : ''
       ].filter(Boolean).join(' ')
     }
   })
   ```

5. **模板使用**：
   ```vue
   <CwIcon
     :svg="arrowDownIconSvg"
     :styles="arrowIconStyle"
   />
   ```

6. **规范要点**：
   - ✅ **基础样式整合**：所有静态样式（颜色、过渡等）必须整合到 `arrowIconStyles.root` 默认值中
   - ✅ **状态样式独立**：状态样式（如 `rotate-180`）作为独立字符串属性，在 computed 中动态合并
   - ✅ **避免重复**：不在 computed 中重复合并已在默认值中的基础样式
   - ✅ **过渡效果必须**：必须包含 `transition-transform` 等过渡样式，确保状态切换动画流畅
   - ✅ **类型安全**：使用 `Partial<CwIconStyles>` 类型，支持部分覆盖

**完整示例：CwSelect 箭头图标**

```typescript
// CwSelect/types.ts
export interface CwSelectStyles {
  // ... 其他样式
  arrowOpen: string
  /** CwIcon 样式配置（箭头图标） */
  arrowIconStyles?: Partial<CwIconStyles>
}

// CwSelect/styles.ts
export const defaultStyles: CwSelectStyles = {
  // ... 其他样式
  arrowOpen: 'rotate-180',
  arrowIconStyles: {
    root: 'inline-flex items-center justify-center text-current text-[var(--text-color-secondary)] transition-transform duration-200',
    size: 16
  } as Partial<CwIconStyles>,
}

// CwSelect/CwSelect.vue
const popupVisible = ref(false)

// 箭头图标样式
const arrowIconStyle = computed(() => {
  const baseStyles = styles.value
  const iconStyle = baseStyles.arrowIconStyles || {}
  return {
    ...iconStyle,
    root: [
      iconStyle.root || '',
      popupVisible.value ? baseStyles.arrowOpen : ''
    ].filter(Boolean).join(' ')
  }
})
```

```vue
<!-- 模板中使用 -->
<CwIcon
  :svg="arrowDownIconSvg"
  :styles="arrowIconStyle"
/>
```

### 2. 入口文件格式（强制）

本组件库采用分离式入口文件结构，按组件类型分别导出：

#### 2.1 主入口文件 src/index.ts（自研组件）

主入口文件 `src/index.ts` 专门导出自研组件（Cw/Tp 前缀）、业务特定组件（Bs 前缀）和页面组件（Page 前缀），按以下顺序组织：

1. **先导出所有 Cw 组件**（按功能分类）
2. **再导出所有 Bs 组件**（按功能分类）
3. **再导出所有 Tp 组件**（按功能分类）
4. **最后导出所有 Page 组件**

```typescript
// ==================== Cw 组件（自研组件） ====================

// ==================== Data Visualization（数据可视化） ====================
export * from "./components/CwGraph";
export * from "./components/CwHashMap";
export * from "./components/CwUnionFind";

// ==================== 3D & Graphics（3D和图形） ====================
export * from "./components/CwGlsl";
export * from "./components/CwParticleBackground";

// ... 其他 Cw 组件分类

// ==================== Bs 组件（业务特定组件） ====================

// ==================== Display（展示） ====================
export * from "./components/BsAccountBalanceCard";
export * from "./components/BsBenefitCard";

// ==================== Interaction（交互） ====================
export * from "./components/BsFunctionGridItem";

// ==================== Business（业务） ====================
export * from "./components/BsPromotionBanner";

// ==================== Tp 组件（重型第三方库整合） ====================

// ==================== Content Creation（内容创作） ====================
export * from "./components/TpCherryMarkdown";
export * from "./components/TpCodeEditor";
export * from "./components/TpFabricCanvas";
export * from "./components/TpMonacoEditor";
export * from "./components/TpSkiaCanvas";

// ==================== Data Visualization（数据可视化） ====================
export * from "./components/TpChartJs";
export * from "./components/TpVueFlow";

// ==================== 3D & Graphics（3D和图形） ====================
export * from "./components/TpGsap";
export * from "./components/TpHeroScene";
export * from "./components/TpThreeJs";

// ... 其他 Tp 组件分类

// ==================== Page 组件（页面级业务组件） ====================
export * from "./components/PageChangePassword";
export * from "./components/PageChangePhone";
export * from "./components/PageContactInfoMobile";
export * from "./components/PageDeactivateAccountMobile";
```

**分类组织要求：**
- **导出顺序**：先导出所有 Cw 组件，再导出所有 Bs 组件，再导出所有 Tp 组件，最后导出所有 Page 组件
- 按功能分类进行分组
- 每个分类使用注释分隔
- 分类内按字母顺序排列
- 新增组件时按分类归属插入

#### 2.2 Element Plus 组件入口文件 src/ui.ts

Element Plus 包装组件（Ui 前缀）专用入口文件：

```typescript
// Element Plus 包装组件 (Ui 前缀) - 按字母顺序导出
export * from "./components/UiAffix";
export * from "./components/UiAlert";
export * from "./components/UiAnchor";
// ... 其他 Ui 组件按字母顺序导出
```

#### 2.3 Vant 组件入口文件 src/vt.ts

Vant 包装组件（Vt 前缀）专用入口文件：

```typescript
// Vant 包装组件 (Vt 前缀) - 按字母顺序导出
export * from "./components/VtActionBar";
export * from "./components/VtButton";
export * from "./components/VtCard";
// ... 其他 Vt 组件按字母顺序导出
```

#### 2.4 Ant Design Vue 组件入口文件 src/at.ts

Ant Design Vue 包装组件（At 前缀）专用入口文件：

```typescript
// Ant Design Vue 包装组件 (At 前缀) - 按字母顺序导出
export * from "./components/AtButton";
export * from "./components/AtInput";
export * from "./components/AtSelect";
// ... 其他 At 组件按字母顺序导出
```

**分离式入口文件的强制要求：**
- **按组件类型分离**：每种组件类型使用独立的入口文件
- **导出顺序**：`src/index.ts` 中先导出所有 Cw 组件，再导出所有 Tp 组件，最后导出所有 Page 组件
- **Cw/Tp/Page 组件按功能分类**：自研组件按功能分类组织，便于维护和使用
- **组件按字母顺序排列**：每个分类内的组件按字母顺序导出
- **清晰的文件职责**：每个入口文件只负责对应类型的组件导出
- **使用 export * 导出**：必须使用 `export * from "./components/ComponentName"` 格式
- **维护便利性**：新增组件时按功能分类和字母顺序插入到对应位置

### 3. 组件 index.ts 导出格式（强制）

#### 3.1 第三方库包装组件导出格式（Ui/Vt/At开头）

**单组件导出：**
```typescript
export { default as UiComponentName } from './UiComponentName.vue'
export * from'./types'
```

**多组件导出（包含子组件）：**
```typescript
export { default as UiMainComponent } from './UiMainComponent.vue'
export { default as UiSubComponent1 } from './UiSubComponent1.vue'
export { default as UiSubComponent2 } from './UiSubComponent2.vue'
export * from'./types'
```

#### 3.2 自研组件导出格式（Cw/Tp/Page 开头）

**单组件导出：**
```typescript
export { default as CwComponentName } from './CwComponentName.vue'
export * from'./types'
```

**多组件导出（包含子组件）：**
```typescript
export { default as CwMainComponent } from './CwMainComponent.vue'
export { default as CwSubComponent1 } from './CwSubComponent1.vue'
export { default as CwSubComponent2 } from './CwSubComponent2.vue'
export * from'./types'
```

**强制要求：**
- 必须使用 `export { default as ... }` 格式
- 必须使用 `export * from'./types'` 导出类型
- **必须导出所有子组件**（如 UiDropdownItem、UiMenuItem、UiTabPane、VtCell、AtTableColumn、CwCustomItem、TpChartLegend 等）
- 不允许使用其他导出方式
- 不允许添加额外的导入或注释
- **重要：如果组件有子组件，必须在 index.ts 中导出所有子组件**
- 自研组件（Cw/Tp/Page 开头）的导出格式与第三方库包装组件相同

#### 3.3 Dialog 和 Sheet 类型组件的函数式 API 规范（强制）

**适用范围：**
- Dialog 类型组件：如 `CwConfirmDialog`、`CwDynamicDialog` 等
- Sheet 类型组件：如 `CwSelectSheet`、`CwImageCropperSheet` 等

**强制要求：**
1. **只支持函数式 API**：Dialog 和 Sheet 类型组件**必须**只通过函数式 API 使用，禁止直接作为组件使用
2. **函数命名规范**：函数式 API 必须使用 `show{ComponentName}` 格式命名
   - 例如：`showConfirmDialog`、`showSelectSheet`、`showImageCropperSheet`
3. **导出规范**：
   - 在 `index.ts` 中**必须**导出函数式 API：`export { showXxx } from './showXxx'`
   - **禁止导出组件本身**：`index.ts` 中**不得**导出组件（如 `export { default as CwSelectSheet } from './CwSelectSheet.vue'`），只允许导出函数式 API 和类型
   - 函数式 API 必须返回 `Promise<Result>`，Result 类型必须明确
   - 正确的 `index.ts` 格式：
     ```typescript
     // ✅ 正确：只导出函数式 API 和类型
     export { showSelectSheet } from './showSelectSheet'
     export * from './types'
     
     // ❌ 错误：禁止导出组件
     export { default as CwSelectSheet } from './CwSelectSheet.vue'
     ```
4. **实现要求**：
   - 函数式 API 必须使用 `createApp` 创建独立的 Vue 应用实例
   - 必须正确处理组件的生命周期（创建、挂载、卸载）
   - 必须等待关闭动画完成后再清理组件（延迟时间与动画时间一致，通常为 300ms）
   - 必须正确处理 Promise 的 resolve 和 reject
5. **子组件样式配置规范（函数式 API 组件特殊规则）**：
   - **允许作为 props 的直接属性**：函数式 API 组件允许将子组件样式配置作为 props 的直接属性（如 `sheetStyles`、`cropperStyles`），而不是必须在 `styles` 对象中
   - **类型定义示例**：
     ```typescript
     export interface CwSelectSheetProps {
       options?: CwSelectSheetOption[]
       styles?: Partial<CwSelectSheetStyles>
       // ✅ 允许：子组件样式作为 props 的直接属性
       sheetStyles?: Partial<CwDynamicSheetInnerStyles>
     }
     
     export interface CwImageCropperSheetProps {
       styles?: Partial<CwImageCropperSheetStyles>
       // ✅ 允许：子组件样式作为 props 的直接属性
       cropperStyles?: Partial<CwImageCropperStyles>
       sheetStyles?: Partial<CwDynamicSheetInnerStyles>
     }
     ```
   - **使用示例**：
     ```typescript
     // ✅ 正确：使用独立的 props 传递子组件样式
     await showSelectSheet({
       options,
       sheetStyles: {
         body: 'custom-body-style'
       }
     })
     
     await showImageCropperSheet({
       cropperStyles: {
         container: 'custom-container-style'
       },
       sheetStyles: {
         body: 'custom-body-style'
       }
     })
     ```
   - **原因**：函数式 API 组件使用独立的 props 更直观和简洁，符合函数式调用的习惯
   - **注意**：普通组件（非函数式 API）仍然必须将子组件样式配置放在 `styles` 对象中
6. **使用示例**：
   ```typescript
   // ✅ 正确：使用函数式 API
   import { showSelectSheet } from '@/components/CwSelectSheet'
   const result = await showSelectSheet({ options, title: '选择' })
   
   // ❌ 错误：直接作为组件使用（禁止）
   import { CwSelectSheet } from '@/components/CwSelectSheet'
   <CwSelectSheet :options="options" />
   ```


### 4. AI 开发约束（强制）

在使用 AI 辅助开发时，必须严格遵守以下规则：

1. **代码格式不可变更**：AI 不得修改已确定的组件代码格式
2. **导出格式统一**：所有 `index.ts` 文件必须使用统一的导出格式
3. **完全透传原则**：第三方库包装组件只能透传，不得添加自定义逻辑
4. **类型获取规范**：第三方库包装组件的类型必须从对应库获取，不得自定义
5. **自研组件灵活性**：Cw/Tp/Page 开头的自研组件必须有自定义逻辑和类型定义
6. **目录结构固定**：每个组件必须包含 `index.ts`、`types.ts`、`stories` 文件和组件文件
7. **命名规范**：组件名必须以 `Ui`（Element Plus）、`Vt`（Vant）、`At`（Ant Design Vue）、`Tp`（重型第三方整合）、`Cw`（纯自研）或 `Page`（页面级业务组件）开头
8. **子组件导出规范**：必须导出所有子组件，确保用户可以正常使用完整的组件功能
9. **避免混合导出**：严格禁止同时使用命名导出和默认导出，避免构建警告
10. **强制使用 tokens 色值**：**严禁使用 hardcode 色值**（如 `gray-500`、`blue-600`），必须使用 `src/assets/tokens/tokens.css` 中定义的 CSS 变量
11. **Dialog 和 Sheet 组件规范**：Dialog 和 Sheet 类型组件**必须**只提供函数式 API（`showXxx`），禁止直接作为组件使用。函数式 API 必须返回 `Promise<Result>`，并正确处理生命周期和动画清理。**禁止在 `index.ts` 中导出组件本身**，**禁止在 Stories 中直接使用组件**，只能使用函数式 API

### 5. 父子组件结构规范（强制）

当组件库中存在父子组件关系时，必须按照以下规范进行组织：

**组织原则：**
1. **子组件归属**：将子组件的 `.vue` 文件放置在父组件目录下
2. **类型统一**：将子组件的类型定义统一到父组件的 `types.ts` 文件中
3. **导出集中**：在父组件的 `index.ts` 中集中导出父组件和子组件
4. **Stories 规范**：子组件不应该有独立的 `.stories.ts` 文件，只在父组件的 stories 中展示

### 6. types.ts 文件格式（强制）

#### 6.1 第三方库包装组件类型格式（Ui/Vt/At开头）

**基本格式：**
```typescript
export type UiComponentProps = {}
export type UiComponentEmits = {}
```

**强制要求：**
- 不能自定义任何类型
- **所有组件的Props类型都定义为空接口 `{}`**
- **如果被透传的组件没有 emit，我们的组件也不需要定义 emit 接口**
- 接口命名必须以 `Ui`、`Vt` 或 `At` 开头，后跟组件名和 `Props` 或 `Emits`
- **types.ts 文件只包含空对象定义的props，组件和子组件的。不包含其他的任何类型**

#### 6.2 自研组件类型格式（Cw/Tp/Page 开头）

**基本格式：**
```typescript
export interface CwComponentProps {
  // 自定义属性
  title?: string
  disabled?: boolean
  // ... 其他属性
}

export interface CwComponentEmits {
  // 自定义事件
  change: [value: any]
  click: [event: MouseEvent]
  // ... 其他事件
}
```

**自研组件类型要求：**
- 可以自定义任何类型和接口
- 接口命名必须以 `Cw`、`Tp` 或 `Page` 开头，后跟组件名和 `Props` 或 `Emits`
- 可以定义复杂的类型结构
- 可以导入和使用第三方类型
- **styles 和 texts Props 必须是 Partial 类型（强制）**：
  - 所有组件的 `styles` 和 `texts` Props 类型必须定义为 `Partial<组件Styles>` 和 `Partial<组件Texts>`
  - 原因：组件内部需要对 `props.styles` 与 `defaultStyles` 进行浅层合并
  - 示例：
    ```typescript
    export interface CwComponentProps {
      styles?: Partial<CwComponentStyles>
      texts?: Partial<CwComponentTexts>
      // ... 其他属性
    }
    ```

**第三方组件类型格式（强制）：**
```typescript
// 第三方库包装组件必须使用 interface 格式，保持一致性
export interface UiButtonProps {}
export interface UiButtonEmits {}

export interface VtButtonProps {}
export interface VtButtonEmits {}

export interface AtButtonProps {}
export interface AtButtonEmits {}
```

**重要规范：**
- 第三方组件必须使用 `interface` 而不是 `type`
- 必须同时定义 `Props` 和 `Emits` 接口，即使为空
- 接口名称格式：`{前缀}{组件名}Props` 和 `{前缀}{组件名}Emits`
- 所有接口都必须为空对象 `{}`，不允许添加任何属性

**自研组件类型示例：**
```typescript
// 自研组件可以自定义类型
export interface CwDataTableProps<T> {
  data: T[]
  columns: Column[]
  loading?: boolean
  pagination?: PaginationConfig
  // ⚠️ styles 和 texts 必须是 Partial 类型
  styles?: Partial<CwDataTableStyles>
  texts?: Partial<CwDataTableTexts>
}

export interface CwModalProps {
  visible: boolean
  title: string
  width?: string | number
  closable?: boolean
  // ⚠️ styles 和 texts 必须是 Partial 类型
  styles?: Partial<CwModalStyles>
  texts?: Partial<CwModalTexts>
}

// 错误：第三方库包装组件不要导入任何类型，会导致编译错误
// import type { ButtonProps } from 'element-plus'
// export type UiButtonProps = ButtonProps
```

**重要：types.ts 文件格式规则**
- 第三方库包装组件只能使用 `export type` 导出空接口类型
- 自研组件可以使用 `export interface` 导出自定义类型
- 接口/类型名称必须以 `Ui`、`Vt`、`At`、`Cw`、`Tp` 或 `Page` 开头，后跟组件名称和 `Props` 或 `Emits`
- 不允许出现类似 `export type { UiComponentProps }` 的重复导出语句
- **第三方库包装组件的Props类型都定义为空接口 `{}`**
- **自研组件可以定义复杂的Props和Emits类型**
- **types.ts 文件包含组件和子组件的类型定义**
- **强制要求：所有自研组件的 `styles` 和 `texts` Props 类型必须是 `Partial<组件Styles>` 和 `Partial<组件Texts>`**，因为组件内部需要对 `props.styles` 与 `defaultStyles` 进行浅层合并

### 7. stories 文件格式（强制）

#### 7.1 第三方库包装组件 Stories 格式（Ui/Vt/At开头）

第三方库包装组件的 stories 文件必须严格按照以下规则：

**基本格式：**
```typescript
import type { Meta, StoryObj } from '@storybook/vue3-vite'
import { ElComponent } from 'element-plus' // 导入对应的第三方库组件
import UiComponent from './UiComponent.vue'

const meta: Meta<typeof ElComponent> = { // 使用第三方库组件类型
  title: 'Element Plus Components/UiComponent', // 使用对应的组件库分类名称
  component: UiComponent,
  parameters: {
    layout: 'centered'
  },
  tags: ['autodocs'], // 必须添加 autodocs 标签
  argTypes: {
    // 组件重要的prop
    prop1: {
      control: { type: 'text' }
    },
    prop2: {
      control: { type: 'boolean' }
    },
    prop3: {
      control: { type: 'select' },
      options: ['option1', 'option2']
    }
  },
}

export default meta
type Story = StoryObj<typeof meta>

export const Default: Story = {
  args: {
    // 组件重要的prop
    prop1: '默认值',
    prop2: false,
    prop3: 'option1',
  },
  render: (args) => ({
    components: { UiComponent }, // 使用我们的组件渲染，只是使用第三方库组件的props
    setup() {
      return { args };
    },
    template: `
      <UiComponent v-bind="args" />
    `
  }),
}
```

**第三方库包装组件 Stories 强制要求：**
- **Meta 类型必须使用第三方库组件类型**：`Meta<typeof ElComponent>`、`Meta<typeof VanComponent>`、`Meta<typeof AntComponent>`
- **必须导入对应的第三方库组件**：如 `import { ElButton } from 'element-plus'`
- **必须添加 `tags: ['autodocs']`**：启用自动文档生成
- **argTypes 格式简化**：
  - 使用 `control: { type: 'xxx' }` 格式
  - 保留重要的控件配置

**通用 Stories 强制要求：**
- **必须使用 `@storybook/vue3-vite` 而不是 `@storybook/vue3`**
- **title 字段必须使用正确的组件库分类名称（数字前缀用于控制 Storybook 侧边栏排序）**：
  - 自研组件 (Cw 前缀)：`1. Custom Components/{Category}/{ComponentName}` （按功能分类）
  - 业务特定组件 (Bs 前缀)：`2. Business Specific Components/{Category}/{ComponentName}` （按功能分类）
  - 页面级业务组件 (Page 前缀)：`3. Pages/{ComponentName}` （统一归入 Pages 分类）
  - 重型第三方整合组件 (Tp 前缀)：`4. Third Party Components/{Category}/{ComponentName}` （按功能分类）
  - Ant Design Vue 包装组件 (At 前缀)：`5. Ant Design Vue Components/{ComponentName}`
  - Element Plus 包装组件 (Ui 前缀)：`6. Element Plus Components/{ComponentName}`
  - Vant 包装组件 (Vt 前缀)：`7. Vant Components/{ComponentName}`
- **必须使用 `StoryObj<typeof meta>` 定义 Story 类型**

#### 7.2 自研组件 Stories 特殊要求

自研组件（Cw/Tp/Page 开头）的 stories 文件必须按照分类组织：

```typescript
import type { Meta, StoryObj } from '@storybook/vue3-vite'
import CwComponent from './CwComponent.vue'

const meta: Meta<typeof CwComponent> = {
  title: 'Custom Components/{Category}/CwComponent', // 使用分类路径
  component: CwComponent,
  parameters: {
    layout: 'centered',
    docs: {
      description: {
        component: '自研组件的详细描述，包括业务场景和使用方法。'
      }
    }
  },
  argTypes: {
    // 可以定义复杂的 argTypes
    onCustomEvent: { action: 'custom-event' }
  }
}

export default meta
type Story = StoryObj<typeof meta>

export const Default: Story = {
  args: {
    title: '默认标题'
  }
}

export const BusinessScenario: Story = {
  args: {
    title: '业务场景示例',
    data: mockBusinessData
  }
}
```

**自研组件 Stories 要求：**
- 使用 `Custom Components/{Category}/{ComponentName}` 格式
- 可以包含复杂的业务场景示例
- 可以使用 mock 数据展示真实使用场景
- 可以定义详细的组件描述和使用说明
- 分类路径必须与入口文件分类保持一致

#### 7.3 自研组件子组件 Stories 规范（Cw/Tp/Page，强制）

当 Cw/Tp/Page 组件包含子组件时，必须严格遵守以下规范：

**类型定义规范：**
```typescript
// 在主组件的 stories 文件中定义子组件类型
type ConversationCellStory = StoryObj<typeof CwChatConversationCell>
type MessageTextStory = StoryObj<typeof CwChatMessageText>
type MessageImageStory = StoryObj<typeof CwChatMessageImage>
// ... 其他子组件类型
```

**子组件 Story 定义规范：**
```typescript
// 子组件 story 必须使用其专用类型，而不是通用的 StoryObj
export const ConversationCell: ConversationCellStory = {
  args: {
    // 必须使用子组件自己的 props，不能使用主组件的 props
    name: '张三',
    avatar: 'https://example.com/avatar.jpg',
    description: '你好，最近怎么样？',
    time: '5分钟前',
    showRedDot: true
  },
  render: (args) => ({
    components: { CwChatConversationCell },
    setup() {
      const handleClick = (event: MouseEvent) => {
        console.log('Conversation cell clicked:', event)
      }
      return { args, handleClick }
    },
    template: `
      <div class="w-full p-4">
        <CwChatConversationCell
          v-bind="args"
          @click="handleClick"
        />
      </div>
    `
  })
}
```

**类型定义格式：**
```typescript
import type { Meta, StoryObj } from '@storybook/vue3-vite'
import CwMainComponent from './CwMainComponent.vue'
import CwSubComponent1 from './CwSubComponent1.vue'
import CwSubComponent2 from './CwSubComponent2.vue'

const meta: Meta<typeof CwMainComponent> = {
  title: 'Custom Components/{Category}/CwMainComponent',
  component: CwMainComponent,
  // ...
}

export default meta
type Story = StoryObj<typeof meta>

// 为每个子组件定义独立的 Story 类型
type CwSubComponent1Story = StoryObj<typeof CwSubComponent1>
type CwSubComponent2Story = StoryObj<typeof CwSubComponent2>

export const Default: Story = { /* ... */ }

// 子组件 story 必须使用其专用的类型
export const SubComponent1: CwSubComponent1Story = {
  render: () => ({
    components: { CwSubComponent1 },
    setup() {
      return {};
    },
    template: `<CwSubComponent1>Content</CwSubComponent1>`
  }),
}

export const SubComponent2: CwSubComponent2Story = {
  render: () => ({
    components: { CwSubComponent2 },
    setup() {
      return {};
    },
    template: `<CwSubComponent2>Content</CwSubComponent2>`
  }),
}
```

**强制要求：**
1. **类型定义**：必须在 stories 文件中为每个子组件定义独立的 Story 类型，如 `type CwSubComponent1Story = StoryObj<typeof CwSubComponent1>`
2. **类型使用**：每个子组件 story 必须使用其专用的类型定义，而不是主组件的 `Story` 类型
3. **类型安全**：不允许使用通用的 `StoryObj` 或主组件的 `Story` 类型来定义子组件 story
4. **独立导出**：子组件必须从 index.ts 独立导出，供外部直接使用
5. **导入声明**：必须在文件顶部正确导入所有子组件
6. **Props 独立性**：子组件 story 的 `args` 必须只包含该子组件自己的 props
7. **事件处理**：子组件的事件处理函数必须符合该子组件的 emits 定义

### 8. 违规处理

任何违反上述规则的代码都必须立即修正，不允许例外情况。AI 在开发过程中必须：
- 你不需要启动服务器来进行验证，因为我会一直启动一个预览服务，你只需要告诉我你修改完成即可
- 严格检查代码格式是否符合规范
- 确保所有导出格式统一
- 验证 types.ts 文件中的类型定义正确性
- 自研组件确保类型定义完整和准确
- 验证 stories 文件中的 Meta 泛型和响应式要求
- 验证组件功能正常
- 保持代码风格一致性
- 严格区分第三方库包装组件（Ui/Vt/At）和自研组件（Cw/Tp/Page）的不同要求
- **严格检查 tokens 使用**：确保所有 Cw/Tp/Page 组件使用 tokens 色值，严禁 `gray-500`、`blue-600` 等 hardcode 色值，必须使用 `var(--brand-color-6)` 等 tokens 变量
- **严格检查 Tailwind CSS 使用**：确保所有 Cw/Tp/Page 组件只使用 Tailwind CSS，禁止使用自定义 CSS 样式