# Conflicts:
#	src/utils/auth.js
This commit is contained in:
Phoenix 2025-05-19 10:53:11 +08:00
commit c93023effa
16 changed files with 1827 additions and 407 deletions

26
src/api/search.js Normal file
View File

@ -0,0 +1,26 @@
import { post, get, upload } from '@/utils/request'
//ES搜索-主页搜索什么都有、指定用户、指定群、群与用户概览
export const ServeSeachQueryAll = (data = {}) => {
return post('/api/v1/elasticsearch/query-all', data)
}
//ES搜索聊天记录-主页搜索什么都有、聊天记录
export const ServeQueryTalkRecord = (data = {}) => {
return post('/api/v1/elasticsearch/query-talk-record', data)
}
//查看存在聊天记录的天数
export const ServeTalkDate = (data) => {
return post('/api/v1/talk/date', data)
}
//获取会话Id
export const ServeGetSessionId = (data) => {
return post('/api/v1/talk/session/getId', data)
}
//获取用户所在群聊列表
export const ServeUserGroupChatList = (data) => {
return post('/api/v1/group/user/list', data)
}

View File

@ -29,3 +29,8 @@ export const ServeGetUserDetail = () => {
export const ServeGetUserSetting = () => {
return get('/api/v1/users/setting')
}
//根据erpUserId查询聊天系统用户详情
export const getUserInfoByERPUserId = (data) => {
return post('/api/v1/users/info', data)
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 657 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 286 B

View File

@ -1,6 +1,6 @@
<script lang="ts" setup>
import { reactive, computed, watch, ref } from 'vue'
import { NEmpty, NPopover, NPopconfirm } from 'naive-ui'
import { NEmpty, NPopover, NPopconfirm, NSwitch } from 'naive-ui'
import { useUserStore } from '@/store'
import GroupLaunch from './GroupLaunch.vue'
import GroupManage from './manage/index.vue'
@ -17,11 +17,16 @@ import customModal from '@/components/common/customModal.vue'
const userStore = useUserStore()
const { showUserInfoModal } = useInject()
const emit = defineEmits(['close', 'to-talk'])
const emit = defineEmits(['close', 'to-talk', 'handleSearchRecordByConditionModalShow'])
const props = defineProps({
gid: {
type: Number,
default: 0
},
talkType: {
// 1: 2:
type: Number,
default: 0
}
})
@ -53,7 +58,7 @@ const state = reactive({
actionBtns: {
confirmBtn: true,
cancelBtn: true
}, //
} //
})
const members = ref<any[]>([])
@ -195,12 +200,17 @@ const showChatSettingOperateModal = (type: string) => {
break
}
}
const showSearchRecordByConditionModal = () => {
emit('handleSearchRecordByConditionModalShow', true)
}
</script>
<template>
<section class="el-container is-vertical section">
<header class="el-header header bdr-b">
<div class="left-icon" @click="emit('to-talk')">
<n-icon size="21" :component="Comment" />
<header class="el-header header">
<!-- <div class="left-icon" @click="emit('to-talk')"> -->
<div class="left-icon" style="cursor: unset;">
<!-- <n-icon size="21" :component="Comment" /> -->
</div>
<div class="center-text">
<!-- <span>群信息</span> -->
@ -213,7 +223,7 @@ const showChatSettingOperateModal = (type: string) => {
</header>
<main class="el-main main me-scrollbar me-scrollbar-thumb">
<div class="info-box">
<div class="info-box" v-if="talkType === 2">
<div class="b-box">
<div class="block">
<div class="title">群成员</div>
@ -284,7 +294,30 @@ const showChatSettingOperateModal = (type: string) => {
</div>
</div>
<div class="member-box">
<div class="info-box">
<div class="b-box b-box-bottomBorder" @click="showSearchRecordByConditionModal">
<div class="block">
<div class="title">查找聊天记录</div>
<img class="icon-right" src="@/assets/image/icon/arrow-right-grey.png" alt="" />
</div>
</div>
<div class="b-box">
<div class="block">
<div class="title">置顶会话</div>
<n-switch />
</div>
</div>
<div class="b-box">
<div class="block">
<div class="title">消息免打扰</div>
<n-switch />
</div>
</div>
</div>
<div class="member-box" v-if="talkType === 2">
<div class="flex">
<n-input placeholder="搜索" v-model:value="state.keywords" :clearable="true" round>
<template #prefix>
@ -335,7 +368,7 @@ const showChatSettingOperateModal = (type: string) => {
清空聊天记录
</n-button>
<n-button
v-if="isAdmin || isLeader"
v-if="talkType === 2 && (isAdmin || isLeader)"
class="btn"
type="error"
ghost
@ -343,7 +376,13 @@ const showChatSettingOperateModal = (type: string) => {
>
解散该群
</n-button>
<n-button class="btn" type="error" ghost @click="showChatSettingOperateModal('quit')">
<n-button
v-if="talkType === 2"
class="btn"
type="error"
ghost
@click="showChatSettingOperateModal('quit')"
>
退出群聊
</n-button>
</div>
@ -404,8 +443,11 @@ const showChatSettingOperateModal = (type: string) => {
height: 100%;
.header {
width: 100%;
width: calc(100% - 40px);
height: 60px;
margin: 0 20px;
box-sizing: border-box;
border-bottom: 2px solid #efeff2;
display: flex;
align-items: center;
@ -425,7 +467,7 @@ const showChatSettingOperateModal = (type: string) => {
.left-icon,
.right-icon {
width: 50px;
width: 30px;
height: 100%;
flex-shrink: 0;
cursor: pointer;
@ -437,7 +479,7 @@ const showChatSettingOperateModal = (type: string) => {
}
.main {
padding: 15px;
padding: 15px 20px;
.info-box {
.b-box {
@ -498,6 +540,9 @@ const showChatSettingOperateModal = (type: string) => {
color: #999999;
}
}
.b-box-bottomBorder {
border-bottom: 1px solid #f0f0f2;
}
}
.member-box {
@ -572,7 +617,7 @@ const showChatSettingOperateModal = (type: string) => {
.btn {
width: calc(100% - 50px);
background-color: #fff;
color: #CF3050;
color: #cf3050;
}
}

View File

@ -0,0 +1,896 @@
<template>
<div class="outer-layer search-by-condition-page">
<div class="root">
<div v-if="state.condition === 'date'" class="search-by-date">
<tm-time-picker
:show="state.showMonthPicker"
:showDetail="{
year: true,
month: true,
day: false,
hour: false,
minute: false,
second: false,
am_pm: false
}"
:showSuffix="{
year: '',
month: ''
}"
:defaultValue="state.nowDate"
format="YYYY年MM月"
start=""
:end="state.maxDate"
@confirm="confirmSelectedMonth"
:round="0"
:title="'请选择聊天日期'"
>
<div class="search-date-picker">
<span class="text-[14px] font-regular">
{{ state.selectedMonth }}
</span>
<img src="@/static/image/search/down-pointer.png" />
</div>
</tm-time-picker>
<tm-calendar-view
:show="true"
:hideTool="true"
:hideButton="true"
:dateStyle="state.dateStyle"
:defaultValue="state.selectedDateArray"
v-model="state.selectedDateArray"
:disabledDate="state.disabledDateArray"
@click="selectDate"
model="day"
:end="state.maxDate"
@getDArray="getDArray"
:showDefault="false"
></tm-calendar-view>
</div>
<div
class="search-by-condition-input-list"
v-if="
state.condition === 'imgAndVideo' ||
state.condition === 'file' ||
state.condition === 'link' ||
state.condition === 'member'
"
:style="{
padding: state.condition === 'imgAndVideo' ? '0 14px' : ''
}"
>
<div
class="search-by-condition-input"
v-if="state.condition === 'file' || state.condition === 'link'"
>
<!-- <customInput
:searchText="state.searchText"
:first_talk_record_infos="state.first_talk_record_infos"
@inputSearchText="inputSearchText"
></customInput> -->
<span
@click="cancelSearch"
class="search-by-condition-input-text text-[16px] font-medium"
>
取消
</span>
</div>
<div class="search-by-condition-list">
<div class="condition-dimensionality">
<div
class="condition-dimensionality-each"
v-for="(conditionItem, conditionIndex) in state.searchResultList"
:key="conditionIndex"
>
<div class="condition-dimensionality-each-month">
<span class="text-[14px] font-regular">
{{ conditionItem.dateMonth }}
</span>
</div>
<div
class="condition-each-resultList"
:class="[
state.condition === 'imgAndVideo' ? 'condition-type-imgAndVideo-result' : ''
]"
>
<div
class="condition-each-resultList-each"
v-for="(item, index) in conditionItem.monthResultList"
:key="index"
:style="{
border:
state.condition === 'imgAndVideo' || state.condition === 'member' ? '0' : '',
padding: state.condition === 'imgAndVideo' ? '0 0 5px' : ''
}"
>
<div class="condition-result-member" v-if="state.condition === 'member'">
<searchItem
@click="toDialogueByMember(item)"
:searchResultKey="'search_by_member_condition'"
:searchItem="item"
:searchText="state.searchText"
:searchRecordDetail="true"
></searchItem>
</div>
<div
class="condition-result-imgAndVideo"
v-if="state.condition === 'imgAndVideo'"
>
<div
class="condition-result-imgAndVideo-area"
v-if="item?.extra?.items?.length > 0"
>
<div
class="condition-result-imgAndVideo-each"
v-for="(imgItem, imgIndex) in item?.extra?.items"
:key="imgIndex"
>
<tm-image
preview
:src="imgItem?.content"
v-if="imgItem?.type == 3"
model="aspectFill"
/>
</div>
</div>
<!-- <div class="condition-result-imgAndVideo-area" v-if="item?.extra?.url">
<template v-if="item?.msg_type === 3">
<tm-image preview :src="item?.extra?.url" model="aspectFill" />
</template>
<template v-else-if="item?.msg_type === 5">
<div class="video-preview" @click="onPlay(item?.extra?.url)">
<tm-image :src="item?.extra?.cover" model="aspectFill" />
<div class="play-icon">
<img :src="playCircle" />
</div>
</div>
</template>
</div> -->
</div>
<div
class="condition-each-result-main"
v-if="state.condition === 'file' || state.condition === 'link'"
>
<searchItem :searchItem="item" :conditionType="state.msg_type"></searchItem>
<span class="text-[12px] font-medium condition-each-result-main-date">
{{ item.dateTime }}
</span>
</div>
<div
class="condition-each-result-attachments"
@click="previewPDF(item)"
v-if="state.condition === 'file' || state.condition === 'link'"
>
<div class="attachment-avatar">
<img :src="item?.extra?.file_avatar" v-if="state.condition === 'file'" />
<img
src="@/static/image/search/result-link-icon.png"
v-if="state.condition === 'link'"
/>
</div>
<div class="attachment-info">
<div class="attachment-info-title">
<span class="text-[14px] font-regular" v-if="state.condition === 'file'">
{{ item?.extra?.name }}
</span>
<span class="text-[14px] font-regular" v-if="state.condition === 'link'">
分享链接
</span>
</div>
<div
class="attachment-sub-info"
:style="{
margin: state.condition === 'file' ? '10px 0 0' : ''
}"
>
<span class="text-[12px] font-regular" v-if="state.condition === 'file'">
{{ item?.extra?.typeText }}
</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'">
{{ item?.extra?.content }}
</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- <ZPaging
ref="zPaging"
:show-scrollbar="false"
@query="queryAllSearch"
:refresher-enabled="false"
:auto="false"
:loading-more-default-as-loading="true"
:inside-more="true"
v-model="state.flatList"
>
<template #top v-if="state.showPageTitle">
<customNavbar :title="state.pageTitle"></customNavbar>
</template>
</ZPaging> -->
<teleport to="body">
<div v-show="open" class="video-container">
<video
:src="currentVideoUrl"
controls
@fullscreenchange="fullscreenchange"
:id="currentVideoUrl"
playsinline
webkit-playsinline
x5-playsinline
class="fullscreen-video"
></video>
</div>
</teleport>
</div>
</div>
</template>
<script setup>
// import { useDialogueStore } from '@/store'
// 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 useZPaging from '@/uni_modules/z-paging/components/z-paging/js/hooks/useZPaging.js'
// const zPaging = ref()
// useZPaging(zPaging)
// const dialogueStore = useDialogueStore()
// const dialogueParams = reactive({
// talk_type: computed(() => dialogueStore.talk.talk_type),
// receiver_id: computed(() => dialogueStore.talk.receiver_id)
// })
const dialogueParams = reactive({
talk_type: 1,
receiver_id: 1
})
import { onMounted, reactive, computed, ref, nextTick, watch } from 'vue'
import searchItem from './searchItem.vue'
import { ServeFindTalkRecords } from '@/api/chat.js'
import { ServeTalkDate, ServeGetSessionId } from '@/api/search.js'
import { parseTime } from '@/utils/datetime'
import { fileFormatSize, fileSuffix } from '@/utils/strings'
let nowDay = new Date().setHours(0, 0, 0, 0)
const props = defineProps({
conditionType: {
//
type: String,
default: ''
}
})
const state = reactive({
pageTitle: '', //
dateStyle: [], //
nowDate: new Date(nowDay), //
maxDate: new Date(nowDay), //
selectedDateArray: Array(new Date(nowDay)), //
showMonthPicker: false, //
selectedMonth: new Date(nowDay), //
disabledDateArray: [], //
dArray: [], //
showPageTitle: false, //
searchText: '', //
first_talk_record_infos: Object,
searchResultList: [], //
cursor: 0, //
msg_type: 0, //
group_member_id: 0, //id
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 open = ref(false)
const currentVideoUrl = ref('')
const fullscreenchange = (e) => {
if (!e.detail.fullScreen) {
videoContext.value.stop()
videoContext.value.seek(0)
open.value = false
}
}
async function onPlay(url) {
currentVideoUrl.value = url
open.value = true
// DOM
await nextTick()
//
videoContext.value = uni.createVideoContext(url, getCurrentInstance())
setTimeout(() => {
//
videoContext.value.requestFullScreen({ direction: 2 })
//
setTimeout(() => {
videoContext.value.play()
}, 100)
}, 200)
}
onMounted(() => {
state.selectedMonth = parseTime(state.selectedMonth, '{y}年{m}月')
state.dateStyle = [
{
date: state.nowDate, //
text: false, //
color: '#46299D', //.
extra: '今天' //
}
]
})
//
const ServeQueryTalkDate = (month) => {
let params = {
month: month,
talk_type: dialogueParams.talk_type, //12
receiver_id: dialogueParams.receiver_id //id
}
const resp = ServeTalkDate(params)
console.log(resp)
resp.then(({ code, data }) => {
console.log(data)
if (code == 200) {
if (data && data.length > 0) {
const formattedData = data.map(
(item) => item.substring(0, 4) + '/' + item.substring(4, 6) + '/' + item.substring(6, 8)
)
let disabledDateArray = state.dArray.filter((dIt) => !formattedData.includes(dIt))
disabledDateArray = disabledDateArray.map((item) => item.replace(/\//g, '-'))
console.log(disabledDateArray)
state.disabledDateArray = disabledDateArray
} else {
state.disabledDateArray = state.dArray
}
} else {
}
})
resp.catch(() => {})
}
//
const selectDate = async (e) => {
if (e == parseTime(state.nowDate, '{y}/{m}/{d}')) {
console.log('==今日')
state.dateStyle = [
{
date: state.nowDate, //
text: false, //
color: '#46299D', //.
extra: '今天' //
}
]
} else {
state.dateStyle = [
{
date: state.nowDate, //
text: false, //
color: '', //.
extra: '今天' //
},
{
date: new Date(e), //
text: false, //
color: '#46299D' //.
}
]
}
const sessionId = await getSessionId(dialogueParams.talk_type, dialogueParams.receiver_id)
uni.navigateTo({
url:
'/pages/dialog/index?sessionId=' +
sessionId +
'&keepDialogInfo=1' +
'&recordDate=' +
parseTime(e, '{y}-{m}-{d}')
})
}
//Id
const getSessionId = (talk_type, receiver_id) => {
return new Promise((resolve, reject) => {
let params = {
talkType: talk_type,
receiverId: receiver_id
}
const resp = ServeGetSessionId(params)
console.log(resp)
resp.then(({ code, data }) => {
console.log(data)
if (code == 200) {
resolve(data?.sessionId)
} else {
}
})
resp.catch(() => {})
})
}
//
const confirmSelectedMonth = (e) => {
console.log(e)
state.selectedMonth = parseTime(e, '{y}年{m}月')
// console.log()
let newDate = new Date(e)
newDate.setHours(0, 0, 0, 0)
newDate.setDate(1)
state.selectedDateArray = Array(new Date(newDate))
state.dateStyle = [
{
date: state.nowDate, //
text: false, //
color: '', //.
extra: '今天' //
}
]
ServeQueryTalkDate(parseTime(e, '{y}{m}'))
}
//
const getDArray = (dArray) => {
state.dArray = dArray
}
//
const inputSearchText = (e) => {
state.searchText = e
state.cursor = 0
queryAllSearch()
}
//
const cancelSearch = () => {
const pages = getCurrentPages()
if (pages.length > 1) {
uni.navigateBack({
delta: 1
})
} else {
uni.reLaunch({
url: '/pages/index/index'
})
}
}
//
const queryAllSearch = () => {
let params = {
talk_type: dialogueParams.talk_type, //12
receiver_id: dialogueParams.receiver_id, //idid
msg_type: state.msg_type, //0:;2:;3:;4:;5:;6:;7:;9:;11;12
cursor: state.cursor, //
limit: 10, //
no_limit: '', //1
direction: 'up', //downup
start_time: '',
end_time: '',
group_member_user_id: state.group_member_id, //id
file_name: state.msg_type === 6 ? state.searchText : ''
}
console.log(params)
const resp = ServeFindTalkRecords(params)
console.log(resp)
resp.then(({ code, data }) => {
console.log(data)
if (code == 200) {
// cursor0searchResultList
let dateList = state.cursor === 0 ? [] : state.searchResultList
let noMore = false
if (data?.items?.length > 0) {
data.items.forEach((item) => {
item.dateTime = parseTime(item?.created_at, '{m}/{d}')
if (item?.extra) {
item.extra.fileSize = fileFormatSize(item?.extra?.size)
item.extra.typeText = item?.extra?.name ? fileSuffix(item?.extra?.name) : ''
item.extra.file_avatar = fileTypeAvatar(item?.extra?.typeText)
console.log(item.extra.type)
}
let year = new Date(item.created_at).getFullYear()
let month = new Date(item.created_at).getMonth() + 1
let dateMonth =
year == state.nowDate.getFullYear() && month == state.nowDate.getMonth() + 1
? '这个月'
: year + '年' + month + '月'
if (dateList.length > 0) {
let hasAdd = false
dateList.forEach((dateItem) => {
if (dateItem.dateMonth === dateMonth) {
dateItem.monthResultList.push(item)
hasAdd = true
}
})
if (!hasAdd) {
console.log(dateList)
dateList.push({
dateMonth: dateMonth,
monthResultList: [item]
})
}
} else {
dateList.push({
dateMonth: dateMonth,
monthResultList: [item]
})
}
})
} else {
noMore = true
}
//
state.searchResultList = dateList
// z-paging
state.flatList = dateList.reduce((acc, group) => {
return acc.concat(group.monthResultList)
}, [])
if (state.cursor === 0) {
// zPaging.value?.complete(state.flatList)
} else {
// zPaging.value?.completeByNoMore(state.flatList, noMore)
}
state.cursor = data?.cursor
} else {
if (state.cursor === 0) {
state.searchResultList = []
state.flatList = []
}
// zPaging.value?.complete([])
}
})
resp.catch(() => {
if (state.cursor === 0) {
state.searchResultList = []
state.flatList = []
}
// zPaging.value?.complete([])
})
}
//
const fileTypeAvatar = (fileType) => {
// let file_type_avatar = fileType_Files
// if (fileType) {
// if (fileType === 'ppt' || fileType === 'pptx') {
// file_type_avatar = fileType_PPT
// } else if (fileType === 'pdf') {
// file_type_avatar = fileType_PDF
// } else if (fileType === 'doc' || fileType === 'docx') {
// file_type_avatar = fileType_WORD
// } else if (fileType === 'xls' || fileType === 'xlsx') {
// file_type_avatar = fileType_EXCEL
// } else {
// file_type_avatar = fileType_Files
// }
// }
// return file_type_avatar
return ''
}
const previewPDF = (item) => {
console.log(item)
if (typeof plus !== 'undefined') {
downloadAndOpenFile(item)
} else {
document.addEventListener('plusready', () => {
downloadAndOpenFile(item)
})
}
}
const downloadAndOpenFile = (item) => {
uni.showLoading({ title: '加载中...', mask: true })
const downloadUrl = item?.extra?.path
const options = {
filename: '_doc/downloads/' //
}
const dtask = plus.downloader.createDownload(downloadUrl, options, function (d, status) {
if (status === 200) {
uni.hideLoading()
const filePath = d.filename
plus.runtime.openFile(
filePath,
{},
function () {},
function (error) {}
)
} else {
uni.hideLoading()
}
})
dtask.start()
}
//
const toDialogueByMember = async (msgInfo) => {
const sessionId = await getSessionId(dialogueParams.talk_type, dialogueParams.receiver_id)
uni.navigateTo({
url:
'/pages/dialog/index?sessionId=' +
sessionId +
'&keepDialogInfo=1' +
'&msgInfo=' +
encodeURIComponent(JSON.stringify(msgInfo))
})
}
</script>
<style scoped lang="scss">
.search-by-date {
.search-date-picker {
padding: 10px 16px;
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-start;
span {
line-height: 20px;
color: #999999;
}
img {
width: 9px;
height: 6px;
margin: 0 0 0 13px;
}
}
}
body:deep(.text-overflow-1) {
color: #666666 !important;
line-height: 22px !important;
font-size: 16px !important;
font-weight: bold !important;
}
body:deep(.tmicon-times-circle-fill) {
width: 19px;
height: 19px;
}
body:deep(.round-3) {
background: linear-gradient(to right, #674bbc, #46299d);
}
.search-by-condition-input-list {
padding: 10px 24px 0 21px;
.search-by-condition-input {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
.search-by-condition-input-text {
flex-shrink: 0;
margin: 0 0 0 10px;
color: #46299d;
}
}
.search-by-condition-list {
.condition-dimensionality {
.condition-dimensionality-each {
.condition-dimensionality-each-month {
padding: 12px 0 5px;
span {
line-height: 20px;
color: #999999;
}
}
.condition-each-resultList {
.condition-each-resultList-each {
border-bottom: 1px solid #f8f8f8;
padding: 0 0 10px;
.condition-each-result-main {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
.condition-each-result-main-date {
line-height: 17px;
color: #999999;
}
}
.condition-each-result-attachments {
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-start;
padding: 12px 15px;
background-color: #f3f3f3;
border-radius: 4px;
.attachment-avatar {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
flex-shrink: 0;
img {
width: 48px;
height: 48px;
}
}
.attachment-info {
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: center;
margin: 0 0 0 11px;
.attachment-info-title {
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-start;
span {
line-height: 20px;
color: #191919;
word-break: break-all;
}
}
.attachment-sub-info {
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-start;
span {
line-height: 17px;
color: #999999;
word-break: break-all;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
text-overflow: ellipsis;
}
}
}
}
}
}
.condition-type-imgAndVideo-result {
border-bottom: 0;
padding: 0;
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-start;
flex-wrap: wrap;
.condition-each-resultList-each {
.condition-result-imgAndVideo {
margin: 0 3px;
:deep(.overflow) {
width: 82px !important;
height: 82px !important;
}
.condition-result-imgAndVideo-area {
:deep(.overflow) {
width: 82px !important;
height: 82px !important;
}
:deep(.round-0) {
width: 82px !important;
height: 82px !important;
}
.video-preview {
position: relative;
width: 82px;
height: 82px;
cursor: pointer;
.play-icon {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
display: flex;
align-items: center;
justify-content: center;
img {
width: 40px !important;
height: 40px !important;
}
}
}
}
}
}
}
}
}
}
}
.video-container {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background: #000;
z-index: 9999;
display: flex;
align-items: center;
justify-content: center;
}
.fullscreen-video {
width: 100%;
height: 100%;
object-fit: contain;
}
</style>

View File

@ -3,7 +3,10 @@
class="search-item"
:class="props?.conditionType ? 'search-item-condition' : ''"
v-if="resultName"
:style="props.searchResultKey === 'talk_record_infos_receiver' ? 'margin: 12px 0 0' : ''"
:style="{
'margin': props.searchResultKey === 'talk_record_infos_receiver' ? '12px 0 0' : '',
'background-color': props.isClickStay ? '#EEE9F8' : ''
}"
>
<div class="search-item-avatar">
<avatarModule
@ -101,7 +104,11 @@ const props = defineProps({
conditionType: {
type: Number,
default: 0
} //
}, //
isClickStay: {
type: Boolean,
default: false
} //
})
// -
const keyMapping = {
@ -259,8 +266,9 @@ const resultDetail = computed(() => {
flex-direction: row;
align-items: center;
justify-content: flex-start;
padding: 11px 0 12px;
border-bottom: 1px solid #f8f8f8;
padding: 11px 10px 12px;
cursor: pointer;
position: relative;
.search-item-avatar{
position: relative;
@ -331,7 +339,21 @@ const resultDetail = computed(() => {
}
}
}
.search-item::after{
content: '';
display: block;
width: 100%;
height: 1px;
position: absolute;
bottom: 0;
left: 10px;
width: calc(100% - 20px);
background-color: #f8f8f8;
}
.search-item-condition {
border: 0;
}
.search-item:hover {
background-color: #f8f8f8;
}
</style>

View File

@ -34,6 +34,11 @@
:searchItem="item"
:searchText="state.searchText"
:searchRecordDetail="props.searchRecordDetail"
:isClickStay="
props.useClickStay &&
typeof state.clickStayItem === 'string' &&
state.clickStayItem === `${item.talk_type}_${item.receiver_id}`
"
></searchItem>
</div>
</div>
@ -127,16 +132,23 @@
// useZPaging(zPaging)
import searchItem from './searchItem.vue'
import { ref, reactive, defineEmits, defineProps, onMounted } from 'vue'
import { ref, reactive, defineEmits, defineProps, onMounted, watch } from 'vue'
const emits = defineEmits(['toMoreResultPage', 'lastIdChange', 'clickSearchItem'])
const emits = defineEmits([
'toMoreResultPage',
'lastIdChange',
'clickSearchItem',
'clickStayItemChange',
'doLoadMore'
])
const state = reactive({
searchText: '', //
searchResultList: [], //
searchResult: null, //
pageNum: 1, //
uid: 1496 //id
uid: 12303, //id
clickStayItem: '' //item
})
const props = defineProps({
@ -174,394 +186,203 @@ const props = defineProps({
hideFirstRecord: {
type: Boolean,
default: false
} ///
}, ///
useClickStay: {
type: Boolean,
default: false
} //使
})
onMounted(() => {
if (props.searchText) {
state.searchText = props.searchText
queryAllSearch(1, 10)
queryAllSearch()
}
})
//
watch(
() => props.searchResultPageSize,
(newVal, oldVal) => {
queryAllSearch()
}
)
//
watch(
() => props.searchText,
(newVal, oldVal) => {
queryAllSearch()
state.clickStayItem = ''
emits('clickStayItemChange', state.clickStayItem)
}
)
//
const inputSearchText = (e) => {
if (e.trim() != state.searchText.trim()) {
state.pageNum = 1
state.searchResult = null //
emits('lastIdChange', 0, 0, 0)
emits('lastIdChange', 0, 0, 0, '', '')
}
state.searchText = e.trim()
if (!e.trim()) {
state.searchResult = null //
emits('lastIdChange', 0, 0, 0)
emits('lastIdChange', 0, 0, 0, '', '')
}
// zPaging.value?.reload()
queryAllSearch()
}
// ES-
const queryAllSearch = (pageNum, searchResultPageSize) => {
const queryAllSearch = (doClearSearchResult) => {
if (doClearSearchResult) {
state.searchResult = null
}
let params = {
key: state.searchText, //
size: searchResultPageSize
size: props.searchResultPageSize
}
if (props.apiParams) {
let apiParams = JSON.parse(decodeURIComponent(props.apiParams))
params = Object.assign({}, params, apiParams)
}
const data = {
user_infos: [
{
id: 127,
mobile: '17706200252',
nickname: '测试-王溢韬',
avatar:
'https://dci-file-new.bj.bcebos.com/fonchain-main/test/runtime/image/avatar/40/b8ed6fea-6662-416d-8bb3-1fd8a8197061.jpg',
created_at: '2025-03-28 11:33:13',
erp_user_id: 18282
},
{
id: 103,
mobile: '13814969538',
nickname: '王静测试',
avatar: '',
created_at: '2025-03-27 14:44:23',
erp_user_id: 2639
},
{
id: 57,
mobile: '13862027511',
nickname: '王西',
avatar:
'https://dci-file-new.bj.bcebos.com/fonchain-main/test/runtime/image/avatar/40/b8ed6fea-6662-416d-8bb3-1fd8a8197061.jpg',
created_at: '2025-03-27 14:44:23',
erp_user_id: 18229
}
],
user_count: 9,
group_infos: null,
group_member_infos: [
{
id: 79,
group_id: 1,
group_name: '泰丰国际',
group_type: 4,
group_avatar: '',
group_num: 103,
user_id: 103,
user_name: '王静',
user_card: '',
created_at: '2025-03-27 14:44:24'
},
{
id: 55,
group_id: 1,
group_name: '泰丰国际',
group_type: 4,
group_avatar: '',
group_num: 103,
user_id: 57,
user_name: '王西',
user_card: '',
created_at: '2025-03-27 14:44:24'
},
{
id: 35,
group_id: 1,
group_name: '泰丰国际',
group_type: 4,
group_avatar: '',
group_num: 103,
user_id: 37,
user_name: '王雯婷',
user_card: '',
created_at: '2025-03-27 14:44:24'
}
],
group_count: 9,
general_infos: [
{
receiver_id: 40,
receiver_name: '孙志远',
receiver_avatar:
'https://dci-file-new.bj.bcebos.com/fonchain-main/test/runtime/image/avatar/40/b8ed6fea-6662-416d-8bb3-1fd8a8197061.jpg',
group_num: 0,
talk_type: 1,
count: 3,
group_type: 0
},
{
receiver_id: 2,
receiver_name: '吹牛个人群',
receiver_avatar: '',
group_num: 4,
talk_type: 2,
count: 2,
group_type: 3
},
{
receiver_id: 3,
receiver_name: '丰链30自己人',
receiver_avatar:
'https://e-cdn.fontree.cn/fonchain-main/prod/image/default/fonchain-chat/3d273326-d2a5-4ad4-a974-32d020c6b3f9.jpg?width=1080&height=2412',
group_num: 8,
talk_type: 2,
count: 2,
group_type: 3
}
],
record_count: 3
}
if ((data.user_infos || []).length > 0) {
;(data.user_infos || []).forEach((item) => {
item.group_type = 0
})
}
if ((data.group_infos || []).length > 0) {
;(data.group_infos || []).forEach((item) => {
item.group_type = item.type
item.groupTempType = 'group_infos'
})
}
if ((data.group_member_infos || []).length > 0) {
;(data.group_member_infos || []).forEach((item) => {
item.groupTempType = 'group_member_infos'
})
}
if ((data.talk_record_infos || []).length > 0) {
let receiverInfo = JSON.parse(JSON.stringify(data.talk_record_infos[0]))
if (receiverInfo.talk_type === 1) {
//
if (receiverInfo.user_id === state.uid) {
//
}
if (receiverInfo.receiver_id === state.uid) {
//
let temp_id = receiverInfo.receiver_id
let temp_name = receiverInfo.receiver_name
let temp_avatar = receiverInfo.receiver_avatar
receiverInfo.receiver_id = receiverInfo.user_id
receiverInfo.receiver_name = receiverInfo.user_name
receiverInfo.receiver_avatar = receiverInfo.user_avatar
receiverInfo.user_id = temp_id
receiverInfo.user_name = temp_name
receiverInfo.user_avatar = temp_avatar
}
}
state.first_talk_record_infos = Object.assign({}, state.first_talk_record_infos, receiverInfo)
;(data.talk_record_infos || []).forEach((item) => {
item.group_type = 0
})
}
let tempGeneral_infos = Array.isArray(data.general_infos)
? [...data.general_infos]
: data.general_infos
delete data.general_infos
data.combinedGroup = (data.group_infos || []).concat(data.group_member_infos || [])
data.general_infos = tempGeneral_infos
const resp = props.apiRequest(params)
resp.then(({ code, data }) => {
console.log(data)
if (code == 200) {
if ((data.user_infos || []).length > 0) {
;(data.user_infos || []).forEach((item) => {
item.group_type = 0
})
}
if ((data.group_infos || []).length > 0) {
;(data.group_infos || []).forEach((item) => {
item.group_type = item.type
item.groupTempType = 'group_infos'
})
}
if ((data.group_member_infos || []).length > 0) {
;(data.group_member_infos || []).forEach((item) => {
item.groupTempType = 'group_member_infos'
})
}
if ((data.talk_record_infos || []).length > 0) {
let receiverInfo = JSON.parse(JSON.stringify(data.talk_record_infos[0]))
if (receiverInfo.talk_type === 1) {
//
if (receiverInfo.user_id === state.uid) {
//
}
if (receiverInfo.receiver_id === state.uid) {
//
let temp_id = receiverInfo.receiver_id
let temp_name = receiverInfo.receiver_name
let temp_avatar = receiverInfo.receiver_avatar
receiverInfo.receiver_id = receiverInfo.user_id
receiverInfo.receiver_name = receiverInfo.user_name
receiverInfo.receiver_avatar = receiverInfo.user_avatar
receiverInfo.user_id = temp_id
receiverInfo.user_name = temp_name
receiverInfo.user_avatar = temp_avatar
}
}
state.first_talk_record_infos = Object.assign(
{},
state.first_talk_record_infos,
receiverInfo
)
;(data.talk_record_infos || []).forEach((item) => {
item.group_type = 0
})
}
//
let isEmpty = true
let dataKeys = Object.keys(data)
let paginationKey = ''
dataKeys.forEach((item) => {
if (Array.isArray(data[item]) && data[item].length > 0) {
paginationKey = item
isEmpty = false
let tempGeneral_infos = Array.isArray(data.general_infos)
? [...data.general_infos]
: data.general_infos
delete data.general_infos
data.combinedGroup = (data.group_infos || []).concat(data.group_member_infos || [])
data.general_infos = tempGeneral_infos
//
let isEmpty = true
let dataKeys = Object.keys(data)
let paginationKey = ''
dataKeys.forEach((item) => {
if (Array.isArray(data[item]) && data[item].length > 0) {
paginationKey = item
isEmpty = false
}
})
if (isEmpty) {
if (state.pageNum === 1) {
//
state.searchResult = null
// zPaging.value?.complete([])
} else {
//
// zPaging.value?.complete(state.searchResult ? [state.searchResult] : [])
}
} else {
if (props.isPagination) {
if (state.pageNum === 1) {
//
state.searchResult = data
} else {
//
data[paginationKey] = (state.searchResult?.[paginationKey] || []).concat(
data[paginationKey]
)
state.searchResult = data
}
emits(
'lastIdChange',
data.last_id,
data.last_group_id,
data.last_member_id,
data.last_receiver_user_name,
data.last_receiver_group_name
)
let total = data.count
if (props.searchRecordDetail) {
if (state?.first_talk_record_infos?.talk_type === 1) {
total = data.user_record_count
} else if (state?.first_talk_record_infos?.talk_type === 2) {
total = data.group_record_count
}
}
// zPaging.value?.completeByTotal([data], total)
} else {
state.searchResult = data
// zPaging.value?.complete([data])
}
}
state.pageNum = state.pageNum + 1
} else {
if (state.pageNum === 1) {
//
state.searchResult = null
// zPaging.value?.complete([])
} else {
//
// zPaging.value?.complete(state.searchResult ? [state.searchResult] : [])
}
}
})
if (isEmpty) {
if (pageNum === 1) {
//
resp.catch(() => {
if (state.pageNum === 1) {
//
state.searchResult = null
// zPaging.value?.complete([])
} else {
//
//
// zPaging.value?.complete(state.searchResult ? [state.searchResult] : [])
}
} else {
if (props.isPagination) {
if (pageNum === 1) {
//
state.searchResult = data
} else {
//
if (
paginationKey &&
Array.isArray((state?.searchResult && state?.searchResult[paginationKey]) || [])
) {
data[paginationKey] = state.searchResult[paginationKey].concat(data[paginationKey])
}
state.searchResult = data
}
emits(
'lastIdChange',
data.last_id,
data.last_group_id,
data.last_member_id,
data.last_receiver_user_name,
data.last_receiver_group_name
)
let total = data.count
if (props.searchRecordDetail) {
if (state?.first_talk_record_infos?.talk_type === 1) {
total = data.user_record_count
} else if (state?.first_talk_record_infos?.talk_type === 2) {
total = data.group_record_count
}
}
// zPaging.value?.completeByTotal([data], total)
} else {
state.searchResult = data
// zPaging.value?.complete([data])
}
}
// const resp = props.apiRequest(params)
// resp.then(({ code, data }) => {
// console.log(data)
// if (code == 200) {
// if ((data.user_infos || []).length > 0) {
// ;(data.user_infos || []).forEach((item) => {
// item.group_type = 0
// })
// }
// if ((data.group_infos || []).length > 0) {
// ;(data.group_infos || []).forEach((item) => {
// item.group_type = item.type
// item.groupTempType = 'group_infos'
// })
// }
// if ((data.group_member_infos || []).length > 0) {
// ;(data.group_member_infos || []).forEach((item) => {
// item.groupTempType = 'group_member_infos'
// })
// }
// if ((data.talk_record_infos || []).length > 0) {
// let receiverInfo = JSON.parse(JSON.stringify(data.talk_record_infos[0]))
// if (receiverInfo.talk_type === 1) {
// //
// if (receiverInfo.user_id === state.uid) {
// //
// }
// if (receiverInfo.receiver_id === state.uid) {
// //
// let temp_id = receiverInfo.receiver_id
// let temp_name = receiverInfo.receiver_name
// let temp_avatar = receiverInfo.receiver_avatar
// receiverInfo.receiver_id = receiverInfo.user_id
// receiverInfo.receiver_name = receiverInfo.user_name
// receiverInfo.receiver_avatar = receiverInfo.user_avatar
// receiverInfo.user_id = temp_id
// receiverInfo.user_name = temp_name
// receiverInfo.user_avatar = temp_avatar
// }
// }
// state.first_talk_record_infos = Object.assign(
// {},
// state.first_talk_record_infos,
// receiverInfo,
// )
// ;(data.talk_record_infos || []).forEach((item) => {
// item.group_type = 0
// })
// }
// let tempGeneral_infos = Array.isArray(data.general_infos)
// ? [...data.general_infos]
// : data.general_infos
// delete data.general_infos
// data.combinedGroup = (data.group_infos || []).concat(
// data.group_member_infos || [],
// )
// data.general_infos = tempGeneral_infos
// //
// let isEmpty = true
// let dataKeys = Object.keys(data)
// let paginationKey = ''
// dataKeys.forEach((item) => {
// if (Array.isArray(data[item]) && data[item].length > 0) {
// paginationKey = item
// isEmpty = false
// }
// })
// if (isEmpty) {
// if (pageNum === 1) {
// //
// state.searchResult = null
// zPaging.value?.complete([])
// } else {
// //
// zPaging.value?.complete(state.searchResult ? [state.searchResult] : [])
// }
// } else {
// if (props.isPagination) {
// if (pageNum === 1) {
// //
// state.searchResult = data
// } else {
// //
// if (
// paginationKey &&
// Array.isArray(
// (state?.searchResult && state?.searchResult[paginationKey]) || [],
// )
// ) {
// data[paginationKey] = state.searchResult[paginationKey].concat(
// data[paginationKey],
// )
// }
// state.searchResult = data
// }
// emits(
// 'lastIdChange',
// data.last_id,
// data.last_group_id,
// data.last_member_id,
// data.last_receiver_user_name,
// data.last_receiver_group_name,
// )
// let total = data.count
// if (props.searchRecordDetail) {
// if (state?.first_talk_record_infos?.talk_type === 1) {
// total = data.user_record_count
// } else if (state?.first_talk_record_infos?.talk_type === 2) {
// total = data.group_record_count
// }
// }
// zPaging.value?.completeByTotal([data], total)
// } else {
// state.searchResult = data
// zPaging.value?.complete([data])
// }
// }
// } else {
// if (pageNum === 1) {
// //
// state.searchResult = null
// zPaging.value?.complete([])
// } else {
// //
// zPaging.value?.complete(state.searchResult ? [state.searchResult] : [])
// }
// }
// })
// resp.catch(() => {
// if (pageNum === 1) {
// //
// state.searchResult = null
// zPaging.value?.complete([])
// } else {
// //
// zPaging.value?.complete(state.searchResult ? [state.searchResult] : [])
// }
// })
})
}
//
@ -648,6 +469,12 @@ const toMoreResultPage = (searchResultKey) => {
//
const clickSearchItem = (searchResultKey, searchItem) => {
console.log(searchResultKey, searchItem)
if (props.useClickStay) {
state.clickStayItem = searchItem.talk_type + '_' + searchItem.receiver_id
} else {
state.clickStayItem = ''
}
emits('clickStayItemChange', state.clickStayItem)
let talk_type = searchItem.talk_type
let receiver_id = searchItem.receiver_id
if (searchResultKey === 'user_infos') {
@ -684,6 +511,16 @@ const clickSearchItem = (searchResultKey, searchItem) => {
encodeURIComponent(JSON.stringify(searchItem))
)
}
//
const doLoadMore = (doClearSearchResult) => {
queryAllSearch(doClearSearchResult)
}
// doLoadMore
defineExpose({
doLoadMore
})
</script>
<style lang="scss" scoped>
.search-list {
@ -706,7 +543,7 @@ const clickSearchItem = (searchResultKey, searchItem) => {
}
.search-result {
width: 100%;
padding: 0 10px;
// padding: 0 10px;
display: flex;
flex-direction: row;
align-items: flex-start;
@ -715,13 +552,13 @@ const clickSearchItem = (searchResultKey, searchItem) => {
.search-result-list {
width: 100%;
// padding: 0 9px;
// padding: 0 10px;
.search-result-part {
margin: 18px 0 0;
.result-title {
padding: 0 0 5px;
padding: 0 10px 5px;
border-bottom: 1px solid #f8f8f8;
span {
line-height: 20px;
@ -729,13 +566,17 @@ const clickSearchItem = (searchResultKey, searchItem) => {
}
}
.result-has-more {
padding: 10px 0;
padding: 10px;
border-bottom: 1px solid #f8f8f8;
cursor: pointer;
span {
color: #191919;
line-height: 20px;
}
}
.result-has-more:hover {
background-color: #f8f8f8;
}
}
}
}

View File

@ -1,6 +1,6 @@
import dropsize from './dropsize'
import focus from './focus'
import loading from './loading'
import dropsize from './dropsize.js'
import focus from './focus.js'
import loading from './loading.js'
const directives = {
dropsize,

View File

@ -0,0 +1,118 @@
import { Local } from "@/utils/erpStorage.js";
import {debounce} from 'lodash-es'
import { setupDirective as setupCustomDirectives } from '@/directive/index.ts'
// 缓存按钮权限列表的key
const BTN_PERMISSION_KEY = 'ruleBtn';
// 工具函数: 获取权限列表
const getPermissionList = () => Local.get(BTN_PERMISSION_KEY) || [];
// 工具函数: 安全地移除DOM元素
const safeRemoveElement = (el) => {
el.style.display = 'none';
requestAnimationFrame(() => el.parentNode?.removeChild(el));
};
export default (app) => {
// 防抖函数
const directives = {
// 禁止输入空格
'no-space': {
mounted(el) {
const input = el.tagName === 'INPUT' ? el : el.querySelector('input');
if (!input) return;
const handleInput = debounce((e) => {
const value = e.target.value;
const trimmedValue = value.replace(/\s/g, '');
if (value !== trimmedValue) {
e.target.value = trimmedValue;
// 创建一次性的input事件
const inputEvent = new InputEvent('input', { bubbles: true });
e.target.dispatchEvent(inputEvent);
}
}, 100);
input.addEventListener('input', handleInput);
// 存储清理函数
el._cleanup = () => input.removeEventListener('input', handleInput);
},
unmounted(el) {
// 清理事件监听
el._cleanup?.();
}
},
// 上拉加载更多
'loadmore': {
mounted(el, binding) {
const handleScroll = debounce(() => {
const { scrollHeight, scrollTop, clientHeight } = el;
const threshold = 50; // 提前触发的距离
const isNearBottom = scrollHeight - scrollTop - clientHeight <= threshold;
if (isNearBottom && typeof binding.value === 'function') {
binding.value();
}
}, 200);
el.addEventListener('scroll', handleScroll, { passive: true });
el._cleanup = () => el.removeEventListener('scroll', handleScroll);
},
unmounted(el) {
el._cleanup?.();
}
},
// 按钮权限控制
'permission': {
mounted(el, binding) {
if (!binding.value) return;
const btnList = getPermissionList();
const hasPermission = btnList.includes(binding.value);
if (!hasPermission) {
safeRemoveElement(el);
}
}
}
};
// 注册指令
Object.entries(directives).forEach(([name, directive]) => {
app.directive(name, directive);
});
// 权限检查函数
const hasPermission = (permissions) => {
const permissionList = getPermissionList();
if (!permissionList.length) return false;
const requiredPermissions = Array.isArray(permissions) ? permissions : [permissions];
return requiredPermissions.some(permission => permissionList.includes(permission));
};
// 缓存代理以提高性能
// 注册全局权限检查方法
app.config.globalProperties.$hadRule = new Proxy(hasPermission, {
// 创建缓存Map
cache: new Map(),
// 代理函数调用
apply(target, thisArg, args) {
// 将参数序列化作为缓存key
const key = JSON.stringify(args);
// 如果缓存中存在,直接返回缓存结果
if (this.cache.has(key)) {
return this.cache.get(key);
}
// 如果缓存中不存在,执行原函数并缓存结果
const result = target.apply(thisArg, args);
this.cache.set(key, result);
return result;
}
});
// 注册 src/directive 里的 loading、dropsize、focus 等指令
setupCustomDirectives(app)
};

View File

@ -5,3 +5,8 @@ export * from './naive-ui'
export * from './sms-lock'
export * from './ws-socket'
export * from './pinia'
import directive from './directive'
export function setupDirective(app) {
directive(app)
}

View File

@ -18,7 +18,7 @@ export function isLoggedIn() {
*/
export function getAccessToken() {
// return storage.get(AccessToken) || ''
return JSON.parse(localStorage.getItem('token'))||'46d71a72d8d845ad7ed23eba9bdde260e635407190c2ce1bf7fd22088e41682ea07773ec65cae8946d2003f264d55961f96e0fc5da10eb96d3a348c1664e9644ce2108c311309f398ae8ea1b8200bfd490e5cb6e8c52c9e5d493cbabb163368f8351420451a631dbfa749829ee4cda49b77b5ed2d3dced5d0f2b7dd9ee76ba5465c84a17c23af040cd92b6b2a4ea48befbb5c729dcdad0a9c9668befe84074cc24f78899c1d947f8e7f94c7eda5325b8ed698df729e76febb98549ef3482ae942fb4f4a1c92d21836fa784728f0c5483aab2760a991b6b36e6b10c84f840a6433a6ecc31dee36e8f1c6158818bc89d22851b5e2f6cbf01ab4b3da6e3d06b57c8f750e106226a5a4b9d7fc1d381a54cb92375c09ba1fa8e5fde0392d919c2f2cbcc7e4e2eca8d9860749af00374b249f7d04e2bc43a1fa4e7d7384dde0212f0a5'
return JSON.parse(localStorage.getItem('token'))||'79b5c732d96d2b27a48a99dfd4a5566c43aaa5796242e854ebe3ffc198d6876b9628e7b764d9af65ab5dbb2d517ced88170491b74b048c0ba827c0d3741462cb89dc59ed46653a449af837a8262941caaef1334d640773710f8cd96473bacfb190cba595a5d6a9c87d70f0999a3ebb41147213b31b4bdccffca66a56acf3baab5af0154f0dce360079f37709f78e13711036899344bddb0fb4cf0f2890287cb62c3fcbe33368caa5e213624577be8b8420ab75b1f50775ee16142a4321c5d56995f37354a66a969da98d95ba6e65d142ed097e04b411c1ebad2f62866d0ec7e1838420530a9941dbbcd00490199f8b8962d32ca9acda2e80272e2558c2a366762349b830b666ec5fa17a2d4304de907fc1e15f4bef1a68f350975080df1c9577b69543e38d63124a06b7b2218b126be05c2669b3dcdb9c8a30daf38c781bfb6a'
}
/**

View File

@ -1,6 +1,6 @@
<script lang="ts" setup>
import { reactive, computed,watch } from 'vue'
import { NDrawer } from 'naive-ui'
import { reactive, computed, watch } from 'vue'
import { NDrawer, NCard, NTag } from 'naive-ui'
import { useUserStore, useDialogueStore, useUploadsStore } from '@/store'
import PanelHeader from './panel/PanelHeader.vue'
import PanelContent from './panel/PanelContent.vue'
@ -8,6 +8,8 @@ import PanelFooter from './panel/PanelFooter.vue'
import GroupPanel from '@/components/group/GroupPanel.vue'
import GroupNotice from '@/components/group/GroupNotice.vue'
import UploadsModal from '@/components/base/UploadsModal.vue'
import customModal from '@/components/common/customModal.vue'
import historyRecord from '@/components/search/searchByCondition.vue'
const userStore = useUserStore()
const dialogueStore = useDialogueStore()
@ -31,8 +33,16 @@ const talkParams = reactive({
const state = reactive({
//
isShowGroupAside: false,
isShowGroupNotice: false
isShowGroupNotice: false,
isShowSearchRecordByConditionModal: false, //
customSearchRecordByConditionModalStyle: {
width: '997px',
height: '740px',
backgroundColor: '#F9F9FD'
}, //
searchRecordByConditionText: '', //
conditionTag: '', //
conditionType: '' //
})
const events = {
@ -44,15 +54,34 @@ const events = {
}
}
watch(() => talkParams, (newValue, oldValue) => {
console.log(newValue);
},{deep:true,immediate:true})
watch(
() => talkParams,
(newValue, oldValue) => {
console.log(newValue)
},
{ deep: true, immediate: true }
)
// Header
const onPanelHeaderEvent = (eventType: string) => {
events[eventType] && events[eventType]()
}
///
const changeConditionTag = (tag) => {
console.log(tag)
state.conditionType = tag
if (tag === 'file') {
state.conditionTag = '文件'
} else if (tag === 'imgAndVideo') {
state.conditionTag = '图片与视频'
} else if (tag === 'date') {
state.conditionTag = '日期'
} else if (tag === 'member') {
state.conditionTag = '群成员'
} else {
state.conditionTag = ''
}
}
</script>
<template>
@ -130,12 +159,131 @@ const onPanelHeaderEvent = (eventType: string) => {
show-mask="transparent"
to="#drawer-container"
>
<GroupPanel :gid="talkParams.receiver_id" @close="state.isShowGroupAside = false" />
<GroupPanel
:gid="talkParams.receiver_id"
@close="state.isShowGroupAside = false"
:talkType="talkParams.type"
@handleSearchRecordByConditionModalShow="state.isShowSearchRecordByConditionModal = true"
/>
</n-drawer>
<customModal
v-model:show="state.isShowSearchRecordByConditionModal"
:title='`${talkParams.type === 1 ? "与" : ""}"${talkParams.username}"的聊天记录`'
:style="state.customSearchRecordByConditionModalStyle"
:customCloseBtn="true"
:closable="false"
>
<template #content>
<div class="search-record-modal-searchArea">
<n-card style="padding: 0 12px;">
<div class="search-record-input">
<span class="search-record-input-title">搜索</span>
<n-input
type="text"
v-model:value="state.searchRecordByConditionText"
placeholder="请输入"
clearable
>
<template #clear-icon>
<img src="@/assets/image/icon/close-btn-grey-line.png" alt="close" />
</template>
<template #prefix>
<n-tag closable v-if="state.conditionTag" @close="changeConditionTag('')">
{{ state.conditionTag }}
</n-tag>
</template>
</n-input>
</div>
<div class="search-area-condition">
<span @click="changeConditionTag('file')">文件</span>
<span @click="changeConditionTag('imgAndVideo')">图片与视频</span>
<span @click="changeConditionTag('date')">日期</span>
<span @click="changeConditionTag('member')">群成员</span>
</div>
</n-card>
</div>
<div class="search-record-modal-content">
<n-card style="padding: 0 12px;">
<div class="search-record-card" v-if="state.searchRecordByConditionText || state.conditionType">
<historyRecord :conditionType="state.conditionType" />
</div>
<div class="search-record-empty" v-if="!state.searchRecordByConditionText && !state.conditionType">
<img src="@/assets/image/chatList/search-empty.png" alt="" />
<span>无内容</span>
</div>
</n-card>
</div>
</template>
</customModal>
</template>
<style lang="less" scoped>
.drawer-target {
overflow: hidden;
}
.search-record-modal-searchArea {
box-sizing: border-box;
width: 100%;
padding: 0 12px;
:deep(.n-card) {
border: 0;
box-shadow: 0 3px 6px 1px rgba(188, 188, 188, 0.18);
}
:deep(.n-card__content) {
padding: 27px 12px 0;
}
.search-record-input {
display: flex;
align-items: center;
justify-content: center;
.search-record-input-title {
width: 78px;
}
}
.search-area-condition {
display: flex;
align-items: center;
justify-content: flex-start;
padding: 22px 0 11px;
span {
font-size: 14px;
color: #46299d;
font-weight: 400;
line-height: 20px;
margin: 0 62px 0 0;
cursor: pointer;
}
}
}
.search-record-modal-content {
box-sizing: border-box;
width: 100%;
padding: 0 12px;
margin: 18px 0 0;
:deep(.n-card) {
border: 0;
box-shadow: 0 3px 6px 1px rgba(188, 188, 188, 0.18);
}
.search-record-card {
}
.search-record-empty {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 455px;
box-sizing: border-box;
img {
width: 160px;
height: 104px;
}
span {
font-size: 14px;
color: #999;
font-weight: 400;
margin: 13px 0 0;
}
}
}
</style>

View File

@ -7,7 +7,8 @@ import {
reactive,
onBeforeMount,
getCurrentInstance,
h
h,
nextTick
} from 'vue'
import { onBeforeRouteUpdate } from 'vue-router'
import { useDialogueStore, useTalkStore } from '@/store'
@ -36,6 +37,11 @@ import xNDataTable from '@/components/x-naive-ui/x-n-data-table/index.vue'
import flTree from '@/components/flnlayout/tree/flnindex.vue'
import { processError, processSuccess } from '@/utils/helper/message.js'
import chatAppSearchList from '@/components/search/searchList.vue'
import { ServeSeachQueryAll, ServeQueryTalkRecord, ServeUserGroupChatList } from '@/api/search'
import { getUserInfoByERPUserId } from '@/api/user'
import { useRouter } from 'vue-router'
const router = useRouter()
const currentInstance = getCurrentInstance()
const $request = currentInstance?.appContext.config.globalProperties?.$request
@ -79,24 +85,28 @@ const renderChatAppSearch = () => {
console.log(searchText, searchResultKey, talk_type, receiver_id)
const result = JSON.parse(decodeURIComponent(res))
console.log(result)
},
onToMoreResultPage: (searchResultKey, searchText) => {
if (searchResultKey === 'general_infos') {
state.ServeQueryTalkRecordParams = encodeURIComponent(
JSON.stringify({
talk_type: 0, //12
receiver_id: 0, //
last_group_id: 0, //id
last_member_id: 0, //id
last_receiver_user_name: '', //
last_receiver_group_name: '' //
})
)
state.isShowSearchRecordModal = true
}
console.log(searchResultKey, searchText)
}
},
{}
)
}
//ES-
const ServeSeachQueryAll = () => {
let url = '/api/v1/elasticsearch/query-all'
let params = {}
let config = {
baseURL: import.meta.env.VITE_BASE_API
}
return $request.HTTP.components.postDataByParams(url, params, config).then((res) => {
console.log(res)
})
}
const state = reactive({
isShowAddressBookModal: false, //
customModalStyle: {
@ -174,7 +184,7 @@ const state = reactive({
field: 'groupName',
width: 200,
render(row, index) {
return row.groupName
return row.group_name
}
},
{
@ -183,7 +193,16 @@ const state = reactive({
width: 400,
ellipsis: true,
render(row, index) {
return row.groupType
let groupType = row.group_type
if (groupType == 1) {
return '普通群'
} else if (groupType == 2) {
return '部门群'
} else if (groupType == 3) {
return '项目群'
} else if (groupType == 4) {
return '公司群'
}
}
},
{
@ -225,7 +244,17 @@ const state = reactive({
type: 'render',
render: renderChatAppSearch
}
] //
], //
isShowSearchRecordModal: false, //
customSearchRecordModalStyle: {
width: '997px',
height: '740px',
backgroundColor: '#F9F9FD'
}, //
searchRecordText: '', //
ServeQueryTalkRecordParams: '', //
ServeQueryTalkRecordDetailParams: '', //
isShowSearchRecordDetailInfo: false //
})
const items = computed((): ISession[] => {
@ -260,10 +289,39 @@ watch(
state.addressBookTableWidth = 800
state.clickKey = 3
state.treeRefreshCount++
state.addressBookPage = 1
}
getDepPoisUser()
}
)
watch(
() => state.groupChatListSearchGroupName,
(newValue, oldValue) => {
if (newValue) {
state.groupChatListPage = 1
} else {
state.groupChatListPage = 1
}
getUserGroupChatList()
}
)
// watch(
// () => state.searchRecordText,
// (newValue, oldValue) => {
// console.log(newValue, 'newValue')
// state.ServeQueryTalkRecordParams = encodeURIComponent(
// JSON.stringify({
// talk_type: 0, //12
// receiver_id: 0, //
// last_group_id: 0, //id
// last_member_id: 0, //id
// last_receiver_user_name: '', //
// last_receiver_group_name: '' //
// })
// )
// }
// )
//
const loadStatus = computed(() => talkStore.loadStatus)
@ -323,6 +381,7 @@ onBeforeRouteUpdate(onInitialize)
onBeforeMount(() => {
getTreeData()
getUserGroupChatList()
})
onMounted(() => {
@ -397,8 +456,40 @@ const getDepPoisUser = () => {
})
}
//
const handleEnterChat = (row) => {
const handleEnterChat = async (row) => {
console.log(row)
if (state.addressBookCurrentTab === 'employeeAddressBook') {
//
await getUserInfoByERPUserId({ erp_user_id: row.ID }).then((res) => {
// console.log(res)
if (res.code === 200) {
let sysUserInfo = res.data
talkStore.toTalk(1, sysUserInfo.sys_id, router)
}
})
} else if (state.addressBookCurrentTab === 'groupChatList') {
//
talkStore.toTalk(2, row.id, router)
}
state.isShowAddressBookModal = false
resetAddressBookModal()
}
//
const resetAddressBookModal = () => {
nextTick(() => {
state.addressBookCurrentTab = 'employeeAddressBook'
state.addressBookSearchNickName = ''
state.groupChatListSearchGroupName = ''
state.addressBookTableWidth = 800
state.clickKey = 3
state.treeRefreshCount++
state.addressBookPage = 1
state.addressBookPageSize = 10
state.groupChatListPage = 1
state.groupChatListPageSize = 10
getDepPoisUser()
getUserGroupChatList()
})
}
//
const handleAddressBookPagination = (page) => {
@ -427,16 +518,121 @@ const handleAddressBookTabChange = (value) => {
//
const changeGroupChatListSearch = (value) => {
console.log(value, 'value')
if (!value.groupName?.trim()) {
state.groupChatListSearchGroupName = ''
} else {
state.groupChatListSearchGroupName = value.groupName
}
}
//
const getUserGroupChatList = () => {
let params = {
page: state.groupChatListPage,
page_size: state.groupChatListPageSize,
group_name: state.groupChatListSearchGroupName
}
ServeUserGroupChatList(params).then((res) => {
// console.log(res)
if (res.code === 200) {
state.groupChatListData = res?.data?.items || []
state.groupChatListTotal = res?.data?.total || 0
}
})
}
//
const handleGroupChatListPagination = (value) => {
console.log(value, 'value')
state.groupChatListPage = value
getUserGroupChatList()
}
//
const handleGroupChatListPaginationSize = (value) => {
console.log(value, 'value')
state.groupChatListPageSize = value
state.groupChatListPage = 1
getUserGroupChatList()
}
//
const handleClickSearchItem = (searchText, searchResultKey, talk_type, receiver_id, res) => {
console.log(searchText, searchResultKey, talk_type, receiver_id)
const result = JSON.parse(decodeURIComponent(res))
console.log(result)
if (searchResultKey === 'general_infos') {
state.ServeQueryTalkRecordDetailParams = encodeURIComponent(
JSON.stringify({
last_group_id: 0, //id
last_member_id: 0, //id
receiver_id: receiver_id, //
talk_type: talk_type //12
})
)
nextTick(() => {
searchDetailListRef.value?.doLoadMore(true)
})
}
}
//item
const handleClickStayItemChange = (item) => {
if (item) {
state.isShowSearchRecordDetailInfo = true
} else {
state.isShowSearchRecordDetailInfo = false
}
}
// ref
const searchListRef = ref()
// ref
const searchDetailListRef = ref()
//
const loadMoreRecordList = () => {
searchListRef.value?.doLoadMore()
}
//
const loadMoreRecordDetail = () => {
searchDetailListRef.value?.doLoadMore()
}
const handleMoreRecordLastIdChange = (
last_id,
last_group_id,
last_member_id,
last_receiver_user_name,
last_receiver_group_name
) => {
let idChanges = {
last_id,
last_group_id,
last_member_id,
last_receiver_user_name,
last_receiver_group_name
}
state.ServeQueryTalkRecordParams = encodeURIComponent(
JSON.stringify(
Object.assign({}, JSON.parse(decodeURIComponent(state.ServeQueryTalkRecordParams)), idChanges)
)
)
}
const handleRecordDetailLastIdChange = (last_id, last_group_id, last_member_id) => {
let idChanges = {
last_id,
last_group_id,
last_member_id
}
state.ServeQueryTalkRecordDetailParams = encodeURIComponent(
JSON.stringify(
Object.assign(
{},
JSON.parse(decodeURIComponent(state.ServeQueryTalkRecordDetailParams)),
idChanges
)
)
)
}
</script>
@ -645,6 +841,70 @@ const handleGroupChatListPaginationSize = (value) => {
</div>
</template>
</customModal>
<customModal
v-model:show="state.isShowSearchRecordModal"
title="搜索聊天记录"
:style="state.customSearchRecordModalStyle"
:customCloseBtn="true"
:closable="false"
>
<template #content>
<div class="search-record-modal-content">
<n-card style="padding: 0 12px;">
<div class="search-record-input">
<span class="search-record-input-title">搜索</span>
<n-input
type="text"
v-model:value="state.searchRecordText"
placeholder="请输入"
clearable
>
<template #clear-icon>
<img src="@/assets/image/icon/close-btn-grey-line.png" alt="close" />
</template>
</n-input>
</div>
<div class="search-record-card" v-if="state.searchRecordText">
<div class="search-record-list" v-loadmore="loadMoreRecordList">
<chatAppSearchList
ref="searchListRef"
:searchResultPageSize="10"
:listLimit="false"
:apiRequest="ServeQueryTalkRecord"
:apiParams="state.ServeQueryTalkRecordParams"
:searchText="state.searchRecordText"
:isPagination="true"
searchResultKey="general_infos"
@clickSearchItem="handleClickSearchItem"
:useClickStay="true"
@clickStayItemChange="handleClickStayItemChange"
@lastIdChange="handleMoreRecordLastIdChange"
></chatAppSearchList>
</div>
<div class="search-record-detail" v-loadmore="loadMoreRecordDetail">
<chatAppSearchList
ref="searchDetailListRef"
v-if="state.isShowSearchRecordDetailInfo"
:searchResultPageSize="10"
:listLimit="false"
:apiRequest="ServeQueryTalkRecord"
:apiParams="state.ServeQueryTalkRecordDetailParams"
:searchText="state.searchRecordText"
:isPagination="true"
:searchRecordDetail="true"
@lastIdChange="handleRecordDetailLastIdChange"
></chatAppSearchList>
</div>
</div>
<div class="search-record-empty" v-if="!state.searchRecordText">
<img src="@/assets/image/chatList/search-empty.png" alt="" />
<span>暂无搜索内容</span>
</div>
</n-card>
</div>
</template>
</customModal>
</template>
<style lang="less" scoped>
@ -802,4 +1062,58 @@ html[theme-mode='dark'] {
}
}
}
.search-record-modal-content {
box-sizing: border-box;
width: 100%;
padding: 0 12px;
:deep(.n-card) {
border: 0;
box-shadow: 0 3px 6px 1px rgba(188, 188, 188, 0.18);
}
.search-record-input {
display: flex;
align-items: center;
justify-content: center;
.search-record-input-title {
width: 78px;
}
}
.search-record-card {
display: flex;
flex-direction: row;
gap: 20px;
padding: 28px 0 0;
.search-record-list {
width: 260px;
height: 517px;
border: 1px solid #efeff5;
overflow-y: scroll;
}
.search-record-detail {
width: 578px;
height: 517px;
border: 1px solid #efeff5;
overflow-y: scroll;
}
}
.search-record-empty {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 28px 0 0;
height: 519px;
img {
width: 160px;
height: 104px;
}
span {
font-size: 14px;
color: #999;
font-weight: 400;
margin: 13px 0 0;
}
}
}
</style>

View File

@ -67,12 +67,12 @@ const onSetMenu = () => {
@click="emit('evnet', 'notice')"
/>
<n-icon
v-show="type == 2"
:component="Peoples"
:size="18"
class="icon"
@click="emit('evnet', 'group')"
/>
>
<img style="width: 20px; height: 20px;" src="@/assets/image/chatList/chat-settings.png" alt="" />
</n-icon>
</div>
</header>
</template>