# Vben Admin Web-Antd AI 开发规则手册

## 项目概述

本项目基于 **Vben Admin 5.x** 框架开发，使用 Vue 3 + TypeScript + Ant Design Vue。项目采用 **monorepo** 结构，当前应用位于 `apps/web-antd`，依赖 `packages` 目录下的框架核心包。

### 技术栈
- **框架**: Vue 3.5+ (Composition API)
- **语言**: TypeScript 5.8+
- **UI 库**: Ant Design Vue 4.x
- **状态管理**: Pinia
- **路由**: Vue Router 4.x
- **构建工具**: Vite 6.x
- **包管理器**: pnpm (必须使用，不能用 npm/yarn)
- **图标**: Iconify + Lucide

---

## 项目结构

```
apps/web-antd/
├── src/
│   ├── api/              # API 请求封装
│   │   ├── core/         # 核心 API (auth, menu, user)
│   │   ├── request.ts    # 请求客户端配置
│   │   └── index.ts      # 导出所有 API
│   ├── router/           # 路由配置
│   │   ├── routes/       # 路由定义
│   │   │   ├── core/     # 核心路由 (login, 404等)
│   │   │   └── modules/  # 业务路由模块
│   │   └── index.ts      # 路由实例
│   ├── store/            # Pinia 状态管理
│   ├── views/            # 页面组件
│   │   ├── _core/        # 核心页面 (login, 404)
│   │   ├── dashboard/    # 仪表盘
│   │   └── demos/        # 示例页面
│   ├── layouts/          # 布局组件（如需自定义）
│   ├── locales/          # 国际化配置
│   ├── adapter/          # 组件适配器
│   ├── app.vue           # 根组件
│   ├── main.ts           # 入口文件
│   └── preferences.ts    # 应用偏好设置
├── package.json
└── vite.config.ts
```

---

## 核心开发规范

### 1. 命名规范

#### 文件命名
- **组件文件**: PascalCase，如 `UserProfile.vue`, `DataTable.vue`
- **工具文件**: kebab-case，如 `format-date.ts`, `use-table.ts`
- **API 文件**: kebab-case，如 `user-api.ts`, `order-api.ts`
- **路由文件**: kebab-case，如 `user-management.ts`

#### 变量命名
- **组件变量**: PascalCase，如 `const UserTable = defineComponent(...)`
- **普通变量**: camelCase，如 `const userInfo = ref({})`
- **常量**: UPPER_SNAKE_CASE，如 `const API_BASE_URL = '...'`
- **类型/接口**: PascalCase，如 `interface UserInfo { ... }`

### 2. Vue 组件规范

**必须使用 Composition API + `<script setup>`**:

```vue
<script lang="ts" setup>
import { ref, computed, onMounted } from 'vue';
import type { UserInfo } from '#/api/types';

// 定义组件名（可选，但推荐）
defineOptions({ name: 'UserProfile' });

// Props 定义
interface Props {
  userId: string;
  showActions?: boolean;
}

const props = withDefaults(defineProps<Props>(), {
  showActions: true,
});

// Emits 定义
const emit = defineEmits<{
  update: [user: UserInfo];
  delete: [id: string];
}>();

// 响应式数据
const loading = ref(false);
const userInfo = ref<UserInfo | null>(null);

// 计算属性
const displayName = computed(() => {
  return userInfo.value?.name || 'Unknown';
});

// 方法
async function fetchUser() {
  loading.value = true;
  try {
    const data = await getUserApi(props.userId);
    userInfo.value = data;
  } finally {
    loading.value = false;
  }
}

// 生命周期
onMounted(() => {
  fetchUser();
});
</script>

<template>
  <div class="user-profile">
    <a-spin :spinning="loading">
      <h3>{{ displayName }}</h3>
    </a-spin>
  </div>
</template>

<style scoped>
.user-profile {
  padding: 16px;
}
</style>
```

### 3. 导入路径别名

