257 lines
8.4 KiB
Vue
257 lines
8.4 KiB
Vue
<template>
|
|
<view class="page msg edge">
|
|
<fy-header title="消息" :extra="unreadLabel">
|
|
<template #right>
|
|
<view class="plus" @click.stop="menu = !menu">
|
|
<view class="plus-h" />
|
|
<view class="plus-v" />
|
|
</view>
|
|
</template>
|
|
</fy-header>
|
|
<view v-if="menu" class="mask" @click="menu = false" />
|
|
<view v-if="menu" class="drop" :style="{ top: dropTop + 'px' }">
|
|
<view class="caret" />
|
|
<view class="drop-item" @click="newGroup">
|
|
<wd-icon name="usergroup" size="32rpx" color="#374151" />
|
|
<text>发起群聊</text>
|
|
</view>
|
|
<view class="drop-item" @click="addMate">
|
|
<wd-icon name="user-add" size="32rpx" color="#374151" />
|
|
<text>添加同事</text>
|
|
</view>
|
|
</view>
|
|
<fy-scroll class="edge-scroll list" dock @refresh="reload">
|
|
<view class="finder" @click="goSearch">
|
|
<wd-icon name="search" size="28rpx" color="#9CA3AF" />
|
|
<text class="finder-txt">搜索</text>
|
|
</view>
|
|
<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, menu: false, dropTop: 64 }
|
|
},
|
|
computed: {
|
|
unreadLabel() {
|
|
if (!this.unread) return ''
|
|
return '(' + (this.unread > 99 ? '99+' : this.unread) + ')'
|
|
}
|
|
},
|
|
created() {
|
|
let status = 20
|
|
try { status = uni.getSystemInfoSync().statusBarHeight || 20 } catch (_) {}
|
|
this.dropTop = status + uni.upx2px(88) + 6
|
|
},
|
|
onShow() {
|
|
if (this.off) this.off()
|
|
this.guard()
|
|
store.refreshPeoplePhotos()
|
|
this.pull()
|
|
this.off = store.on(() => this.pull())
|
|
},
|
|
onHide() {
|
|
this.menu = false
|
|
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) }
|
|
})
|
|
}
|
|
}
|
|
})
|
|
},
|
|
newGroup() {
|
|
this.menu = false
|
|
uni.navigateTo({ url: '/pages/chat/create' })
|
|
},
|
|
addMate() {
|
|
this.menu = false
|
|
uni.navigateTo({ url: '/pages/search/search' })
|
|
},
|
|
goSearch() {
|
|
uni.navigateTo({
|
|
url: '/pages/search/search',
|
|
animationType: 'slide-in-right',
|
|
animationDuration: 280
|
|
})
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style scoped>
|
|
.msg { display: flex; flex-direction: column; height: 100%; background: #F5F7FA; overflow: hidden; }
|
|
.finder {
|
|
margin: 8rpx 24rpx 12rpx;
|
|
height: 56rpx;
|
|
border-radius: 12rpx;
|
|
background: #fff;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
color: #9CA3AF;
|
|
}
|
|
.finder-txt { margin-left: 8rpx; font-size: 26rpx; line-height: 1; }
|
|
.list { background: #F5F7FA; }
|
|
.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: transparent; }
|
|
.ico { font-size: 36rpx; color: #1677FF; padding: 8rpx 12rpx; }
|
|
.plus {
|
|
width: 56rpx;
|
|
height: 56rpx;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
position: relative;
|
|
}
|
|
.plus-h, .plus-v {
|
|
position: absolute;
|
|
background: #111827;
|
|
border-radius: 2rpx;
|
|
transition: transform 180ms ease;
|
|
}
|
|
.plus-h { width: 28rpx; height: 3rpx; }
|
|
.plus-v { width: 3rpx; height: 28rpx; }
|
|
.mask { position: fixed; left: 0; right: 0; top: 0; bottom: 0; z-index: 30; background: rgba(17, 24, 39, 0.04); }
|
|
.drop {
|
|
position: fixed;
|
|
right: 16rpx;
|
|
z-index: 31;
|
|
width: 280rpx;
|
|
padding: 8rpx;
|
|
background: #fff;
|
|
border-radius: 20rpx;
|
|
box-shadow: 0 12rpx 40rpx rgba(17, 24, 39, 0.1);
|
|
transform-origin: calc(100% - 28rpx) 0;
|
|
animation: drop-in 180ms cubic-bezier(0.2, 0.8, 0.2, 1);
|
|
}
|
|
.caret {
|
|
position: absolute;
|
|
top: -10rpx;
|
|
right: 28rpx;
|
|
width: 20rpx;
|
|
height: 20rpx;
|
|
background: #fff;
|
|
transform: rotate(45deg);
|
|
border-radius: 4rpx;
|
|
box-shadow: -2rpx -2rpx 6rpx rgba(17, 24, 39, 0.04);
|
|
}
|
|
.drop-item {
|
|
height: 84rpx;
|
|
padding: 0 20rpx;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 16rpx;
|
|
font-size: 28rpx;
|
|
color: #111827;
|
|
border-radius: 14rpx;
|
|
}
|
|
.drop-item + .drop-item { margin-top: 4rpx; }
|
|
@keyframes drop-in {
|
|
from { opacity: 0; transform: translateY(-8rpx) scale(0.96); }
|
|
to { opacity: 1; transform: none; }
|
|
}
|
|
</style>
|