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

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

---

## 1. 项目边界

### 1.1 技术栈版本锁定

| 技术 | 版本 | 禁止事项 |
|------|------|----------|
| Node.js | ^20.19.0 \|\| >=22.13.0 | 禁止使用 Node 18 及以下 |
| pnpm | >=9 | 禁止使用 npm/yarn |
| Vue | ^3.5.25 | Composition API only，禁止 Options API |
| TypeScript | ^5.9.3 | 禁止 any 滥用，新代码必须提供类型 |
| Vite | ^7.2.7 | -- |
| Element Plus | ^2.12.0 | 禁止引入 Ant Design / Naive UI 等其他组件库 |
| Pinia | ^3.0.4 | 禁止 Vuex |
| Vue Router | ^4.6.3 | -- |
| Tailwind CSS | ^4.1.17 | 用于工具类，禁止替代组件级样式 |
| ECharts | ^6.0.0 | 图表统一使用 ECharts |
| Axios | ^1.13.2 | HTTP 请求统一使用 `http` 实例，禁止直接 `axios` |
| dayjs | ^1.11.19 | 禁止引入 moment.js |
| Sass | ^1.95.1 | 样式预处理统一使用 SCSS |

### 1.2 后端 API 对接

| 配置项 | 值 |
|--------|-----|
| 后端服务地址 | `http://localhost:7070/admin` |
| 开发端口 | 8888 |
| API 代理 | vite.config.ts 中 `apiPaths` 动态生成 |
| 生产 publicPath | `/admin/` |
| 路由模式 | hash |

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

```
src/
├── api/          # API 请求函数（每个模块一个文件）
├── assets/       # 静态资源（图标、图片、字体）
├── components/   # 全局公共组件（ReXxx 命名）
├── config/       # 应用配置
├── directives/   # 自定义指令
├── layout/       # 布局框架
├── plugins/      # 插件注册（ElementPlus、I18n、ECharts、VxeTable）
├── router/       # 路由配置（modules/ 下按模块拆分）
├── store/        # Pinia 状态管理（modules/ 下按模块拆分）
├── style/        # 全局样式（reset、index、tailwind、element-plus、dark）
├── utils/        # 工具函数
├── views/        # 页面视图（每个模块一个目录）
│   └── {module}/
│       ├── index.vue      # 页面入口
│       └── utils/
│           ├── hook.tsx   # 业务逻辑 hook（composable）
│           └── types.ts   # 模块类型定义
└── main.ts       # 应用入口
```

---

## 2. 代码生成规则

### 2.1 页面视图 (views)

**强制结构**：每个业务页面必须按以下模式组织：

```
views/{module}/
├── index.vue       # 纯模板 + 结构，不含业务逻辑
└── utils/
    ├── hook.tsx    # 业务逻辑 composable（export function useXxx）
    └── types.ts    # 模块内类型定义
```

**index.vue 模板**：
```vue
<script setup lang="ts">
import { ref } from "vue";
import { useXxx } from "./utils/hook";  // 使用 hook.tsx 中的 composable
import { PureTableBar } from "@/components/RePureTableBar";
import { useRenderIcon } from "@/components/ReIcon/src/hooks";

defineOptions({
  name: "XxxManage"  // 必须：组件名使用 PascalCase + Manage
});

const formRef = ref();
const tableRef = ref();

const {
  form, loading, columns, dataList, pagination,
  onSearch, resetForm, handleSizeChange, handleCurrentChange,
  // ...其他业务方法
} = useXxx(tableRef);
</script>

<template>
  <div class="main">
    <!-- 搜索表单 -->
    <el-form ref="formRef" :inline="true" :model="form"
             class="search-form bg-bg_color w-full pl-8 pt-[12px] overflow-auto">
      <!-- el-form-item 列表 -->
    </el-form>

    <!-- 表格 -->
    <PureTableBar title="xxx管理" :columns="columns" @refresh="onSearch">
      <template v-slot="{ size, dynamicColumns }">
        <pure-table
          ref="tableRef"
          row-key="id"
          adaptive
          :adaptiveConfig="{ offsetBottom: 108 }"
          align-whole="center"
          table-layout="auto"
          :loading="loading"
          :size="size"
          :data="dataList"
          :columns="dynamicColumns"
          :pagination="{ ...pagination, size }"
          :header-cell-style="{
            background: 'var(--el-fill-color-light)',
            color: 'var(--el-text-color-primary)'
          }"
          @page-size-change="handleSizeChange"
          @page-current-change="handleCurrentChange"
        >
          <template #operation="{ row }">
            <!-- 操作按钮 -->
          </template>
        </pure-table>
      </template>
    </PureTableBar>
  </div>
</template>

<style lang="scss" scoped>
.search-form {
  :deep(.el-form-item) {
    margin-bottom: 12px;
  }
}
</style>
```