使用 `#/` 别名指向 `src/` 目录：

```typescript
// ✅ 正确
import { useAuthStore } from '#/store';
import type { UserInfo } from '#/api/types';
import { $t } from '#/locales';

// ❌ 错误 - 不要使用相对路径
import { useAuthStore } from '../../../store';
```

### 4. 框架组件导入

框架提供的组件从 `@vben/*` 包导入：

```typescript
// UI 组件
import { Button, Modal, Drawer } from '@vben/common-ui';
import { Page, Card } from '@vben/common-ui';
import { VbenForm, VbenTable } from '@vben/common-ui';

// 布局组件
import { BasicLayout, PageWrapper } from '@vben/layouts';

// 图标
import { SvgIcon, IconSvg } from '@vben/icons';

// Hooks
import { usePreferences, useAppConfig } from '@vben/hooks';

// 工具函数
import { formatDate, formatMoney } from '@vben/utils';

// 权限
import { useAccess } from '@vben/access';

// Store
import { useAccessStore, useUserStore } from '@vben/stores';

// 请求
import { RequestClient } from '@vben/request';
```

---

## 开发模式详解

### 1. 创建新页面

#### 步骤 1: 创建视图组件

在 `src/views/` 下创建页面组件：

```vue
<!-- src/views/user/user-list.vue -->
<script lang="ts" setup>
import { ref, onMounted } from 'vue';
import { VbenTable } from '@vben/common-ui';
import type { VbenTableColumn } from '@vben/common-ui';
import { getUserListApi } from '#/api/user';

defineOptions({ name: 'UserList' });

const loading = ref(false);
const dataSource = ref([]);

const columns: VbenTableColumn[] = [
  { title: '用户名', dataIndex: 'username', key: 'username' },
  { title: '邮箱', dataIndex: 'email', key: 'email' },
  { title: '状态', dataIndex: 'status', key: 'status' },
];

async function fetchData() {
  loading.value = true;
  try {
    const data = await getUserListApi();
    dataSource.value = data;
  } finally {
    loading.value = false;
  }
}

onMounted(() => {
  fetchData();
});
</script>

<template>
  <div class="p-5">
    <VbenTable
      :columns="columns"
      :data-source="dataSource"
      :loading="loading"
    />
  </div>
</template>
```

#### 步骤 2: 添加路由配置

在 `src/router/routes/modules/` 下创建路由模块：

```typescript
// src/router/routes/modules/user.ts
import type { RouteRecordRaw } from 'vue-router';
import { $t } from '#/locales';

const routes: RouteRecordRaw[] = [
  {
    meta: {
      icon: 'lucide:users',
      title: '用户管理',
      order: 10, // 菜单排序
    },
    name: 'User',
    path: '/user',
    children: [
      {
        name: 'UserList',
        path: '/user/list',
        component: () => import('#/views/user/user-list.vue'),
        meta: {
          icon: 'lucide:user-cog',
          title: '用户列表',
        },
      },
      {
        name: 'UserDetail',
        path: '/user/detail/:id',
        component: () => import('#/views/user/user-detail.vue'),
        meta: {
          hideInMenu: true, // 不在菜单中显示
          title: '用户详情',
          activeMenu: '/user/list', // 激活父菜单
        },
      },
    ],
  },
];

export default routes;
```

**路由配置要点：**
- 必须 `export default routes`
- `component` 使用动态导入 `() => import(...)`
- 使用 `#/views/` 别名
- 图标使用 Iconify 格式，如 `lucide:users`
- `meta.order` 控制菜单顺序（数字越小越靠前）

#### 步骤 3: 添加国际化（可选）

```typescript
// src/locales/lang/zh-CN/user.ts
export default {
  userManagement: '用户管理',
  userList: '用户列表',
  userName: '用户名',
  email: '邮箱',
};
```

### 2. API 请求规范

#### 在 `src/api/` 下创建 API 模块：

