354 lines
7.8 KiB
Plaintext
354 lines
7.8 KiB
Plaintext
<template>
|
|
<view class="message-item" :class="{ 'unread': !getIsRead(), 'urgent': item.is_urgent, 'starred': getIsStarred() }" @click="handleClick">
|
|
<!-- 消息类型指示器 -->
|
|
<view class="message-indicator" :style="{ backgroundColor: getTypeColor(getMessageType()) }"></view>
|
|
|
|
<!-- 消息头部 -->
|
|
<view class="message-header">
|
|
<view class="message-meta">
|
|
<text class="message-type">{{ getTypeName(getMessageType()) }}</text>
|
|
<text class="message-time">{{ formatTime(item.created_at) }}</text>
|
|
<text v-if="item.is_urgent" class="urgent-badge">紧急</text>
|
|
</view>
|
|
<view class="message-actions">
|
|
<text v-if="getIsStarred()" class="star-icon">★</text>
|
|
<text v-if="getIsArchived()" class="archive-icon"></text>
|
|
<text class="more-icon" @click.stop="toggleActionMenu">⋯</text>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- 消息主体 -->
|
|
<view class="message-body">
|
|
<text class="message-title" v-if="item.title !== null">{{ item.title }}</text>
|
|
<text class="message-content">{{ getDisplayContent(item.content) }}</text>
|
|
|
|
<!-- 附件指示 -->
|
|
<view v-if="hasAttachments(item)" class="attachment-indicator">
|
|
<text class="attachment-icon"></text>
|
|
<text class="attachment-count">{{ getAttachmentCount(item) }}个附件</text>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- 消息底部 -->
|
|
<view class="message-footer">
|
|
<text class="sender-info">{{ getSenderInfo(item) }}</text>
|
|
<view class="message-stats">
|
|
<text v-if="item.reply_count > 0" class="reply-count">{{ item.reply_count }}回复</text>
|
|
<text v-if="item.read_count > 0 && item.total_recipients > 0" class="read-status">
|
|
{{ item.read_count }}/{{ item.total_recipients }}已读
|
|
</text>
|
|
</view>
|
|
</view>
|
|
<!-- 操作菜单 -->
|
|
<view v-if="showActionMenu" class="action-menu" @click.stop="">
|
|
<view class="action-item" @click="handleAction('read')">
|
|
<text>{{ getIsRead() ? '标为未读' : '标为已读' }}</text>
|
|
</view>
|
|
<view class="action-item" @click="handleAction('star')">
|
|
<text>{{ getIsStarred() ? '取消收藏' : '收藏' }}</text> </view>
|
|
<view class="action-item" @click="handleAction('archive')">
|
|
<text>{{ getIsArchived() ? '取消归档' : '归档' }}</text>
|
|
</view>
|
|
<view class="action-item danger" @click="handleAction('delete')">
|
|
<text>删除</text>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</template>
|
|
|
|
<script lang="uts">
|
|
import { formatTime, getTypeName, getTypeColor, truncateText } from '../../utils/msgUtils.uts'
|
|
import type { MessageWithRecipient } from '../../utils/msgTypes.uts'
|
|
|
|
export default {
|
|
name: 'MessageItem', props: {
|
|
item: {
|
|
type: Object as PropType<MessageWithRecipient>,
|
|
required: true
|
|
},
|
|
showActionButtons: {
|
|
type: Boolean,
|
|
default: true
|
|
}
|
|
},
|
|
emits: ['click', 'action'],
|
|
data() {
|
|
return {
|
|
showActionMenu: false as boolean
|
|
}
|
|
},
|
|
methods: { handleClick() {
|
|
this.$emit('click', this.item)
|
|
},
|
|
|
|
toggleActionMenu() {
|
|
this.showActionMenu = !this.showActionMenu
|
|
},
|
|
|
|
handleAction(action: string) {
|
|
this.showActionMenu = false
|
|
this.$emit('action', {
|
|
action: action,
|
|
item: this.item
|
|
})
|
|
},
|
|
|
|
formatTime(dateStr: string): string {
|
|
return formatTime(dateStr)
|
|
},
|
|
|
|
getTypeName(typeId: string): string {
|
|
return getTypeName(typeId)
|
|
},
|
|
|
|
getTypeColor(typeId: string): string {
|
|
return getTypeColor(typeId)
|
|
},
|
|
getDisplayContent(content: string | null): string {
|
|
if (content === null) {
|
|
return '无内容'
|
|
}
|
|
if (content.length > 100) {
|
|
return content.substring(0, 100) + '...'
|
|
}
|
|
return content
|
|
},
|
|
hasAttachments(item: MessageWithRecipient): boolean {
|
|
if (item.attachments === null) {
|
|
return false
|
|
}
|
|
const attachments = item.attachments as UTSJSONObject
|
|
const files = attachments.getAny('files')
|
|
return files !== null
|
|
},
|
|
|
|
getAttachmentCount(item: MessageWithRecipient): number {
|
|
if (item.attachments === null) {
|
|
return 0
|
|
}
|
|
const attachments = item.attachments as UTSJSONObject
|
|
const files = attachments.getAny('files') as Array<UTSJSONObject> | null
|
|
return files !== null ? files.length : 0
|
|
},
|
|
|
|
getSenderInfo(item: MessageWithRecipient): string {
|
|
if (item.sender_name !== null) {
|
|
return `来自: ${item.sender_name}`
|
|
}
|
|
if (item.sender_type === 'system') {
|
|
return '系统消息'
|
|
} else if (item.sender_type === 'device') {
|
|
return '设备消息'
|
|
} else {
|
|
return '未知发送者'
|
|
}
|
|
},
|
|
|
|
// 获取消息类型代码
|
|
getMessageType(): string {
|
|
return this.item.message_type ?? this.item.message_type_id ?? 'default'
|
|
},
|
|
|
|
// 获取是否已读
|
|
getIsRead(): boolean {
|
|
return this.item.is_read ?? false
|
|
},
|
|
|
|
// 获取是否已收藏
|
|
getIsStarred(): boolean {
|
|
return this.item.is_starred ?? false
|
|
},
|
|
|
|
// 获取是否已归档
|
|
getIsArchived(): boolean {
|
|
return this.item.is_archived ?? false
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style scoped>
|
|
.message-item {
|
|
position: relative;
|
|
background-color: #ffffff;
|
|
border-radius: 8px;
|
|
margin-bottom: 12px;
|
|
padding: 16px;
|
|
border-left: 4px solid #e0e0e0;
|
|
}
|
|
|
|
.message-item.unread {
|
|
background-color: #f8f9ff;
|
|
border-left-color: #2196f3;
|
|
}
|
|
|
|
.message-item.urgent {
|
|
border-left-color: #f44336;
|
|
}
|
|
|
|
.message-item.starred {
|
|
background-color: #fffbf0;
|
|
}
|
|
|
|
.message-indicator {
|
|
position: absolute;
|
|
left: 0;
|
|
top: 0;
|
|
bottom: 0;
|
|
width: 4px;
|
|
border-radius: 2px;
|
|
}
|
|
|
|
.message-header {
|
|
display: flex;
|
|
flex-direction: row;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-bottom: 8px;
|
|
}
|
|
|
|
.message-meta {
|
|
display: flex;
|
|
flex-direction: row;
|
|
align-items: center;
|
|
gap: 8px;
|
|
}
|
|
|
|
.message-type {
|
|
font-size: 12px;
|
|
color: #666666;
|
|
background-color: #f5f5f5;
|
|
padding: 2px 6px;
|
|
border-radius: 4px;
|
|
}
|
|
|
|
.message-time {
|
|
font-size: 12px;
|
|
color: #999999;
|
|
}
|
|
|
|
.urgent-badge {
|
|
font-size: 10px;
|
|
color: #ffffff;
|
|
background-color: #f44336;
|
|
padding: 2px 6px;
|
|
border-radius: 4px;
|
|
}
|
|
|
|
.message-actions {
|
|
display: flex;
|
|
flex-direction: row;
|
|
align-items: center;
|
|
gap: 8px;
|
|
}
|
|
|
|
.star-icon {
|
|
color: #ff9800;
|
|
font-size: 16px;
|
|
}
|
|
|
|
.archive-icon {
|
|
font-size: 14px;
|
|
}
|
|
|
|
.more-icon {
|
|
font-size: 16px;
|
|
color: #666666;
|
|
padding: 4px;
|
|
}
|
|
|
|
.message-body {
|
|
margin-bottom: 8px;
|
|
}
|
|
|
|
.message-title {
|
|
font-size: 16px;
|
|
font-weight: 400;
|
|
color: #333333;
|
|
margin-bottom: 4px;
|
|
line-height: 1.4;
|
|
}
|
|
|
|
.message-content {
|
|
font-size: 14px;
|
|
color: #666666;
|
|
line-height: 1.5;
|
|
}
|
|
|
|
.attachment-indicator {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 4px;
|
|
margin-top: 8px;
|
|
}
|
|
|
|
.attachment-icon {
|
|
font-size: 14px;
|
|
color: #2196f3;
|
|
}
|
|
|
|
.attachment-count {
|
|
font-size: 12px;
|
|
color: #2196f3;
|
|
}
|
|
|
|
.message-footer {
|
|
display: flex;
|
|
flex-direction: row;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-top: 8px;
|
|
}
|
|
|
|
.sender-info {
|
|
font-size: 12px;
|
|
color: #999999;
|
|
}
|
|
|
|
.message-stats {
|
|
display: flex;
|
|
flex-direction: row;
|
|
align-items: center;
|
|
gap: 12px;
|
|
}
|
|
|
|
.reply-count,
|
|
.read-status {
|
|
font-size: 12px;
|
|
color: #999999;
|
|
}
|
|
|
|
.action-menu {
|
|
position: absolute;
|
|
top: 40px;
|
|
right: 16px;
|
|
background-color: #ffffff;
|
|
border: 1px solid #e0e0e0;
|
|
border-radius: 8px;
|
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
|
z-index: 10;
|
|
}
|
|
|
|
.action-item {
|
|
padding: 12px 16px;
|
|
border-bottom: 1px solid #f0f0f0;
|
|
}
|
|
|
|
.action-item:last-child {
|
|
border-bottom: none;
|
|
}
|
|
|
|
.action-item text {
|
|
font-size: 14px;
|
|
color: #333333;
|
|
}
|
|
|
|
.action-item.danger text {
|
|
color: #f44336;
|
|
}
|
|
|
|
.action-item:active {
|
|
background-color: #f5f5f5;
|
|
}
|
|
</style>
|