Skip to content

前端开发指南

无论后端是MVC还是Webflux, 前端的开发实现都是一致的, 本节就以项目成员管理模块为例说明如何实现前端的全量代码开发. 最终效果如下图所示:

member-list.png

member-edit.png

1. 项目概述

项目成员管理模块是一个典型的CRUD(增删改查)功能模块,主要实现:

  • 成员列表展示与分页
  • 成员信息的新增、编辑、删除
  • 搜索和筛选功能
  • 批量操作

核心文件结构:

member/
├── index.vue          # 主页面组件
├── member-drawer.vue  # 表单抽屉组件
└── types.ts          # 类型定义(可选)

2. 技术栈与依赖

核心技术栈

  • Vue 3 + Composition API - 响应式框架
  • TypeScript - 类型安全
  • Ant Design Vue - UI组件库
  • VxeGrid - 高性能表格组件
  • Vben Admin - 管理后台框架

主要依赖组件

typescript
import { useVbenDrawer, useVbenForm, useVbenVxeGrid } from '@vben/common-ui'
import { Button, InputSearch, Select, Modal, message } from 'ant-design-vue'

3. 项目结构

src/views/project/member/
├── index.vue              # 主页面 - 列表展示和操作
├── member-drawer.vue      # 抽屉组件 - 表单处理
└── types.ts              # 类型定义文件

src/api/project/
└── member.ts             # API接口定义

src/locales/
└── lang/                 # 国际化文件

4. 开发步骤

4.1 API接口准备

首先定义所需的API接口:

typescript
// 主要接口
- getProjectMemberListApi()    // 获取成员列表
- saveProjectMemberApi()       // 保存成员信息
- deleteProjectMemberApi()     // 删除成员
- getOptionsApi()              // 获取选项数据(角色、性别等)

关键点:

  • API返回的数据格式要与前端组件期望的格式一致
  • 分页参数要符合VxeGrid的要求
  • 错误处理机制要完善

4.2 主页面开发

4.2.1 组件导入和基础设置

vue
<script lang="ts" setup>
import type { VxeGridProps } from '#/adapter';
import { ref, onMounted } from 'vue';
import { Page, useVbenDrawer } from '@vben/common-ui';
import { Button, InputSearch, Select, Modal, message } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter';
</script>

4.2.2 抽屉组件配置

typescript
const [Drawer, drawerApi] = useVbenDrawer({
  connectedComponent: MemberDrawer,
});

function openDrawer(row?: any) {
  drawerApi.setData(row || {});
  drawerApi.open();
}

关键点:

  • connectedComponent 连接表单组件
  • setData() 传递数据给抽屉组件
  • 区分新增和编辑模式

4.2.3 表格配置

typescript
const gridOptions: VxeGridProps<MemberType> = {
  columns: [
    { type: 'checkbox', width: 40 },           // 选择框
    { title: '序号', type: 'seq', width: 50 }, // 序号
    { field: 'name', title: '姓名' },          // 数据字段
    {
      field: 'action',
      fixed: 'right',
      slots: { default: 'action' },           // 操作列插槽
      title: '操作',
      width: 140,
    },
  ],
  proxyConfig: {
    ajax: {
      query: async ({ page }) => {
        return await getProjectMemberListApi({
          page: page.currentPage,
          pageSize: page.pageSize,
          searchKey: searchText.value,
          role: selectedRole.value,
          sex: selectedSex.value,
        });
      },
    },
  },
};

关键点:

  • proxyConfig 配置自动分页和数据加载
  • slots 自定义操作列
  • 筛选参数实时传递给API

4.2.4 搜索和筛选功能

typescript
const searchText = ref('');
const selectedRole = ref('');
const selectedSex = ref('');

function handleSearch() {
  gridApi.reload();
}

4.2.5 批量操作功能

typescript
const selectedRows = ref<MemberType[]>([]);

const gridEvents = {
  checkboxChange({ records }: { records: any[] }) {
    selectedRows.value = records;
  },
  checkboxAll({ records }: { records: any[] }) {
    selectedRows.value = records;
  },
};

async function handleDeleteSelected() {
  if (selectedRows.value.length === 0) return;
  
  Modal.confirm({
    title: '删除确认',
    content: `确定要删除选中的 ${selectedRows.value.length} 条记录吗?`,
    async onOk() {
      try {
        await Promise.all(selectedRows.value.map(row => deleteProjectMemberApi(row.id)));
        selectedRows.value = [];
        await gridApi.reload();
        message.success('删除成功');
      } catch {
        message.error('删除失败');
      }
    }
  });
}

4.2.6 选项数据加载

typescript
const roleOptions = ref<Option[]>([]);
const sexOptions = ref<Option[]>([]);

async function loadRoleOptions() {
  try {
    const data = await getOptionsApi({
      pathOrBeanName: 'com.matrix.app.common.enums.Role'
    });
    roleOptions.value = data;
  } catch (error) {
    console.error('加载角色选项失败', error);
  }
}

onMounted(() => {
  loadRoleOptions();
  loadSexOptions();
});

4.3 表单抽屉组件开发

4.3.1 属性定义

typescript
const props = defineProps<{
  onSave: () => void;
  roleOptions: { label: string; value: string }[];
  sexOptions: { label: string; value: string }[];
}>();

4.3.2 表单配置

