周末学习不停歇,最近新开一个VUE3全新系列,这一系列会从0开始学习VUE3,使用Vite、TS、Pinia、Element-Plus、mittBus等新知识点,既是查漏补缺,也是知识分享。
目前项目的登录、鉴权、动态菜单、权限按钮、页面布局、标签页、数据增删改查案例等基本功能都已经写完,整体效果如动图,欢迎各位小伙伴可以加入到这个项目,可以提交PR,早期参与贡献的,可以作为核心成员。

代码地址:
https://github.com/anjoy8/bcvp.vue3.git
这是每篇文章一节课一个分支,方便大家学习,系列文章:
本文参考的是开源项目
https://gitee.com/HalseySpicy/Geeker-Admin/tree/template
今天主要是帮助使用本框架的同学可以快速的基于底座进行设计业务逻辑,基于现有的模板,按照统一约定好的规则,直接对关键字进行查找替换即可,本文先说一下查询,我把增删改查分成四个模块,方便大家对照:

根据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了。
每一个页面的api接口都需要事件来触发调用,所以需要定义一个事件的ts进行封装。
在srcviewsPermissionmoduleFunctions.ts文件中
@ -0,0 +1,83 @@// moduleFunctions.tsimport { 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对象和接口进行引用,然后设计具体的业务逻辑即可。
页面的渲染是必不可少的,而且是需要有一定的规则,根据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>
在业务页面进行渲染中,第一步不仅需要渲染表格数据,夹带着还需要渲染顶部功能按钮条,或者其他的用户数据,所以只要查询功能可以正常显示,那下面的编辑、新增和删除基本就没有多大的问题了。
打完收工:

