AGENTS.md 13 KB

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 模板

<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 模板

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 模板

export interface XxxItem {
  id: number;
  // 字段定义
}

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

2.2 API 层 (api/)

强制规则

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

API 文件模板

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/)

路由文件模板

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 模板

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 前缀命名(如 ReDialogReIcon
  • 组件导出使用 index.ts 桶文件
  • 禁止在公共组件中写入业务逻辑

3. 样式规则

3.1 样式方案

场景 方案 说明
布局与间距 Tailwind CSS w-fullpl-8pt-[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 响应格式

后端统一返回格式:

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.vueorder.ts
组件名 PascalCase DeviceManagePureTableBar
变量/函数 camelCase handleOpenDoordataList
常量 UPPER_SNAKE_CASE VITE_PORT
CSS 类名 kebab-case / BEM search-formorder-item
路由 path kebab-case /device/list
Store ID kebab-case + pure- 前缀 pure-user
API 函数 camelCase + 动词前缀 getDeviceListcreateCoupon

6.2 Git 规范

  • Commit 格式遵循 @commitlint/config-conventional
  • 允许的类型:featfixdocsstylerefactortestchore
  • 代码提交前自动 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/tablePaginationProps
T11 表格不使用 PureTableBar 禁止。列表页必须使用 PureTableBar 包裹
T12 图标不使用 Remix Icon 禁止。统一使用 ri: 前缀图标或 ~icons/ 前缀自动导入
T13 表单重置不调用 resetFields 禁止。重置必须使用 Element Plus 的 resetFields()