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