```typescript
// src/api/user.ts
import { requestClient } from './request';

export interface UserInfo {
  id: string;
  username: string;
  email: string;
  status: 'active' | 'inactive';
}

export interface UserListParams {
  page?: number;
  pageSize?: number;
  keyword?: string;
}

/**
 * 获取用户列表
 */
export async function getUserListApi(params?: UserListParams) {
  return requestClient.get<UserInfo[]>('/user/list', { params });
}

/**
 * 获取用户详情
 */
export async function getUserDetailApi(id: string) {
  return requestClient.get<UserInfo>(`/user/${id}`);
}

/**
 * 创建用户
 */
export async function createUserApi(data: Partial<UserInfo>) {
  return requestClient.post<UserInfo>('/user', data);
}

/**
 * 更新用户
 */
export async function updateUserApi(id: string, data: Partial<UserInfo>) {
  return requestClient.put<UserInfo>(`/user/${id}`, data);
}

/**
 * 删除用户
 */
export async function deleteUserApi(id: string) {
  return requestClient.delete(`/user/${id}`);
}
```

**API 规范：**
- 使用 `requestClient` 发起请求（已配置拦截器、token、错误处理）
- 函数名以 `Api` 结尾
- 使用 TypeScript 定义请求参数和返回类型
- 添加 JSDoc 注释说明用途

### 3. 状态管理 (Pinia)

#### 创建 Store：

```typescript
// src/store/modules/user.ts
import { defineStore } from 'pinia';
import { ref, computed } from 'vue';
import type { UserInfo } from '#/api/types';
import { getUserDetailApi } from '#/api/user';

export const useUserStore = defineStore('user', () => {
  // State
  const userInfo = ref<UserInfo | null>(null);
  const loading = ref(false);

  // Getters
  const isLoggedIn = computed(() => !!userInfo.value);
  const userName = computed(() => userInfo.value?.username || 'Guest');

  // Actions
  async function fetchUserInfo() {
    loading.value = true;
    try {
      const data = await getUserDetailApi('me');
      userInfo.value = data;
    } finally {
      loading.value = false;
    }
  }

  function clearUser() {
    userInfo.value = null;
  }

  return {
    // State
    userInfo,
    loading,
    // Getters
    isLoggedIn,
    userName,
    // Actions
    fetchUserInfo,
    clearUser,
  };
});
```

**使用 Store：**

```vue
<script setup lang="ts">
import { useUserStore } from '#/store';

const userStore = useUserStore();

// 读取状态
console.log(userStore.userName);

// 调用方法
userStore.fetchUserInfo();
</script>
```

### 4. 使用框架表单组件 (VbenForm)

```vue
<script setup lang="ts">
import { ref } from 'vue';
import { VbenForm } from '@vben/common-ui';
import type { VbenFormSchema } from '@vben/common-ui';
import { message } from 'ant-design-vue';

const formSchema: VbenFormSchema[] = [
  {
    component: 'Input',
    label: '用户名',
    fieldName: 'username',
    required: true,
    rules: [{ required: true, message: '请输入用户名' }],
  },
  {
    component: 'Input',
    label: '邮箱',
    fieldName: 'email',
    required: true,
    componentProps: {
      type: 'email',
    },
    rules: [
      { required: true, message: '请输入邮箱' },
      { type: 'email', message: '邮箱格式不正确' },
    ],
  },
  {
    component: 'Select',
    label: '状态',
    fieldName: 'status',
    componentProps: {
      options: [
        { label: '启用', value: 'active' },
        { label: '禁用', value: 'inactive' },
      ],
    },
  },
  {
    component: 'DatePicker',
    label: '出生日期',
    fieldName: 'birthday',
  },
];

const formValue = ref({});

async function handleSubmit(values: any) {
  console.log('提交数据:', values);
  message.success('保存成功');
}
</script>

<template>
  <div class="p-5">
    <VbenForm
      v-model="formValue"
      :schema="formSchema"
      @submit="handleSubmit"
    />
  </div>
</template>
```