**hook.tsx 模板**：
```tsx
import { ref, reactive, onMounted } from "vue";
import type { PaginationProps } from "@pureadmin/table";
import { message } from "@/utils/message";
import { getXxxList } from "@/api/xxx";
import type { XxxItem, XxxSearchForm } from "./types";

export function useXxx(tableRef: Ref) {
  const form = reactive<XxxSearchForm>({ /* 初始值 */ });
  const dataList = ref([]);
  const loading = ref(true);
  const pagination = reactive<PaginationProps>({
    total: 0,
    pageSize: 10,
    currentPage: 1,
    background: true
  });

  const columns: TableColumnList = [
    { label: "xxx", prop: "xxx", minWidth: 120 },
    // ...
  ];

  const onSearch = async () => {
    loading.value = true;
    try {
      const { data } = await getXxxList({ ...form, page: pagination.currentPage, pageSize: pagination.pageSize });
      dataList.value = data.list;
      pagination.total = data.total;
    } finally {
      loading.value = false;
    }
  };

  const resetForm = (formEl: any) => {
    if (!formEl) return;
    formEl.resetFields();
    onSearch();
  };

  const handleSizeChange = (val: number) => {
    pagination.pageSize = val;
    onSearch();
  };

  const handleCurrentChange = (val: number) => {
    pagination.currentPage = val;
    onSearch();
  };

  onMounted(() => { onSearch(); });

  return {
    form, loading, columns, dataList, pagination,
    onSearch, resetForm, handleSizeChange, handleCurrentChange
  };
}
```

**types.ts 模板**：
```ts
export interface XxxItem {
  id: number;
  // 字段定义
}

export interface XxxSearchForm {
  // 搜索表单字段
}
```

### 2.2 API 层 (api/)

**强制规则**：

1. 每个 API 文件对应一个后端模块，文件名与后端路径一致
2. 统一使用 `http.request<T>(method, url, config)` 调用
3. 必须定义 `Result` 和 `ResultTable` 类型（可复用已有定义）

**API 文件模板**：
```ts
import { http } from "@/utils/http";

type Result = {
  code: number;
  message: string;
  data?: any;
};

type ResultTable = {
  code: number;
  message: string;
  data?: {
    list: Array<any>;
    total: number;
    pageSize: number;
    currentPage: number;
  };
};

// 列表查询
export const getXxxList = (params: {
  page?: number;
  pageSize?: number;
  // ...搜索参数
}) => {
  return http.request<ResultTable>("get", "/xxx/list", { params });
};

// 详情查询
export const getXxxById = (id: number) => {
  return http.request<Result>("get", `/xxx/${id}`);
};

// 新增
export const createXxx = (data: any) => {
  return http.request<Result>("post", "/xxx", { data });
};

// 修改状态
export const updateXxxStatus = (id: number, status: number) => {
  return http.request<Result>("put", `/xxx/${id}/status?status=${status}`);
};

// 删除
export const deleteXxx = (id: number) => {
  return http.request<Result>("delete", `/xxx/${id}`);
};
```

**禁止事项**：
- 禁止直接 `import axios` 调用，必须使用 `http` 实例
- 禁止在 API 函数中处理 UI 逻辑（如 ElMessage）
- 禁止硬编码后端地址

### 2.3 路由 (router/modules/)

**路由文件模板**：
```ts
export default {
  path: "/xxx",
  redirect: "/xxx/list",
  meta: {
    icon: "ri:xxx-line",       // Remix Icon
    title: "xxx管理",
    rank: 数字                  // 菜单排序
  },
  children: [
    {
      path: "/xxx/list",
      name: "XxxList",         // PascalCase
      component: () => import("@/views/xxx/index.vue"),
      meta: {
        icon: "ri:xxx-line",
        title: "xxx管理"
      }
    }
  ]
} satisfies RouteConfigsTable;
```

**强制规则**：
- 路由 path 使用 kebab-case
- 路由 name 使用 PascalCase
- 图标统一使用 Remix Icon（`ri:xxx-line`）
- 懒加载必须用 `() => import()` 语法

### 2.4 Store (store/modules/)

**Store 模板**：
```ts
import { defineStore } from "pinia";
import { store } from "../utils";

export const useXxxStore = defineStore("pure-xxx", {
  state: (): XxxState => ({
    // 状态
  }),
  actions: {
    // 方法命名：SET_XXX (修改状态)、async方法 (异步操作)
  }
});

export function useXxxStoreHook() {
  return useXxxStore(store);
}
```

**强制规则**：
- Store ID 前缀 `pure-`
- 必须导出 `useXxxStoreHook` 辅助函数
- 状态修改方法以 `SET_` 前缀命名

### 2.5 组件 (components/)

**强制规则**：
- 全局公共组件以 `Re` 前缀命名（如 `ReDialog`、`ReIcon`）
- 组件导出使用 `index.ts` 桶文件
- 禁止在公共组件中写入业务逻辑

---

## 3. 样式规则

### 3.1 样式方案

