【Vue3+Vite+TS】7.0 组件五:通知菜单
必备UI组件
将用到的组件:
Badge 徽章
Popover 弹出框/气泡卡片
Tabs 标签页
Avatar 头像
Tag 标签
Scrollbar 滚动条
组件设计
新建src\components\baseline\notification\index.ts
import { App } from 'vue'
import Notification from './src/index.vue'
export { Notification }
//组件可通过use的形式使用
export default {
Notification,
install(app: App) {
app.component('bs-notification', Notification)
},
}
新建src\components\baseline\notification\src\index.vue
<template>
<el-badge
style="cursor: pointer"
:value="value"
:max="max"
v-bind="$attrs"
:is-dot="isDot"
>
<component :is="icon"></component>
</el-badge>
</template>
<script lang="ts" setup>
const props = defineProps({
//说明:图标
icon: {
type: String,
default: 'el-icon-bell',
},
//说明:通知数量
value: {
type: [String, Number],
default: 0,
},
color: {
type: String,
default: 'el-icon-bell',
},
//徽章最大值
max: {
type: Number,
},
//是否显示小圆点
isDot: {
type: Boolean,
default: false,
},
})
</script>
<style lang="scss" scoped></style>
新建src\views\baseline\notification\index.vue
<template>
<div class="bs-wrapper">
<bs-notification value="50"></bs-notification>
<br />
<br />
<bs-notification value="50" max="30"></bs-notification>
<br />
<br />
<bs-notification value="50" :is-dot="true"></bs-notification>
<br />
<br />
<bs-notification value="50" icon="el-icon-chat-round"></bs-notification>
</div>
</template>
<script lang="ts" setup></script>
<style lang="scss" scoped>
.bs-wrapper {
.flex {
display: flex;
}
div {
margin-right: 0.1rem;
}
}
</style>
修改src\router\index.ts
/*
* @Author: bobokaka
* @Date: 2021-12-19 11:26:38
* @LastEditTime: 2021-12-24 23:34:35
* @LastEditors: Please set LastEditors
* @Description: 路由
* @FilePath: \vue3-element-ui-baseline\src\router\index.ts
*/
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'
import Home from '../views/Home/index.vue'
import Container from '../components/baseline/container/src/index.vue'
const routes: RouteRecordRaw[] = [
{
path: '/',
component: Container,
children: [
{
path: '/',
component: Home,
},
{
path: '/chooseIcon',
component: () => import('../views/baseline/chooseIcon/index.vue'),
},
{
path: '/chooseArea',
component: () => import('../views/baseline/chooseArea/index.vue'),
},
{
path: '/trend',
component: () => import('../views/baseline/trend/index.vue'),
},
{
path: '/notification',
component: () => import('../views/baseline/notification/index.vue'),
},
],
},
]
const router = createRouter({
routes,
history: createWebHistory(),
})
export default router
打开网页http://localhost:8080/notification
实现点击打开一个列表组件
修改src\views\baseline\notification\index.vue
<template>
<div class="bs-wrapper">
<!-- <bs-notification value="50"></bs-notification>
<br />
<br />
<bs-notification value="50" max="30"></bs-notification>
<br />
<br />
<bs-notification value="50" :is-dot="true"></bs-notification>
<br />
<br />
<bs-notification value="50" icon="el-icon-chat-round"></bs-notification> -->
<bs-notification value="50"></bs-notification>
</div>
</template>
<script lang="ts" setup></script>
<style lang="scss" scoped>
.bs-wrapper {
.flex {
display: flex;
}
div {
margin-right: 0.1rem;
}
}
</style>
修改src\components\baseline\notification\index.ts
<template>
<el-popover
placement="bottom"
title="Title"
:width="200"
trigger="click"
content="this is content, this is content, this is content"
>
<template #reference>
<el-badge
style="cursor: pointer"
:value="value"
:max="max"
v-bind="$attrs"
:is-dot="isDot"
>
<component :is="icon"></component>
</el-badge>
</template>
</el-popover>
</template>
<script lang="ts" setup>
const props = defineProps({
//说明:图标
icon: {
type: String,
default: 'el-icon-bell',
},
//说明:通知数量
value: {
type: [String, Number],
default: 0,
},
color: {
type: String,
default: 'el-icon-bell',
},
//徽章最大值
max: {
type: Number,
},
//是否显示小圆点
isDot: {
type: Boolean,
default: false,
},
})
</script>
<style lang="scss" scoped></style>
从官网复制一个过来,点击图标的效果如上,这里我们不需要标题,删掉如下代码:
title="Title"
整体修改后如下:
<template>
<el-popover placement="bottom" :width="300" trigger="hover">
<template #default>
<slot></slot>
</template>
<template #reference>
<el-badge
style="cursor: pointer"
:value="value"
:max="max"
v-bind="$attrs"
:is-dot="isDot"
>
<component :is="icon"></component>
</el-badge>
</template>
</el-popover>
</template>
修改src\views\baseline\notification\index.vue
<template>
<div class="bs-wrapper">
<!-- <bs-notification value="50"></bs-notification>
<br />
<br />
<bs-notification value="50" max="30"></bs-notification>
<br />
<br />
<bs-notification value="50" :is-dot="true"></bs-notification>
<br />
<br />
<bs-notification value="50" icon="el-icon-chat-round"></bs-notification> -->
<bs-notification value="50">
<template #default>这是一个标题</template>
</bs-notification>
</div>
</template>
<script lang="ts" setup></script>
<style lang="scss" scoped>
.bs-wrapper {
.flex {
display: flex;
}
div {
margin-right: 0.1rem;
}
}
</style>
菜单组件封装
新建src\components\baseline\list\src\index.vue
<template>List</template>
<script lang="ts" setup></script>
<style lang="scss" scoped></style>
新建src\components\baseline\list\index.ts
import { App } from 'vue'
import List from './src/index.vue'
export { List }
//组件可通过use的形式使用
export default {
List,
install(app: App) {
app.component('bs-list', List)
},
}
修改src\views\baseline\notification\index.vue
import { App } from 'vue'
import ChooseArea from './chooseArea'
import ChooseIcon from './chooseIcon'
import Container from './container'
import Trend from './trend'
import Notification from './notification'
import List from './list'
const components = [
ChooseArea,
ChooseIcon,
Container,
Trend,
Notification,
List,
]
export { ChooseArea, ChooseIcon, Container, Trend, Notification, List }
//组件可通过use的形式使用
export default {
install(app: App) {
components.map(item => {
app.use(item)
})
},
ChooseArea,
ChooseIcon,
Container,
Trend,
Notification,
List,
}
修改src\components\baseline\notification\src\index.vue
<template>
<div class="bs-wrapper">
<!-- <bs-notification value="50"></bs-notification>
<br />
<br />
<bs-notification value="50" max="30"></bs-notification>
<br />
<br />
<bs-notification value="50" :is-dot="true"></bs-notification>
<br />
<br />
<bs-notification value="50" icon="el-icon-chat-round"></bs-notification> -->
<bs-notification value="50">
<template #default><bs-list></bs-list></template>
</bs-notification>
</div>
</template>
......
运行效果如下:
列表组件
这里需要构建一个即将出场的列表组件。
新建src\components\baseline\list\src\index.vue
<template>List</template>
<script lang="ts" setup>
import { PropType } from 'vue'
import { ActionOptions, listOptions } from './type'
const props = defineProps({
list: {
type: Array as PropType<listOptions[]>,
required: true,
},
//操作的内容
actions: {
type: Array as PropType<ActionOptions[]>,
default: [],
},
})
</script>
<style lang="scss" scoped></style>
新建src\components\baseline\list\src\type.ts
export interface ListItem {
//头像
avatar?: string
//标题
title?: string
//描述
desc: string
//时间
time?: string
//标签内容
tag?: string
tagType?: 'success' | 'info' | 'warning' | 'danger'
}
/**
* 列表
*/
export interface listOptions {
title: string
content: ListItem[]
}
export interface ActionOptions {
text: string
icon?: string
}
新建src\components\baseline\list\index.ts
import { App } from 'vue'
import List from './src/index.vue'
export { List }
//组件可通过use的形式使用
export default {
List,
install(app: App) {
app.component('bs-list', List)
},
}
初始化,完毕,开始使用改造:
准备数据,新建src\views\baseline\notification\data.ts
/*
* @Author: your name
* @Date: 2021-12-28 18:45:30
* @LastEditTime: 2021-12-29 01:03:45
* @LastEditors: Please set LastEditors
* @Description: 模拟数据
* @FilePath: \vue3-element-ui-baseline\src\views\baseline\notification\data.ts
*/
export const list = [
{
title: '通知',
content: [
{
title: '鲁迅回复了你的邮件',
time: '2021-12-24 17:47:54',
avatar: 'https://upload-images.jianshu.io/upload_images/16102290-ce12da7b12204094.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240',
},
{
title: '刘备邀请你参加会议',
time: '2021-12-28 9:15:27',
avatar: 'https://www.baidu.com',
},
{
title: '诸葛孔明已批准了你的休假申请',
time: '2021-12-27 16:47:33',
avatar: 'https://www.baidu.com',
},
],
},
{
title: '关注',
content: [
{
title: '鲁迅 评论了你',
desc: '愿中国青年都摆脱冷气,只是向上走,不必听自暴自弃者的话。',
time: '2021-12-24 17:47:54',
avatar: 'https://www.baidu.com',
},
{
title: '刘备 评论了你',
desc: '惟贤惟德,能服于人!',
time: '2021-12-28 9:15:27',
avatar: 'https://www.baidu.com',
},
{
title: '诸葛孔明 评论了你',
desc: '恢弘志士之气,不宜妄自菲薄。',
time: '2021-12-27 16:47:33',
avatar: 'https://www.baidu.com',
},
],
},
{
title: '代办',
content: [
{
title: '鲁迅发表文章,待审核',
desc: '需要在2021-12-30 18:00:00前审核',
avatar: 'https://www.baidu.com',
tag: '未开始',
tagType: '',
},
{
title: '刘备出兵计划发起',
desc: '刘备提交于2021-12-27 16:04:00 需要在2021-12-30 18:00:00前审核',
time: '2021-12-28 9:15:27',
avatar: 'https://www.baidu.com',
tag: '即将过期',
tagType: 'danger',
},
{
title: '诸葛孔明科目二考试',
desc: '需要在2021-12-29 18:00:00前审核',
time: '2021-12-27 16:47:33',
avatar: 'https://www.baidu.com',
tag: '已过期',
tagType: 'warning',
},
{
title: '鲁迅发表文章,待审核',
desc: '需要在2021-12-30 18:00:00前审核',
avatar: 'https://www.baidu.com',
tag: '未开始',
tagType: '',
},
{
title: '刘备出兵计划发起',
desc: '刘备提交于2021-12-27 16:04:00 需要在2021-12-30 18:00:00前审核',
time: '2021-12-28 9:15:27',
avatar: 'https://www.baidu.com',
tag: '即将过期',
tagType: 'danger',
},
{
title: '诸葛孔明科目二考试',
desc: '需要在2021-12-29 18:00:00前审核',
time: '2021-12-27 16:47:33',
avatar: 'https://www.baidu.com',
tag: '已过期',
tagType: 'warning',
},
],
},
]
export const actions = [
{
text: '清空代办',
icon: 'el-icon-delete',
},
{
text: '查看更多',
icon: 'el-icon-edit',
},
]
修改src\views\baseline\notification\index.vue
<template>
<div class="bs-wrapper">
<!-- <bs-notification value="50"></bs-notification>
<br />
<br />
<bs-notification value="50" max="30"></bs-notification>
<br />
<br />
<bs-notification value="50" :is-dot="true"></bs-notification>
<br />
<br />
<bs-notification value="50" icon="el-icon-chat-round"></bs-notification> -->
<bs-notification value="50">
<template #default>
<bs-list :list="list" :actions="actions"></bs-list>
</template>
</bs-notification>
</div>
</template>
<script lang="ts" setup>
import { list, actions } from './data'
</script>
<style lang="scss" scoped>
.bs-wrapper {
.flex {
display: flex;
}
div {
margin-right: 0.1rem;
}
}
</style>
修改src\components\baseline\list\src\index.vue
<!--
* @Author: your name
* @Date: 2021-12-23 00:07:25
* @LastEditTime: 2021-12-29 01:50:17
* @LastEditors: Please set LastEditors
* @Description: 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
* @FilePath: \vue3-element-ui-baseline\src\components\baseline\trend\src\index.vue
-->
<template>
<div class="bs-wrapper">
<el-tabs class="tabs">
<el-tab-pane
v-for="(item, index) in list"
v-model="activeName"
:key="index"
:label="item.title"
:name="index + ''"
>
<div class="list">
<el-scrollbar max-height="3rem">
<!-- 待办信息 -->
<div
class="list__box"
v-for="(item2, index2) in item.content"
:key="index2"
>
<!-- 头像 -->
<div class="list__box__avatar">
<el-avatar
size="small"
:src="item2.avatar"
></el-avatar>
</div>
<div class="list__box__content">
<div
v-if="item2.title"
class="list__box__content__title"
>
<!-- 主题和报警 -->
<div class="title">{{ item2.title }}</div>
<el-tag
class="tag"
size="mini"
v-if="item2.tag"
:type="item2.tagType"
>
{{ item2.tag }}
</el-tag>
</div>
<!-- 说明 -->
<div
v-if="item2.desc"
class="list__box__content__desc"
>
{{ item2.desc }}
</div>
<!-- 时间 -->
<div
v-if="item2.time"
class="list__box__content__time"
>
{{ item2.time }}
</div>
</div>
</div>
</el-scrollbar>
</div>
<div class="actions">
<div
class="actions__content"
:class="{ border: actionIndex + 1 !== actions.length }"
v-for="(actionItem, actionIndex) in actions"
:key="index"
>
<div
v-if="actionItem.icon"
class="actions__content__icon"
>
<component :is="actionItem.icon"></component>
</div>
<div class="actions__content__text">
{{ actionItem.text }}
</div>
</div>
</div>
</el-tab-pane>
</el-tabs>
</div>
</template>
<script lang="ts" setup>
import { ref, PropType } from 'vue'
import { ActionOptions, listOptions } from './type'
const props = defineProps({
list: {
type: Array as PropType<listOptions[]>,
required: true,
},
//操作的内容
actions: {
type: Array as PropType<ActionOptions[]>,
default: [],
},
})
const activeName = ref('0')
// console.log('props.list', props.list)
// console.log('props.actions', props.actions)
</script>
<style lang="scss" scoped>
.bs-wrapper {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
}
.tabs {
//让tabs值占满格分布并横向摆放
::v-deep(.el-tabs__nav) {
width: 100%;
display: flex;
}
::v-deep(.el-tabs__item) {
flex: 1;
text-align: center;
}
.list {
padding-bottom: 0.04rem;
&__box {
display: flex;
align-items: center;
padding: 0.12rem 0.2rem;
cursor: pointer; //鼠标悬浮变小手
&:hover {
background-color: #e6f6ff;
}
&__avatar {
flex: 1;
}
&__content {
flex: 3;
&__title {
display: flex;
align-items: center; //垂直方向居中
justify-content: space-between; //水平方向两边排列
.title {
}
.tag {
}
}
&__desc,
&__time {
font-size: 0.12rem;
color: #999;
margin-top: 0.04rem;
}
}
}
}
.actions {
height: 0.5rem;
display: flex;
align-items: center;
&__content {
height: 0.5rem;
cursor: pointer; //鼠标悬浮变小手
flex: 1;
display: flex;
align-items: center;
justify-content: center; //水平方向两边排列
border-top: 2px solid #eee;
// &:hover {
// background-color: #e6f6ff;
// }
&__icon {
margin-right: 0.08rem;
position: relative;
top: 0.02rem;
}
&__text {
}
}
}
}
.border {
border-right: 2px solid #eee;
}
</style>
修改src\style\ui.scss
//修改组件库内部的样式
//1.需要自定义一个类名空间
//2.浏览器中调试样式
//3.调试好的类名放在这个类名中
//4.在App.vue里面引入这个文件
//5.在组件内需要改样式的元素的父元素加上这个类名
.bk--choose-icon-dialog-body-height {
.el-dialog__body {
height: 5rem;
overflow-y: scroll;
overflow-x: auto;
}
}
//去掉el-tabs盒子默认的padding
.el-popper {
padding: 0 !important;
}
效果如下:
list组件完善
修改src\components\baseline\list\src\index.vue
<!--
* @Author: your name
* @Date: 2021-12-23 00:07:25
* @LastEditTime: 2021-12-29 02:17:41
* @LastEditors: Please set LastEditors
* @Description: 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
* @FilePath: \vue3-element-ui-baseline\src\components\baseline\trend\src\index.vue
-->
<template>
<div class="bs-wrapper">
<el-tabs class="tabs">
<el-tab-pane
v-for="(item, index) in list"
v-model="activeName"
:key="index"
:label="item.title"
:name="index + ''"
>
<div class="list">
<el-scrollbar max-height="3rem">
<!-- 待办信息 -->
<div
class="list__box"
v-for="(item2, index2) in item.content"
:key="index2"
@click="handleChickList(item2, index2)"
>
<!-- 头像 -->
<div class="list__box__avatar">
<el-avatar
size="small"
:src="item2.avatar"
></el-avatar>
</div>
<div class="list__box__content">
<div
v-if="item2.title"
class="list__box__content__title"
>
<!-- 主题和报警 -->
<div class="title">{{ item2.title }}</div>
<el-tag
class="tag"
size="mini"
v-if="item2.tag"
:type="item2.tagType"
>
{{ item2.tag }}
</el-tag>
</div>
<!-- 说明 -->
<div
v-if="item2.desc"
class="list__box__content__desc"
>
{{ item2.desc }}
</div>
<!-- 时间 -->
<div
v-if="item2.time"
class="list__box__content__time"
>
{{ item2.time }}
</div>
</div>
</div>
</el-scrollbar>
</div>
<div class="actions">
<div
class="actions__content"
:class="{ border: actionIndex + 1 !== actions.length }"
v-for="(actionItem, actionIndex) in actions"
:key="index"
@click="handleChickActions(actionItem, actionIndex)"
>
<div
v-if="actionItem.icon"
class="actions__content__icon"
>
<component :is="actionItem.icon"></component>
</div>
<div class="actions__content__text">
{{ actionItem.text }}
</div>
</div>
</div>
</el-tab-pane>
</el-tabs>
</div>
</template>
<script lang="ts" setup>
import { ref, PropType } from 'vue'
import { ActionOptions, listOptions, ListItem } from './type'
const props = defineProps({
list: {
type: Array as PropType<listOptions[]>,
required: true,
},
//操作的内容
actions: {
type: Array as PropType<ActionOptions[]>,
default: [],
},
})
const activeName = ref('0')
const emits = defineEmits(['chickList', 'chickActions'])
const handleChickList = (item: ListItem, index: number) => {
emits('chickList', { item, index })
}
const handleChickActions = (item: ActionOptions, index: number) => {
emits('chickActions', { item, index })
}
// console.log('props.list', props.list)
// console.log('props.actions', props.actions)
</script>
<style lang="scss" scoped>
.bs-wrapper {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
}
.tabs {
//让tabs值占满格分布并横向摆放
::v-deep(.el-tabs__nav) {
width: 100%;
display: flex;
}
::v-deep(.el-tabs__item) {
flex: 1;
text-align: center;
}
.list {
padding-bottom: 0.04rem;
&__box {
display: flex;
align-items: center;
padding: 0.12rem 0.2rem;
cursor: pointer; //鼠标悬浮变小手
&:hover {
background-color: #e6f6ff;
}
&__avatar {
flex: 1;
}
&__content {
flex: 3;
&__title {
display: flex;
align-items: center; //垂直方向居中
justify-content: space-between; //水平方向两边排列
.title {
}
.tag {
}
}
&__desc,
&__time {
font-size: 0.12rem;
color: #999;
margin-top: 0.04rem;
}
}
}
}
.actions {
height: 0.5rem;
display: flex;
align-items: center;
&__content {
height: 0.5rem;
cursor: pointer; //鼠标悬浮变小手
flex: 1;
display: flex;
align-items: center;
justify-content: center; //水平方向两边排列
border-top: 2px solid #eee;
// &:hover {
// background-color: #e6f6ff;
// }
&__icon {
margin-right: 0.08rem;
position: relative;
top: 0.02rem;
}
&__text {
}
}
}
}
.border {
border-right: 2px solid #eee;
}
</style>
修改src\views\baseline\notification\index.vue
<!--
* @Author: your name
* @Date: 2021-12-23 00:07:25
* @LastEditTime: 2021-12-29 02:16:52
* @LastEditors: Please set LastEditors
* @Description:组件五:通知菜单
* @FilePath: \vue3-element-ui-baseline\src\components\baseline\trend\src\index.vue
-->
<template>
<div class="bs-wrapper">
<!-- <bs-notification value="50"></bs-notification>
<br />
<br />
<bs-notification value="50" max="30"></bs-notification>
<br />
<br />
<bs-notification value="50" :is-dot="true"></bs-notification>
<br />
<br />
<bs-notification value="50" icon="el-icon-chat-round"></bs-notification> -->
<bs-notification value="50">
<template #default>
<bs-list
:list="list"
:actions="actions"
@chickList="handleChickList"
@chickActions="handleChickActions"
></bs-list>
</template>
</bs-notification>
</div>
</template>
<script lang="ts" setup>
import { list, actions } from './data'
const handleChickList = (val: any) => {
console.log('handleChickList:', val)
}
const handleChickActions = (val: any) => {
console.log('handleChickActions:', val)
}
</script>
<style lang="scss" scoped>
.bs-wrapper {
.flex {
display: flex;
}
div {
margin-right: 0.1rem;
}
}
</style>
这样就实现了点击响应.
如上代码为了兼容别名使用,修改vite.config.ts
增加如下代码:
resolve: {
alias: {
'@': '/src',
'@style': '/src/style',
'@com': '/src/components',
'@baseline': '/src/components/baseline',
'@business': '/src/components/business',
},
},
完整代码如下:
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()],
resolve: {
alias: {
'@': '/src',
'@style': '/src/style',
'@com': '/src/components',
'@baseline': '/src/components/baseline',
'@business': '/src/components/business',
},
},
server: {
port: 8080,
},
})
修改tsconfig.json
增加如下代码:
"types": ["vite/client"],
// ++ 这里加上baseUrl 和 path即可 ++
"baseUrl": "./",
"paths": {
// 根据别名配置相关路径
"@/*": ["./src/*"],
"@style/*": ["./src/style/*"],
"@com/*": ["./src/components/*"],
"@baseline/*": ["./src/components/baseline/*"],
"@business/*": ["./src/components/business/*"]
}
完整代码如下:
{
"compilerOptions": {
"target": "esnext",
"useDefineForClassFields": true,
"module": "esnext",
"moduleResolution": "node",
"strict": true,
"jsx": "preserve",
"sourceMap": true,
"resolveJsonModule": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"importHelpers": true,
"isolatedModules": true,
"lib": ["esnext", "dom"],
"types": ["vite/client"],
// ++ 这里加上baseUrl 和 path即可 ++
"baseUrl": "./",
"paths": {
// 根据别名配置相关路径
"@/*": ["./src/*"],
"@style/*": ["./src/style/*"],
"@com/*": ["./src/components/*"],
"@baseline/*": ["./src/components/baseline/*"],
"@business/*": ["./src/components/business/*"]
}
},
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"]
}
结果如下:
发表评论 (审核通过后显示评论):