### 5. 使用框架表格组件 (VbenTable)

```vue
<script setup lang="ts">
import { ref } from 'vue';
import { VbenTable } from '@vben/common-ui';
import type { VbenTableColumn } from '@vben/common-ui';
import { Button, Tag } from 'ant-design-vue';

const columns: VbenTableColumn[] = [
  {
    title: '用户名',
    dataIndex: 'username',
    key: 'username',
    width: 200,
  },
  {
    title: '邮箱',
    dataIndex: 'email',
    key: 'email',
  },
  {
    title: '状态',
    dataIndex: 'status',
    key: 'status',
    width: 100,
    customRender: ({ text }) => {
      const color = text === 'active' ? 'green' : 'red';
      return <Tag color={color}>{text}</Tag>;
    },
  },
  {
    title: '操作',
    key: 'action',
    width: 200,
    customRender: ({ record }) => (
      <>
        <Button type="link" onClick={() => handleEdit(record)}>编辑</Button>
        <Button type="link" danger onClick={() => handleDelete(record.id)}>删除</Button>
      </>
    ),
  },
];

const dataSource = ref([
  { id: '1', username: 'admin', email: 'admin@example.com', status: 'active' },
  { id: '2', username: 'user', email: 'user@example.com', status: 'inactive' },
]);

function handleEdit(record: any) {
  console.log('编辑:', record);
}

function handleDelete(id: string) {
  console.log('删除:', id);
}
</script>

<template>
  <VbenTable
    :columns="columns"
    :data-source="dataSource"
    :pagination="{ pageSize: 10 }"
  />
</template>
```

### 6. 权限控制

```vue
<script setup lang="ts">
import { useAccess } from '@vben/access';

const { hasAccessByCodes } = useAccess();

// 检查权限
const canEdit = hasAccessByCodes(['user:edit']);
const canDelete = hasAccessByCodes(['user:delete']);
</script>

<template>
  <div>
    <!-- 使用 v-access 指令控制显示 -->
    <a-button v-access="'user:create'">创建用户</a-button>
    <a-button v-access="'user:edit'" v-if="canEdit">编辑</a-button>
    <a-button v-access="'user:delete'" v-if="canDelete">删除</a-button>
  </div>
</template>
```

### 7. 使用图标

```vue
<script setup lang="ts">
import { IconSvg } from '@vben/icons';
import { SvgUserIcon, SvgSettingsIcon } from '@vben/icons';
</script>

<template>
  <div>
    <!-- 方式 1: 使用 IconSvg 组件 + Iconify 名称 -->
    <IconSvg icon="lucide:user" class="text-xl" />
    <IconSvg icon="mdi:settings" :size="24" />
    
    <!-- 方式 2: 使用预定义的 SVG 组件 -->
    <SvgUserIcon class="text-xl" />
    <SvgSettingsIcon :size="24" />
  </div>
</template>
```

**常用图标集：**
- `lucide:*` - Lucide 图标（推荐）
- `mdi:*` - Material Design Icons
- `carbon:*` - Carbon Icons
- `heroicons:*` - Heroicons

浏览图标：https://icon-sets.iconify.design/

---

## 样式规范

### 1. 使用 Tailwind CSS

优先使用 Tailwind CSS 工具类：

```vue
<template>
  <div class="p-5 bg-white rounded-lg shadow-md">
    <h2 class="text-2xl font-bold mb-4">标题</h2>
    <div class="flex items-center gap-4">
      <a-button type="primary">按钮</a-button>
    </div>
  </div>
</template>
```

### 2. Scoped 样式

需要自定义样式时使用 `<style scoped>`：

```vue
<style scoped>
.custom-class {
  /* 自定义样式 */
}

/* 深度选择器 - 影响子组件 */
:deep(.ant-btn) {
  border-radius: 8px;
}

/* 插槽选择器 */
:slotted(.slot-content) {
  color: red;
}
</style>
```

