feat(system): 站内信页面 UI 改版,列表与详情更易读

- 改为 el-card 卡片布局,标题/未读数/操作按钮收进卡片头

- 列表项:未读红点 + 左侧主色边、标题右侧显示时间、元信息加分隔

- 刷新按钮同时刷新列表与未读数;无未读时禁用「全部已读」

- 详情正文改为浅底内容块(限高滚动),元信息加分隔线

- 分页增加总数显示
This commit is contained in:
TermiNexus committed 2026-09-28 18:03:23 +08:00
1 parent 5405a4dade
commit a918c9d488
1 file changed
+145 -69
+145 -69
View File
@@ -218,6 +218,12 @@ function handlePageChange(page: number): void {
loadList()
}
/** 刷新:重新加载列表与未读数 */
function refresh(): void {
loadList()
loadUnread()
}
// === 详情 ===
/** 打开消息详情(同时触发后端标记已读,再刷新列表与未读数) */
@@ -405,10 +411,27 @@ onBeforeUnmount(() => {
</script>
<template>
<div class="message-page" v-loading="loading">
<h1 class="page-title">
<i class="fas fa-envelope icon-primary" />站内信
</h1>
<div class="message-page">
<el-card shadow="never" class="msg-card">
<template #header>
<div class="msg-header">
<i class="fas fa-envelope header-icon" />
<span class="header-title">站内信</span>
<el-tag v-if="unreadCount > 0" type="danger" size="small" effect="dark" round>
{{ unreadCount }} 条未读
</el-tag>
<div class="spacer" />
<el-button type="primary" size="small" @click="openCompose">
<i class="fas fa-paper-plane" /> 发送消息
</el-button>
<el-button size="small" :disabled="unreadCount === 0" @click="markAllRead">
<i class="fas fa-check-double" /> 全部已读
</el-button>
<el-button size="small" :loading="loading" @click="refresh">
<i class="fas fa-sync-alt" /> 刷新
</el-button>
</div>
</template>
<!-- 筛选 Tab -->
<el-tabs v-model="currentFilter" class="msg-tabs" @tab-change="handleTabChange">
@@ -423,22 +446,9 @@ onBeforeUnmount(() => {
</el-tab-pane>
</el-tabs>
<!-- 工具栏 -->
<div class="msg-toolbar">
<el-button type="primary" size="small" @click="openCompose">
<i class="fas fa-paper-plane" /> 发送消息
</el-button>
<el-button size="small" @click="markAllRead">
<i class="fas fa-check-double" /> 全部已读
</el-button>
<el-button size="small" @click="loadList">
<i class="fas fa-sync-alt" /> 刷新
</el-button>
</div>
<!-- 消息列表 -->
<div class="msg-list">
<el-empty v-if="!loading && list.length === 0" description="暂无消息" :image-size="60" />
<div v-loading="loading" class="msg-list">
<el-empty v-if="!loading && list.length === 0" description="暂无消息" :image-size="80" />
<div
v-for="msg in list"
:key="msg.MessageUUID"
@@ -455,12 +465,16 @@ onBeforeUnmount(() => {
</div>
<!-- 主体 -->
<div class="msg-item-body">
<div class="msg-item-title">{{ msg.Title || '(无标题)' }}</div>
<div class="msg-item-title">
<span v-if="msg.IsRead === 0" class="unread-dot" />
<span class="title-text">{{ msg.Title || '(无标题)' }}</span>
<span class="msg-item-time">{{ msg.CreateTime || '' }}</span>
</div>
<div class="msg-item-content">{{ msg.Content || '' }}</div>
<div class="msg-item-meta">
<span><i class="fas fa-user" /> {{ msg.SenderName || '系统' }}</span>
<span class="meta-sep">·</span>
<span>{{ MSG_TYPE_NAMES[msg.MsgType] || '消息' }}</span>
<span>{{ msg.CreateTime || '' }}</span>
</div>
</div>
<!-- 悬停删除 -->
@@ -469,6 +483,7 @@ onBeforeUnmount(() => {
circle
size="small"
type="danger"
plain
title="删除"
@click.stop="deleteMsg(msg.MessageUUID)"
>
@@ -484,11 +499,12 @@ onBeforeUnmount(() => {
:current-page="currentPage"
:page-size="PAGE_SIZE"
:total="total"
layout="prev, pager, next"
layout="total, prev, pager, next"
background
@current-change="handlePageChange"
/>
</div>
</el-card>
<!-- 详情弹窗 -->
<el-dialog v-model="detailVisible" width="600px" :show-close="true" @close="closeDetail">
@@ -577,26 +593,47 @@ onBeforeUnmount(() => {
<style scoped lang="scss">
.message-page {
padding: 20px;
max-width: 900px;
max-width: 960px;
margin: 0 auto;
box-sizing: border-box;
}
.page-title {
font-size: 18px;
font-weight: 600;
margin: 0 0 16px;
display: flex;
align-items: center;
gap: 8px;
/* ==================== 卡片外框 ==================== */
.msg-card {
:deep(.el-card__header) {
padding: 14px 20px;
}
.icon-primary {
color: var(--el-color-primary);
:deep(.el-card__body) {
padding: 12px 20px 20px;
}
}
.msg-header {
display: flex;
align-items: center;
gap: 10px;
.header-icon {
color: var(--el-color-primary);
}
.header-title {
font-size: 15px;
font-weight: 600;
color: var(--el-text-color-primary);
}
.spacer {
flex: 1;
}
}
/* ==================== 筛选 Tab ==================== */
.msg-tabs {
margin-bottom: 12px;
:deep(.el-tabs__header) {
margin-bottom: 14px;
}
}
// 未读 tab 角标位置微调
@@ -604,58 +641,54 @@ onBeforeUnmount(() => {
margin-left: 6px;
}
.msg-toolbar {
display: flex;
gap: 12px;
margin-bottom: 16px;
}
/* ==================== 消息列表 ==================== */
.msg-list {
display: flex;
flex-direction: column;
gap: 8px;
min-height: 260px;
}
.msg-item {
background: var(--el-bg-color, #fff);
border-radius: 8px;
border: 1px solid var(--el-border-color, #e2e8f0);
padding: 16px 20px;
cursor: pointer;
transition: all 0.2s;
display: flex;
gap: 14px;
align-items: flex-start;
gap: 14px;
position: relative;
padding: 14px 16px;
background: var(--el-bg-color, #fff);
border: 1px solid var(--el-border-color-lighter);
border-radius: 10px;
cursor: pointer;
transition: border-color 0.2s, box-shadow 0.2s;
&:hover {
border-color: var(--el-color-primary);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
border-color: var(--el-color-primary-light-5);
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.06);
.msg-item-actions {
opacity: 1;
}
}
// 未读:左侧主色边
// 未读:左侧主色边 + 标题加粗 + 红点
&.unread {
border-left: 3px solid var(--el-color-primary);
.msg-item-title {
.title-text {
font-weight: 700;
}
}
}
.msg-item-icon {
width: 36px;
height: 36px;
border-radius: 8px;
flex-shrink: 0;
width: 40px;
height: 40px;
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
flex-shrink: 0;
font-size: 15px;
}
.msg-item-body {
@@ -664,34 +697,62 @@ onBeforeUnmount(() => {
}
.msg-item-title {
display: flex;
align-items: center;
gap: 6px;
margin-bottom: 5px;
}
.unread-dot {
flex-shrink: 0;
width: 7px;
height: 7px;
border-radius: 50%;
background: var(--el-color-danger);
}
.title-text {
flex: 1;
min-width: 0;
font-size: 14px;
font-weight: 600;
margin-bottom: 4px;
color: var(--el-text-color-primary);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.msg-item-time {
flex-shrink: 0;
font-size: 12px;
color: var(--el-text-color-placeholder);
}
.msg-item-content {
font-size: 13px;
color: var(--el-text-color-secondary);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
margin-bottom: 4px;
margin-bottom: 6px;
}
.msg-item-meta {
display: flex;
align-items: center;
gap: 8px;
font-size: 12px;
color: var(--el-text-color-placeholder);
display: flex;
gap: 12px;
.meta-sep {
color: var(--el-border-color);
}
}
.msg-item-actions {
position: absolute;
top: 50%;
right: 12px;
right: 14px;
transform: translateY(-50%);
opacity: 0;
transition: opacity 0.2s;
@@ -699,37 +760,52 @@ onBeforeUnmount(() => {
.msg-pagination {
display: flex;
justify-content: center;
margin-top: 20px;
justify-content: flex-end;
margin-top: 16px;
}
// 详情弹窗
.detail-title {
font-size: 18px;
font-weight: 600;
display: flex;
align-items: center;
gap: 8px;
font-size: 16px;
font-weight: 600;
min-width: 0;
i {
color: var(--el-color-primary);
}
span {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
.detail-meta {
display: flex;
flex-wrap: wrap;
gap: 16px;
font-size: 13px;
color: var(--el-text-color-secondary);
margin-bottom: 16px;
display: flex;
gap: 16px;
flex-wrap: wrap;
padding-bottom: 12px;
margin-bottom: 14px;
border-bottom: 1px solid var(--el-border-color-lighter);
}
.detail-content {
font-size: 14px;
line-height: 1.7;
line-height: 1.75;
color: var(--el-text-color-regular);
white-space: pre-wrap;
word-break: break-word;
background: var(--el-fill-color-lighter);
border-radius: 8px;
padding: 12px 14px;
max-height: 46vh;
overflow-y: auto;
}
.detail-footer {