3-1趋势标记-图标的组合使用实现上升下降趋势
<template>
<div class="trend">
<div class="text" :style="{ color: textColor }">
<slot v-if="slots.default"></slot>
<div v-else>{{ text }}</div>
</div>
<div class="icon">
<component
:is="`el-icon-${toLine(upIcon)}`"
:style="{ color: !reverseColor ? upIconColor : '#52c41a' }"
v-if="type === 'up'"
></component>
<component
:is="`el-icon-${toLine(downIcon)}`"
:style="{ color: !reverseColor ? downIconColor : '#f5222d' }"
v-else
></component>
</div>
</div>
</template>
<script lang='ts' setup>
import { useSlots, computed } from 'vue'
import { toLine } from '../../../utils'
let props = defineProps({
// 标记当前趋势是上升(up)还是下降(down)
type: {
type: String,
default: 'up'
},
// 上升趋势显示的图标
upIcon: {
type: String,
default: 'ArrowUp'
},
// 下降趋势显示的图标
downIcon: {
type: String,
default: 'ArrowDown'
},
// 趋势显示的文字
// 1. 父组件传递过来的数据
// 2. 插槽
text: {
type: String,
default: '文字'
},
// 颜色翻转只在默认的颜色下生效 如果使用了自定义颜色 这个属性就不生效了
reverseColor: {
type: Boolean,
default: false
},
// 上升趋势图标颜色
upIconColor: {
type: String,
default: '#f5222d'
},
// 下降趋势的图标颜色
downIconColor: {
type: String,
default: '#52c41a'
},
// 上升趋势文字颜色
upTextColor: {
type: String,
default: 'rgb(0,0,0)'
},
// 下降趋势的文字颜色
downTextColor: {
type: String,
default: 'rgb(0,0,0)'
}
})
// 获取插槽内容
let slots = useSlots()
// 文字颜色
let textColor = computed(() => {
return props.type === 'up' ? props.upTextColor : props.downTextColor
})
</script>
<style lang='scss' scoped>
.trend {
display: flex;
align-items: center;
.text {
font-size: 12px;
margin-right: 4px;
}
.icon {
svg {
width: 0.8em;
height: 0.8em;
}
}
}
</style>
3-2趋势标记-动态绑定class的妙用实现颜色反转
获取slot插槽内容
let slots = useSlots()
如果使用了slot插槽就不用使用默认的props
<slot v-if="slots.default"></slot>
<div v-else>{{ text }}</div>
3-3趋势标记-计算属性的妙用实现文字颜色
组合式组件使用动态style样式
<component
:is="`el-icon-${toLine(downIcon)}`"
:style="{ color: !reverseColor ? downIconColor : '#f5222d' }"
v-else
></component>
3-4通知菜单-icon和badge组件的组合使用
<template>
<el-popover popper-class="notification-popper-class" placement="bottom" :width="300" trigger="click">
<template #default>
<slot></slot>
</template>
<template #reference>
<el-badge style="cursor: pointer;" :value="value" :max="max" :is-dot="isDot">
<component :is="`el-icon-${toLine(icon)}`"></component>
</el-badge>
</template>
</el-popover>
</template>
<script lang='ts' setup>
import { toLine } from '../../../utils'
let props = defineProps({
// 显示的图标
icon: {
type: String,
default: 'Bell'
},
// 通知数量
value: {
type: [String, Number],
default: ''
},
// 最大值
max: {
type: Number
},
// 是否显示小圆点
isDot: {
type: Boolean,
default: false
},
})
</script>
<style lang='scss' scoped>
svg {
width: 1.5em;
height: 1.5em;
}
</style>
3-5通知菜单-封装一个列表组件(上)
列表组件源码
<template>
<div class="list-tabs__item">
<el-tabs>
<el-tab-pane v-for="(item, index) in list" :key="index" :label="item.title">
<el-scrollbar max-height="300px">
<div
class="container"
@click="clickItem(item1, index1)"
v-for="(item1, index1) in item.content"
:key="index1"
>
<div class="avatar" v-if="item1.avatar">
<el-avatar size="small" :src="item1.avatar"></el-avatar>
</div>
<div class="content">
<div v-if="item1.title" class="title">
<div>{{ item1.title }}</div>
<el-tag v-if="item1.tag" size="mini" :type="item1.tagType">{{ item1.tag }}</el-tag>
</div>
<div class="time" v-if="item1.desc">{{ item1.desc }}</div>
<div class="time" v-if="item1.time">{{ item1.time }}</div>
</div>
</div>
<div class="actions">
<div
class="a-item"
:class="{ 'border': i !== actions.length }"
v-for="(action, i) in actions"
:key="i"
@click="clickAction(action, i)"
>
<div class="a-icon" v-if="action.icon">
<component :is="`el-icon-${toLine(action.icon)}`"></component>
</div>
<div class="a-text">{{ action.text }}</div>
</div>
</div>
</el-scrollbar>
</el-tab-pane>
</el-tabs>
</div>
</template>
<script lang='ts' setup>
import { PropType } from 'vue'
import { ListOptions, ActionOptions, ListItem } from './types'
import { toLine } from '../../../utils'
let props = defineProps({
// 列表的内容
list: {
type: Array as PropType<ListOptions[]>,
required: true
},
// 操作的内容
actions: {
type: Array as PropType<ActionOptions[]>,
default: () => []
}
})
let emits = defineEmits(['clickItem', 'clickAction'])
let clickItem = (item: ListItem, index: number) => {
emits('clickItem', { item, index })
}
let clickAction = (item: ActionOptions, index: number) => {
emits('clickAction', { item, index })
}
</script>
<style lang='scss' scoped>
.container {
display: flex;
align-items: center;
padding: 12px 20px;
cursor: pointer;
&:hover {
background: #e6f6ff;
}
.avatar {
flex: 1;
}
.content {
flex: 3;
.title {
display: flex;
align-items: center;
justify-content: space-between;
}
.time {
font-size: 12px;
color: #999;
margin-top: 4px;
}
}
}
.actions {
height: 50px;
display: flex;
align-items: center;
border-top: 1px solid #eee;
.a-item {
height: 50px;
flex: 1;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
.a-icon {
margin-right: 4px;
position: relative;
top: 2px;
}
}
}
.border {
border-right: 1px solid #eee;
}
</style>
类型定义: 可选属性和enum的使用
// 列表的每一项
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
}
Vue3 使用具名插槽slot
<template #default>
<slot></slot>
</template>
<template #reference>
<el-badge style="cursor: pointer;" :value="value" :max="max" :is-dot="isDot">
<component :is="`el-icon-${toLine(icon)}`"></component>
</el-badge>
</template>
TS 定义props类型, TS的类型别名的使用,将Array类型断言为PropType类型,泛型类型为具体的值
list: {
type: Array as PropType<ListOptions[]>,
required: true
},
3-6通知菜单-封装一个列表组件(下)
3-7通知菜单-完善list组件并融合进通知菜单
作用域插槽的使用
App.vue代码:
<template>
<div>
<show-names :names="names">
<template v-slot:default="slotProps">
<span>{{slotProps.item}}-{{slotProps.index}}</span>
</template>
</show-names>
</div>
</template>
<script>
import ShowNames from './ShowNames.vue';
export default {
components: {
ShowNames,
},
data() {
return {
names: ["why", "kobe", "james", "curry"]
}
}
}
</script>
ShowNames.vue代码:
<template>
<div>
<template v-for="(item, index) in names" :key="item">
<!-- 插槽prop -->
<slot :item="item" :index="index"></slot>
</template>
</div>
</template>
<script>
export default {
props: {
names: {
type: Array,
default: () => []
}
}
}
</script>