typescript
const [Form, formApi] = useVbenForm({
  commonConfig: {
    componentProps: {
      class: 'w-full',
    },
  },
  handleSubmit: onSubmit,
  schema: [
    {
      component: 'Input',
      componentProps: { type: 'hidden' },
      fieldName: 'id',                        // 隐藏ID字段
    },
    {
      component: 'Input',
      componentProps: {
        placeholder: '请输入姓名',
      },
      fieldName: 'name',
      label: '姓名',
      rules: 'required',                      // 必填验证
    },
    {
      component: 'Select',
      componentProps: () => ({
        placeholder: '请选择角色',
        options: props.roleOptions,           // 动态选项
        allowClear: true,
      }),
      fieldName: 'role',
      label: '角色',
      rules: 'required',
    },
    // ... 其他字段
  ],
  showDefaultActions: false,                  // 隐藏默认操作按钮
});

关键点:

  • 使用函数形式的 componentProps 获取动态数据
  • rules 配置表单验证
  • showDefaultActions: false 自定义操作按钮

4.3.3 抽屉配置

typescript
const [Drawer, drawerApi] = useVbenDrawer({
  onCancel() {
    drawerApi.close();
  },
  onConfirm: async () => {
    const isValid = await formApi.validate();
    if (isValid.valid) {
      try {
        await formApi.submitForm();
        drawerApi.close();
        message.success(values.id ? '编辑成功' : '新增成功');
        props.onSave();
      } catch (error: any) {
        message.error(error.message || '提交失败');
      }
    } else {
      message.error('请填写必填项');
    }
  },
  onOpenChange(isOpen: boolean) {
    if (isOpen) {
      const values = drawerApi.getData<Record<string, any>>();
      if (values) {
        formApi.setValues(values);          // 编辑时设置表单值
      }
    } else {
      formApi.setValues({});               // 关闭时清空表单
    }
  },
});

4.4 数据交互与状态管理

4.4.1 数据流转

主页面 -> 点击新增/编辑 -> 抽屉组件
抽屉组件 -> 提交表单 -> API请求 -> 回调主页面 -> 刷新列表

4.4.2 状态同步

typescript
// 主页面
function handleSave() {
  gridApi.reload();  // 重新加载列表数据
}

// 抽屉组件
props.onSave();      // 通知主页面刷新

5. 关键技术点

5.1 组件通信

  • Props传递: 父组件向子组件传递选项数据
  • 事件回调: 子组件通过回调通知父组件更新数据
  • 抽屉数据传递: 通过 drawerApi.setData()drawerApi.getData()

5.2 表单处理

  • 动态Schema: 使用函数形式的配置支持响应式数据
  • 表单验证: 使用内置的 rules 进行验证
  • 数据双向绑定: 自动处理表单数据的获取和设置

5.3 表格功能

  • 自动分页: 通过 proxyConfig 配置
  • 批量选择: 通过 checkbox 类型列和事件处理
  • 实时搜索: 搜索条件变化时自动重新加载数据

5.4 国际化支持

typescript
import { $t } from '#/locales';

// 使用方式
title: $t('member.name')
placeholder: $t('member.form.name.placeholder')

6. 挂载菜单

菜单挂载无需编码, 运行矩阵星云前后端工程后, 直接在界面上输入即可. 如下图所示: menu-def.png

7. 最佳实践

7.1 错误处理

typescript
try {
  await saveProjectMemberApi(data);
} catch (error: any) {
  console.error('保存失败:', error);
  throw new Error(error.message || '提交失败');
}

7.2 Loading状态

  • VxeGrid 自带 loading 效果
  • 抽屉提交时会自动显示 loading

7.3 数据类型安全

typescript
interface MemberType {
  id: string;
  name: string;
  sex: string;
  role: string;
}

const gridOptions: VxeGridProps<MemberType> = { ... }

7.4 组件复用

  • 抽屉组件可以被其他页面引用
  • 表单Schema可以提取为配置文件

8. 常见问题

8.1 表单数据不更新

问题: 编辑时表单显示的还是上次的数据 解决: 确保在 onOpenChange 中正确设置和清空表单数据

8.2 选项数据为空

问题: 下拉选项不显示 解决: 检查选项数据的加载时机和数据格式

8.3 批量删除不生效

问题: 选中数据但删除失败 解决: 检查 gridEvents 的事件绑定和 selectedRows 的更新

8.4 分页问题

问题: 分页参数传递错误 解决: 确保API接口的分页参数名称与 proxyConfig 中的一致

9. 扩展功能

9.1 导出功能

typescript
async function handleExport() {
  // 使用VxeGrid的导出功能
  await gridApi.exportData({
    filename: 'members',
    type: 'xlsx'
  });
}

9.2 高级搜索

typescript
// 添加更多搜索条件
const advancedSearch = ref({
  dateRange: [],
  status: '',
  department: ''
});

9.3 权限控制

typescript
// 根据用户权限显示操作按钮
<Button v-if="hasPermission('member:add')" type="primary">
  新增
</Button>

9.4 数据校验增强

typescript
// 自定义验证规则
{
  fieldName: 'phone',
  label: '手机号',
  rules: [
    { required: true, message: '请输入手机号' },
    { pattern: /^1[3-9]\d{9}$/, message: '手机号格式不正确' }
  ],
}

总结

这个member模块展示了一个完整的CRUD功能实现,包含了现代Vue 3项目中的最佳实践。通过这个例子,开发者可以掌握:

  1. 组件化开发思维 - 主页面与表单组件分离
  2. 数据驱动 - 通过配置化的方式构建表格和表单
  3. 用户体验 - 完善的加载状态、错误提示、操作确认
  4. 代码复用 - 抽屉组件、表单配置可以在多个模块中复用
  5. 类型安全 - 完整的TypeScript类型定义

按照这个模式,可以快速开发出类似的功能模块,只需要修改字段定义、API接口和业务逻辑即可。