更新ContactModal.vue组件,添加NRadio和NVirtualList支持,优化联系人选择逻辑,调整搜索过滤器,改进用户界面和交互体验。
This commit is contained in:
parent
fad84e5bf3
commit
94cf0f9f63
2
components.d.ts
vendored
2
components.d.ts
vendored
@ -59,7 +59,9 @@ declare module 'vue' {
|
|||||||
NoticeTab: typeof import('./src/components/group/manage/NoticeTab.vue')['default']
|
NoticeTab: typeof import('./src/components/group/manage/NoticeTab.vue')['default']
|
||||||
NotificationApi: typeof import('./src/components/common/NotificationApi.vue')['default']
|
NotificationApi: typeof import('./src/components/common/NotificationApi.vue')['default']
|
||||||
NProgress: typeof import('naive-ui')['NProgress']
|
NProgress: typeof import('naive-ui')['NProgress']
|
||||||
|
NRadio: typeof import('naive-ui')['NRadio']
|
||||||
NTag: typeof import('naive-ui')['NTag']
|
NTag: typeof import('naive-ui')['NTag']
|
||||||
|
NVirtualList: typeof import('naive-ui')['NVirtualList']
|
||||||
RevokeMessage: typeof import('./src/components/talk/message/RevokeMessage.vue')['default']
|
RevokeMessage: typeof import('./src/components/talk/message/RevokeMessage.vue')['default']
|
||||||
RouterLink: typeof import('vue-router')['RouterLink']
|
RouterLink: typeof import('vue-router')['RouterLink']
|
||||||
RouterView: typeof import('vue-router')['RouterView']
|
RouterView: typeof import('vue-router')['RouterView']
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, computed } from 'vue'
|
import { ref, computed, onMounted, watch } from 'vue'
|
||||||
import { NModal, NInput, NScrollbar, NCheckbox, NTabs, NTab } from 'naive-ui'
|
import { NModal, NInput, NScrollbar, NCheckbox, NTabs, NTab, NButton, NIcon, NImage, NRadio, NVirtualList, NEmpty } from 'naive-ui'
|
||||||
import { Search, Delete } from '@icon-park/vue-next'
|
import { Search, Delete } from '@icon-park/vue-next'
|
||||||
import { ServeGetContacts } from '@/api/contact'
|
import { ServeGetContacts } from '@/api/contact'
|
||||||
import { ServeGetGroups } from '@/api/group'
|
import { ServeGetGroups } from '@/api/group'
|
||||||
import XNModal from '@/components/x-naive-ui/x-n-modal/index.vue'
|
import XNModal from '@/components/x-naive-ui/x-n-modal/index.vue'
|
||||||
const emit = defineEmits(['close', 'on-submit'])
|
const emit = defineEmits(['close', 'on-submit'])
|
||||||
|
import { CloseCircle } from '@vicons/ionicons5'
|
||||||
interface Item {
|
interface Item {
|
||||||
id: number
|
id: number
|
||||||
type: number
|
type: number
|
||||||
@ -17,16 +17,16 @@ interface Item {
|
|||||||
keyword: string
|
keyword: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const tabsIndex = ref<number>(1)
|
|
||||||
const isShowBox = ref(true)
|
const isShowBox = ref(true)
|
||||||
const loading = ref(true)
|
const loading = ref(true)
|
||||||
const items = ref<Item[]>([])
|
const items = ref<Item[]>([])
|
||||||
const keywords = ref('')
|
const keywords = ref('')
|
||||||
const loadGroupStatus = ref(false)
|
const loadGroupStatus = ref(false)
|
||||||
|
|
||||||
|
// 搜索过滤器:不再按类型过滤,将好友和群组融合在一起
|
||||||
const searchFilter = computed(() => {
|
const searchFilter = computed(() => {
|
||||||
return items.value.filter((item: Item) => {
|
return items.value.filter((item: Item) => {
|
||||||
return tabsIndex.value == item.type && item.keyword.match(keywords.value) != null
|
return item.keyword.toLowerCase().includes(keywords.value.toLowerCase())
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -40,6 +40,7 @@ const isCanSubmit = computed(() => {
|
|||||||
|
|
||||||
const onLoad = () => {
|
const onLoad = () => {
|
||||||
onLoadContact()
|
onLoadContact()
|
||||||
|
onLoadGroup()
|
||||||
}
|
}
|
||||||
|
|
||||||
const onLoadContact = () => {
|
const onLoadContact = () => {
|
||||||
@ -55,7 +56,7 @@ const onLoadContact = () => {
|
|||||||
avatar: item.avatar,
|
avatar: item.avatar,
|
||||||
type: 1,
|
type: 1,
|
||||||
name: item.remark || item.nickname,
|
name: item.remark || item.nickname,
|
||||||
keyword: item.remark + item.nickname,
|
keyword: (item.remark || '') + item.nickname,
|
||||||
remark: item.remark,
|
remark: item.remark,
|
||||||
checked: false
|
checked: false
|
||||||
}
|
}
|
||||||
@ -75,6 +76,7 @@ const onLoadGroup = async () => {
|
|||||||
loading.value = true
|
loading.value = true
|
||||||
let { code, data } = await ServeGetGroups()
|
let { code, data } = await ServeGetGroups()
|
||||||
if (code != 200) {
|
if (code != 200) {
|
||||||
|
loading.value = false
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -101,6 +103,13 @@ const onMaskClick = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const onTriggerContact = (item: any) => {
|
const onTriggerContact = (item: any) => {
|
||||||
|
// 如果是单选模式,先取消所有选中
|
||||||
|
if (selectType.value === 1) {
|
||||||
|
items.value.forEach(contact => {
|
||||||
|
contact.checked = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
let data = items.value.find((val: any) => val.id === item.id)
|
let data = items.value.find((val: any) => val.id === item.id)
|
||||||
|
|
||||||
if (data) {
|
if (data) {
|
||||||
@ -108,6 +117,18 @@ const onTriggerContact = (item: any) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const onRemoveContact = (item: any) => {
|
||||||
|
let data = items.value.find((val: any) => val.id === item.id)
|
||||||
|
|
||||||
|
if (data) {
|
||||||
|
data.checked = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onCancel = () => {
|
||||||
|
isShowBox.value = false
|
||||||
|
}
|
||||||
|
|
||||||
const onSubmit = () => {
|
const onSubmit = () => {
|
||||||
let data = checkedFilter.value.map((item: any) => {
|
let data = checkedFilter.value.map((item: any) => {
|
||||||
return {
|
return {
|
||||||
@ -119,136 +140,113 @@ const onSubmit = () => {
|
|||||||
emit('on-submit', data)
|
emit('on-submit', data)
|
||||||
}
|
}
|
||||||
|
|
||||||
const onTabs = (value: number) => {
|
// 1 单选 2 多选
|
||||||
tabsIndex.value = value
|
const selectType = ref(1)
|
||||||
if (value == 2) {
|
const changeSelectType = () => {
|
||||||
onLoadGroup()
|
selectType.value = selectType.value == 1 ? 2 : 1
|
||||||
}
|
|
||||||
|
// 切换选择模式时清空已选择的联系人
|
||||||
|
items.value.forEach(item => {
|
||||||
|
item.checked = false
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
onLoad()
|
onLoad()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<x-n-modal
|
<x-n-modal v-model:show="isShowBox" title="合并转发/逐条转发" style="width: 997px; height: 740px;background-color: #F9F9FD"
|
||||||
v-model:show="isShowBox"
|
:on-after-leave="onMaskClick" content-style="display: flex; justify-content: center; align-items: center;">
|
||||||
title="选择联系人"
|
<div class="w-927px h-627px bg-#fff rounded-3px px-35px py-20px">
|
||||||
class="modal-radius"
|
<div class="flex items-center justify-between mb-28px">
|
||||||
style="width: 997px; height: 740px;background-color: #F9F9FD"
|
<div class="text-#333639">搜索</div>
|
||||||
:on-after-leave="onMaskClick"
|
<div class="w-779px h-34px">
|
||||||
>
|
<n-input v-model:value="keywords" type="text" clearable placeholder="请输入">
|
||||||
<section class="el-container launch-box">
|
<template #prefix>
|
||||||
<aside class="el-aside bdr-r" style="width: 240px">
|
<n-icon :component="Search" />
|
||||||
<section class="el-container is-vertical height100">
|
</template>
|
||||||
<header class="el-header tabs">
|
</n-input>
|
||||||
<n-tabs type="line" justify-content="space-around" @update:value="onTabs">
|
|
||||||
<n-tab name="1"> 好友 </n-tab>
|
|
||||||
<n-tab name="2"> 群聊 </n-tab>
|
|
||||||
<!-- <n-tab name="企业"> 企业 </n-tab> -->
|
|
||||||
</n-tabs>
|
|
||||||
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<header class="el-header sub-header">
|
|
||||||
<n-input placeholder="搜索" v-model:value="keywords" clearable size="small">
|
|
||||||
<template #prefix>
|
|
||||||
<n-icon :component="Search" />
|
|
||||||
</template>
|
|
||||||
</n-input>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<main class="el-main" v-loading="loading" loading-text="加载中...">
|
|
||||||
<n-scrollbar>
|
|
||||||
<div class="friend-items">
|
|
||||||
<div
|
|
||||||
class="friend-item pointer"
|
|
||||||
v-for="item in searchFilter"
|
|
||||||
:key="item.id"
|
|
||||||
@click="onTriggerContact(item)"
|
|
||||||
>
|
|
||||||
<div class="avatar">
|
|
||||||
<im-avatar
|
|
||||||
class="pointer"
|
|
||||||
:src="item.avatar"
|
|
||||||
:size="25"
|
|
||||||
:username="item.remark || item.name"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="content">
|
|
||||||
<span class="text-ellipsis">{{ item.remark || item.name }}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="checkbox">
|
|
||||||
<n-checkbox size="small" :checked="item.checked" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</n-scrollbar>
|
|
||||||
</main>
|
|
||||||
</section>
|
|
||||||
</aside>
|
|
||||||
|
|
||||||
<main class="el-main">
|
|
||||||
<section class="el-container is-vertical height100">
|
|
||||||
<main class="el-main o-hidden">
|
|
||||||
<n-scrollbar class="friend-items">
|
|
||||||
<div class="friend-items">
|
|
||||||
<div v-show="!checkedFilter.length" style="padding-top: 100px">
|
|
||||||
<n-empty size="200" description="暂无数据">
|
|
||||||
<template #icon>
|
|
||||||
<img src="@/assets/image/no-data.svg" alt="" />
|
|
||||||
</template>
|
|
||||||
</n-empty>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
class="friend-item pointer"
|
|
||||||
v-for="item in checkedFilter"
|
|
||||||
:key="item.id"
|
|
||||||
@click="onTriggerContact(item)"
|
|
||||||
>
|
|
||||||
<div class="avatar">
|
|
||||||
<im-avatar
|
|
||||||
class="pointer"
|
|
||||||
:src="item.avatar"
|
|
||||||
:size="25"
|
|
||||||
:username="item.remark || item.name"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="content">
|
|
||||||
<span class="text-ellipsis">
|
|
||||||
{{ item.remark || item.name }}
|
|
||||||
</span>
|
|
||||||
<span v-if="item.type == 2" class="badge group">群</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="checkbox">
|
|
||||||
<n-icon :size="16" :component="Delete" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</n-scrollbar>
|
|
||||||
</main>
|
|
||||||
</section>
|
|
||||||
</main>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<template #footer>
|
|
||||||
<div class="footer">
|
|
||||||
<div>
|
|
||||||
<span>已选择({{ checkedFilter.length }})</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<n-button type="tertiary" @click="isShowBox = false"> 取消 </n-button>
|
|
||||||
<n-button type="primary" class="mt-l15" @click="onSubmit" :disabled="isCanSubmit">
|
|
||||||
确定
|
|
||||||
</n-button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
<div class="flex justify-between">
|
||||||
|
<div class="w-260px h-517px rounded-4px border-1px border-solid border-#E5E5E5 px-12px">
|
||||||
|
<div class="border-b-2px border-b-solid border-b-#FBFBFB h-35px flex items-center justify-end">
|
||||||
|
<n-button text color="#46299D" class="text-14px" @click="changeSelectType">
|
||||||
|
{{ selectType === 1 ? '多选' : '单选' }}
|
||||||
|
</n-button>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<n-virtual-list v-if="!loading" style="max-height: 470px" :item-size="65" :items="searchFilter">
|
||||||
|
<template #default="{ item }">
|
||||||
|
<div class="flex items-center border-b-2px border-b-solid h-65px border-b-#FBFBFB"
|
||||||
|
@click="onTriggerContact(item)">
|
||||||
|
<div class="mr-22px">
|
||||||
|
<n-radio v-if="selectType === 1" :checked="item.checked" />
|
||||||
|
<n-checkbox v-else :checked="item.checked" />
|
||||||
|
</div>
|
||||||
|
<div class="mr-10px">
|
||||||
|
<n-image class="w-42px h-42px rounded-full" :src="item.avatar" />
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center">
|
||||||
|
<span class="text-ellipsis">{{ item.name }}</span>
|
||||||
|
<span v-if="item.type == 2" class="badge group ml-2">群</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</n-virtual-list>
|
||||||
|
<div v-else class="flex-center h-470px">
|
||||||
|
<span>加载中...</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="w-578px h-517px rounded-4px border-1px border-solid border-#E5E5E5 px-12px">
|
||||||
|
<div class="border-b-2px border-b-solid border-b-#FBFBFB h-35px flex items-center text-14px text-#000">
|
||||||
|
发送给
|
||||||
|
</div>
|
||||||
|
<div class="h-350px border-b-2px border-b-solid border-b-#FBFBFB">
|
||||||
|
<div v-if="checkedFilter.length > 0">
|
||||||
|
<n-virtual-list style="max-height: 350px" :item-size="65" :items="checkedFilter">
|
||||||
|
<template #default="{ item }">
|
||||||
|
<div class="flex items-center border-b-2px border-b-solid h-65px border-b-#FBFBFB pr-20px">
|
||||||
|
<div class="mr-10px">
|
||||||
|
<n-image class="w-42px h-42px rounded-full" :src="item.avatar" />
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center">
|
||||||
|
<span class="text-ellipsis">{{ item.name }}</span>
|
||||||
|
<span v-if="item.type == 2" class="badge group ml-2">群</span>
|
||||||
|
</div>
|
||||||
|
<n-button class="ml-auto" text color="#C7C7C9" @click="onRemoveContact(item)">
|
||||||
|
<n-icon :component="CloseCircle" size="18" />
|
||||||
|
</n-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</n-virtual-list>
|
||||||
|
</div>
|
||||||
|
<div v-else class="flex-center h-350px">
|
||||||
|
<n-empty size="medium" description="暂无选择联系人">
|
||||||
|
<template #icon>
|
||||||
|
<img src="@/assets/image/no-data.svg" alt="" />
|
||||||
|
</template>
|
||||||
|
</n-empty>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col items-center justify-center h-120px">
|
||||||
|
<div class="text-14px text-#999999 mb-23px">
|
||||||
|
<span>[{{ selectType === 1 ? '合并转发' : '逐条转发' }}]</span>
|
||||||
|
<span v-if="checkedFilter.length > 0">
|
||||||
|
{{ checkedFilter.map(item => item.name).join('、') }}的会话记录
|
||||||
|
</span>
|
||||||
|
<span v-else>请选择联系人</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-center items-center">
|
||||||
|
<n-button color="#C7C7C9" class="w-250px h-34px text-14px text-#fff mr-10px" @click="onCancel">取消</n-button>
|
||||||
|
<n-button color="#46299D" class="w-250px h-34px text-14px text-#fff"
|
||||||
|
@click="onSubmit" :disabled="isCanSubmit">发送</n-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</x-n-modal>
|
</x-n-modal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -257,74 +255,27 @@ onLoad()
|
|||||||
font-weight: unset;
|
font-weight: unset;
|
||||||
}
|
}
|
||||||
|
|
||||||
.launch-box {
|
.flex-center {
|
||||||
height: 410px;
|
display: flex;
|
||||||
width: 100%;
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-ellipsis {
|
||||||
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
.sub-header {
|
max-width: 180px;
|
||||||
height: 50px;
|
|
||||||
padding: 10px 15px;
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.friend-items {
|
|
||||||
height: 100%;
|
|
||||||
overflow-y: auto;
|
|
||||||
padding: 0 15px;
|
|
||||||
|
|
||||||
.friend-item {
|
|
||||||
height: 40px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
margin: 5px 0;
|
|
||||||
|
|
||||||
> div {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.avatar {
|
|
||||||
width: 30px;
|
|
||||||
justify-content: flex-start;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content {
|
|
||||||
flex: 1 auto;
|
|
||||||
padding-left: 8px;
|
|
||||||
overflow: hidden;
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 400;
|
|
||||||
justify-content: flex-start;
|
|
||||||
&:hover {
|
|
||||||
color: #409eff;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.checkbox {
|
|
||||||
flex-shrink: 0;
|
|
||||||
width: 30px;
|
|
||||||
justify-content: flex-end;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.badge {
|
.badge {
|
||||||
&.group {
|
&.group {
|
||||||
color: #3370ff !important;
|
color: #3370ff !important;
|
||||||
background-color: #e1eaff !important;
|
background-color: #e1eaff !important;
|
||||||
|
font-size: 12px;
|
||||||
|
padding: 0 5px;
|
||||||
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
margin: 0 3px;
|
margin: 0 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.footer {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
Loading…
Reference in New Issue
Block a user