256 lines
6.1 KiB
Vue
256 lines
6.1 KiB
Vue
<script setup lang="ts">
|
||
import { computed, onMounted } from 'vue'
|
||
import { storeToRefs } from 'pinia'
|
||
import { useRouter } from 'vue-router'
|
||
import { AppButton, FilterPanel, PaginationBar } from '@/components'
|
||
// 页面容器:只做组件引入、props 分发、事件中转,数据只经 store
|
||
import { useMessageStore } from '@/stores/modules/message'
|
||
import type { MessageType } from '@/types/business/message'
|
||
import MessageTabs from './cpns/MessageTabs.vue'
|
||
import MessageList from './cpns/MessageList.vue'
|
||
|
||
const router = useRouter()
|
||
const messageStore = useMessageStore()
|
||
const { query, list, total, unreadCount, tabCounts } = storeToRefs(messageStore)
|
||
|
||
const keyword = computed({
|
||
get: () => query.value.keyword,
|
||
set: (value: string) => messageStore.setQuery({ ...query.value, keyword: value }),
|
||
})
|
||
|
||
function onSearch(): void {
|
||
void messageStore.search()
|
||
}
|
||
|
||
function onReset(): void {
|
||
void messageStore.resetQuery()
|
||
}
|
||
|
||
function onPageChange(page: number): void {
|
||
void messageStore.changePage(page)
|
||
}
|
||
|
||
function onTabChange(type: MessageType | 'all'): void {
|
||
messageStore.setQuery({ ...query.value, type })
|
||
void messageStore.search()
|
||
}
|
||
|
||
function onView(id: string): void {
|
||
void router.push(`/message/${id}`)
|
||
}
|
||
|
||
function onMarkAllRead(): void {
|
||
void messageStore.markAllRead()
|
||
}
|
||
|
||
onMounted(() => {
|
||
void Promise.all([
|
||
messageStore.fetchList(),
|
||
messageStore.fetchUnreadCount(),
|
||
messageStore.fetchTabCounts(),
|
||
])
|
||
})
|
||
</script>
|
||
|
||
<template>
|
||
<div class="message">
|
||
<header class="message__head">
|
||
<h1 class="message__title">
|
||
消息中心
|
||
</h1>
|
||
</header>
|
||
<p class="message__subtitle text-secondary">
|
||
统一接收项目进度、售后工单、账单待办等站内消息,重要业务动态实时提醒
|
||
</p>
|
||
|
||
<div class="message__body">
|
||
<MessageTabs
|
||
class="message__tabs"
|
||
:active="query.type"
|
||
:counts="tabCounts"
|
||
@tab-change="onTabChange"
|
||
/>
|
||
|
||
<div class="message__main">
|
||
<div class="message__toolbar">
|
||
<FilterPanel
|
||
class="message__filter"
|
||
v-model:keyword="keyword"
|
||
keyword-placeholder="搜索消息标题、内容"
|
||
@search="onSearch"
|
||
@reset="onReset"
|
||
/>
|
||
<AppButton
|
||
class="message__mark-read"
|
||
type="default"
|
||
:disabled="unreadCount === 0"
|
||
@click="onMarkAllRead"
|
||
>
|
||
全部已读
|
||
</AppButton>
|
||
</div>
|
||
|
||
<MessageList :messages="list" @view="onView" />
|
||
|
||
<PaginationBar
|
||
class="message__pagination"
|
||
:page="query.page"
|
||
:page-size="query.pageSize"
|
||
:total="total"
|
||
@page-change="onPageChange"
|
||
/>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<style lang="less" scoped>
|
||
.message {
|
||
.flex(column, flex-start, stretch);
|
||
gap: @spacing-base;
|
||
max-width: @container-width;
|
||
margin: 0 auto;
|
||
padding: @spacing-xl @spacing-base @spacing-xxl;
|
||
|
||
&__head {
|
||
.flex(row, flex-start);
|
||
margin-bottom: -10px; // 标题→副标题净距 8 × 0.77(叠加父级 gap 16)
|
||
}
|
||
|
||
// 设计 43:标题 22 × 0.77,700,#1F2937
|
||
&__title {
|
||
font-size: 17px;
|
||
font-weight: 700;
|
||
color: @heading-color;
|
||
}
|
||
|
||
// 设计 43:副标题整行居中,13 → 可读下限 11,#6B7280(text-secondary)
|
||
&__subtitle {
|
||
text-align: center;
|
||
font-size: 11px;
|
||
}
|
||
|
||
&__body {
|
||
.flex(row, flex-start);
|
||
gap: 26px; // 左右栏间距 34 × 0.77
|
||
align-items: flex-start;
|
||
}
|
||
|
||
&__tabs {
|
||
flex-shrink: 0;
|
||
width: 156px; // 左分类卡宽 202 × 0.77
|
||
}
|
||
|
||
&__main {
|
||
.flex(column, flex-start, stretch);
|
||
gap: 15px; // 卡片纵向间距 19 × 0.77
|
||
flex: 1;
|
||
min-width: 0;
|
||
}
|
||
|
||
// 搜索行白卡:设计 43 x243..1897 / y370..435,输入与「全部已读」同卡
|
||
&__toolbar {
|
||
.flex(row, flex-start);
|
||
align-items: center;
|
||
gap: 11px;
|
||
padding: 10px 17px; // 卡内边距 22/16 × 0.77
|
||
border-radius: @radius-base;
|
||
background: @white;
|
||
}
|
||
|
||
& .message__filter {
|
||
flex: 1;
|
||
min-width: 0;
|
||
padding: 0;
|
||
background: transparent;
|
||
}
|
||
|
||
// 「全部已读」:设计 43 白底浅描边 12 → 11 / 500 #4B5563
|
||
& .message__mark-read {
|
||
flex-shrink: 0;
|
||
height: 26px; // 34 × 0.77
|
||
padding: 0 16px;
|
||
border: 1px solid @border-color;
|
||
border-radius: 6px;
|
||
background: @white;
|
||
color: @text-color;
|
||
font-size: 11px;
|
||
font-weight: 500;
|
||
|
||
&:hover:not(:disabled) {
|
||
border-color: @primary-color;
|
||
background: @white;
|
||
color: @primary-color;
|
||
}
|
||
}
|
||
|
||
& .message__pagination {
|
||
justify-content: flex-end;
|
||
align-self: stretch;
|
||
padding: 0;
|
||
}
|
||
|
||
// FilterPanel 内部按设计 43 覆写(灰底输入框 / 灰重置 / 蓝搜索,重置在前)
|
||
.message__filter {
|
||
:deep(.filter-panel__bar) {
|
||
gap: 6px;
|
||
}
|
||
|
||
:deep(.filter-panel__body) {
|
||
margin-top: 0;
|
||
}
|
||
|
||
:deep(.filter-panel__input) {
|
||
order: 0;
|
||
flex: 0 1 349px; // 输入框宽 453 × 0.77
|
||
max-width: 349px;
|
||
height: 29px; // 38 × 0.77
|
||
margin-right: 2px; // 叠加 bar gap 6 ≈ 输入→按钮 8 × 0.77
|
||
padding: 0 10px;
|
||
border: none;
|
||
border-radius: 6px;
|
||
background: @finance-field-bg; // 设计 43 实取 @input-bg
|
||
color: @text-color;
|
||
font-size: 11px; // 设计 13 → 可读下限 11
|
||
|
||
&::placeholder {
|
||
color: @contract-placeholder; // 设计 43 实取 #B3B9C4
|
||
}
|
||
|
||
&:focus {
|
||
border: none;
|
||
}
|
||
}
|
||
|
||
:deep(.filter-panel__btn) {
|
||
order: 1;
|
||
min-width: 46px; // 重置 57 × 0.77
|
||
height: 29px;
|
||
padding: 0 12px;
|
||
border: none;
|
||
border-radius: 6px;
|
||
background: @contract-reset-bg; // 设计 43 重置底 #EEF0F3
|
||
color: @text-color;
|
||
font-size: 11px;
|
||
font-weight: 500;
|
||
|
||
&:hover {
|
||
background: @contract-reset-bg;
|
||
color: @text-color;
|
||
}
|
||
}
|
||
|
||
:deep(.filter-panel__btn--primary) {
|
||
order: 2;
|
||
background: @primary-strong; // 设计 43 搜索底 @primary-strong
|
||
color: @white;
|
||
|
||
&:hover {
|
||
background: @primary-strong;
|
||
color: @white;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
</style>
|