152 lines
5.5 KiB
Vue
152 lines
5.5 KiB
Vue
<template>
|
||
<view class="page msg">
|
||
<fy-header :title="unread ? ('消息(' + unread + ')') : '消息'">
|
||
<template #right>
|
||
<text class="ico sm" @click="goSearch">⌕</text>
|
||
<text class="ico" @click="plus">+</text>
|
||
</template>
|
||
</fy-header>
|
||
<scroll-view scroll-y class="list">
|
||
<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" class="av" :src="c.avatar" mode="aspectFill" />
|
||
<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>
|
||
</scroll-view>
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
import FyHeader from '../../components/fy-header.vue'
|
||
import { store } from '../../utils/store.js'
|
||
|
||
export default {
|
||
components: { FyHeader },
|
||
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()
|
||
},
|
||
avText(c) {
|
||
if (c.type === 'work') return '工'
|
||
if (c.type === 'files') return '文'
|
||
if (c.type === 'group') return '群'
|
||
return (c.name || '?').slice(0, 1)
|
||
},
|
||
avClass(c) {
|
||
if (c.type === 'work') return 'work'
|
||
if (c.type === 'files') return 'file'
|
||
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: 100vh; background: #fff; }
|
||
.list { flex: 1; height: 0; }
|
||
.ico.sm { font-size: 36rpx; }
|
||
.item { display: flex; align-items: center; padding: 20rpx 24rpx; background: #fff; }
|
||
.item.pin { background: #f7f7f7; }
|
||
.av-wrap { position: relative; width: 96rpx; height: 96rpx; flex-shrink: 0; }
|
||
.av { width: 96rpx; height: 96rpx; border-radius: 12rpx; }
|
||
.av.txt { display: flex; align-items: center; justify-content: center; font-size: 34rpx; font-weight: 600; }
|
||
.av.work { background: #2563eb; color: #fff; }
|
||
.av.file { background: #e2e7ff; color: #434655; }
|
||
.av.plain { background: #e2e7ff; color: #004ac6; }
|
||
.abs { position: absolute; top: -8rpx; right: -8rpx; }
|
||
.dotu { position: absolute; top: -4rpx; right: -4rpx; width: 16rpx; height: 16rpx; border-radius: 50%; background: #fa5151; }
|
||
.meta { flex: 1; margin-left: 20rpx; min-width: 0; padding: 4rpx 0; border-bottom: 1rpx solid #f0f0f0; }
|
||
.top { display: flex; justify-content: space-between; align-items: center; }
|
||
.name { font-size: 32rpx; font-weight: 500; max-width: 420rpx; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||
.time { font-size: 22rpx; color: #b2b2b2; }
|
||
.bot { display: flex; align-items: center; margin-top: 8rpx; }
|
||
.prev { flex: 1; font-size: 24rpx; color: #888; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||
.mute { font-size: 22rpx; margin-left: 8rpx; }
|
||
.empty { padding: 120rpx; text-align: center; color: #888; font-size: 26rpx; }
|
||
.ico { font-size: 40rpx; color: #111; padding: 8rpx 16rpx; }
|
||
</style>
|