158 lines
6.3 KiB
Vue
158 lines
6.3 KiB
Vue
<template>
|
||
<view class="page">
|
||
<fy-header title="通讯录">
|
||
<template #right>
|
||
<text class="ico" @click="goGroup">建群</text>
|
||
</template>
|
||
</fy-header>
|
||
<view class="search">
|
||
<input v-model="q" placeholder="搜索同事 / 部门 / 手机号" />
|
||
</view>
|
||
<view class="org" @click="goGroups">
|
||
<view class="obox">👥</view>
|
||
<view class="ometa">
|
||
<text class="on">群聊</text>
|
||
<text class="os">{{ groupCount }} 个群</text>
|
||
</view>
|
||
<text class="arr">›</text>
|
||
</view>
|
||
<view class="org" @click="goSearch">
|
||
<view class="obox">🏛</view>
|
||
<view class="ometa">
|
||
<text class="on">全员架构</text>
|
||
<text class="os">共 {{ deptCount }} 个部门 · {{ peopleCount }} 位在职成员</text>
|
||
</view>
|
||
<text class="arr">›</text>
|
||
</view>
|
||
<view v-for="g in groups" :key="g.name" class="block">
|
||
<view class="dept" @click="toggle(g.name)">
|
||
<text>{{ g.name }}</text>
|
||
<text class="cnt">({{ g.people.length }})</text>
|
||
</view>
|
||
<view v-if="open[g.name] !== false" class="card">
|
||
<view v-for="p in g.people" :key="p.id" class="item" @click="openPerson(p)">
|
||
<view class="av-wrap">
|
||
<image v-if="p.avatar" class="av" :src="p.avatar" mode="aspectFill" />
|
||
<view v-else class="av txt">{{ p.name.slice(0, 1) }}</view>
|
||
<view v-if="p.online" class="dot" />
|
||
</view>
|
||
<view class="meta">
|
||
<text class="name">{{ p.name }}</text>
|
||
<text class="sub">{{ p.online ? '在线' : '离线' }} · {{ p.title }}</text>
|
||
</view>
|
||
<text class="chip">{{ p.title }}</text>
|
||
<text class="mini" @click.stop="chat(p)">💬</text>
|
||
<text class="mini" @click.stop="call(p)">☎</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
<view v-if="!groups.length" class="empty">没有匹配的同事</view>
|
||
<text class="foot">仅显示当前所属企业成员 · 数据实时同步</text>
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
import FyHeader from '../../components/fy-header.vue'
|
||
import { store } from '../../utils/store.js'
|
||
import { toast } from '../../utils/format.js'
|
||
|
||
export default {
|
||
components: { FyHeader },
|
||
data() {
|
||
return { q: '', open: {}, off: null }
|
||
},
|
||
computed: {
|
||
peopleCount() {
|
||
return Object.keys(store.people).length
|
||
},
|
||
deptCount() {
|
||
return new Set(Object.values(store.people).map((p) => p.department || '未分组')).size
|
||
},
|
||
groupCount() {
|
||
return store.conversations.filter((c) => c.type === 'group').length
|
||
},
|
||
groups() {
|
||
const q = this.q.trim()
|
||
const list = Object.values(store.people).filter((p) => {
|
||
if (store.me && p.id === store.me.id) return false
|
||
if (!q) return true
|
||
return (p.name + p.department + p.phone + p.title).includes(q)
|
||
})
|
||
const map = {}
|
||
list.forEach((p) => {
|
||
const d = p.department || '未分组'
|
||
if (!map[d]) map[d] = []
|
||
map[d].push(p)
|
||
})
|
||
return Object.keys(map).sort().map((name) => ({ name, people: map[name] }))
|
||
}
|
||
},
|
||
onShow() {
|
||
if (store.phase !== 'app') uni.reLaunch({ url: '/pages/login/login' })
|
||
if (this.off) this.off()
|
||
store.refreshPeoplePhotos()
|
||
this.off = store.on(() => this.$forceUpdate())
|
||
},
|
||
onHide() { if (this.off) this.off() },
|
||
methods: {
|
||
toggle(name) {
|
||
this.open[name] = this.open[name] === false
|
||
},
|
||
openPerson(p) {
|
||
uni.navigateTo({ url: '/pages/contact/detail?id=' + encodeURIComponent(p.id) })
|
||
},
|
||
chat(p) {
|
||
const conv = store.openDirect(p)
|
||
uni.navigateTo({ url: '/pages/chat/chat?sid=' + encodeURIComponent(conv.id) })
|
||
},
|
||
call(p) {
|
||
if (!p.phone) return toast('该同事未登记手机号')
|
||
uni.makePhoneCall({ phoneNumber: String(p.phone) })
|
||
},
|
||
goSearch() { uni.navigateTo({ url: '/pages/search/search' }) },
|
||
goGroup() { uni.navigateTo({ url: '/pages/chat/create' }) },
|
||
goGroups() {
|
||
const groups = store.conversations.filter((c) => c.type === 'group')
|
||
if (!groups.length) {
|
||
uni.navigateTo({ url: '/pages/chat/create' })
|
||
return
|
||
}
|
||
uni.showActionSheet({
|
||
itemList: groups.slice(0, 6).map((c) => c.name || '群聊'),
|
||
success: (res) => {
|
||
const c = groups[res.tapIndex]
|
||
if (c) uni.navigateTo({ url: '/pages/chat/chat?sid=' + encodeURIComponent(c.id) })
|
||
}
|
||
})
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style scoped>
|
||
.search { padding: 16rpx 24rpx; }
|
||
.search input { height: 80rpx; background: #fff; border-radius: 12rpx; padding: 0 24rpx; font-size: 26rpx; }
|
||
.org { margin: 0 24rpx 16rpx; background: #fff; border-radius: 16rpx; padding: 20rpx; display: flex; align-items: center; }
|
||
.obox { width: 64rpx; height: 64rpx; border-radius: 12rpx; background: #eaedff; display: flex; align-items: center; justify-content: center; }
|
||
.ometa { flex: 1; margin-left: 16rpx; }
|
||
.on { display: block; font-size: 28rpx; font-weight: 700; }
|
||
.os { font-size: 22rpx; color: #737686; }
|
||
.arr { color: #c3c6d7; }
|
||
.block { margin: 8rpx 24rpx 24rpx; }
|
||
.dept { display: flex; align-items: center; padding: 16rpx 12rpx; font-size: 26rpx; font-weight: 600; background: #f2f3ff; border-radius: 12rpx 12rpx 0 0; }
|
||
.cnt { color: #737686; font-weight: 400; font-size: 22rpx; margin-left: 8rpx; }
|
||
.item { display: flex; align-items: center; padding: 20rpx 20rpx; }
|
||
.av-wrap { position: relative; width: 72rpx; height: 72rpx; }
|
||
.av { width: 72rpx; height: 72rpx; border-radius: 50%; }
|
||
.av.txt { background: #eaedff; color: #434655; display: flex; align-items: center; justify-content: center; font-weight: 600; }
|
||
.dot { position: absolute; right: 0; bottom: 0; width: 16rpx; height: 16rpx; border-radius: 50%; background: #006c49; border: 2rpx solid #fff; }
|
||
.meta { flex: 1; margin-left: 16rpx; min-width: 0; }
|
||
.name { display: block; font-size: 28rpx; font-weight: 600; }
|
||
.sub { font-size: 22rpx; color: #737686; }
|
||
.chip { font-size: 20rpx; color: #004ac6; background: #e2e7ff; padding: 4rpx 12rpx; border-radius: 999rpx; margin-right: 8rpx; }
|
||
.mini { width: 56rpx; text-align: center; font-size: 28rpx; color: #004ac6; }
|
||
.empty { text-align: center; color: #737686; padding: 80rpx; }
|
||
.foot { display: block; text-align: center; color: #737686; font-size: 22rpx; padding: 20rpx 0 40rpx; }
|
||
.ico { padding: 8rpx 16rpx; color: #2563eb; font-size: 26rpx; }
|
||
</style>
|