xiaoxian521
3 years ago
8 changed files with 458 additions and 7 deletions
-
9src/layout/components/navbar.vue
-
146src/layout/components/notice/data.ts
-
80src/layout/components/notice/index.vue
-
168src/layout/components/notice/noticeItem.vue
-
23src/layout/components/notice/noticeList.vue
-
3src/layout/components/sidebar/horizontal.vue
-
27src/plugins/element-plus/index.ts
-
9src/style/sidebar.scss
@ -0,0 +1,146 @@ |
|||
export interface ListItem { |
|||
avatar: string; |
|||
title: string; |
|||
datetime: string; |
|||
type: string; |
|||
description: string; |
|||
status?: "" | "success" | "warning" | "info" | "danger"; |
|||
extra?: string; |
|||
} |
|||
|
|||
export interface TabItem { |
|||
key: string; |
|||
name: string; |
|||
list: ListItem[]; |
|||
} |
|||
|
|||
export const noticesData: TabItem[] = [ |
|||
{ |
|||
key: "1", |
|||
name: "通知", |
|||
list: [ |
|||
{ |
|||
avatar: |
|||
"https://gw.alipayobjects.com/zos/rmsportal/ThXAXghbEsBCCSDihZxY.png", |
|||
title: "你收到了 12 份新周报", |
|||
datetime: "一年前", |
|||
description: "", |
|||
type: "1" |
|||
}, |
|||
{ |
|||
avatar: |
|||
"https://gw.alipayobjects.com/zos/rmsportal/OKJXDXrmkNshAMvwtvhu.png", |
|||
title: "你推荐的 前端高手 已通过第三轮面试", |
|||
datetime: "一年前", |
|||
description: "", |
|||
type: "1" |
|||
}, |
|||
{ |
|||
avatar: |
|||
"https://gw.alipayobjects.com/zos/rmsportal/kISTdvpyTAhtGxpovNWd.png", |
|||
title: "这种模板可以区分多种通知类型", |
|||
datetime: "一年前", |
|||
description: "", |
|||
type: "1" |
|||
}, |
|||
{ |
|||
avatar: |
|||
"https://gw.alipayobjects.com/zos/rmsportal/GvqBnKhFgObvnSGkDsje.png", |
|||
title: |
|||
"展示标题内容超过一行后的处理方式,如果内容超过1行将自动截断并支持tooltip显示完整标题。", |
|||
datetime: "一年前", |
|||
description: "", |
|||
type: "1" |
|||
}, |
|||
{ |
|||
avatar: |
|||
"https://gw.alipayobjects.com/zos/rmsportal/GvqBnKhFgObvnSGkDsje.png", |
|||
title: "左侧图标用于区分不同的类型", |
|||
datetime: "一年前", |
|||
description: "", |
|||
type: "1" |
|||
}, |
|||
{ |
|||
avatar: |
|||
"https://gw.alipayobjects.com/zos/rmsportal/GvqBnKhFgObvnSGkDsje.png", |
|||
title: "左侧图标用于区分不同的类型", |
|||
datetime: "一年前", |
|||
description: "", |
|||
type: "1" |
|||
} |
|||
] |
|||
}, |
|||
{ |
|||
key: "2", |
|||
name: "消息", |
|||
list: [ |
|||
{ |
|||
avatar: |
|||
"https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg", |
|||
title: "李白 评论了你", |
|||
description: "长风破浪会有时,直挂云帆济沧海", |
|||
datetime: "一年前", |
|||
type: "2" |
|||
}, |
|||
{ |
|||
avatar: |
|||
"https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg", |
|||
title: "李白 回复了你", |
|||
description: "行路难,行路难,多歧路,今安在。", |
|||
datetime: "一年前", |
|||
type: "2" |
|||
}, |
|||
{ |
|||
avatar: |
|||
"https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg", |
|||
title: "标题", |
|||
description: |
|||
"请将鼠标移动到此处,以便测试超长的消息在此处将如何处理。本例中设置的描述最大行数为2,超过2行的描述内容将被省略并且可以通过tooltip查看完整内容", |
|||
datetime: "一年前", |
|||
type: "2" |
|||
} |
|||
] |
|||
}, |
|||
{ |
|||
key: "3", |
|||
name: "代办", |
|||
list: [ |
|||
{ |
|||
avatar: "", |
|||
title: "任务名称", |
|||
description: "任务需要在 2021-11-16 20:00 前启动", |
|||
datetime: "", |
|||
extra: "未开始", |
|||
status: "info", |
|||
type: "3" |
|||
}, |
|||
{ |
|||
avatar: "", |
|||
title: "第三方紧急代码变更", |
|||
description: |
|||
"一拳提交于 2021-11-16,需在 2021-11-18 前完成代码变更任务", |
|||
datetime: "", |
|||
extra: "马上到期", |
|||
status: "danger", |
|||
type: "3" |
|||
}, |
|||
{ |
|||
avatar: "", |
|||
title: "信息安全考试", |
|||
description: "指派小仙于 2021-12-12 前完成更新并发布", |
|||
datetime: "", |
|||
extra: "已耗时 8 天", |
|||
status: "warning", |
|||
type: "3" |
|||
}, |
|||
{ |
|||
avatar: "", |
|||
title: "vue-pure-admin 版本发布", |
|||
description: "vue-pure-admin 版本发布", |
|||
datetime: "", |
|||
extra: "进行中", |
|||
type: "3" |
|||
} |
|||
] |
|||
} |
|||
]; |
@ -0,0 +1,80 @@ |
|||
<script setup lang="ts"> |
|||
import { ref } from "vue"; |
|||
import NoticeList from "./noticeList.vue"; |
|||
import { noticesData } from "./data"; |
|||
|
|||
const activeName = ref(noticesData[0].name); |
|||
const notices = ref(noticesData); |
|||
|
|||
let noticesNum = ref(0); |
|||
notices.value.forEach(notice => { |
|||
noticesNum.value += notice.list.length; |
|||
}); |
|||
</script> |
|||
|
|||
<template> |
|||
<el-dropdown trigger="click" placement="bottom-end"> |
|||
<span class="dropdown-badge"> |
|||
<el-badge :value="noticesNum" :max="99"> |
|||
<el-icon class="header-notice-icon"><bell /></el-icon> |
|||
</el-badge> |
|||
</span> |
|||
<template #dropdown> |
|||
<el-dropdown-menu> |
|||
<el-tabs v-model="activeName" class="dropdown-tabs"> |
|||
<template v-for="item in notices" :key="item.key"> |
|||
<el-tab-pane |
|||
:label="`${item.name}(${item.list.length})`" |
|||
:name="item.name" |
|||
> |
|||
<el-scrollbar max-height="330px"> |
|||
<div class="noticeList-container"> |
|||
<NoticeList :list="item.list" /> |
|||
</div> |
|||
</el-scrollbar> |
|||
</el-tab-pane> |
|||
</template> |
|||
</el-tabs> |
|||
</el-dropdown-menu> |
|||
</template> |
|||
</el-dropdown> |
|||
</template> |
|||
|
|||
<style lang="scss" scoped> |
|||
.dropdown-badge { |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
height: 48px; |
|||
width: 60px; |
|||
cursor: pointer; |
|||
|
|||
.header-notice-icon { |
|||
font-size: 18px; |
|||
} |
|||
} |
|||
|
|||
.dropdown-tabs { |
|||
width: 336px; |
|||
background-color: #fff; |
|||
box-shadow: 0 2px 8px rgb(0 0 0 / 15%); |
|||
border-radius: 4px; |
|||
|
|||
:deep(.el-tabs__header) { |
|||
margin: 0; |
|||
} |
|||
|
|||
:deep(.el-tabs__nav-scroll) { |
|||
display: flex; |
|||
justify-content: center; |
|||
} |
|||
|
|||
:deep(.el-tabs__nav-wrap)::after { |
|||
height: 1px; |
|||
} |
|||
|
|||
:deep(.noticeList-container) { |
|||
padding: 15px 24px 0 24px; |
|||
} |
|||
} |
|||
</style> |
@ -0,0 +1,168 @@ |
|||
<script setup lang="ts"> |
|||
import { ListItem } from "./data"; |
|||
import { ref, PropType, nextTick } from "vue"; |
|||
|
|||
const props = defineProps({ |
|||
noticeItem: { |
|||
type: Object as PropType<ListItem>, |
|||
default: () => {} |
|||
} |
|||
}); |
|||
|
|||
const titleRef = ref(null); |
|||
const descriptionRef = ref(null); |
|||
const titleTooltip = ref(false); |
|||
const descriptionTooltip = ref(false); |
|||
|
|||
function hoverTitle() { |
|||
nextTick(() => { |
|||
titleRef.value?.scrollWidth > titleRef.value?.clientWidth |
|||
? (titleTooltip.value = true) |
|||
: (titleTooltip.value = false); |
|||
}); |
|||
} |
|||
|
|||
function hoverDescription(event, description) { |
|||
// currentWidth 为文本在页面中所占的宽度,创建标签,加入到页面,获取currentWidth ,最后在移除 |
|||
let tempTag = document.createElement("span"); |
|||
tempTag.innerText = description; |
|||
tempTag.className = "getDescriptionWidth"; |
|||
document.querySelector("body").appendChild(tempTag); |
|||
let currentWidth = ( |
|||
document.querySelector(".getDescriptionWidth") as HTMLSpanElement |
|||
).offsetWidth; |
|||
document.querySelector(".getDescriptionWidth").remove(); |
|||
|
|||
// cellWidth为容器的宽度 |
|||
const cellWidth = event.target.offsetWidth; |
|||
|
|||
// 当文本宽度大于容器宽度两倍时,代表文本显示超过两行 |
|||
currentWidth > 2 * cellWidth |
|||
? (descriptionTooltip.value = true) |
|||
: (descriptionTooltip.value = false); |
|||
} |
|||
</script> |
|||
|
|||
<template> |
|||
<div class="notice-container"> |
|||
<el-avatar |
|||
v-if="props.noticeItem.avatar" |
|||
:size="30" |
|||
:src="props.noticeItem.avatar" |
|||
class="notice-container-avatar" |
|||
></el-avatar> |
|||
<div class="notice-container-text"> |
|||
<div class="notice-text-title"> |
|||
<el-tooltip |
|||
popper-class="notice-title-popper" |
|||
:disabled="!titleTooltip" |
|||
:content="props.noticeItem.title" |
|||
placement="top-start" |
|||
> |
|||
<div |
|||
ref="titleRef" |
|||
class="notice-title-content" |
|||
@mouseover="hoverTitle" |
|||
> |
|||
{{ props.noticeItem.title }} |
|||
</div> |
|||
</el-tooltip> |
|||
<el-tag |
|||
v-if="props.noticeItem?.extra" |
|||
:type="props.noticeItem?.status" |
|||
size="small" |
|||
class="notice-title-extra" |
|||
>{{ props.noticeItem?.extra }} |
|||
</el-tag> |
|||
</div> |
|||
|
|||
<el-tooltip |
|||
popper-class="notice-title-popper" |
|||
:disabled="!descriptionTooltip" |
|||
:content="props.noticeItem.description" |
|||
placement="top-start" |
|||
> |
|||
<div |
|||
ref="descriptionRef" |
|||
class="notice-text-description" |
|||
@mouseover="hoverDescription($event, props.noticeItem.description)" |
|||
> |
|||
{{ props.noticeItem.description }} |
|||
</div> |
|||
</el-tooltip> |
|||
<div class="notice-text-datetime"> |
|||
{{ props.noticeItem.datetime }} |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</template> |
|||
|
|||
<style> |
|||
.notice-title-popper { |
|||
max-width: 238px; |
|||
} |
|||
</style> |
|||
<style scoped lang="scss"> |
|||
.notice-container { |
|||
display: flex; |
|||
align-items: flex-start; |
|||
justify-content: space-between; |
|||
padding: 12px 0; |
|||
border-bottom: 1px solid #f0f0f0; |
|||
|
|||
.notice-container-avatar { |
|||
margin-right: 16px; |
|||
background: #fff; |
|||
} |
|||
|
|||
.notice-container-text { |
|||
display: flex; |
|||
flex-direction: column; |
|||
justify-content: space-between; |
|||
flex: 1; |
|||
|
|||
.notice-text-title { |
|||
display: flex; |
|||
margin-bottom: 8px; |
|||
font-weight: 400; |
|||
font-size: 14px; |
|||
line-height: 1.5715; |
|||
color: rgba(0, 0, 0, 0.85); |
|||
cursor: pointer; |
|||
|
|||
.notice-title-content { |
|||
flex: 1; |
|||
width: 200px; |
|||
overflow: hidden; |
|||
white-space: nowrap; |
|||
text-overflow: ellipsis; |
|||
} |
|||
|
|||
.notice-title-extra { |
|||
float: right; |
|||
margin-top: -1.5px; |
|||
font-weight: 400; |
|||
} |
|||
} |
|||
|
|||
.notice-text-description, |
|||
.notice-text-datetime { |
|||
font-size: 12px; |
|||
line-height: 1.5715; |
|||
color: rgba(0, 0, 0, 0.45); |
|||
} |
|||
|
|||
.notice-text-description { |
|||
display: -webkit-box; |
|||
text-overflow: ellipsis; |
|||
overflow: hidden; |
|||
-webkit-line-clamp: 2; |
|||
-webkit-box-orient: vertical; |
|||
} |
|||
|
|||
.notice-text-datetime { |
|||
margin-top: 4px; |
|||
} |
|||
} |
|||
} |
|||
</style> |
@ -0,0 +1,23 @@ |
|||
<script setup lang="ts"> |
|||
import { PropType } from "vue"; |
|||
import NoticeItem from "./noticeItem.vue"; |
|||
import { ListItem } from "./data"; |
|||
|
|||
const props = defineProps({ |
|||
list: { |
|||
type: Array as PropType<Array<ListItem>>, |
|||
default: () => [] |
|||
} |
|||
}); |
|||
</script> |
|||
|
|||
<template> |
|||
<div v-if="props.list.length"> |
|||
<NoticeItem |
|||
v-for="(item, index) in props.list" |
|||
:noticeItem="item" |
|||
:key="index" |
|||
></NoticeItem> |
|||
</div> |
|||
<el-empty v-else description="暂无数据"></el-empty> |
|||
</template> |
Write
Preview
Loading…
Cancel
Save
Reference in new issue