微耽
15.15MB · 2025-11-05
在 Vue 项目开发中,数据选择弹窗是高频出现的交互组件,比如用户选择、角色选择、部门选择等场景。如果每个选择场景都重复开发弹窗逻辑,不仅会导致代码冗余,还会增加维护成本。本文将深入解析一个基于 Vue 3 和 Arco Design 的选择弹窗工厂函数,带你理解其设计思想、实现细节与应用方式,助力提升组件复用效率。
import type { Component } from 'vue'
import { Message, Modal } from '@arco-design/web-vue'
import { h, ref } from 'vue'
interface CreateSelectDialogParams {
title: string
component: Component
componentProps?: Record<string, any>
tip?: string
}
/**
* 选择弹窗配置选项接口
* @template T 选中数据的类型
*/
interface SelectDialogOptions<T> {
/** 弹窗标题(会覆盖创建参数中的title) */
title?: string
/** 是否允许多选 */
multiple?: boolean
/** 查询参数,通常用于初始化数据 */
queryParams?: Record<string, any>
/** 传递给组件的额外属性 */
componentProps?: Record<string, any>
/** 点击确定按钮后的回调函数 */
onOk?: (data: T) => void
/** 点击确定前的校验函数,返回Promise<boolean>决定是否允许确定 */
onBeforeOk?: (data: T) => Promise<boolean>
}
/**
* 创建一个选择类型的弹窗工厂函数
*
* 该函数返回一个创建特定类型选择弹窗的方法,适用于需要从列表中选择数据的场景。
* 内部使用Vue的createVNode动态渲染组件,并通过ref获取组件实例的方法。
*
* @template T 选中数据的类型
* @param {CreateSelectDialogParams} params 创建弹窗所需的基本参数
* @returns {(options: SelectDialogOptions<T>) => void} 可配置的弹窗创建函数
*
* @example
* // 创建一个用户选择弹窗
* const selectUserDialog = createSelectDialog({
* title: '选择用户',
* component: UserSelectComponent,
* tip: '请至少选择一个用户'
* })
*
* // 打开弹窗并处理选择结果
* selectUserDialog({
* multiple: true,
* queryParams: { status: 'active' },
* onOk: (selectedUsers) => {
* console.log('已选择用户:', selectedUsers)
* }
* })
*/
export const createSelectDialog = <T = any>(params: CreateSelectDialogParams) => {
return (options: SelectDialogOptions<T>) => {
const TableRef = ref<any>()
Modal.open({
// 优先使用options中的title,否则使用params中的title
title: options.title || params.title,
// 动态渲染传入的组件,设置ref引用并合并属性
content: () => h(params.component, {
ref: (e: any) => (TableRef.value = e),
multiple: options.multiple,
queryParams: options.queryParams,
...params.componentProps,
...options.componentProps
}),
// 设置弹窗宽度自适应
width: 'calc(100% - 20px)',
modalStyle: { maxWidth: '1000px' },
bodyStyle: { overflow: 'hidden', height: '500px', padding: 0 },
onBeforeOk: async () => {
// 检查组件是否暴露了必要的getSelectedData方法
if (!TableRef.value?.getSelectedData) {
Message.warning('组件必须暴露getSelectedData方法')
return false
}
// 获取选中的数据
const data = TableRef.value?.getSelectedData?.() || []
// 验证是否选择了数据
if (!data.length) {
Message.warning(params.tip || '请选择数据')
return false
}
// 如果提供了前置校验函数,则调用并根据结果决定是否继续
if (options?.onBeforeOk) {
return await options.onBeforeOk(data)
}
// 调用确定回调函数,传递选中的数据
options.onOk?.(data)
return true
}
})
}
}
在中后台系统中,数据选择弹窗通常具备以下共性需求:
传统开发方式中,这些需求往往通过 “复制粘贴 + 修改” 实现,导致代码重复率高、
逻辑分散而本文解析的createSelectDialog工厂函数,正是为解决这些痛点而生,其核心目标是:封装共性逻辑,暴露个性化配置,实现 “一次定义,多场景复用” 。
在理解函数实现前,我们先梳理其类型接口与整体架构,这是保障代码健壮性和可维护性的基础。
函数通过 TypeScript 接口明确了参数与配置的结构,避免类型混乱,提升开发体验。
该接口定义了创建特定类型选择弹窗的 “固定属性”,是工厂函数的 “原料”:
interface CreateSelectDialogParams {
title: string; // 弹窗默认标题
component: Component; // 嵌入弹窗的选择组件(如用户列表)
componentProps?: Record<string, any>; // 传递给选择组件的默认属性
tip?: string; // 未选择数据时的提示文本
}
。
该接口定义了每次打开弹窗时的 “动态配置”,支持个性化调整,泛型T用于指定选中数据的类型,提升类型安全性:
interface SelectDialogOptions<T> {
title?: string; // 覆盖默认标题
multiple?: boolean; // 单选/多选切换
queryParams?: Record<string, any>; // 初始化查询参数(如筛选“活跃用户”)
componentProps?: Record<string, any>; // 覆盖默认组件属性
onOk?: (data: T) => void; // 确定按钮回调(返回选中数据)
onBeforeOk?: (data: T) => Promise<boolean>; // 确定前校验(如“最多选择10个用户”)
}
createSelectDialog是一个高阶函数,其核心逻辑分为两步:
这种设计的优势在于:将 “固定共性” 与 “动态个性” 分离,一次创建可多次调用,且每次调用可灵活配置。
接下来,我们深入函数内部,解析关键功能的实现逻辑,理解其如何解决数据选择弹窗的核心痛点。
弹窗内容通过 Vue 的h函数(创建虚拟 DOM)动态渲染传入的component,并通过ref获取组件实例,实现方法调用:
content: () => h(params.component, {
ref: (e: any) => (TableRef.value = e), // 绑定组件Ref
multiple: options.multiple, // 传递单选/多选配置
queryParams: options.queryParams, // 传递查询参数
...params.componentProps, // 合并默认组件属性
...options.componentProps // 合并动态组件属性(优先级更高)
})
onBeforeOk是弹窗的 “核心校验逻辑”,负责确保选中数据合法,并支持自定义拦截,流程如下:
onBeforeOk: async () => {
// 1. 检查组件是否暴露getSelectedData方法
if (!TableRef.value?.getSelectedData) {
Message.warning('组件必须暴露getSelectedData方法');
return false;
}
// 2. 获取选中数据
const data = TableRef.value?.getSelectedData?.() || [];
// 3. 校验是否选择数据
if (!data.length) {
Message.warning(params.tip || '请选择数据');
return false;
}
// 4. 自定义前置校验(如异步接口校验)
if (options?.onBeforeOk) {
return await options.onBeforeOk(data);
}
// 5. 触发确定回调,返回选中数据
options.onOk?.(data);
return true;
}
为适配不同屏幕尺寸,函数对弹窗样式做了精细化控制:
width: 'calc(100% - 20px)', // 宽度自适应(左右各留10px边距)
modalStyle: { maxWidth: '1000px' }, // 最大宽度限制(避免大屏下过宽)
bodyStyle: { overflow: 'hidden', height: '500px', padding: 0 } // 固定高度+隐藏滚动
理解了函数设计后,我们通过实际示例,看如何在项目中应用该工厂函数。
假设我们有一个UserSelectComponent(用户选择组件,已暴露getSelectedData方法),通过以下步骤创建用户选择弹窗:
// 1. 导入依赖与组件
import { createSelectDialog } from './createSelectDialog';
import UserSelectComponent from './UserSelectComponent.vue';
// 2. 创建用户选择弹窗函数(固定配置)
const selectUserDialog = createSelectDialog({
title: '选择用户', // 默认标题
component: UserSelectComponent, // 嵌入的用户选择组件
tip: '请至少选择一个用户', // 未选择时的提示
componentProps: { // 传递给用户组件的默认属性
border: false,
showSearch: true
}
});
// 3. 在业务组件中调用(动态配置)
const handleSelectUser = () => {
selectUserDialog({
multiple: true, // 允许多选
queryParams: { status: 'active' }, // 初始化查询“活跃用户”
onBeforeOk: async (selectedUsers) => {
// 自定义校验:最多选择5个用户
if (selectedUsers.length > 5) {
Message.warning('最多只能选择5个用户');
return false;
}
// 异步校验:检查选中用户是否已关联角色
const res = await checkUserRole(selectedUsers.map(u => u.id));
return res.data.isValid;
},
onOk: (selectedUsers) => {
// 确定后的逻辑:如渲染选中用户列表
console.log('已选择用户:', selectedUsers);
// 业务逻辑:更新页面状态、提交表单等
}
});
};
除了用户选择,该工厂函数还可用于角色选择、部门选择等场景,只需替换component参数即可:
// 角色选择弹窗
const selectRoleDialog = createSelectDialog({
title: '选择角色',
component: RoleSelectComponent,
tip: '请选择角色'
});
// 部门选择弹窗
const selectDeptDialog = createSelectDialog({
title: '选择部门',
component: DeptSelectComponent,
tip: '请选择部门'
});
通过这种方式,我们无需重复开发弹窗逻辑,只需关注 “选择组件本身”,极大提升开发效率。
createSelectDialog工厂函数通过 “封装共性、暴露个性” 的设计思想,解决了 Vue 项目中数据选择弹窗的复用问题。其核心在于:
在实际项目中,我们可以基于该函数的设计思路,进一步拓展弹窗的功能(如自定义按钮、支持分页),也可将其封装为 Vue 插件,在全局范围内复用。这种 “抽象共性、灵活扩展” 的组件设计思想,不仅适用于弹窗,也适用于表单、表格等其他高频组件,是提升前端开发效率的关键。