# AGENTS.md - haha-mp 智能体代码生成规则

> 本文件为 AI Agent 代码生成提供强制规则约束，定义生成边界与规范。
> 所有规则均为强制性，Agent 生成代码时必须严格遵守。违反规则的代码不可提交。

---

## 1. 项目边界

### 1.1 技术栈版本锁定

| 技术 | 版本 | 禁止事项 |
|------|------|----------|
| uni-app | 3.0.0-5000720260410001 | 禁止使用 uni-app v2 |
| Vue | 3.5.32 | Composition API only，禁止 Options API |
| TypeScript | ^4.9.4 | 注意：uni-app 限制，不可升级到 TS 5 |
| Vite | 5.2.8 | 禁止升级到 Vite 6+（uni-app 兼容性） |
| Pinia | ^3.0.4 | 禁止 Vuex |
| Sass | ^1.99.0 | 样式预处理统一使用 SCSS |
| 微信小程序 appid | `wxb1cbe4678e0175f2` | 禁止修改 |

### 1.2 目标平台

- **主要平台**：微信小程序（`mp-weixin`）
- 构建命令：`pnpm dev:mp-weixin`（开发）、`pnpm build:mp-weixin`（构建）
- 代码需兼容小程序运行时限制（无 DOM、无 window、无 axios）

### 1.3 后端 API 对接

| 配置项 | 值 |
|--------|-----|
| 后端服务地址 | `https://dev-haha.kuaiyuman.cn/api`（开发环境） |
| 后端应用 | `haha-miniapp`（端口 7077，context-path: /api） |
| 请求方式 | `uni.request`（封装在 `src/utils/request.ts`） |
| 认证方式 | 请求头 `accessToken: {token}` |
| 请求超时 | 30000ms |

### 1.4 目录职责（严格遵循）

```
src/
├── api/          # API 请求函数（每个模块一个文件）
├── components/   # 公共组件
├── pages/        # 小程序页面（每个页面一个目录，含 .vue 文件）
├── static/       # 静态资源（图标、图片、动画样式）
│   ├── icons/    # SVG 图标
│   └── images/   # 图片资源
├── styles/       # 全局样式
├── utils/        # 工具函数
│   ├── request.ts  # HTTP 请求封装
│   ├── auth.ts     # 认证工具
│   └── config.ts   # API 配置
├── App.vue       # 应用入口（onLaunch/onShow/onHide）
├── main.ts       # 创建应用（createSSRApp + Pinia）
├── manifest.json # 小程序配置
├── pages.json    # 页面路由与导航栏配置
└── uni.scss      # 全局 SCSS 变量（Design Tokens）
```

---

## 2. 代码生成规则

### 2.1 页面 (pages/)

**强制结构**：每个页面是一个独立目录，包含同名的 `.vue` 文件

```
pages/{pageName}/
└── {pageName}.vue    # 页面组件
```

**页面 Vue 文件模板**：
```vue
<template>
  <view class="container">
    <!-- 页面内容 -->
  </view>
</template>

<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { onShow, onLoad } from '@dcloudio/uni-app';
import { xxxApi } from '@/api/xxx';

// 响应式数据
const dataList = ref<XxxType[]>([]);
const loading = ref(false);

// 页面加载
onLoad((options: any) => {
  // 处理页面参数
});

// 页面显示（每次从后台切回都触发）
onShow(() => {
  // 刷新数据
});

// 方法
const loadData = async () => {
  if (loading.value) return;
  loading.value = true;
  try {
    uni.showLoading({ title: '加载中...', mask: true });
    const result = await xxxApi();
    dataList.value = result;
  } catch (error: any) {
    console.error('加载失败:', error);
    // 错误已在 request.ts 中统一 toast 提示
  } finally {
    uni.hideLoading();
    loading.value = false;
  }
};
</script>

<style lang="scss">
/* 使用 Design Token 变量，禁止硬编码颜色/间距 */
.container {
  min-height: 100vh;
  background: $color-bg-secondary;
  padding: $spacing-lg;
  box-sizing: border-box;
}
</style>
```

**页面注册**：新增页面必须在 `pages.json` 中注册

```json
{
  "path": "pages/xxx/xxx",
  "style": {
    "navigationBarTitleText": "页面标题",
    "navigationBarBackgroundColor": "#FFD700",
    "navigationBarTextStyle": "black"
  }
}
```

**强制规则**：
- 导航栏背景色统一 `#FFD700`（品牌黄）
- 导航栏文字颜色统一 `black`
- 使用 `<view>` 代替 `<div>`，`<text>` 代替 `<span>`
- 使用 `<image>` 代替 `<img>`，必须指定 `mode` 属性
- 使用 `uni.xxx` API 代替浏览器原生 API
- 使用 `rpx` 单位代替 `px`（1rpx = 屏幕宽度/750）
- 使用 `@dcloudio/uni-app` 生命周期钩子（`onLoad`、`onShow`、`onHide`）
- 禁止使用 DOM API（`document`、`window`、`localStorage`）
- 使用 `uni.getStorageSync/setStorageSync` 代替 `localStorage`
- 路由跳转使用 `uni.navigateTo`/`uni.reLaunch`/`uni.switchTab`

### 2.2 API 层 (api/)

**强制规则**：
1. 每个 API 文件对应一个后端模块
2. 统一使用 `src/utils/request.ts` 中的 `request`/`get`/`post`/`put`/`del`
3. 必须定义 TypeScript 接口，导出供页面使用

**API 文件模板**：
```ts
/**
 * xxx相关API
 */

import { get, post } from '../utils/request';

/**
 * 数据接口定义
 */
export interface XxxInfo {
  id: string;              // 雪花ID，必须为 string 类型
  name: string;
  status: number;
  createTime: string;
}

/**
 * 请求参数接口
 */
export interface XxxListRequest {
  page?: number;
  pageSize?: number;
  status?: number;
}

/**
 * 获取xxx列表
 * @param params 查询参数
 */
export const getXxxList = (params?: XxxListRequest): Promise<XxxInfo[]> => {
  return post<XxxInfo[]>('/xxx/list', params || {});
};

/**
 * 获取xxx详情
 * @param id xxx ID
 */
export const getXxxDetail = (id: string): Promise<XxxInfo> => {
  return get<XxxInfo>(`/xxx/${id}`);
};
```

**禁止事项**：
- 禁止直接使用 `uni.request`（必须通过 `request.ts` 封装）
- 禁止在 API 函数中处理 UI 逻辑（如 `uni.showToast`）
- 禁止使用 `axios`（小程序不支持）
- 禁止硬编码后端地址（使用 `API_CONFIG.baseUrl`）

### 2.3 请求封装 (utils/request.ts)

**已有封装，禁止重写**。关键行为：

- 请求拦截：自动添加 `accessToken` 请求头
- 响应拦截：`code === 200 || code === 0` 为成功，返回 `data` 字段
- 401 处理：清除 token + 1.5s 后跳转登录页
- 错误处理：自动 `uni.showToast` 提示
- 支持 `skipAuth` 参数跳过认证（仅用于免登录接口）

**免登录接口示例**：
```ts
export const getDeviceProducts = (deviceId: string): Promise<DeviceProductsResponse> => {
  return get<DeviceProductsResponse>(`/device/products/${deviceId}`, undefined, { skipAuth: true });
};
```

### 2.4 认证工具 (utils/auth.ts)

**已有封装，禁止重写**。关键行为：

| 函数 | 用途 |
|------|------|
| `getToken()` | 获取 accessToken |
| `setToken(token)` | 存储 accessToken |
| `removeToken()` | 清除 accessToken |
| `isLoggedIn()` | 判断是否已登录 |
| `checkAuth(redirectUrl?)` | 路由守卫，未登录跳转登录页 |
| `clearAuth()` | 清除所有登录信息 + 购物流程数据 |
| `logout()` | 清除登录信息 + reLaunch 到登录页 |

**Token 存储 key**：`accessToken`（uni.getStorageSync）

---

## 3. 样式规则

### 3.1 Design Token 变量

**必须使用 `uni.scss` 中定义的 Design Token**，禁止硬编码：