### 3. 响应式设计

使用 Tailwind 响应式前缀：

```vue
<template>
  <!-- 移动端单列，平板双列，桌面三列 -->
  <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
    <div v-for="item in items" :key="item.id">
      {{ item.name }}
    </div>
  </div>
</template>
```

---

## 常见问题和注意事项

### 1. ❌ 不要做的事

- **不要使用 Options API**（必须用 Composition API）
- **不要使用相对路径导入**（使用 `#/` 别名）
- **不要直接修改 `packages/` 下的框架代码**
- **不要在组件中直接操作 DOM**（使用 Vue 的响应式系统）
- **不要使用 yarn/npm**（必须用 pnpm）
- **不要在路由配置中使用 `require`**（使用 `import()`）

### 2. ✅ 最佳实践

- **组件拆分**: 单个组件不超过 300 行，复杂组件拆分成子组件
- **类型安全**: 所有 API 响应、组件 Props、函数参数都定义 TypeScript 类型
- **错误处理**: 使用 `try-catch` 处理异步错误
- **加载状态**: 异步操作要有 loading 状态
- **国际化**: 所有用户可见的文本使用 `$t()` 函数
- **权限控制**: 敏感操作添加权限检查

### 3. 性能优化

```vue
<script setup lang="ts">
import { computed, ref } from 'vue';

// ✅ 使用 computed 缓存计算结果
const filteredList = computed(() => {
  return list.value.filter(item => item.status === 'active');
});

// ✅ 大列表使用虚拟滚动
import { VirtualList } from '@vben/common-ui';
</script>

<template>
  <!-- ✅ 使用 v-show 而不是 v-if（频繁切换的元素） -->
  <div v-show="visible">内容</div>
  
  <!-- ✅ 长列表使用 key -->
  <div v-for="item in list" :key="item.id">
    {{ item.name }}
  </div>
</template>
```

### 4. 调试技巧

```typescript
// 开发环境打印日志
if (import.meta.env.DEV) {
  console.log('调试信息:', data);
}

// 使用 Vue Devtools
// Chrome 扩展：Vue.js devtools
```

---

## 开发命令

```bash
# 启动开发服务器
pnpm run dev:antd

# 构建生产版本
pnpm run build:antd

# 类型检查
pnpm run check:type

# 代码格式化
pnpm run format

# 代码检查
pnpm run lint

# 单元测试
pnpm run test:unit
```

---

## API Mock 数据（开发阶段）

如果后端接口未就绪，可以使用 Mock 数据：

```typescript
// src/api/user.ts
import { requestClient } from './request';

export async function getUserListApi() {
  // 开发环境使用 mock 数据
  if (import.meta.env.DEV) {
    return Promise.resolve([
      { id: '1', username: 'admin', email: 'admin@example.com', status: 'active' },
      { id: '2', username: 'user', email: 'user@example.com', status: 'inactive' },
    ]);
  }
  
  // 生产环境调用真实 API
  return requestClient.get('/user/list');
}
```

---

## 环境变量配置

配置文件位置：
- `.env` - 所有环境
- `.env.development` - 开发环境
- `.env.production` - 生产环境

```bash
# .env.development
VITE_APP_TITLE=DevOps 管理系统
VITE_API_URL=http://localhost:8080/api
```

使用方式：

```typescript
import { useAppConfig } from '@vben/hooks';

const { apiURL } = useAppConfig(import.meta.env, import.meta.env.PROD);
console.log('API地址:', apiURL);
```

---

## 总结

遵循本手册的规范，可以确保：
1. ✅ 代码风格统一，易于维护
2. ✅ 充分利用框架能力，避免重复造轮子
3. ✅ 类型安全，减少运行时错误
4. ✅ 性能优化，用户体验良好
5. ✅ 团队协作顺畅，代码可读性强

**开发新功能前，请先参考 `playground` 和 `demos` 目录下的示例代码！**
