BCVP.VUE3系列第十四课(1):快速对接业务之查询模板

艺帆风顺 发布于 2025-04-03 31 次阅读


BCVP 开发者社区出品BCVP V3开发数字化服务化绿色化

周末学习不停歇,最近新开一个VUE3全新系列,这一系列会从0开始学习VUE3,使用Vite、TS、Pinia、Element-Plus、mittBus等新知识点,既是查漏补缺,也是知识分享。

目前项目的登录、鉴权、动态菜单、权限按钮、页面布局、标签页、数据增删改查案例等基本功能都已经写完,整体效果如动图,欢迎各位小伙伴可以加入到这个项目,可以提交PR,早期参与贡献的,可以作为核心成员。

代码地址:

https://github.com/anjoy8/bcvp.vue3.git

这是每篇文章一节课一个分支,方便大家学习,系列文章:

第一课:项目初始化与核心知识点说明

第二课:基于泛型基类封装Axios请求

第三课:封装Axios拦截器

第四课:登录页设计

第五课:获取用户信息

第六课:获取动态菜单接口

第七课:基于布局模式实现动态菜单渲染

第八课:丰富面包屑组件

第九课:实现tabs标签栏

第十课:个人中心模块

第十一课:基于总线实现框架多种布局样式

第十二课:渲染动态权限按钮

第十三课:框架底座已写完,欢迎加入我们!

0、本文介绍

本文参考的是开源项目

https://gitee.com/HalseySpicy/Geeker-Admin/tree/template

今天主要是帮助使用本框架的同学可以快速的基于底座进行设计业务逻辑,基于现有的模板,按照统一约定好的规则,直接对关键字进行查找替换即可,本文先说一下查询,我把增删改查分成四个模块,方便大家对照:

1、API接口模块

根据BCVP框架的约定好的定义,基于按钮级别的RBAC模式下,每一个按钮都会对应一个api以及前端的function事件,并且将其配置给角色role,因此每一个业务页面都需要一个api接口模块,来统一封装接口和interface对象