```scss
/* 主色系 */
$color-primary: #FFC107;          // 品牌黄
$color-primary-light: #FFE082;
$color-primary-dark: #FFA000;

/* 文字颜色 */
$color-text-primary: #2C2C2C;     // 主文字
$color-text-secondary: #8C8C8C;   // 辅助文字
$color-text-tertiary: #BDBDBD;    // 三级文字

/* 背景色 */
$color-bg-primary: #FFFFFF;
$color-bg-secondary: #FAFAFA;
$color-bg-tertiary: #F5F5F5;

/* 边框/阴影 */
$color-border: #EEEEEE;
$shadow-primary: 0 8rpx 24rpx rgba(255, 193, 7, 0.25);

/* 间距系统 */
$spacing-xs: 8rpx;
$spacing-sm: 16rpx;
$spacing-md: 24rpx;
$spacing-lg: 32rpx;
$spacing-xl: 48rpx;
$spacing-xxl: 64rpx;

/* 圆角 */
$radius-sm: 8rpx;
$radius-md: 16rpx;
$radius-lg: 24rpx;
$radius-xl: 32rpx;

/* 动画 */
$duration-fast: 150ms;
$duration-normal: 300ms;
$ease-out: cubic-bezier(0.25, 0.1, 0.25, 1);
$bounce: cubic-bezier(0.68, -0.55, 0.265, 1.55);
```

### 3.2 样式编写规范

| 规则 | 说明 |
|------|------|
| 单位 | 统一使用 `rpx`（设计稿 750rpx 宽度），禁止 `px`/`rem`/`em` |
| 颜色 | 使用 Design Token 变量，禁止硬编码颜色值 |
| 间距 | 使用 `$spacing-xxx` 变量 |
| 圆角 | 使用 `$radius-xxx` 变量 |
| 阴影 | 使用 `$shadow-xxx` 变量 |
| 动画 | 使用 `$duration-xxx` + `$ease-out` |
| 作用域 | 全局样式不加 scoped，页面级样式尽量加 `scoped` |
| 性能 | 动画元素添加 `will-change: transform, opacity` + `transform: translateZ(0)` |

### 3.3 禁止事项

- 禁止使用 `px` 作为布局单位（仅 `rpx`）
- 禁止硬编码颜色值（必须使用变量）
- 禁止使用 CSS `filter` 属性（小程序性能差）
- 禁止使用 `backdrop-filter`（兼容性问题）
- 禁止使用 `position: fixed` 在列表项中（性能问题）
- 禁止复杂 `box-shadow` 动画

---

## 4. 路由与导航规则

### 4.1 路由方式

| 场景 | API | 说明 |
|------|-----|------|
| 普通跳转 | `uni.navigateTo` | 可返回，最多 10 层栈 |
| 不可返回跳转 | `uni.reLaunch` | 关闭所有页面，打开目标页 |
| Tab 切换 | `uni.switchTab` | 用于 tabBar 页面（当前无 tabBar） |
| 返回上一页 | `uni.navigateBack` | -- |
| 重定向 | `uni.redirectTo` | 关闭当前页，打开目标页 |

### 4.2 路由传参

```ts
// 传递参数
uni.navigateTo({
  url: '/pages/xxx/xxx?id=' + String(id) + '&name=' + name
});

// 接收参数
onLoad((options: any) => {
  const id = options.id;  // 字符串
});
```

**强制规则**：
- 雪花 ID 传参必须 `String(id)` 防止精度丢失
- 登录页支持 `redirect` 参数：`/pages/login/login?redirect=/pages/index/index`

### 4.3 页面白名单

以下页面允许未登录用户直接访问（定义在 `App.vue`）：

```ts
const LOGIN_WHITE_LIST = [
  'pages/login/login',
  'pages/products/products',  // 柜内商品展示
];
```

新增免登录页面时，必须同步更新此白名单。

---

## 5. 业务规则约束

### 5.1 扫码开门流程

```
用户扫码 → 检查登录 → 检查支付分 → 调用 scanDoor API → 存储 deviceId/outTradeNo/orderNo → 跳转购物页
```

**强制规则**：
- 扫码前必须检查登录状态
- 扫码前必须检查微信支付分开通状态
- 二维码格式：`https://hh.hahabianli.com/B142977?_wxpmm0=...`
- 设备 ID 提取：正则 `/\/([A-Z0-9]+)(\?|$)/`

### 5.2 订单金额显示

- **实付金额字段**：`paidAmount || totalAmount`（优先使用 paidAmount）
- 格式化：`(amount || 0).toFixed(2)`
- 前缀：`¥`

### 5.3 退款时效

- 仅已完成订单（`status === 1`）可申请退款
- 退款期限：支付时间后 7 天内
- 超过 7 天自动隐藏退款按钮

### 5.4 登录认证

