Merge branch 'wyfMain-dev'

This commit is contained in:
wangyifeng 2025-05-19 18:13:18 +08:00
commit 19e4954484
6 changed files with 673 additions and 318 deletions

2
components.d.ts vendored
View File

@ -62,12 +62,14 @@ declare module 'vue' {
NoticeEditor: typeof import('./src/components/group/manage/NoticeEditor.vue')['default'] NoticeEditor: typeof import('./src/components/group/manage/NoticeEditor.vue')['default']
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']
NPopover: typeof import('naive-ui')['NPopover']
NRadio: typeof import('naive-ui')['NRadio'] NRadio: typeof import('naive-ui')['NRadio']
NSpin: typeof import('naive-ui')['NSpin'] NSpin: typeof import('naive-ui')['NSpin']
NVirtualList: typeof import('naive-ui')['NVirtualList'] 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']
SearchByCondition: typeof import('./src/components/search/searchByCondition.vue')['default']
SearchItem: typeof import('./src/components/search/searchItem.vue')['default'] SearchItem: typeof import('./src/components/search/searchItem.vue')['default']
SearchList: typeof import('./src/components/search/searchList.vue')['default'] SearchList: typeof import('./src/components/search/searchList.vue')['default']
SysGroupAdminMessage: typeof import('./src/components/talk/message/system/SysGroupAdminMessage.vue')['default'] SysGroupAdminMessage: typeof import('./src/components/talk/message/system/SysGroupAdminMessage.vue')['default']

6
env/.env.test vendored
View File

@ -2,7 +2,9 @@ ENV = 'development'
VITE_BASE=/ VITE_BASE=/
VUE_APP_PREVIEW=false VUE_APP_PREVIEW=false
VITE_BASE_API=http://114.218.158.24:8503 VITE_BASE_API=http://172.16.100.93:8503
# VITE_BASE_API=http://192.168.88.21:9503
VITE_EPR_BASEURL=http://114.218.158.24:9020 VITE_EPR_BASEURL=http://114.218.158.24:9020
VITE_SOCKET_API=ws://114.218.158.24:8504 VITE_SOCKET_API=ws://172.16.100.93:8504
# VITE_SOCKET_API=ws://192.168.88.21:9504
VUE_APP_WEBSITE_NAME="Lumen IM" VUE_APP_WEBSITE_NAME="Lumen IM"

Binary file not shown.

After

Width:  |  Height:  |  Size: 436 B

View File

@ -1,10 +1,10 @@
<script lang="ts" setup> <script lang="ts" setup>
import { reactive, computed, watch, ref } from 'vue' import { reactive, computed, watch, ref } from 'vue'
import { NEmpty, NPopover, NPopconfirm, NSwitch } from 'naive-ui' import { NEmpty, NPopover, NPopconfirm, NSwitch, NIcon, NInput } from 'naive-ui'
import { useUserStore } from '@/store' import { useUserStore } from '@/store'
import GroupLaunch from './GroupLaunch.vue' import GroupLaunch from './GroupLaunch.vue'
import GroupManage from './manage/index.vue' import GroupManage from './manage/index.vue'
import { Comment, Search, Close, Plus } from '@icon-park/vue-next' import { Comment, Search, Close, Plus, Down, Up } from '@icon-park/vue-next'
import { import {
ServeGroupDetail, ServeGroupDetail,
ServeGetGroupMembers, ServeGetGroupMembers,
@ -13,6 +13,8 @@ import {
} from '@/api/group' } from '@/api/group'
import { useInject } from '@/hooks' import { useInject } from '@/hooks'
import customModal from '@/components/common/customModal.vue' import customModal from '@/components/common/customModal.vue'
import avatarModule from '@/components/avatar-module/index.vue'
import UserCardModal from '@/components/user/UserCardModal.vue'
const userStore = useUserStore() const userStore = useUserStore()
const { showUserInfoModal } = useInject() const { showUserInfoModal } = useInject()
@ -45,7 +47,8 @@ const state = reactive({
name: '', name: '',
profile: '', profile: '',
visit_card: '', visit_card: '',
notice: '' notice: '',
group_type: 0
}, },
remark: '', remark: '',
isShowModal: false, // isShowModal: false, //
@ -58,12 +61,16 @@ const state = reactive({
actionBtns: { actionBtns: {
confirmBtn: true, confirmBtn: true,
cancelBtn: true cancelBtn: true
} // }, //
showAllMember: false, //
openGroupMemberSearch: false, //
isShowUserCardModal: false, //
userInfo: {} //
}) })
const members = ref<any[]>([]) const members = ref<any[]>([])
const search = computed<any[]>(() => { const groupMemberList = computed<any[]>(() => {
if (state.keywords) { if (state.keywords) {
return members.value.filter((item: any) => { return members.value.filter((item: any) => {
return ( return (
@ -110,6 +117,7 @@ function loadDetail() {
state.detail.name = result.group_name state.detail.name = result.group_name
state.detail.profile = result.profile state.detail.profile = result.profile
state.detail.visit_card = result.visit_card state.detail.visit_card = result.visit_card
state.detail.group_type = result.group_type
state.remark = result.visit_card state.remark = result.visit_card
if (result.notice) { if (result.notice) {
@ -127,7 +135,7 @@ function loadMembers() {
group_id: props.gid group_id: props.gid
}).then((res) => { }).then((res) => {
if (res.code == 200) { if (res.code == 200) {
members.value = res.data.items || [] members.value = res.data.sortItems || []
} }
}) })
} }
@ -190,7 +198,7 @@ const showChatSettingOperateModal = (type: string) => {
break break
case 'quit': case 'quit':
state.chatSettingOperateHint = '确定退出群聊' state.chatSettingOperateHint = '确定退出群聊'
const findAdmin = search.value.find((item) => item.leader === 2 || item.leader === 1) const findAdmin = groupMemberList.value.find((item) => item.leader === 2 || item.leader === 1)
const isLastAdmin = findAdmin && findAdmin.user_id === userStore.uid const isLastAdmin = findAdmin && findAdmin.user_id === userStore.uid
if (isLastAdmin) { if (isLastAdmin) {
state.chatSettingOperateSubHint = '退出后,本群将被解散' state.chatSettingOperateSubHint = '退出后,本群将被解散'
@ -204,6 +212,37 @@ const showChatSettingOperateModal = (type: string) => {
const showSearchRecordByConditionModal = () => { const showSearchRecordByConditionModal = () => {
emit('handleSearchRecordByConditionModalShow', true) emit('handleSearchRecordByConditionModalShow', true)
} }
//
const groupTypeText = computed(() => {
let groupTypeText_ = ''
switch (state.detail.group_type) {
case 1:
groupTypeText_ = '普通群'
break
case 2:
groupTypeText_ = '部门群'
break
case 3:
groupTypeText_ = '项目群'
break
case 4:
groupTypeText_ = '公司群'
}
return groupTypeText_
})
//
const cancelSearch = () => {
state.openGroupMemberSearch = false
state.keywords = ''
}
//
const showMemberInfo = (memberItem: any) => {
state.userInfo = memberItem
state.isShowUserCardModal = true
}
</script> </script>
<template> <template>
<section class="el-container is-vertical section"> <section class="el-container is-vertical section">
@ -225,18 +264,106 @@ const showSearchRecordByConditionModal = () => {
<main class="el-main main me-scrollbar me-scrollbar-thumb"> <main class="el-main main me-scrollbar me-scrollbar-thumb">
<div class="info-box" v-if="talkType === 2"> <div class="info-box" v-if="talkType === 2">
<div class="b-box"> <div class="b-box">
<div class="block"> <div class="block" style="height: 34px;">
<div class="title">群成员</div> <div class="title">群成员</div>
<div class="text">{{ members.length }}</div> <!-- <div class="text">{{ members.length }}</div> -->
<img
v-if="!state.openGroupMemberSearch"
src="@/assets/image/icon/search-grey.png"
alt=""
style="width: 16px; height: 16px; cursor: pointer;"
@click="state.openGroupMemberSearch = true"
/>
<div class="group-member-search" v-if="state.openGroupMemberSearch">
<n-input v-model:value="state.keywords" placeholder="请输入" style="width: 170px;" />
<span @click="cancelSearch">取消</span>
</div>
</div>
<!-- <div class="describe">群主已开启新成员入群可查看所有聊天记录</div> -->
<div class="group-member-list">
<div
v-for="(memberItem, memberIndex) in groupMemberList"
:key="memberIndex"
:class="
!state.showAllMember && memberIndex >= 18 && !state.openGroupMemberSearch
? 'group-member-list-each-box'
: ''
"
>
<div
class="group-member-list-each"
v-if="
state.showAllMember ||
(!state.showAllMember && memberIndex < 18 && !state.openGroupMemberSearch) ||
state.openGroupMemberSearch
"
@click="showMemberInfo(memberItem)"
>
<div class="group-member-list-each-avatar">
<avatarModule
:mode="1"
:avatar="memberItem.avatar"
:userName="memberItem.nickname"
:groupType="0"
:customStyle="{
width: '36px',
height: '36px'
}"
:customTextStyle="{
fontSize: '12px',
fontWeight: 'bold',
color: '#fff',
lineHeight: '17px'
}"
></avatarModule>
<span
class="group-member-list-each-admin-tag"
v-if="memberItem.leader == 2 || memberItem.leader == 1"
>管理员</span
>
</div>
<span>{{ memberItem.nickname }}</span>
</div>
</div>
<div
class="group-member-list-more"
v-if="!state.showAllMember && !state.openGroupMemberSearch"
@click="state.showAllMember = true"
>
<span>展开更多</span>
<n-icon :component="Down" />
</div>
<div
class="group-member-list-more"
v-if="state.showAllMember && !state.openGroupMemberSearch"
@click="state.showAllMember = false"
>
<span>收起更多</span>
<n-icon :component="Up" />
</div>
</div> </div>
<div class="describe">群主已开启新成员入群可查看所有聊天记录</div>
</div> </div>
<div class="b-box"> <div class="b-box">
<div class="block"> <div class="block">
<div class="title">群名称</div> <div class="title">群名称</div>
<div>
<span>{{ state.detail.name }}</span>
</div> </div>
<div class="describe">{{ state.detail.name }}</div> </div>
<!-- <div class="describe">{{ state.detail.name }}</div> -->
</div>
<div class="b-box">
<div class="block">
<div class="title">群类型</div>
<div>
<span>{{ groupTypeText }}</span>
</div>
</div>
<!-- <div class="describe">
{{ '暂无群类型' }}
</div> -->
</div> </div>
<!-- <div class="b-box"> <!-- <div class="b-box">
@ -273,16 +400,8 @@ const showSearchRecordByConditionModal = () => {
{{ state.detail.profile ? state.detail.profile : '暂无群简介' }} {{ state.detail.profile ? state.detail.profile : '暂无群简介' }}
</div> </div>
</div> --> </div> -->
<div class="b-box">
<div class="block">
<div class="title">群类型</div>
</div>
<div class="describe">
{{ '暂无群类型' }}
</div>
</div>
<div class="b-box"> <div class="b-box b-box-bottomBorder" style="padding: 0 0 12px;">
<div class="block"> <div class="block">
<div class="title">群公告</div> <div class="title">群公告</div>
<!-- <div class="text"> --> <!-- <div class="text"> -->
@ -295,7 +414,11 @@ const showSearchRecordByConditionModal = () => {
</div> </div>
<div class="info-box"> <div class="info-box">
<div class="b-box b-box-bottomBorder" @click="showSearchRecordByConditionModal"> <div
class="b-box b-box-bottomBorder"
@click="showSearchRecordByConditionModal"
style="cursor: pointer;"
>
<div class="block"> <div class="block">
<div class="title">查找聊天记录</div> <div class="title">查找聊天记录</div>
<img class="icon-right" src="@/assets/image/icon/arrow-right-grey.png" alt="" /> <img class="icon-right" src="@/assets/image/icon/arrow-right-grey.png" alt="" />
@ -317,7 +440,7 @@ const showSearchRecordByConditionModal = () => {
</div> </div>
</div> </div>
<div class="member-box" v-if="talkType === 2"> <!-- <div class="member-box" v-if="talkType === 2">
<div class="flex"> <div class="flex">
<n-input placeholder="搜索" v-model:value="state.keywords" :clearable="true" round> <n-input placeholder="搜索" v-model:value="state.keywords" :clearable="true" round>
<template #prefix> <template #prefix>
@ -339,7 +462,7 @@ const showSearchRecordByConditionModal = () => {
<div class="card">群名片</div> <div class="card">群名片</div>
</div> </div>
<div class="row pointer" v-for="item in search" :key="item.id" @click="onToInfo(item)"> <div class="row pointer" v-for="item in groupMemberList" :key="item.id" @click="onToInfo(item)">
<div class="avatar"> <div class="avatar">
<im-avatar :size="20" :src="item.avatar" :username="item.nickname" /> <im-avatar :size="20" :src="item.avatar" :username="item.nickname" />
</div> </div>
@ -353,7 +476,7 @@ const showSearchRecordByConditionModal = () => {
</div> </div>
</div> </div>
<div class="mt-t20 pd-t20" v-if="search.length == 0"> <div class="mt-t20 pd-t20" v-if="groupMemberList.length == 0">
<n-empty size="200" description="暂无相关数据"> <n-empty size="200" description="暂无相关数据">
<template #icon> <template #icon>
<img src="@/assets/image/no-data.svg" alt="" /> <img src="@/assets/image/no-data.svg" alt="" />
@ -361,14 +484,14 @@ const showSearchRecordByConditionModal = () => {
</n-empty> </n-empty>
</div> </div>
</div> </div>
</div> </div> -->
<div class="chat-settings-btns"> <div class="chat-settings-btns" v-if="talkType === 2">
<n-button class="btn" type="error" ghost @click="showChatSettingOperateModal('clear')"> <n-button class="btn" type="error" ghost @click="showChatSettingOperateModal('clear')">
清空聊天记录 清空聊天记录
</n-button> </n-button>
<n-button <n-button
v-if="talkType === 2 && (isAdmin || isLeader)" v-if="isAdmin || isLeader"
class="btn" class="btn"
type="error" type="error"
ghost ghost
@ -376,19 +499,16 @@ const showSearchRecordByConditionModal = () => {
> >
解散该群 解散该群
</n-button> </n-button>
<n-button <n-button class="btn" type="error" ghost @click="showChatSettingOperateModal('quit')">
v-if="talkType === 2"
class="btn"
type="error"
ghost
@click="showChatSettingOperateModal('quit')"
>
退出群聊 退出群聊
</n-button> </n-button>
</div> </div>
</main> </main>
<!-- <footer class="el-footer footer bdr-t"> --> <footer class="el-footer footer" v-if="talkType === 1">
<n-button class="btn" type="error" ghost @click="showChatSettingOperateModal('clear')">
清空聊天记录
</n-button>
<!-- <template v-if="!isAdmin"> <!-- <template v-if="!isAdmin">
<n-popconfirm negative-text="取消" positive-text="确定" @positive-click="onSignOut"> <n-popconfirm negative-text="取消" positive-text="确定" @positive-click="onSignOut">
<template #trigger> <template #trigger>
@ -407,7 +527,7 @@ const showSearchRecordByConditionModal = () => {
> >
群聊管理 群聊管理
</n-button> --> </n-button> -->
<!-- </footer> --> </footer>
</section> </section>
<GroupLaunch <GroupLaunch
@ -436,6 +556,11 @@ const showSearchRecordByConditionModal = () => {
</div> </div>
</template> </template>
</customModal> </customModal>
<UserCardModal
v-model:show="state.isShowUserCardModal"
v-model:uid="(state.userInfo as any).erp_user_id"
/>
</template> </template>
<style lang="less" scoped> <style lang="less" scoped>
.section { .section {
@ -461,7 +586,7 @@ const showSearchRecordByConditionModal = () => {
.center-text { .center-text {
flex: auto; flex: auto;
font-weight: 500; font-weight: 600;
font-size: 16px; font-size: 16px;
} }
@ -479,7 +604,7 @@ const showSearchRecordByConditionModal = () => {
} }
.main { .main {
padding: 15px 20px; padding: 7px 20px 15px;
.info-box { .info-box {
.b-box { .b-box {
@ -524,6 +649,23 @@ const showSearchRecordByConditionModal = () => {
width: 5px; width: 5px;
height: 9px; height: 9px;
} }
.group-member-search {
display: flex;
align-items: center;
justify-content: center;
gap: 10px;
span {
font-size: 14px;
width: 28px;
display: inline-block;
line-height: 20px;
color: #46299d;
font-weight: 400;
cursor: pointer;
}
}
} }
.describe { .describe {
@ -539,6 +681,83 @@ const showSearchRecordByConditionModal = () => {
line-height: 20px; line-height: 20px;
color: #999999; color: #999999;
} }
.group-member-list {
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-start;
flex-wrap: wrap;
gap: 16px 24px;
padding: 7px 0 16px;
width: 100%;
box-sizing: border-box;
border-bottom: 1px solid #f0f0f2;
.group-member-list-each {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 4px;
cursor: pointer;
.group-member-list-each-avatar {
position: relative;
.group-member-list-each-admin-tag {
font-size: 8px;
font-weight: 400;
line-height: 11px;
color: #fff;
background-color: #cf3050;
border-radius: 8px;
padding: 0px 6px;
position: absolute;
bottom: 0;
left: 0;
width: 36px;
box-sizing: border-box;
}
}
span {
font-size: 12px;
font-weight: 400;
line-height: 17px;
color: #999999;
width: 48px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
display: block;
text-align: center;
}
}
.group-member-list-each-box:nth-child(n + 19) {
display: none;
}
.group-member-list-more {
width: 100%;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
span {
font-size: 14px;
line-height: 20px;
color: #747474;
font-weight: 400;
margin: 0 10px 0 0;
display: inline-block;
}
}
.group-member-list-more:hover {
span {
color: #46299d;
}
}
}
} }
.b-box-bottomBorder { .b-box-bottomBorder {
border-bottom: 1px solid #f0f0f2; border-bottom: 1px solid #f0f0f2;
@ -613,11 +832,13 @@ const showSearchRecordByConditionModal = () => {
align-items: center; align-items: center;
justify-content: center; justify-content: center;
width: 100%; width: 100%;
gap: 30px; gap: 15px;
.btn { .btn {
width: calc(100% - 50px); width: 100%;
background-color: #fff; background-color: #fff;
color: #cf3050; color: #cf3050;
height: 46px;
border-radius: 4px;
} }
} }
@ -625,10 +846,17 @@ const showSearchRecordByConditionModal = () => {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-around; justify-content: space-around;
height: 60px; height: 86px;
padding: 15px; padding: 20px;
.btn { .btn {
width: 48%; width: 100%;
height: 46px;
border-radius: 4px;
:deep(.n-button__content) {
font-size: 14px;
font-weight: 500;
line-height: 20px;
}
} }
} }
} }

View File

@ -1,8 +1,17 @@
<template> <template>
<div class="outer-layer search-by-condition-page"> <div class="outer-layer search-by-condition-page">
<n-infinite-scroll style="height: 455px;" :distance="10">
<div class="root"> <div class="root">
<div v-if="state.condition === 'date'" class="search-by-date"> <div v-if="state.condition === 'dateTimePicker'" class="search-by-date">
<tm-time-picker <n-date-picker
:panel="true"
type="datetime"
:clearable="true"
:first-day-of-week="6"
:is-date-disabled="dateDisabled"
:actions="['clear', 'confirm']"
/>
<!-- <tm-time-picker
:show="state.showMonthPicker" :show="state.showMonthPicker"
:showDetail="{ :showDetail="{
year: true, year: true,
@ -31,8 +40,8 @@
</span> </span>
<img src="@/static/image/search/down-pointer.png" /> <img src="@/static/image/search/down-pointer.png" />
</div> </div>
</tm-time-picker> </tm-time-picker> -->
<tm-calendar-view <!-- <tm-calendar-view
:show="true" :show="true"
:hideTool="true" :hideTool="true"
:hideButton="true" :hideButton="true"
@ -45,7 +54,7 @@
:end="state.maxDate" :end="state.maxDate"
@getDArray="getDArray" @getDArray="getDArray"
:showDefault="false" :showDefault="false"
></tm-calendar-view> ></tm-calendar-view> -->
</div> </div>
<div <div
class="search-by-condition-input-list" class="search-by-condition-input-list"
@ -56,25 +65,25 @@
state.condition === 'member' state.condition === 'member'
" "
:style="{ :style="{
padding: state.condition === 'imgAndVideo' ? '0 14px' : '' padding: state.condition === 'imgAndVideo' ? '20px 38px' : ''
}" }"
> >
<div <!-- <div
class="search-by-condition-input" class="search-by-condition-input"
v-if="state.condition === 'file' || state.condition === 'link'" v-if="state.condition === 'file' || state.condition === 'link'"
> >
<!-- <customInput <customInput
:searchText="state.searchText" :searchText="state.searchText"
:first_talk_record_infos="state.first_talk_record_infos" :first_talk_record_infos="state.first_talk_record_infos"
@inputSearchText="inputSearchText" @inputSearchText="inputSearchText"
></customInput> --> ></customInput>
<span <span
@click="cancelSearch" @click="cancelSearch"
class="search-by-condition-input-text text-[16px] font-medium" class="search-by-condition-input-text text-[16px] font-medium"
> >
取消 取消
</span> </span>
</div> </div> -->
<div class="search-by-condition-list"> <div class="search-by-condition-list">
<div class="condition-dimensionality"> <div class="condition-dimensionality">
<div <div
@ -82,7 +91,10 @@
v-for="(conditionItem, conditionIndex) in state.searchResultList" v-for="(conditionItem, conditionIndex) in state.searchResultList"
:key="conditionIndex" :key="conditionIndex"
> >
<div class="condition-dimensionality-each-month"> <div
class="condition-dimensionality-each-month"
v-if="state.condition === 'imgAndVideo'"
>
<span class="text-[14px] font-regular"> <span class="text-[14px] font-regular">
{{ conditionItem.dateMonth }} {{ conditionItem.dateMonth }}
</span> </span>
@ -99,8 +111,10 @@
:key="index" :key="index"
:style="{ :style="{
border: border:
state.condition === 'imgAndVideo' || state.condition === 'member' ? '0' : '', state.condition === 'imgAndVideo' || state.condition === 'member'
padding: state.condition === 'imgAndVideo' ? '0 0 5px' : '' ? '0'
: '',
padding: state.condition === 'imgAndVideo' ? '' : ''
}" }"
> >
<div class="condition-result-member" v-if="state.condition === 'member'"> <div class="condition-result-member" v-if="state.condition === 'member'">
@ -117,37 +131,60 @@
v-if="state.condition === 'imgAndVideo'" v-if="state.condition === 'imgAndVideo'"
> >
<div <div
class="condition-result-imgAndVideo-area" class="condition-result-imgAndVideo-area condition-result-imgAndVideo-area-imgText"
v-if="item?.extra?.items?.length > 0" v-if="item?.extra?.items?.length > 0"
> >
<n-scrollbar style="height: 131px;">
<div <div
class="condition-result-imgAndVideo-each" class="condition-result-imgAndVideo-each"
v-for="(imgItem, imgIndex) in item?.extra?.items" v-for="(imgItem, imgIndex) in item?.extra?.items"
:key="imgIndex" :key="imgIndex"
> >
<tm-image <n-image
preview
:src="imgItem?.content" :src="imgItem?.content"
v-if="imgItem?.type == 3" v-if="imgItem?.type == 3"
model="aspectFill" :lazy="true"
/> :preview-src="imgItem?.content"
:width="131"
:height="131"
object-fit="cover"
></n-image>
</div> </div>
</n-scrollbar>
</div> </div>
<!-- <div class="condition-result-imgAndVideo-area" v-if="item?.extra?.url"> <div class="condition-result-imgAndVideo-area" v-if="item?.extra?.url">
<template v-if="item?.msg_type === 3"> <template v-if="item?.msg_type === 3">
<tm-image preview :src="item?.extra?.url" model="aspectFill" /> <n-image
:src="item?.extra?.url"
:lazy="true"
:preview-src="item?.extra?.url"
:width="131"
:height="131"
object-fit="cover"
></n-image>
</template> </template>
<template v-else-if="item?.msg_type === 5"> <template v-else-if="item?.msg_type === 5">
<div class="video-preview" @click="onPlay(item?.extra?.url)"> <div class="video-preview" @click="onPlay(item?.extra?.url)">
<tm-image :src="item?.extra?.cover" model="aspectFill" /> <video :src="item?.extra?.url" :controls="false"></video>
<div class="play-icon"> <!-- <n-image
<img :src="playCircle" /> :src="
item?.extra?.url
? item?.extra?.url + '#t=0.001'
: item?.extra?.cover
"
:width="131"
:height="131"
object-fit="cover"
></n-image> -->
<div class="btn-video">
<!-- <img :src="playCircle" /> -->
<n-icon :component="Play" size="40" />
</div> </div>
</div> </div>
</template> </template>
</div> -->
</div> </div>
<div </div>
<!-- <div
class="condition-each-result-main" class="condition-each-result-main"
v-if="state.condition === 'file' || state.condition === 'link'" v-if="state.condition === 'file' || state.condition === 'link'"
> >
@ -155,7 +192,7 @@
<span class="text-[12px] font-medium condition-each-result-main-date"> <span class="text-[12px] font-medium condition-each-result-main-date">
{{ item.dateTime }} {{ item.dateTime }}
</span> </span>
</div> </div> -->
<div <div
class="condition-each-result-attachments" class="condition-each-result-attachments"
@click="previewPDF(item)" @click="previewPDF(item)"
@ -176,6 +213,13 @@
<span class="text-[14px] font-regular" v-if="state.condition === 'link'"> <span class="text-[14px] font-regular" v-if="state.condition === 'link'">
分享链接 分享链接
</span> </span>
<span
class="text-[14px] font-regular"
v-if="state.condition === 'file'"
style="color: #999999;"
>
{{ item.dateTime }}
</span>
</div> </div>
<div <div
class="attachment-sub-info" class="attachment-sub-info"
@ -183,18 +227,17 @@
margin: state.condition === 'file' ? '10px 0 0' : '' margin: state.condition === 'file' ? '10px 0 0' : ''
}" }"
> >
<span class="text-[12px] font-regular" v-if="state.condition === 'file'"> <!-- <span class="text-[12px] font-regular" v-if="state.condition === 'file'">
{{ item?.extra?.typeText }} {{ item?.extra?.typeText }}
</span> </span>
<span
class="text-[12px] font-regular"
v-if="state.condition === 'file'"
style="margin: 0 0 0 10px;"
>
{{ item?.extra?.fileSize }}
</span>
<span class="text-[12px] font-regular" v-if="state.condition === 'link'"> <span class="text-[12px] font-regular" v-if="state.condition === 'link'">
{{ item?.extra?.content }} {{ item?.extra?.content }}
</span> -->
<span class="text-[12px] font-regular" v-if="state.condition === 'file'">
{{ item?.nickname }}
</span>
<span class="text-[12px] font-regular" v-if="state.condition === 'file'">
{{ item?.extra?.fileSize }}
</span> </span>
</div> </div>
</div> </div>
@ -235,38 +278,38 @@
</div> </div>
</teleport> </teleport>
</div> </div>
</n-infinite-scroll>
</div> </div>
</template> </template>
<script setup> <script setup>
// import { useDialogueStore } from '@/store' // import playCircle from '@/assets/image/icon/play-circle.png'
// import customInput from '@/components/custom-input/custom-input.vue' // import customInput from '@/components/custom-input/custom-input.vue'
// import playCircle from '@/static/image/chatList/playCircle@2x.png'
// import fileType_PPT from '@/static/image/search/fileType_PPT.png'
// import fileType_EXCEL from '@/static/image/search/fileType_EXCEL.png'
// import fileType_WORD from '@/static/image/search/fileType_WORD.png'
// import fileType_PDF from '@/static/image/search/fileType_PDF.png'
// import fileType_Files from '@/static/image/search/fileType_Files.png'
// import ZPaging from '@/uni_modules/z-paging/components/z-paging/z-paging.vue' // import ZPaging from '@/uni_modules/z-paging/components/z-paging/z-paging.vue'
// import useZPaging from '@/uni_modules/z-paging/components/z-paging/js/hooks/useZPaging.js' // import useZPaging from '@/uni_modules/z-paging/components/z-paging/js/hooks/useZPaging.js'
// const zPaging = ref() // const zPaging = ref()
// useZPaging(zPaging) // useZPaging(zPaging)
// const dialogueStore = useDialogueStore() import { Play } from '@icon-park/vue-next'
// const dialogueParams = reactive({ import fileType_PPT from '@/assets/image/ppt-text.png'
// talk_type: computed(() => dialogueStore.talk.talk_type), import fileType_EXCEL from '@/assets/image/excel-text.png'
// receiver_id: computed(() => dialogueStore.talk.receiver_id) import fileType_WORD from '@/assets/image/word-text.png'
// }) import fileType_PDF from '@/assets/image/pdf-text.png'
const dialogueParams = reactive({ import fileType_Files from '@/assets/image/file-text.png'
talk_type: 1, import { useDialogueStore } from '@/store'
receiver_id: 1
})
import { onMounted, reactive, computed, ref, nextTick, watch } from 'vue' import { onMounted, reactive, computed, ref, nextTick, watch } from 'vue'
import searchItem from './searchItem.vue' import searchItem from './searchItem.vue'
import { ServeFindTalkRecords } from '@/api/chat.js' import { ServeFindTalkRecords } from '@/api/chat.js'
import { ServeTalkDate, ServeGetSessionId } from '@/api/search.js' import { ServeTalkDate, ServeGetSessionId } from '@/api/search.js'
import { parseTime } from '@/utils/datetime' import { parseTime } from '@/utils/datetime'
import { fileFormatSize, fileSuffix } from '@/utils/strings' import { fileFormatSize, fileSuffix } from '@/utils/strings'
import { NImage, NInfiniteScroll, NScrollbar, NIcon, NDatePicker } from 'naive-ui'
const dialogueStore = useDialogueStore()
//
const dialogueParams = reactive({
talk_type: computed(() => dialogueStore.talk.talk_type),
receiver_id: computed(() => dialogueStore.talk.receiver_id)
})
let nowDay = new Date().setHours(0, 0, 0, 0) let nowDay = new Date().setHours(0, 0, 0, 0)
@ -298,59 +341,6 @@ const state = reactive({
flatList: [] // flatList: [] //
}) })
watch(
() => props?.conditionType,
(newVal, oldVal) => {
console.log(newVal, oldVal)
state.condition = newVal
if (newVal) {
if (newVal === 'member') {
state.showPageTitle = true
state.pageTitle = '按群成员查找'
state.group_member_id = options.groupMemberId
queryAllSearch()
} else if (newVal === 'date') {
state.showPageTitle = true
state.pageTitle = '按日期查找'
ServeQueryTalkDate(parseTime(state.nowDate, '{y}{m}'))
} else if (newVal === 'imgAndVideo') {
state.showPageTitle = true
state.pageTitle = '图片与视频'
state.msg_type = '3,5'
queryAllSearch()
} else if (newVal === 'file') {
console.log(dialogueParams)
let first_talk_record_infos = {
receiver_name: '文件'
}
state.first_talk_record_infos = Object.assign(
{},
state.first_talk_record_infos,
first_talk_record_infos
)
state.msg_type = 6
queryAllSearch()
} else if (newVal === 'link') {
console.log(dialogueParams)
let first_talk_record_infos = {
receiver_name: '链接'
}
state.first_talk_record_infos = Object.assign(
{},
state.first_talk_record_infos,
first_talk_record_infos
)
state.msg_type = 14
queryAllSearch()
}
}
},
{
immediate: true,
deep: true
}
)
const videoContext = ref() const videoContext = ref()
const open = ref(false) const open = ref(false)
const currentVideoUrl = ref('') const currentVideoUrl = ref('')
@ -413,7 +403,7 @@ const ServeQueryTalkDate = (month) => {
(item) => item.substring(0, 4) + '/' + item.substring(4, 6) + '/' + item.substring(6, 8) (item) => item.substring(0, 4) + '/' + item.substring(4, 6) + '/' + item.substring(6, 8)
) )
let disabledDateArray = state.dArray.filter((dIt) => !formattedData.includes(dIt)) let disabledDateArray = state.dArray.filter((dIt) => !formattedData.includes(dIt))
disabledDateArray = disabledDateArray.map((item) => item.replace(/\//g, '-')) disabledDateArray = disabledDateArray.map((item) => item.replace(/\//g, '/'))
console.log(disabledDateArray) console.log(disabledDateArray)
state.disabledDateArray = disabledDateArray state.disabledDateArray = disabledDateArray
} else { } else {
@ -426,6 +416,16 @@ const ServeQueryTalkDate = (month) => {
resp.catch(() => {}) resp.catch(() => {})
} }
//
const dateDisabled = (e) => {
const date = new Date(e)
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
const formattedDate = `${year}/${month}/${day}`
return state.disabledDateArray.includes(formattedDate)
}
// //
const selectDate = async (e) => { const selectDate = async (e) => {
if (e == parseTime(state.nowDate, '{y}/{m}/{d}')) { if (e == parseTime(state.nowDate, '{y}/{m}/{d}')) {
@ -505,7 +505,13 @@ const confirmSelectedMonth = (e) => {
} }
// //
const getDArray = (dArray) => { const getDArray = (selectedMonth) => {
const [year, month] = selectedMonth.split('-').map(Number)
const daysInMonth = new Date(year, month, 0).getDate()
const dArray = Array.from({ length: daysInMonth }, (_, i) => {
const day = i + 1
return `${year}/${String(month).padStart(2, '0')}/${String(day).padStart(2, '0')}`
})
state.dArray = dArray state.dArray = dArray
} }
@ -629,22 +635,21 @@ const queryAllSearch = () => {
// //
const fileTypeAvatar = (fileType) => { const fileTypeAvatar = (fileType) => {
// let file_type_avatar = fileType_Files let file_type_avatar = fileType_Files
// if (fileType) { if (fileType) {
// if (fileType === 'ppt' || fileType === 'pptx') { if (fileType === 'ppt' || fileType === 'pptx') {
// file_type_avatar = fileType_PPT file_type_avatar = fileType_PPT
// } else if (fileType === 'pdf') { } else if (fileType === 'pdf') {
// file_type_avatar = fileType_PDF file_type_avatar = fileType_PDF
// } else if (fileType === 'doc' || fileType === 'docx') { } else if (fileType === 'doc' || fileType === 'docx') {
// file_type_avatar = fileType_WORD file_type_avatar = fileType_WORD
// } else if (fileType === 'xls' || fileType === 'xlsx') { } else if (fileType === 'xls' || fileType === 'xlsx') {
// file_type_avatar = fileType_EXCEL file_type_avatar = fileType_EXCEL
// } else { } else {
// file_type_avatar = fileType_Files file_type_avatar = fileType_Files
// } }
// } }
// return file_type_avatar return file_type_avatar
return ''
} }
const previewPDF = (item) => { const previewPDF = (item) => {
@ -693,9 +698,66 @@ const toDialogueByMember = async (msgInfo) => {
encodeURIComponent(JSON.stringify(msgInfo)) encodeURIComponent(JSON.stringify(msgInfo))
}) })
} }
watch(
() => props?.conditionType,
(newVal, oldVal) => {
console.log(newVal, oldVal)
state.condition = newVal
if (newVal) {
if (newVal === 'member') {
state.showPageTitle = true
state.pageTitle = '按群成员查找'
// state.group_member_id = options.groupMemberId
queryAllSearch()
} else if (newVal === 'dateTimePicker') {
state.showPageTitle = true
state.pageTitle = '按日期查找'
ServeQueryTalkDate(parseTime(state.nowDate, '{y}{m}'))
getDArray(parseTime(state.nowDate, '{y}-{m}'))
} else if (newVal === 'imgAndVideo') {
state.showPageTitle = true
state.pageTitle = '图片与视频'
state.msg_type = '3,5'
queryAllSearch()
} else if (newVal === 'file') {
console.log(dialogueParams)
let first_talk_record_infos = {
receiver_name: '文件'
}
state.first_talk_record_infos = Object.assign(
{},
state.first_talk_record_infos,
first_talk_record_infos
)
state.msg_type = 6
queryAllSearch()
} else if (newVal === 'link') {
console.log(dialogueParams)
let first_talk_record_infos = {
receiver_name: '链接'
}
state.first_talk_record_infos = Object.assign(
{},
state.first_talk_record_infos,
first_talk_record_infos
)
state.msg_type = 14
queryAllSearch()
}
}
},
{
immediate: true,
deep: true
}
)
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.search-by-date { .search-by-date {
:deep(.n-date-panel-header) {
display: none;
}
.search-date-picker { .search-date-picker {
padding: 10px 16px; padding: 10px 16px;
display: flex; display: flex;
@ -729,7 +791,8 @@ body:deep(.round-3) {
} }
.search-by-condition-input-list { .search-by-condition-input-list {
padding: 10px 24px 0 21px; // padding: 10px 24px 0 21px;
padding: 20px 40px;
.search-by-condition-input { .search-by-condition-input {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
@ -746,7 +809,7 @@ body:deep(.round-3) {
.condition-dimensionality { .condition-dimensionality {
.condition-dimensionality-each { .condition-dimensionality-each {
.condition-dimensionality-each-month { .condition-dimensionality-each-month {
padding: 12px 0 5px; padding: 0 0 18px;
span { span {
line-height: 20px; line-height: 20px;
color: #999999; color: #999999;
@ -755,7 +818,6 @@ body:deep(.round-3) {
.condition-each-resultList { .condition-each-resultList {
.condition-each-resultList-each { .condition-each-resultList-each {
border-bottom: 1px solid #f8f8f8; border-bottom: 1px solid #f8f8f8;
padding: 0 0 10px;
.condition-each-result-main { .condition-each-result-main {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
@ -771,8 +833,8 @@ body:deep(.round-3) {
flex-direction: row; flex-direction: row;
align-items: center; align-items: center;
justify-content: flex-start; justify-content: flex-start;
padding: 12px 15px; padding: 14px 0;
background-color: #f3f3f3; // background-color: #f3f3f3;
border-radius: 4px; border-radius: 4px;
.attachment-avatar { .attachment-avatar {
display: flex; display: flex;
@ -791,11 +853,13 @@ body:deep(.round-3) {
align-items: flex-start; align-items: flex-start;
justify-content: center; justify-content: center;
margin: 0 0 0 11px; margin: 0 0 0 11px;
width: 100%;
.attachment-info-title { .attachment-info-title {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
align-items: center; align-items: center;
justify-content: flex-start; justify-content: space-between;
width: 100%;
span { span {
line-height: 20px; line-height: 20px;
color: #191919; color: #191919;
@ -806,7 +870,8 @@ body:deep(.round-3) {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
align-items: center; align-items: center;
justify-content: flex-start; justify-content: space-between;
width: 100%;
span { span {
line-height: 17px; line-height: 17px;
color: #999999; color: #999999;
@ -830,39 +895,66 @@ body:deep(.round-3) {
align-items: center; align-items: center;
justify-content: flex-start; justify-content: flex-start;
flex-wrap: wrap; flex-wrap: wrap;
gap: 12px;
.condition-each-resultList-each { .condition-each-resultList-each {
.condition-result-imgAndVideo { .condition-result-imgAndVideo {
margin: 0 3px; ::-webkit-scrollbar {
display: none;
}
:deep(.overflow) { :deep(.overflow) {
width: 82px !important; width: 131px !important;
height: 82px !important; height: 131px !important;
} }
.condition-result-imgAndVideo-area { .condition-result-imgAndVideo-area {
:deep(.overflow) { :deep(.overflow) {
width: 82px !important; width: 131px !important;
height: 82px !important; height: 131px !important;
} }
:deep(.round-0) { :deep(.round-0) {
width: 82px !important; width: 131px !important;
height: 82px !important; height: 131px !important;
} }
.video-preview { .video-preview {
position: relative; // position: relative;
width: 82px; // .play-icon {
height: 82px; // position: absolute;
cursor: pointer; // top: 50%;
.play-icon { // left: 50%;
position: absolute; // transform: translate(-50%, -50%);
top: 50%; // display: flex;
left: 50%; // align-items: center;
transform: translate(-50%, -50%); // justify-content: center;
display: flex;
align-items: center;
justify-content: center;
img { // img {
width: 40px !important; // width: 40px !important;
height: 40px !important; // height: 40px !important;
// }
// }
display: inline-flex;
position: relative;
cursor: pointer;
width: 131px;
height: 131px;
video {
width: 100%;
height: 100%;
object-fit: cover;
background-color: #333; /* 添加背景色,避免默认显示为灰色 */
}
.btn-video {
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
position: absolute;
cursor: pointer;
color: #fff;
}
&:hover {
.btn-video {
color: #46299d;
} }
} }
} }
@ -871,6 +963,11 @@ body:deep(.round-3) {
} }
} }
} }
.condition-dimensionality-each:nth-child(1) {
.condition-each-result-attachments {
padding: 0 0 14px !important;
}
}
} }
} }
} }

View File

@ -152,11 +152,11 @@ const changeConditionTag = (tag) => {
<n-drawer <n-drawer
v-model:show="state.isShowGroupAside" v-model:show="state.isShowGroupAside"
:width="400" :width="453"
placement="right" placement="right"
:trap-focus="false" :trap-focus="false"
:block-scroll="false" :block-scroll="false"
show-mask="transparent" :show-mask="true || 'transparent'"
to="#drawer-container" to="#drawer-container"
> >
<GroupPanel <GroupPanel
@ -169,7 +169,7 @@ const changeConditionTag = (tag) => {
<customModal <customModal
v-model:show="state.isShowSearchRecordByConditionModal" v-model:show="state.isShowSearchRecordByConditionModal"
:title='`${talkParams.type === 1 ? "与" : ""}"${talkParams.username}"的聊天记录`' :title="`${talkParams.type === 1 ? '与' : ''}&quot;${talkParams.username}&quot;的聊天记录`"
:style="state.customSearchRecordByConditionModalStyle" :style="state.customSearchRecordByConditionModalStyle"
:customCloseBtn="true" :customCloseBtn="true"
:closable="false" :closable="false"
@ -198,17 +198,40 @@ const changeConditionTag = (tag) => {
<div class="search-area-condition"> <div class="search-area-condition">
<span @click="changeConditionTag('file')">文件</span> <span @click="changeConditionTag('file')">文件</span>
<span @click="changeConditionTag('imgAndVideo')">图片与视频</span> <span @click="changeConditionTag('imgAndVideo')">图片与视频</span>
<span @click="changeConditionTag('date')">日期</span> <n-popover trigger="click" placement="bottom-start" style="height: 312px; padding: 0;">
<template #trigger>
<span id="date-condition" @click="changeConditionTag('date')">日期</span>
</template>
<historyRecord conditionType="dateTimePicker" v-if="state.conditionType === 'date'" />
</n-popover>
<n-popover
trigger="click"
placement="bottom-start"
style="height: 505px; padding: 0;"
v-if="talkParams.type === 2"
>
<template #trigger>
<span @click="changeConditionTag('member')">群成员</span> <span @click="changeConditionTag('member')">群成员</span>
</template>
<div>
<text>这里是memberList</text>
</div>
</n-popover>
</div> </div>
</n-card> </n-card>
</div> </div>
<div class="search-record-modal-content"> <div class="search-record-modal-content">
<n-card style="padding: 0 12px;"> <n-card>
<div class="search-record-card" v-if="state.searchRecordByConditionText || state.conditionType"> <div
class="search-record-card"
v-if="state.searchRecordByConditionText || state.conditionType"
>
<historyRecord :conditionType="state.conditionType" /> <historyRecord :conditionType="state.conditionType" />
</div> </div>
<div class="search-record-empty" v-if="!state.searchRecordByConditionText && !state.conditionType"> <div
class="search-record-empty"
v-if="!state.searchRecordByConditionText && !state.conditionType"
>
<img src="@/assets/image/chatList/search-empty.png" alt="" /> <img src="@/assets/image/chatList/search-empty.png" alt="" />
<span>无内容</span> <span>无内容</span>
</div> </div>
@ -261,6 +284,9 @@ const changeConditionTag = (tag) => {
width: 100%; width: 100%;
padding: 0 12px; padding: 0 12px;
margin: 18px 0 0; margin: 18px 0 0;
:deep(.n-card__content) {
padding: 0;
}
:deep(.n-card) { :deep(.n-card) {
border: 0; border: 0;
box-shadow: 0 3px 6px 1px rgba(188, 188, 188, 0.18); box-shadow: 0 3px 6px 1px rgba(188, 188, 188, 0.18);