| 场景 | 方案 | 说明 |
|------|------|------|
| 布局与间距 | Tailwind CSS | `w-full`、`pl-8`、`pt-[12px]` 等 |
| 组件级样式 | `<style lang="scss" scoped>` | 必须加 scoped |
| 全局样式 | `src/style/` | reset、主题、动画 |
| 深度选择器 | `:deep()` | 禁止 `::v-deep`、`/deep/`、`>>>` |

### 3.2 禁止事项

- 禁止在组件内写非 scoped 的全局样式
- 禁止使用内联 `style=""` 属性（Tailwind 例外）
- 禁止覆盖 Element Plus 主题变量（通过 CSS 变量定制）

---

## 4. 认证与权限规则

### 4.1 Token 管理

- Token 存储在 Cookie（`authorized-token`）+ localStorage（`user-info`）
- 请求头格式：`Authorization: Bearer {token}`
- Token 自动刷新：`PureHttp` 类内置无感刷新机制
- 401 响应自动登出：响应拦截器处理

### 4.2 权限控制

| 层级 | 实现方式 | 示例 |
|------|----------|------|
| 路由级别 | `meta.roles` | 路由守卫校验 |
| 按钮级别 | `<Auth>` 组件 / `v-perms` 指令 | `<Auth value="device:open">按钮</Auth>` |
| API 级别 | 后端 `@RequirePermission` | 后端控制 |

### 4.3 登录流程

1. 调用 `login()` API
2. `setToken()` 存储 token 到 Cookie + localStorage
3. `useUserStoreHook().SET_ROLES/SET_PERMS` 存储权限
4. 路由守卫 `initRouter()` 动态加载菜单

---

## 5. 请求与响应规范

### 5.1 响应格式

后端统一返回格式：
```ts
type Result = {
  code: number;     // 200=成功
  message: string;
  data?: any;
};

type ResultTable = {
  code: number;
  message: string;
  data?: {
    list: Array<any>;
    total: number;
    pageSize: number;
    currentPage: number;
  };
};
```

### 5.2 HTTP 客户端规则

- 统一使用 `src/utils/http/` 中的 `http` 实例
- 请求超时：10000ms
- Content-Type：`application/json`
- 参数序列化：qs (stringify)
- 禁止在业务代码中直接 `import axios`

---

## 6. 开发规范

### 6.1 命名规则

| 类型 | 命名风格 | 示例 |
|------|----------|------|
| 文件名 | kebab-case | `device-manage.vue`、`order.ts` |
| 组件名 | PascalCase | `DeviceManage`、`PureTableBar` |
| 变量/函数 | camelCase | `handleOpenDoor`、`dataList` |
| 常量 | UPPER_SNAKE_CASE | `VITE_PORT` |
| CSS 类名 | kebab-case / BEM | `search-form`、`order-item` |
| 路由 path | kebab-case | `/device/list` |
| Store ID | kebab-case + `pure-` 前缀 | `pure-user` |
| API 函数 | camelCase + 动词前缀 | `getDeviceList`、`createCoupon` |

### 6.2 Git 规范

- Commit 格式遵循 `@commitlint/config-conventional`
- 允许的类型：`feat`、`fix`、`docs`、`style`、`refactor`、`test`、`chore`
- 代码提交前自动 lint-staged

### 6.3 代码格式化

- Prettier 配置：双引号、无尾逗号、箭头函数省略括号
- ESLint：TypeScript strict mode + Vue 3 推荐
- 禁止 `debugger`（已配置 `no-debugger: off` 但不推荐）

---

## 7. 常见陷阱

| 编号 | 陷阱 | 规则 |
|------|------|------|
| T1 | 直接使用 `axios` | 禁止。必须使用 `http` 实例 |
| T2 | 在 API 层处理 UI 逻辑 | 禁止。API 层只负责请求，UI 反馈在 hook 或组件中处理 |
| T3 | 组件内写非 scoped 样式 | 禁止。必须 `<style lang="scss" scoped>` |
| T4 | 不提供 TypeScript 类型 | 禁止。新代码必须提供类型定义 |
| T5 | 在 index.vue 中写业务逻辑 | 禁止。逻辑必须抽离到 `utils/hook.tsx` |
| T6 | 路由不使用懒加载 | 禁止。必须 `() => import()` |
| T7 | 使用 Vuex | 禁止。统一使用 Pinia |
| T8 | 使用 moment.js | 禁止。统一使用 dayjs |
| T9 | 修改全局样式时不走 style/ | 禁止。全局样式改动必须修改 `src/style/` 下文件 |
| T10 | 分页不使用 `PaginationProps` | 禁止。分页配置必须使用 `@pureadmin/table` 的 `PaginationProps` |
| T11 | 表格不使用 `PureTableBar` | 禁止。列表页必须使用 `PureTableBar` 包裹 |
| T12 | 图标不使用 Remix Icon | 禁止。统一使用 `ri:` 前缀图标或 `~icons/` 前缀自动导入 |
| T13 | 表单重置不调用 `resetFields` | 禁止。重置必须使用 Element Plus 的 `resetFields()` |
