场景分析
在管理后台开发中,用户密码修改是一个典型的多组件协作场景。我们通常会遇到以下需求:
- 在用户列表页面展示操作按钮
- 点击按钮弹出密码修改对话框
- 对话框需要获取当前操作用户信息
- 提交时需将用户ID与新密码一并发送
组件结构设计
采用主从组件模式:
- 主组件:用户列表页面(index.vue)
- 子组件:密码修改对话框(update-password)
核心问题
在现有实现中,当用户点击修改密码按钮时,虽然可以通过openPasswordDialog方法获取当前行数据,但在提交表单时却无法将用户ID传递给提交处理函数,导致无法完成API请求。
主组件(index.vue)
<script setup lang="ts">
import { UpdatePassword } from '@smart-campus/components';
// 重置密码dialog
const passwordVisable = ref(false);
// 接受当前行的属性,并将处理后的属性绑定到组件上
let passwordBind;
const openPasswordDialog = (row: User) => {
passwordBind = {
title: `重置用户${row.username}的密码`,
isConfirm: true,
minLevel: 'medium',
strengthLevel: 'strong',
rules: { newPassword: passwordRules },
};
passwordVisable.value = true;
};
</script>
<template>
<!-- 修改密码 -->
<update-password v-model:visible="passwordVisable" v-bind="passwordBind" />
...
<!-- 表格区域 -->
<el-table>
<el-table-column>
<template #operation="{ row }">
<!-- 打开模态框,并传入当前行 -->
<el-button @click="openPasswordDialog(row)"> 修改密码 </el-button>
</template>
</el-table-column>
</el-table>
</template>
解决方案:闭包与回调函数的巧妙运用
解决方案概述
通过JavaScript的闭包特性,我们可以在打开对话框时保留用户信息,确保在提交表单时能够同时访问用户ID和新密码。
子组件改造
首先,我们需要修改子组件的props定义,添加onSubmit回调函数属性:
// 在子组件的props类型定义中添加
onSubmit?: (newPassword: string) => Promise<void> | void;
关键实现点:
- 优先使用props传入的
onSubmit回调:提供更灵活的控制方式 - 保持向后兼容:如果没有提供
onSubmit则触发submit事件 - 确保表单验证通过后才执行提交:保证数据有效性
async function handleSubmit() {
try {
await formRef.value?.validate();
if (isFormValid.value) {
// 优先使用 props.onSubmit,如果不存在则使用 emits
if (props.onSubmit) {
await props.onSubmit(form.newPassword);
} else {
emits('submit', form.newPassword);
}
handleCancel();
}
} catch (error) {
console.error('密码修改失败:', error);
}
}
总的代码如下:
<template>
<el-dialog v-model="dialogVisible">
<el-form :model="form" :rules="props.rules" ref="formRef" >
<el-form-item prop="newPassword">
<el-input
v-model="form.newPassword"
type="password"
show-password
placeholder="请输入新密码"
/>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="handleCancel">取消</el-button>
<el-button type="primary" @click="handleSubmit" :disabled="!isFormValid" >
确定
</el-button>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { ref, reactive, computed } from 'vue';
import { ElDialog, ElForm, ElFormItem, ElInput, ElButton } from 'element-plus';
import type { FormItemRule, FormInstance } from 'element-plus';
const formRef = ref<FormInstance>();
type IProps = {
// 其他属性...
/** 提交回调函数 */
onSubmit?: (newPassword: string) => Promise<void> | void;
};
const props = defineProps<IProps>();
const emits = defineEmits(['submit', 'update:visible']);
// 使用计算属性处理 v-model 双向绑定
const dialogVisible = computed({
get: () => props.visible,
set: (val) => emits('update:visible', val),
});
// 表单数据
const form = reactive({
newPassword: '',
confirmPassword: '',
});
// 表单验证
const isFormValid = computed(() => {
...
return boolean
});
// 提交操作
async function handleSubmit() {
try {
await formRef.value?.validate();
if (isFormValid.value) {
// 优先使用 props.onSubmit,如果不存在则使用 emits
if (props.onSubmit) {
await props.onSubmit(form.newPassword);
} else {
emits('submit', form.newPassword);
}
handleCancel();
}
} catch (error) {
console.error('密码修改失败:', error);
}
}
</script>
主组件优化
利用JavaScript闭包特性,在打开对话框时保留用户信息:
let passwordBind;
const openPasswordDialog = (row: User) => {
passwordBind = {
title: `重置用户${row.username}的密码`,
isConfirm: true,
minLevel: 'weak',
strengthLevel: 'strong',
rules: { newPassword: passwordRules },
onSubmit: (newValue: string) => editPassword(row, newValue), // 闭包保留row引用
};
passwordVisable.value = true;
};
const editPassword = async (row: User, newValue: string) => {
// 这里可以同时访问row.id和newValue
await updateUserPassword({
id: row.id,
newPassword: newValue
});
};
方案优势
- 代码解耦:子组件不关心具体的业务逻辑,只负责密码输入和验证
- 数据完整性:确保提交时同时拥有用户ID和新密码
- 灵活性:既支持通过props传递回调函数,也支持通过emit事件
- 可维护性:业务逻辑集中在主组件,便于统一管理
总结
通过合理运用JavaScript闭包特性和Vue的组件通信机制,我们优雅地解决了密码修改功能中用户ID传递的问题。这种方案不仅解决了当前问题,还为类似场景提供了可复用的模式,值得在组件开发中推广应用。