156 lines
5.9 KiB
Vue
156 lines
5.9 KiB
Vue
<template>
|
||
<view class="page msg edge">
|
||
<fy-header :title="unread ? ('消息(' + unread + ')') : '消息'">
|
||
<template #right>
|
||
<text class="ico sm" @click="goSearch">⌕</text>
|
||
<text class="ico" @click="plus">+</text>
|
||
</template>
|
||
</fy-header>
|
||
<fy-scroll class="edge-scroll list" dock @refresh="reload">
|
||
<view v-if="!rows.length" class="empty">暂无会话</view>
|
||
<view
|
||
v-for="c in rows"
|
||
:key="c.id"
|
||
class="item"
|
||
:class="{ pin: c.pinned }"
|
||
@click="open(c)"
|
||
@longpress="act(c)"
|
||
>
|
||
<view class="av-wrap">
|
||
<image v-if="c.avatar && c.type !== 'files'" class="av" :src="c.avatar" mode="aspectFill" />
|
||
<view v-else-if="c.type === 'files'" class="av"><fy-file-mark /></view>
|
||
<view v-else class="av txt" :class="avClass(c)">{{ avText(c) }}</view>
|
||
<text v-if="c.unread && !c.muted" class="badge abs">{{ c.unread > 99 ? '99+' : c.unread }}</text>
|
||
<text v-else-if="c.unread && c.muted" class="dotu" />
|
||
</view>
|
||
<view class="meta">
|
||
<view class="top">
|
||
<text class="name">{{ c.name }}</text>
|
||
<text class="time">{{ c.lastTime }}</text>
|
||
</view>
|
||
<view class="bot">
|
||
<text class="prev">{{ c.muted && c.unread ? '[' + c.unread + '条]' : '' }}{{ c.lastMessage || ' ' }}</text>
|
||
<text v-if="c.muted" class="mute">🔕</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</fy-scroll>
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
import FyHeader from '../../components/fy-header.vue'
|
||
import FyScroll from '../../components/fy-scroll.vue'
|
||
import FyFileMark from '../../components/fy-file-mark.vue'
|
||
import { store } from '../../utils/store.js'
|
||
|
||
export default {
|
||
components: { FyHeader, FyFileMark, FyScroll },
|
||
data() {
|
||
return { rows: [], unread: 0, off: null }
|
||
},
|
||
onShow() {
|
||
if (this.off) this.off()
|
||
this.guard()
|
||
store.refreshPeoplePhotos()
|
||
this.pull()
|
||
this.off = store.on(() => this.pull())
|
||
},
|
||
onHide() {
|
||
if (this.off) this.off()
|
||
this.off = null
|
||
},
|
||
methods: {
|
||
guard() {
|
||
if (store.phase === 'login') uni.reLaunch({ url: '/pages/login/login' })
|
||
if (store.phase === 'forcePassword') uni.redirectTo({ url: '/pages/password/password' })
|
||
},
|
||
pull() {
|
||
this.rows = store.conversations.slice()
|
||
this.unread = store.unreadTotal()
|
||
},
|
||
reload() {
|
||
store.refreshPeoplePhotos().finally(() => this.pull())
|
||
},
|
||
avText(c) {
|
||
if (c.type === 'work') return '工'
|
||
if (c.type === 'group') return '群'
|
||
return (c.name || '?').slice(0, 1)
|
||
},
|
||
avClass(c) {
|
||
if (c.type === 'work') return 'work'
|
||
return 'plain'
|
||
},
|
||
open(c) {
|
||
if (c.type === 'work') {
|
||
uni.navigateTo({ url: '/pages/chat/work?sid=' + encodeURIComponent(c.id) })
|
||
return
|
||
}
|
||
uni.navigateTo({ url: '/pages/chat/chat?sid=' + encodeURIComponent(c.id) })
|
||
},
|
||
act(c) {
|
||
const items = [
|
||
c.pinned ? '取消置顶' : '置顶聊天',
|
||
c.unread ? '标为已读' : '标为未读',
|
||
c.muted ? '关闭免打扰' : '消息免打扰',
|
||
'删除该聊天'
|
||
]
|
||
uni.showActionSheet({
|
||
itemList: items,
|
||
success: (res) => {
|
||
if (res.tapIndex === 0) store.setSession(c, { pinned: !c.pinned })
|
||
else if (res.tapIndex === 1) {
|
||
if (c.unread) {
|
||
c.unread = 0
|
||
store.emit()
|
||
} else store.markUnread(c)
|
||
} else if (res.tapIndex === 2) store.setSession(c, { muted: !c.muted })
|
||
else if (res.tapIndex === 3) {
|
||
uni.showModal({
|
||
title: '删除聊天',
|
||
content: '仅从消息列表移除,聊天记录仍保留在服务器。',
|
||
success: (r) => { if (r.confirm) store.hideConv(c) }
|
||
})
|
||
}
|
||
}
|
||
})
|
||
},
|
||
plus() {
|
||
uni.showActionSheet({
|
||
itemList: ['发起群聊', '添加同事'],
|
||
success: (res) => {
|
||
if (res.tapIndex === 0) uni.navigateTo({ url: '/pages/chat/create' })
|
||
else uni.navigateTo({ url: '/pages/search/search' })
|
||
}
|
||
})
|
||
},
|
||
goSearch() { uni.navigateTo({ url: '/pages/search/search' }) }
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style scoped>
|
||
.msg { display: flex; flex-direction: column; height: 100%; background: #F5F7FA; overflow: hidden; }
|
||
.list { background: #fff; }
|
||
.ico.sm { font-size: 34rpx; color: #1677FF; }
|
||
.item { display: flex; align-items: center; padding: 22rpx 28rpx; background: #fff; }
|
||
.item.pin { background: #F7F8FA; }
|
||
.av-wrap { position: relative; width: 92rpx; height: 92rpx; flex-shrink: 0; }
|
||
.av { width: 92rpx; height: 92rpx; border-radius: 20rpx; overflow: hidden; }
|
||
.av.txt { display: flex; align-items: center; justify-content: center; font-size: 32rpx; font-weight: 600; }
|
||
.av.work { background: #1677FF; color: #fff; }
|
||
.av.file { background: #F3F6FB; color: #4B5563; }
|
||
.av.plain { background: #E8F1FF; color: #1677FF; }
|
||
.abs { position: absolute; top: -8rpx; right: -8rpx; }
|
||
.dotu { position: absolute; top: -4rpx; right: -4rpx; width: 16rpx; height: 16rpx; border-radius: 50%; background: #DC2626; }
|
||
.meta { flex: 1; margin-left: 20rpx; min-width: 0; padding: 8rpx 0 18rpx; border-bottom: 1rpx solid #F0F2F5; }
|
||
.top { display: flex; justify-content: space-between; align-items: center; }
|
||
.name { font-size: 30rpx; font-weight: 600; max-width: 420rpx; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||
.time { font-size: 22rpx; color: #9CA3AF; }
|
||
.bot { display: flex; align-items: center; margin-top: 8rpx; }
|
||
.prev { flex: 1; font-size: 24rpx; color: #6B7280; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||
.mute { font-size: 22rpx; margin-left: 8rpx; color: #9CA3AF; }
|
||
.empty { padding: 160rpx 40rpx; text-align: center; color: #6B7280; font-size: 28rpx; background: #fff; }
|
||
.ico { font-size: 36rpx; color: #1677FF; padding: 8rpx 12rpx; }
|
||
</style>
|