学习关键语句:
饿了么组件dialog组件使用
dialog组件二次封装
vue3中封住的组件使用update触发更新
vue3中封装组件使用v-model:属性值来传值
写在前面
这是我遇到的一个页面需求 , 其中一个对话框的内容是很常用的 , 所以我将它封装出来才写的一篇文章
现在给出如下需求:
封装一个组件 , 要求每次选择用户的时候 , 能从组件返回选择的用户id数组
并且能在选择用户是看到选择了哪些用户
- 如果你只是想看如何封装组件 , 请根据目录点击 ‘封装对话框组件’ 这个内容
文章末尾附带本文项目文件链接
开始
提示 : 本项目使用 vite 创建的 vue3 项目 , 使用 express 创建的后端
创建项目文件夹
随意新建一个文件夹 , 起名为 test , 在该文件夹下 , 再新建一个文件夹为 server
开启终端 , 输入创建项目命令
npm create vite
依次输入项目名称 dialog , 框架选择 Vue , 语言选择 typescript
创建完成后 , 根据提示 输入提示的命令
cd dialog
npm install
npm run dev
点击打开最后这个网址就完成了项目最初的创建
现在我们来创建后端服务器
打开终端 , 进入 server 文件夹 , 输入以下命令
npm init
测试使用 , 全部按回车默认过去就OK了
命令行中安装依赖
npm install
同时我们再创建一个文件 起名为 app.js
全部创建完后的目录如下
安装库和配置
现在需要在前端和后端中各安装需要的库
其中 , 前端需要安装 elementUI 和 axios
后端需要安装 express , cors , body-parser 和 connect-multiparty
注意 安装依赖前请确定在对的终端下 , 不可下错了位置
后端
优先进行后端的配置
- 安装 express 制作服务器
npm install express --save
- 安装 cors 解决跨域
npm install cors --save
- 引入中间件解决读取参数问题
npm install body-parser --save
npm install connect-multiparty --save
配置完成之后我们来写服务器文件 app.js
app.js
// 导入 express
const express = require('express')
// 创建 app
const app = express()
// 设置跨域访问
const cors = require('cors')
app.use(cors())
// 处理POST参数
const bodyParser = require('body-parser')
const multiparty = require('connect-multiparty')
// 处理 x-www-form-urlencoded
app.use(bodyParser.urlencoded({
extended:true
}));
// 处理 application/json
app.use(bodyParser.json())
// 处理 mutipart/form-data
app.use(multiparty())
// 测试接口能否正常调用
app.get('/hello', (req, res) => {
res.send("Hello shaoyahu !")
})
// 我们写一个获取用户信息的接口
// 模拟数据库中的用户数据
let db = [
{ id: 1, name: '张一' },
{ id: 2, name: '张二' },
{ id: 3, name: '张三' },
{ id: 4, name: '熊大' },
{ id: 5, name: '熊二' },
{ id: 6, name: '熊三' },
{ id: 7, name: '李三' },
{ id: 8, name: '李四' },
]
app.post('/getUserData', (req, res) => {
let txt = req.body.name
let data = db.filter(item => {
return item.name.includes(txt)
})
res.send({
status: 200,
data,
msg: '获取成功'
})
})
// 启动
app.listen(3000, () => {
console.log('express server running at http://127.0.0.1:' + 3000);
})
写好服务器文件后在终端输入如下命令启动服务器
node app.js
当控制台打印出我们设置的语句时表示服务器已经启动成功
现在简单测试一下调用接口
在浏览器中输入
http://localhost:3000/hello
如果出现 Hello shaoyahu ! 就说明接口调用成功了
那么后端先写到这里 , 我们现在来看前端
前端
- 安装element plus
npm install element-plus --save
安装完毕后打开 main.ts 文件进行引入和挂载
import { createApp } from 'vue'
import ElementPlus from 'element-plus' // 加入
import 'element-plus/dist/index.css' // 加入
import App from './App.vue'
const app = createApp(App) // 修改
app.use(ElementPlus) // 修改
app.mount('#app') // 修改
这样就能使用饿了么的组件了
- 安装 axios
npm install axios --save
在 src 目录下新建文件 axios.ts
axios.ts
进行简单的封装 , 不添加拦截器
import axios from 'axios'
const service = axios.create({
// 根据刚才的后端地址和端口配置写出请求基础路径
baseURL: 'http://localhost:3000'
})
export default service
在 src 目录下新建文件夹起名 api
在 api 文件夹中新建文件起名为 user.ts
user.ts
专门存放和用户有关的接口请求
我们写一个获取用户信息的请求
import axios from '../axios'
// 获取用户信息
export function getUserData(data: { name: string }){
return axios.post('/getUserData', data)
}
axios 相关的操作完成后目录结构如下图
![在这里插入图片描述](https://img-blog.csdnimg.cn/9b9d31c0af2d4168a5cf42c2fd02f121.png
编写页面内容
我们将 App.vue 页面的内容清空 , 写入一个简单的提示词 , 输入框和按钮
App.vue
<template>
<div class="index">
<span>选中的用户</span>
<el-input v-model="searchTxt" type="textarea"
style="width: 220px;" disabled :placeholder="'请选择用户'" />
<el-button @click="toSelectPerson">选择用户</el-button>
</div>
</template>
<script setup lang='ts'>
import { ref } from "vue";
let searchTxt = ref('')
const toSelectPerson = () => {}
</script>
我们的逻辑是 , 点击选择用户按钮 , 打开一个对话框 , 在对话框中我们选择好需要的用户后关闭对话框 , 将在对话框中选中的用户在输入框中展现
那么 , 由于这个选择人物的对话框会在多处使用到 , 所以我们现在就来封装一个对话框组件吧
封装对话框组件
我们在 src 下的 components 文件夹中新建文件 起名为 myDialog.vue
myDialog.vue
需要注意的点如下
- 开启关闭对话框的方法必须使用 v-model: 来给属性值
- 饿了么组件的对话框原始的 v-model 需要修改成 :model-value 才不会报错
- 这个文件里的样式我没写在下方 , 但是末尾文件中会有样式
- 通过点击遮罩层关闭掉对话框的方式虽然可以关闭对话框 , 但实际上传入的值仍然为 true , 解决方法有两个 , 第一个是通用的 , 将打开对话框和关闭对话框的方法写在组件上 , 通过操控组件来打开和关闭对话框 , 第二个办法则是参考了饿了么组件提供的一个属性 before-close , 在这个属性中 , 我们手动 emit 传入的值修改为 false 就可以了
<template>
<el-dialog :model-value="props.selectUserShow"
:title="props.title" :width="props.width"
:draggable="drag" :before-close="beforeClose">
<slot>
<div>
<span>搜索 : </span>
<el-input v-model="searchTxt" :placeholder="'输入关键词'">
<template #append>
<el-button @click="searchPerson">查询</el-button>
</template>
</el-input>
<div>
<div>
<span>选择用户</span>
</div>
<div>
<div v-for="item, index in data" :key="index" style="margin: 20px;">
<el-checkbox v-model="item.checked" :label="item.name" size="large" @change="select(item)" />
</div>
</div>
<div>
<span>已选择用户</span>
<div>{{ selectedTxt }}</div>
</div>
</div>
</div>
</slot>
<template #footer>
<span>
<el-button type="primary" @click="emit('confirm', selectedData)">
确定
</el-button>
<el-button @click="emit('update:selectUserShow', false)">
取消
</el-button>
</span>
</template>
</el-dialog>
</template>
<script setup lang='ts'>
import { computed, ref } from 'vue'
import { getUserData } from '../api/user.js'
export interface IData {
id: number,
name: string,
checked?: boolean
}
const props = defineProps({
// 控制组件的显示与隐藏
selectUserShow: {
type: Boolean,
default: false,
required: true
},
// 传进来的已经选择了的用户数组
selectUserList: {
type: Array<IData>,
default: [],
required: true
},
title: {
type: String,
default: ''
},
width: {
type: String,
default: '80%'
},
drag: {
type: Boolean,
default: true
}
})
const emit = defineEmits(['confirm', 'update:selectUserShow'])
// 查询用户的关键词
let searchTxt = ref('')
// 查询结果数据
let data = ref<IData[]>([])
// 已经选择的数据数组
let selectedData = ref<IData[]>(props.selectUserList)
let selectedTxt = computed(() => {
let t = ''
selectedData.value.map((item: IData) => {
t += `${item.name} , `
})
t = t.substring(0, t.length - 2)
return t
})
// 查询用户
const searchPerson = () => {
getUserData({ name: searchTxt.value })
.then((res: any) => {
data.value = res.data.data
addSelectedStatus(data.value)
})
}
// 选择用户
const select = (obj: IData) => {
if (obj.checked) {
selectedData.value.push(obj)
} else {
selectedData.value = selectedData.value.filter((item: IData) => {
return item.id !== obj.id
})
}
}
// 给所有人添加选中状态
// 需要判断查询出来的用户中是否已经有被选中的用户
function addSelectedStatus(data: IData[]) {
data.forEach((item: any) => {
let flag = selectedData.value.some((item2: IData) => {
return item2.id === item.id
})
if (flag) {
item.checked = true
} else {
item.checked = false
}
})
}
// 关闭对话框之前
const beforeClose = (done: Function) => {
emit('update:selectUserShow', false)
done()
}
searchPerson()
// 导出搜索用户的方法 , 是为了防止一个页面多个查询用户共用一个组件导致已选中的打开无法显示已选中的情况出现 , 使用方法为在打开组件时调用这个方法
defineExpose({
searchPerson
})
</script>
这样一个对话框组件就被封装完成了 , 但是由于添加了很多属性和内容 , 所以定制度是比较高的 , 我将所有默认的内容都放在了默认插槽中 , 如果需要另一个对话框的话可以使用插槽覆盖掉原有的内容 , 不过还是建议重新封装一个
补完页面
对话框组件已经封装好了 , 现在我们回头去使用这个组件
App.vue
<template>
<div class="index">
<span>选中的用户</span>
<el-input v-model="userTxt" type="textarea" style="width:220px" disabled :placeholder="'请选择用户'" />
<el-button @click="toSelectPerson">选择用户</el-button>
</div>
{{ selectUserList }}
<myDialog v-model:selectUserShow="selectUserShow" :selectUserList="selectUserList" @confirm="confirmUser"></myDialog>
</template>
<script setup lang='ts'>
import { ref } from "vue";
import myDialog, { IData } from './components/myDialog.vue'
let userTxt = ref('')
let selectUserShow = ref(false)
const toSelectPerson = () => {
selectUserShow.value = true
}
let selectUserList = ref<IData[]>([])
const confirmUser = (selectedData: IData[]) => {
userTxt.value = ''
selectUserShow.value = false
selectUserList.value = selectedData
selectUserList.value.map((item: IData) => {
userTxt.value += `${item.name} , `
})
userTxt.value = userTxt.value.substring(0, userTxt.value.length - 2)
}
</script>
结束
就这样这个组件就完成了 , 不过不是很建议大家使用传入值来控制组件的显示和隐藏 , 推荐还是使用组件 expose 出的方法来操作
文件链接
最后附上文件链接
https://download.csdn.net/download/shaoyahu/87477013