vue3的基础使用(二)
2026年5月13日大约 5 分钟
计算属性,defineModel(),watch()侦听器
示例用户列表的分页(父组件)
下面代码父组件定义计算属性和侦听器
src\pages\laravel-fast-api\v1\user\user\userList\index.vue
<!-- 分页区开始 -->
<template #paginate>
<div>
<YhPaginate
v-model:current-page="currentPage"
v-model:page-size="pageSize"
v-model:total="total"
:page-sizes="[5, 10, 20, 30, 40, 50,100]"
/>
</div>
</template>
<!-- 分页区结束 -->
<script setup lang="ts">
//搜索查询条件
const where = ref({
//查找内容
find: "",
//查找下标
findSelectIndex: 0,
//时间范围
timeRange: [],
//是否禁用
switch: null,
//排序方式
sortType: 2,
// 0 不导出 1导出
isExport: 0,
// 导出类型 1本页 2全部
exportType: 1,
// 分页
currentPage: 1,
//每页条数
pageSize: 10,
//数据总数
total: 0,
});
//将分页处理定义为计算属性
//当前页
const currentPage = computed({
get() {
return where.value.currentPage;
},
set(newValue) {
where.value.currentPage = newValue;
},
});
//页面条数
const pageSize = computed({
get() {
return where.value.pageSize;
},
set(newValue) {
where.value.pageSize = newValue;
},
});
//计算总数
const total = computed({
get() {
return where.value.total;
},
set(newValue) {
where.value.total = newValue;
},
});
//侦听分页变化
watch([currentPage, pageSize], () =>
{
getUserList()
})
</script>子组件
src\pages\laravel-fast-api\v1\components\element\Paginate\index.vue
:
<!--
* @Descripttion:
* @version: v1
* @Author: youhujun 2900976495@qq.com
* @Date: 2025-09-29 17:40:09
* @LastEditors: youhujun youhu8888@163.com & xueer
* @LastEditTime: 2026-05-13 21:59:13
* @FilePath: \vue3-element-admin-youhujun\src\pages\laravel-fast-api\v1\components\element\Paginate\index.vue
* Copyright (C) 2025 youhujun. All rights reserved.
-->
<template>
<div>
<el-row class="paginate-box" type="flex">
<el-col :span="12" :push="8">
<el-pagination
:current-page="currentPage"
:page-sizes="pageSizes"
:page-size="pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="total"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</el-col>
</el-row>
</div>
</template>
<script lang="ts" setup>
import { throttle } from "@/utils/index";
defineOptions({
name: "YhPaginate",
});
//父组件传值
const { pageSizes = [5,10, 20, 30, 40,50,100], total = 0 } = defineProps<{
pageSizes?: number[];
total?: number;
}>();
//当前分页
const currentPage = defineModel<number>("currentPage", {
default: 1,
});
//分页条数
const pageSize = defineModel<number>("pageSize", {
default: 10,
});
const handleSizeChange = throttle((value: number) => {
//console.log(`每页 ${value} 条`)
pageSize.value = value
}, 500);
const handleCurrentChange = throttle((value: number) => {
//console.log(`当前页: ${value}`)
currentPage.value = value
}, 500);
</script>
<style lang="scss" scoped>
.paginate-box {
margin-top: 1rem;
}
</style>父组件向子组件传值
仍然看上面示例,代码如下
//父组件传值
const { pageSizes = [5,10, 20, 30, 40,50,100], total = 0 } = defineProps<{
pageSizes?: number[];
total?: number;
}>();
//当前分页
const currentPage = defineModel<number>("currentPage", {
default: 1,
});可以总结:
普通只读 props(不双向绑定):用 解构+直接默认值,抛弃 withDefaults
双向绑定 props:直接用 defineModel,抛弃 defineProps+defineEmits
子组件向父组件传值 emit事件
组件可以显式地通过 defineEmits() 宏来声明它要触发的事件:
通过下面的示例代码可以看出,在子组件
<script setup>
const emit = defineEmits(["close-dialog"]);
</script>之后触发事件
emit("close-dialog", false);父组件监听事件
<CreateUserForm
ref="createFormRef"
:props-create-form="propsCreateForm"
@close-dialog="listenToCloseDialog"
>
</CreateUserForm>代码示例:
父组件示例代码:
<!-- 弹窗内容开始 -->
<CreateUserForm
ref="createFormRef"
:props-create-form="propsCreateForm"
@close-dialog="listenToCloseDialog"
>
</CreateUserForm>
<script setup lang="ts">
import YhDialog from "@/pages/laravel-fast-api/v1/components/element/Dialog/index.vue";
import CreateUserForm from "./components/CreateUserForm.vue";
//创建表单
const propsCreateForm = reactive({
source_user_uid: 0,
phone: "",
password: "abc123",
nick_name: "",
sex: 0,
});
//监听关闭弹窗
const listenToCloseDialog = () => {
loading.value = true;
//重新获取用户列表
getUserList();
//初始化表单
initPropsCreateForm();
//initpropsUpdateForm()
//初始化查看详情
//initPropsRowData()
dialogVisible.value = false;
};
</script>子组件
<template>
<div>
<el-form
ref="createFormRef"
:model="createForm"
:rules="createRules"
label-width="220px"
:inline="true"
label-position="left"
status-icon
inline-message
style="width: 80%; padding: 30px 20px; margin-right: auto; margin-left: auto"
>
<el-row type="flex" justify="center">
<el-col :span="16">
<el-form-item label="手机号" prop="phone">
<el-input v-model="createForm.phone" placeholder="手机号" clearable />
</el-form-item>
</el-col>
<el-col :span="8" style="line-height: 40px; vertical-align: middle">注册手机号</el-col>
</el-row>
<el-row type="flex" justify="center">
<el-col :span="16">
<el-form-item label="'(默认密码:abc123)" prop="password">
<el-input v-model="createForm.password" placeholder="请填写密码" clearable />
</el-form-item>
</el-col>
<el-col :span="8" style="line-height: 40px; vertical-align: middle">密码</el-col>
</el-row>
<el-row type="flex" justify="center">
<el-col :span="16">
<el-form-item label="昵称" prop="nick_name">
<el-input v-model="createForm.nick_name" placeholder="请填写昵称" clearable />
</el-form-item>
</el-col>
<el-col :span="8" style="line-height: 40px; vertical-align: middle">用户昵称</el-col>
</el-row>
<el-row type="flex" justify="center">
<el-col :span="16">
<el-form-item label="性别" prop="sex">
<el-radio-group v-model="createForm.sex">
<el-radio :value="10">男</el-radio>
<el-radio :value="20">女</el-radio>
<el-radio :value="0">未知</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<el-col :span="8" style="line-height: 40px; vertical-align: middle">性别</el-col>
</el-row>
<el-row type="flex" justify="center">
<el-col :span="6">
<el-form-item>
<el-button type="primary" @click="submitForm(createFormRef)">添加用户</el-button>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item>
<el-button @click="resetForm(createFormRef)">重置</el-button>
</el-form-item>
</el-col>
</el-row>
</el-form>
</div>
</template>
<script setup lang="ts">
//系统
import type { FormInstance, FormRules } from "element-plus";
import { validPhone, validPassword } from "@/utils/validate";
//api和type
import type { CreateUserFrom } from "@/api/laravel-fast-api/v1/user/user/user-type";
import UserAPi from "@/api/laravel-fast-api/v1/user/user/user-api";
//定义组件名称
defineOptions({
name: "CreateUserForm",
});
//emit
const emit = defineEmits(["close-dialog"]);
const createFormRef = ref<FormInstance>();
const createForm = reactive<CreateUserFrom>({
source_user_uid: 0,
phone: "",
password: "abc123",
nick_name: "",
sex: 0,
});
// 手机号验证
const validatePhone: (
_rule: any,
value: string,
callback: (error?: Error | string) => void
) => void = (_rule, value, callback) => {
if (!validPhone(value)) {
callback(new Error("手机号不正确"));
} else {
callback();
}
};
// 密码验证
const validatePassword: (
_rule: any,
value: string,
callback: (error?: Error | string) => void
) => void = (_rule, value, callback) => {
if (!validPassword(value)) {
callback(new Error("请输入正确密码"));
} else {
callback();
}
};
const createRules = reactive<FormRules<CreateUserFrom>>({
source_user_uid: [
{ type: "number", required: true, message: "必须有值", trigger: "blur" },
// 数字类型不需要字符串正则,删除或改为数字范围验证
{ type: "number", min: 0, message: "不能为负数", trigger: "blur" },
],
phone: [
{ type: "string", required: true, message: "请输入手机号", trigger: "blur" },
{ validator: validatePhone, trigger: "blur" },
],
password: [
{ type: "string", required: true, message: "请输入密码", trigger: "blur" },
{ validator: validatePassword, trigger: "blur" },
],
nick_name: [
{
pattern: /^[a-zA-Z0-9_\u4e00-\u9fa5]{0,10}$/,
message: "只能以字母,数字,汉字和_这种字符组成",
trigger: "blur",
},
],
});
const {propsCreateForm = { source_user_uid: 0,
phone: "",
password: "abc123",
nick_name: "",
sex: 0,}} = defineProps<{
propsCreateForm?: CreateUserFrom;
}>();
//监听父级表单值的变化
watch(
() => propsCreateForm,
(newVal) => {
createForm.source_user_uid = newVal.source_user_uid;
createForm.phone = "";
createForm.password = "abc123";
createForm.nick_name = "";
createForm.sex = 0;
},
{ deep: true, immediate: true } // 因为是对象,需要开启深度监听
);
//初始化表单
const initCreateForm = () => {
createForm.source_user_uid = 0;
createForm.phone = "";
createForm.password = "abc123";
createForm.nick_name = "";
createForm.sex = 0;
};
//提交
const submitForm = async (formEl: FormInstance | undefined) => {
if (!formEl) return;
try {
// 1. 表单校验(async/await 语法更简洁)
const valid = await formEl.validate();
// 2. 调用后端新增接口
if (valid) {
//console.log(createForm)
const result = await UserAPi.create(createForm);
// 3. 提交成功提示
ElMessage.success(result.msg);
// 4. 重置表单(可选,根据业务需求)
resetForm(formEl);
// 初始化表单
initCreateForm();
// 6. 关闭对话框(可选,根据业务需求)
emit("close-dialog", false);
}
} catch (error) {
// 5. 处理错误(校验失败或接口报错)
const err = error as Error;
ElMessage.error(`提交失败:${err.message || "未知错误"}`);
//console.log('提交错误:', error);
}
};
//重置
const resetForm = (formEl: FormInstance | undefined) => {
if (!formEl) return;
formEl.resetFields();
};
</script>
<style lang="scss" scoped>
/**表单 */
.content :deep(.el-form-item__label) {
width: 150px;
}
</style>