- **唯一登录方式**：微信手机号快速登录（`open-type="getPhoneNumber"`）
- API：`/login/miniapp-phone`，参数 `{ code, phoneCode }`
- 登录成功后存储 token + userInfo
- 支持登录后跳转回原页面（redirect 参数）

---

## 6. 小程序特有约束

### 6.1 兼容性规则

| 编号 | 约束 | 说明 |
|------|------|------|
| C1 | 禁止 DOM API | 无 `document`/`window`/`localStorage` |
| C2 | 禁止 axios | 小程序不支持 XHR，必须用 `uni.request` |
| C3 | 图片必须指定 mode | `<image mode="aspectFit/aspectFill">` |
| C4 | 禁止 v-html | 小程序不支持 |
| C5 | 禁止动态组件 | `<component :is>` 有限制 |
| C6 | 使用 rpx 单位 | 适配不同屏幕宽度 |
| C7 | 事件使用 @tap | 优先使用 `@tap` 代替 `@click` |
| C8 | 按钮反馈 | 操作按钮添加 `hover-class` |
| C9 | 安全区适配 | 底部 `padding-bottom: env(safe-area-inset-bottom)` |
| C10 | 包体积 | 单个包不超过 2MB，主包不超过 2MB |

### 6.2 性能优化规则

| 编号 | 规则 | 说明 |
|------|------|------|
| P1 | 动画使用 `transform` + `opacity` | 避免触发 layout/paint |
| P2 | 动画元素添加 `will-change` | `will-change: transform, opacity` |
| P3 | 动画元素添加 `transform: translateZ(0)` | 开启 GPU 加速 |
| P4 | 禁止 `filter: blur()` | 小程序性能极差 |
| P5 | 禁止复杂 `box-shadow` 动画 | 改用静态阴影 |
| P6 | 长列表使用虚拟列表 | 超过 100 项必须虚拟滚动 |
| P7 | 图片懒加载 | `<image lazy-load>` |
| P8 | 轮询必须有超时和清理 | 避免页面切换后持续请求 |

### 6.3 小程序 API 使用规范

| 需求 | 使用 | 禁止 |
|------|------|------|
| 存储 | `uni.getStorageSync/setStorageSync` | `localStorage` |
| 路由 | `uni.navigateTo/reLaunch` | `router.push` |
| 提示 | `uni.showToast/showLoading/showModal` | 自定义 toast |
| 扫码 | `uni.scanCode` | 第三方扫码 |
| 拨号 | `uni.makePhoneCall` | `tel:` 链接 |
| 震动 | `uni.vibrateShort` | -- |
| 登录 | `uni.login` | -- |

---

## 7. 常见陷阱

| 编号 | 陷阱 | 规则 |
|------|------|------|
| T1 | 雪花 ID 使用 number 类型 | 禁止。必须为 `string` 类型，防止 JS 精度丢失 |
| T2 | 路由传参直接传 number ID | 禁止。必须 `String(id)` |
| T3 | 使用 `localStorage` | 禁止。必须 `uni.getStorageSync/setStorageSync` |
| T4 | 使用 `axios` | 禁止。小程序不支持，使用封装的 `request.ts` |
| T5 | 使用 `document/window` | 禁止。小程序无 DOM |
| T6 | 使用 `px` 单位 | 禁止。统一使用 `rpx` |
| T7 | 硬编码颜色值 | 禁止。使用 `uni.scss` 中的 Design Token |
| T8 | 新页面不注册 pages.json | 禁止。每个新页面必须在 pages.json 中配置 |
| T9 | 免登录页面不更新白名单 | 禁止。同步更新 App.vue 中 LOGIN_WHITE_LIST |
| T10 | 轮询不设置超时 | 禁止。所有轮询必须有 timeout + 页面卸载清理 |
| T11 | 在 API 层处理 UI 逻辑 | 禁止。API 层只负责请求 |
| T12 | 使用 `<div>/<span>/<img>` | 禁止。使用 `<view>/<text>/<image>` |
| T13 | 导航栏颜色不一致 | 禁止。统一 `#FFD700` 背景 + `black` 文字 |
| T14 | 实付金额取 totalAmount | 禁止。优先取 `paidAmount`，fallback `totalAmount` |
| T15 | 不处理安全区 | 禁止。底部内容必须适配 `safe-area-inset-bottom` |
