437 lines
8.9 KiB
Plaintext
437 lines
8.9 KiB
Plaintext
<template>
|
|
<view class="message-list">
|
|
<!-- 加载状态 -->
|
|
<view v-if="loading && messages.length === 0" class="loading-container">
|
|
<text class="loading-text">加载中...</text>
|
|
</view>
|
|
|
|
<!-- 空状态 -->
|
|
<view v-else-if="!loading && messages.length === 0" class="empty-container">
|
|
<text class="empty-icon"></text>
|
|
<text class="empty-text">暂无消息</text>
|
|
<text class="empty-desc">您还没有收到任何消息</text>
|
|
</view>
|
|
|
|
<!-- 消息列表 -->
|
|
<scroll-view
|
|
v-else
|
|
class="message-scroll"
|
|
scroll-y="true"
|
|
@scrolltolower="handleLoadMore"
|
|
:refresher-enabled="true"
|
|
@refresherrefresh="handleRefresh"
|
|
:refresher-triggered="refreshing"
|
|
>
|
|
<!-- 批量选择模式头部 -->
|
|
<view v-if="selectionMode" class="selection-header">
|
|
<view class="selection-info">
|
|
<text class="selection-count">已选择 {{ selectedIds.length }} 条消息</text>
|
|
</view>
|
|
<view class="selection-actions">
|
|
<button class="action-btn" @click="handleBatchRead">
|
|
<text class="action-text">标为已读</text>
|
|
</button>
|
|
<button class="action-btn" @click="handleBatchStar">
|
|
<text class="action-text">收藏</text>
|
|
</button>
|
|
<button class="action-btn danger" @click="handleBatchDelete">
|
|
<text class="action-text">删除</text>
|
|
</button>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- 消息项列表 -->
|
|
<view class="message-items">
|
|
<view
|
|
v-for="message in messages"
|
|
:key="message.id"
|
|
class="message-item-wrapper"
|
|
:class="{ 'selected': isSelected(message.id) }"
|
|
>
|
|
<!-- 选择框 -->
|
|
<checkbox
|
|
v-if="selectionMode"
|
|
class="selection-checkbox"
|
|
:checked="isSelected(message.id)"
|
|
@change="handleToggleSelection(message.id)"
|
|
/>
|
|
|
|
<!-- 消息内容 -->
|
|
<MessageItem
|
|
:item="message"
|
|
:show-actions="!selectionMode"
|
|
@click="handleMessageClick"
|
|
@action="handleMessageAction"
|
|
/>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- 加载更多 -->
|
|
<view v-if="hasMore" class="load-more-container">
|
|
<view v-if="loadingMore" class="loading-more">
|
|
<text class="loading-text">加载更多...</text>
|
|
</view>
|
|
<button v-else class="load-more-btn" @click="handleLoadMore">
|
|
<text class="load-more-text">加载更多</text>
|
|
</button>
|
|
</view>
|
|
|
|
<!-- 已到底部 -->
|
|
<view v-else-if="messages.length > 0" class="end-container">
|
|
<text class="end-text">已显示全部消息</text>
|
|
</view>
|
|
</scroll-view>
|
|
|
|
<!-- 浮动操作按钮 -->
|
|
<view class="fab-container">
|
|
<view v-if="!selectionMode" class="fab-group">
|
|
<button class="fab-btn" @click="handleToggleSelection">
|
|
<text class="fab-icon">☑️</text>
|
|
</button>
|
|
<button class="fab-btn primary" @click="handleCompose">
|
|
<text class="fab-icon">✏️</text>
|
|
</button>
|
|
</view>
|
|
<view v-else class="fab-group">
|
|
<button class="fab-btn" @click="handleCancelSelection">
|
|
<text class="fab-icon">❌</text>
|
|
</button>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</template>
|
|
|
|
<script lang="uts">
|
|
import MessageItem from './MessageItem.uvue'
|
|
import type { Message } from '../../utils/msgTypes.uts'
|
|
|
|
export default {
|
|
name: 'MessageList',
|
|
components: {
|
|
MessageItem
|
|
},
|
|
props: {
|
|
messages: {
|
|
type: Array as PropType<Array<Message>>,
|
|
default: (): Array<Message> => []
|
|
},
|
|
loading: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
loadingMore: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
refreshing: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
hasMore: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
selectionMode: {
|
|
type: Boolean,
|
|
default: false
|
|
}
|
|
},
|
|
emits: ['click', 'action', 'refresh', 'load-more', 'compose', 'batch-action', 'toggle-selection'],
|
|
data() {
|
|
return {
|
|
selectedIds: [] as Array<string>
|
|
}
|
|
},
|
|
watch: {
|
|
selectionMode(newVal: boolean) {
|
|
if (!newVal) {
|
|
this.selectedIds = []
|
|
}
|
|
}
|
|
},
|
|
methods: {
|
|
handleMessageClick(message: Message) {
|
|
if (this.selectionMode) {
|
|
this.handleToggleSelection(message.id)
|
|
} else {
|
|
this.$emit('click', message)
|
|
}
|
|
},
|
|
|
|
handleMessageAction(event: any) {
|
|
this.$emit('action', event)
|
|
},
|
|
|
|
handleRefresh() {
|
|
this.$emit('refresh')
|
|
},
|
|
|
|
handleLoadMore() {
|
|
if (!this.loadingMore && this.hasMore) {
|
|
this.$emit('load-more')
|
|
}
|
|
},
|
|
|
|
handleCompose() {
|
|
this.$emit('compose')
|
|
},
|
|
|
|
handleToggleSelection() {
|
|
this.$emit('toggle-selection', !this.selectionMode)
|
|
},
|
|
|
|
handleCancelSelection() {
|
|
this.selectedIds = []
|
|
this.$emit('toggle-selection', false)
|
|
},
|
|
|
|
isSelected(messageId: string): boolean {
|
|
return this.selectedIds.indexOf(messageId) !== -1
|
|
},
|
|
|
|
handleToggleSelection(messageId: string) {
|
|
const index = this.selectedIds.indexOf(messageId)
|
|
if (index !== -1) {
|
|
this.selectedIds.splice(index, 1)
|
|
} else {
|
|
this.selectedIds.push(messageId)
|
|
}
|
|
},
|
|
|
|
handleBatchRead() {
|
|
if (this.selectedIds.length === 0) {
|
|
return
|
|
}
|
|
|
|
this.$emit('batch-action', {
|
|
action: 'read',
|
|
messageIds: [...this.selectedIds]
|
|
})
|
|
},
|
|
|
|
handleBatchStar() {
|
|
if (this.selectedIds.length === 0) {
|
|
return
|
|
}
|
|
|
|
this.$emit('batch-action', {
|
|
action: 'star',
|
|
messageIds: [...this.selectedIds]
|
|
})
|
|
},
|
|
|
|
handleBatchDelete() {
|
|
if (this.selectedIds.length === 0) {
|
|
return
|
|
}
|
|
|
|
uni.showModal({
|
|
title: '确认删除',
|
|
content: `确定要删除选中的 ${this.selectedIds.length} 条消息吗?`,
|
|
success: (res) => {
|
|
if (res.confirm) {
|
|
this.$emit('batch-action', {
|
|
action: 'delete',
|
|
messageIds: [...this.selectedIds]
|
|
})
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style scoped>
|
|
.message-list {
|
|
height: 100%;
|
|
position: relative;
|
|
}
|
|
|
|
.loading-container,
|
|
.empty-container {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
height: 300px;
|
|
}
|
|
|
|
.loading-text {
|
|
font-size: 14px;
|
|
color: #666666;
|
|
}
|
|
|
|
.empty-icon {
|
|
font-size: 48px;
|
|
margin-bottom: 16px;
|
|
}
|
|
|
|
.empty-text {
|
|
font-size: 18px;
|
|
font-weight: 400;
|
|
color: #333333;
|
|
margin-bottom: 8px;
|
|
}
|
|
|
|
.empty-desc {
|
|
font-size: 14px;
|
|
color: #666666;
|
|
}
|
|
|
|
.message-scroll {
|
|
height: 100%;
|
|
}
|
|
|
|
.selection-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
padding: 12px 16px;
|
|
background-color: #f5f5f5;
|
|
border-bottom: 1px solid #e0e0e0;
|
|
}
|
|
|
|
.selection-count {
|
|
font-size: 14px;
|
|
color: #333333;
|
|
}
|
|
|
|
.selection-actions {
|
|
display: flex;
|
|
gap: 8px;
|
|
}
|
|
|
|
.action-btn {
|
|
padding: 6px 12px;
|
|
border: 1px solid #e0e0e0;
|
|
border-radius: 4px;
|
|
background-color: #ffffff;
|
|
}
|
|
|
|
.action-btn.danger {
|
|
border-color: #f44336;
|
|
}
|
|
|
|
.action-text {
|
|
font-size: 12px;
|
|
color: #333333;
|
|
}
|
|
|
|
.action-btn.danger .action-text {
|
|
color: #f44336;
|
|
}
|
|
|
|
.message-items {
|
|
padding: 16px;
|
|
}
|
|
|
|
.message-item-wrapper {
|
|
display: flex;
|
|
align-items: flex-start;
|
|
gap: 12px;
|
|
margin-bottom: 12px;
|
|
border-radius: 8px;
|
|
transition: background-color 0.2s;
|
|
}
|
|
|
|
.message-item-wrapper.selected {
|
|
background-color: #e3f2fd;
|
|
}
|
|
|
|
.selection-checkbox {
|
|
margin-top: 16px;
|
|
}
|
|
|
|
.load-more-container,
|
|
.end-container {
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
padding: 16px;
|
|
}
|
|
|
|
.loading-more {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
}
|
|
|
|
.load-more-btn {
|
|
padding: 8px 16px;
|
|
border: 1px solid #e0e0e0;
|
|
border-radius: 4px;
|
|
background-color: #ffffff;
|
|
}
|
|
|
|
.load-more-text {
|
|
font-size: 14px;
|
|
color: #666666;
|
|
}
|
|
|
|
.end-text {
|
|
font-size: 12px;
|
|
color: #999999;
|
|
}
|
|
|
|
.fab-container {
|
|
position: fixed;
|
|
bottom: 24px;
|
|
right: 24px;
|
|
z-index: 100;
|
|
}
|
|
|
|
.fab-group {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 12px;
|
|
}
|
|
|
|
.fab-btn {
|
|
width: 56px;
|
|
height: 56px;
|
|
border-radius: 50%;
|
|
background-color: #ffffff;
|
|
border: 1px solid #e0e0e0;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
}
|
|
|
|
.fab-btn.primary {
|
|
background-color: #2196f3;
|
|
border-color: #2196f3;
|
|
}
|
|
|
|
.fab-icon {
|
|
font-size: 20px;
|
|
}
|
|
|
|
.fab-btn.primary .fab-icon {
|
|
color: #ffffff;
|
|
}
|
|
|
|
.fab-btn:active {
|
|
transform: scale(0.95);
|
|
}
|
|
|
|
/* 响应式布局 */
|
|
@media (max-width: 480px) {
|
|
.selection-header {
|
|
flex-direction: column;
|
|
gap: 8px;
|
|
align-items: stretch;
|
|
}
|
|
|
|
.selection-actions {
|
|
justify-content: space-between;
|
|
}
|
|
|
|
.fab-container {
|
|
bottom: 16px;
|
|
right: 16px;
|
|
}
|
|
}
|
|
</style>
|