参考srcapimoduleApi.ts

    @ -0,0 +1,49 @@import { get, post, put, del, type BaseResponse, type PageModel } from '@/utils/axiosInstance';
    /** * 请求的入参接口 * @interface ModuleRequest */export interface ModuleRequest { page: number; pageSize: number; key: string; f: string;}
    /** * 实体模型响应接口 * @interface Module */export interface Module { IsDeleted: boolean; Name: string; LinkUrl: string; Area: string | null; Controller: string | null; Action: string | null; Icon: string | null; Code: string | null; OrderSort: number; Description: string | null; IsMenu: boolean; Enabled: boolean; CreateId: string; CreateBy: string; CreateTime: string; ModifyId: string | null; ModifyBy: string | null; ModifyTime: string; ParentId: string; Id: string;}
    // 获取业务数据列表export const getModuleListApi = async (params: ModuleRequest): Promise>> => { try { const response = await get>>('/api/module/get', params); return response; } catch (error) { throw new Error('请求失败'); }};

    根据需要,你在写新业务的时候,只需要把module对应的关键字替换成新的业务即可,比如role,那就角色管理这一块代码就ok了。

    2、function事件模块

    每一个页面的api接口都需要事件来触发调用,所以需要定义一个事件的ts进行封装。

    srcviewsPermissionmoduleFunctions.ts文件中

      @ -0,0 +1,83 @@// moduleFunctions.ts
      import { reactive, toRaw, ref } from 'vue';import { getModuleListApi } from '@/api/moduleApi'; // 接口import type { ModuleRequest, Module } from '@/api/moduleApi';// 模型类import { ElMessage, ElForm, ElMessageBox } from "element-plus";import { formatDate } from "@/utils";
      export const modules = ref([]);// 数据数组export const listLoading = refboolean>(false);// 表格loadingexport const total = refnumber>(0);// 总数据条数export const page = refnumber>(1);//当前页export const pageSize = refnumber>(20);// 每页数据条数
      export const addFormVisible = ref(false);export const addLoading = ref(false);export const editFormVisible = ref(false);export const editLoading = ref(false);export const isResouceShow = ref(0);// 创建一个 ref 引用 el-formexport const addFormRef = reftypeof ElForm> | null>(null);export const editFormRef = reftypeof ElForm> | null>(null);export const currentRow = refnull>(null);
      // ↓↓↓↓↓ 查询 ↓↓↓↓↓export const handleQuery = async (filters: { name: string }) => { currentRow.value = null; page.value = 1;
      const para = ref({ page: page.value, pageSize: 20, f: '0', key: filters.name, });
      listLoading.value = true; try { const { response } = await getModuleListApi(para.value); modules.value = response.data ?? []; total.value = response.dataCount; } finally { listLoading.value = false; }};// ↑↑↑↑↑ 查询 ↑↑↑↑↑

      // ↓↓↓↓↓ 新增 ↓↓↓↓↓
      export const handleAdd = async () => { ElMessage.warning('handleAdd');
      };
      export const addSubmit = async () => { ElMessage.warning('addSubmit');
      };
      // ↑↑↑↑↑ 新增 ↑↑↑↑↑

      // ↓↓↓↓↓ 编辑 ↓↓↓↓↓
      export const handleEdit = async () => { ElMessage.warning('handleEdit');
      };export const editSubmit = async () => { ElMessage.warning('editSubmit');
      };
      // ↑↑↑↑↑ 编辑 ↑↑↑↑↑
      // ↓↓↓↓↓ 删除 ↓↓↓↓↓// 删除数据export const handleDel = async () => { ElMessage.warning('handleDel');
      };// ↑↑↑↑↑ 删除 ↑↑↑↑↑

      可以看到,这个就是一个统一的模板了,将interface对象和接口进行引用,然后设计具体的业务逻辑即可。

      3、Vue业务页面模块

      页面的渲染是必不可少的,而且是需要有一定的规则,根据BCVP框架的约定大于配置的设计模式,路由就是文件夹名/文件名,且大小写是敏感的,但是也可以适配,看各自的需求,最好是要完全一致。

      页面srcviewsPermissionModule.vue

        @ -0,0 +1,153 @@template> section>  toolbar :button-list="buttonList">toolbar>
        el-table :data="modules" ref="tableRef" v-loading="listLoading" @select="dialogCheck" @row-click="selectCurrentRow" row-key="Id" border style="width: 100%"> el-table-column type="selection" width="50">el-table-column> el-table-column type="index" width="80"> el-table-column> el-table-column prop="LinkUrl" label="接口地址" width="" sortable> el-table-column> el-table-column prop="Name" label="描述" width="300" sortable> el-table-column> el-table-column prop="CreateBy" label="创建者" width="" sortable> el-table-column> el-table-column prop="Enabled" label="状态" width="100"> template #default="{ row }"> el-tag :type="row.Enabled ? 'success' : 'danger'">{{ row.Enabled ? '正常' : '禁用' }}el-tag> template> el-table-column> el-table-column prop="CreateTime" label="创建时间" :formatter="formatCreateTime" width="250" sortable>el-table-column> el-table-column prop="ModifyTime" label="更新时间" :formatter="formatModifyTime" width="250" sortable>el-table-column> el-table> el-col :span="24" class="toolbar"> el-pagination layout="total, prev, pager, next, jumper" @current-change="handleCurrentChange" :page-size="pageSize" :total="total" :page-sizes="[10, 20, 50, 100]" :current-page="page" style="float: right"> el-pagination> el-col>



        section>template>
        script setup lang="ts" name="module">import { ref, onMounted, onUnmounted } from 'vue';import { ElForm, ElTable } from 'element-plus';import Toolbar from "@/components/toolbar.vue";import mittBusT from "@/utils/mittBusT";import { getButtonList } from "@/utils";import { useAuthMenuStore } from "@/stores/modules/authMenu";import { getModuleListApi, type Module, type ModuleRequest } from '@/api/moduleApi';
        // 从 moduleFunctions.ts 导入import { handleQuery, handleAdd, handleEdit, handleDel, modules, listLoading, isResouceShow, page, pageSize, total, addLoading, addFormRef, addSubmit, currentRow, editFormVisible, editLoading, editFormRef, editSubmit} from './moduleFunctions';// 定义 filtersconst filters = refname: string }>({ name: '' });// 加载按钮const buttonList = ref([]);
        // 创建函数映射表const functionMap: RecordFunction> = { handleQuery, handleAdd, handleEdit, handleDel, // 可以在此添加其他需要调用的函数};const callFunction = (item: Menu.MenuOptions) => { const filters = { name: item.search, };
        if (item.Func && typeof item.Func === 'string') { // 假设所有可用函数都在 functionMap 中定义 const func = functionMap[item.Func]; if (typeof func === 'function') { func(filters); } else { console.error(`Function ${item.Func} is not defined in functionMap.`); } } else { console.error('Func property is not a valid string.'); }};
        // 格式化时间const formatCreateTime = (row: Module) => row.CreateTime;const formatModifyTime = (row: Module) => row.ModifyTime;
        // 定义表格数据的类型const tableRef = reftypeof ElTable> | null>(null);
        // 选中当前行const dialogCheck = async (selection: Module[], row?: Module) => { currentRow.value = null; if (tableRef.value) { tableRef.value.clearSelection(); } if (selection.length === 0) { return; } if (row) { selectCurrentRow(row); }};const selectCurrentRow = (val: Module) => { if (!val) return; currentRow.value = val; if (tableRef.value) { tableRef.value.clearSelection(); tableRef.value.toggleRowSelection(val, true); }};
        // 数据分页const handleCurrentChange = async (val: number) => { page.value = val;
        const para = ref({ page: page.value, pageSize: 20, f: '0', key: filters.value.name, });
        listLoading.value = true; try { const { response } = await getModuleListApi(para.value); modules.value = response.data ?? []; total.value = response.dataCount; } finally { listLoading.value = false; }};
        // 钩子函数onMounted(async () => { const authStore = useAuthMenuStore(); const routers = authStore.authMenuListGet; buttonList.value = getButtonList(window.location.pathname, routers);
        // 监听事件 mittBusT.on('callFunction', callFunction);
        // 获取数据 await handleQuery(filters.value);});
        // 在组件卸载时移除监听onUnmounted(() => { mittBusT.off('callFunction', callFunction);});script>

        在业务页面进行渲染中,第一步不仅需要渲染表格数据,夹带着还需要渲染顶部功能按钮条,或者其他的用户数据,所以只要查询功能可以正常显示,那下面的编辑、新增和删除基本就没有多大的问题了。

        打完收工: