Compare commits

..

No commits in common. "main" and "zhangyuanshan-20250310" have entirely different histories.

126 changed files with 6980 additions and 16563 deletions

2
auto-imports.d.ts vendored
View File

@ -70,6 +70,6 @@ declare global {
// for type re-export
declare global {
// @ts-ignore
export type { Component, Slot, Slots, ComponentPublicInstance, ComputedRef, DirectiveBinding, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, MaybeRef, MaybeRefOrGetter, VNode, WritableComputedRef } from 'vue'
export type { Component, ComponentPublicInstance, ComputedRef, DirectiveBinding, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, MaybeRef, MaybeRefOrGetter, VNode, WritableComputedRef } from 'vue'
import('vue')
}

7
components.d.ts vendored
View File

@ -8,8 +8,6 @@ export {}
/* prettier-ignore */
declare module 'vue' {
export interface GlobalComponents {
AsyncError: typeof import('./src/components/async-error/index.vue')['default']
AsyncLoading: typeof import('./src/components/async-loading/index.vue')['default']
AudioMessage: typeof import('./src/components/talk/message/AudioMessage.vue')['default']
Avatar: typeof import('./src/components/base/Avatar.vue')['default']
AvatarCropper: typeof import('./src/components/base/AvatarCropper.vue')['default']
@ -38,20 +36,15 @@ declare module 'vue' {
RevokeMessage: typeof import('./src/components/talk/message/RevokeMessage.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
SysGroupAdminMessage: typeof import('./src/components/talk/message/system/SysGroupAdminMessage.vue')['default']
SysGroupCancelMutedMessage: typeof import('./src/components/talk/message/system/SysGroupCancelMutedMessage.vue')['default']
SysGroupCreateMessage: typeof import('./src/components/talk/message/system/SysGroupCreateMessage.vue')['default']
SysGroupDismissed: typeof import('./src/components/talk/message/system/SysGroupDismissed.vue')['default']
SysGroupInfoChangeMessage: typeof import('./src/components/talk/message/system/SysGroupInfoChangeMessage.vue')['default']
SysGroupJoinMessage: typeof import('./src/components/talk/message/system/SysGroupJoinMessage.vue')['default']
SysGroupMemberCancelMutedMessage: typeof import('./src/components/talk/message/system/SysGroupMemberCancelMutedMessage.vue')['default']
SysGroupMemberKickedMessage: typeof import('./src/components/talk/message/system/SysGroupMemberKickedMessage.vue')['default']
SysGroupMemberMutedMessage: typeof import('./src/components/talk/message/system/SysGroupMemberMutedMessage.vue')['default']
SysGroupMemberQuitMessage: typeof import('./src/components/talk/message/system/SysGroupMemberQuitMessage.vue')['default']
SysGroupMemberRemovedMessage: typeof import('./src/components/talk/message/system/SysGroupMemberRemovedMessage.vue')['default']
SysGroupMutedMessage: typeof import('./src/components/talk/message/system/SysGroupMutedMessage.vue')['default']
SysGroupTransferMessage: typeof import('./src/components/talk/message/system/SysGroupTransferMessage.vue')['default']
SysPushMessage: typeof import('./src/components/talk/message/system/sysPushMessage.vue')['default']
SysTextMessage: typeof import('./src/components/talk/message/system/SysTextMessage.vue')['default']
TabbarItem: typeof import('./src/components/x-tabbar/components/tabbar-item/index.vue')['default']
TextMessage: typeof import('./src/components/talk/message/TextMessage.vue')['default']

8
env/.env.dev vendored
View File

@ -5,10 +5,4 @@ VITE_SHOW_CONSOLE = true
# 是否开启sourcemap
VITE_SHOW_SOURCEMAP = true
# baseUrl
# VITE_BASEURL = 'http://172.16.100.93:8503'
VITE_BASEURL = 'http://192.168.88.47:9503'
#VITE_SOCKET_API
# VITE_SOCKET_API = 'ws://172.16.100.93:8504'
VITE_SOCKET_API = 'ws://192.168.88.47:9504'
# EPRAPI baseUrl
VITE_EPR_BASEURL = 'http://114.218.158.24:9020'
VITE_BASEURL = 'http://warehouse.szjixun.cn/oa_backend'

19
env/.env.prod vendored
View File

@ -1,21 +1,8 @@
# 变量必须以 VITE_ 为前缀才能暴露给外部读取
NODE_ENV = 'prod'
# 是否显示console
VITE_SHOW_CONSOLE = false
VITE_SHOW_CONSOLE = true
# 是否开启sourcemap
VITE_SHOW_SOURCEMAP = false
VITE_SHOW_SOURCEMAP = true
# baseUrl
VITE_BASEURL = 'https://chat-out.szjixun.cn' #体制外
#VITE_SOCKET_API
VITE_SOCKET_API = 'wss://chat-out.szjixun.cn' #体制外
# EPRAPI baseUrl
VITE_EPR_BASEURL = 'https://erpapi-out.szjixun.cn' #体制外
# # baseUrl
# VITE_BASEURL = 'https://chat.szjixun.cn' #体制内
# #VITE_SOCKET_API
# VITE_SOCKET_API = 'wss://chat.szjixun.cn' #体制内
# # EPRAPI baseUrl
# VITE_EPR_BASEURL = 'https://erpapi.fontree.cn' #体制内
VITE_BASEURL = 'https://oa-a.szjixun.cn/api'

12
env/.env.test vendored
View File

@ -4,16 +4,10 @@ NODE_ENV = 'test'
VITE_SHOW_CONSOLE = true
# 是否开启sourcemap
VITE_SHOW_SOURCEMAP = true
# # baseUrl
VITE_BASEURL = 'http://172.16.100.93:8503'
# #VITE_SOCKET_API
VITE_SOCKET_API = 'ws://172.16.100.93:8504'
# baseUrl
# VITE_BASEURL = 'http://192.168.88.21:9503'
# VITE_BASEURL = 'https://warehouse.szjixun.cn/oa_backend'
VITE_BASEURL = 'http://172.16.100.93:8503'
#VITE_SOCKET_API
# VITE_SOCKET_API = 'ws://192.168.88.21:9504'
VITE_SOCKET_API = 'ws://172.16.100.93:8504'
# EPRAPI baseUrl
VITE_EPR_BASEURL = 'http://114.218.158.24:9020'

View File

@ -2,16 +2,14 @@
"name": "unihelper",
"version": "0.0.0",
"private": true,
"type": "module",
"packageManager": "pnpm@8.14.1",
"license": "MIT",
"scripts": {
"dev:h5": "uni --mode dev --port 2468",
"test:h5": "uni --mode test --port 2468",
"prod:h5": "uni --mode prod",
"build:h5:test": "uni build --mode test",
"build:h5:prod": "uni build --mode prod",
"preview:h5": "uni preview --mode test",
"preview:h5:prod": "uni preview --mode prod"
"build:h5:prod": "uni build --mode prod"
},
"dependencies": {
"@dcloudio/uni-app": "3.0.0-alpha-4000020240111001",
@ -40,9 +38,7 @@
"lodash": "^4.17.21",
"nzh": "^1.0.13",
"pinia-plugin-persistedstate": "^4.1.3",
"quill": "^1.3.7",
"quill-mention": "^4.1.0",
"recorder-core": "^1.3.25011100",
"quill-mention": "^6.0.2",
"vconsole": "^3.15.1",
"vue": "^3.3.8",
"vue-i18n": "11.0.0-rc.1"
@ -66,7 +62,7 @@
"lint-staged": "^15.2.0",
"naive-ui": "^2.41.0",
"pinia": "2.0.36",
"sass": "1.62.1",
"sass": "^1.77.8",
"simple-git-hooks": "^2.9.0",
"typescript": "^5.3.3",
"unocss": "^0.58.9",

File diff suppressed because it is too large Load Diff

View File

@ -1,33 +1,28 @@
<script setup>
import { useStatus } from '@/store/status'
import { useUserStore, useDialogueListStore } from '@/store'
import { useUserStore } from '@/store'
import { useProvideUserModal } from '@/hooks'
import {useAuth} from "@/store/auth";
const {token} = useAuth()
import ws from '@/connect'
import {uniStorage} from "@/utils/uniStorage.js"
const { statusBarHeight } = useStatus()
const { uid, isShow } = useProvideUserModal()
const userStore = useUserStore()
const root = document.documentElement
root.style.setProperty('--statusBarHeight', `${statusBarHeight.value}px`)
const handleWebview = () => {
let statusBarHeight_ = window?.plus?.navigator?.getStatusbarHeight()
let statusBarHeight = window?.plus?.navigator?.getStatusbarHeight()
const webview = plus.webview.currentWebview()
// webview.setStyle({
// top: statusBarHeight_,
// bottom: 0,
// })
console.log("webview", webview)
webview.setStyle({
top: statusBarHeight,
bottom: 0,
})
// console.log(webview)
token.value = webview.token
if(webview?.doClearDialogueList){
useDialogueListStore().dialogueList.value = []
uniStorage.removeItem('dialogueList')
}
userStore.loadSetting()
ws.connect()
}
const init = () => {
userStore.loadSetting()
ws.connect()
if (typeof plus !== 'undefined') {
handleWebview()
} else {

View File

@ -1,47 +0,0 @@
import request from '@/service/index.js'
// 查询用户是否需要添加好友
export const ServeCheckFriend = (data) => {
return request({
url: '/api/v1/contact/friend/check',
method: 'POST',
data,
})
}
// 主动添加好友(单向好友)
export const ServeAddFriend = (data) => {
return request({
url: '/api/v1/contact/friend/add',
method: 'POST',
data,
})
}
// 查询我的好友列表
export const ServeQueryFriendsList = (data) => {
return request({
url: '/api/v1/contact/friend/list',
method: 'POST',
data,
})
}
// 删除好友(单向好友)
export const ServeDeleteFriend = (data) => {
return request({
url: '/api/v1/contact/friend/delete',
method: 'POST',
data,
})
}
//添加我的好友时候的搜索接口
export const ServeFriendSearch = (data) => {
return request({
url: '/api/v1/contact/friend/search',
method: 'POST',
data,
})
}

View File

@ -1,7 +1,5 @@
import request from '@/service/index.js'
import qs from 'qs'
import { useTalkStore, useDialogueStore } from '@/store'
import { handleFindWebview } from '@/utils/common'
// 获取聊天列表服务接口
export const ServeGetTalkList = (data) => {
@ -40,15 +38,7 @@ export const ServeTopTalkList = (data) => {
}
// 清除聊天消息未读数服务接口
export const ServeClearTalkUnreadNum = (data, unReadNum) => {
console.log('=======chatApp==UnreadNum', unReadNum)
if (
!useTalkStore().items[
useTalkStore().findTalkIndex(useDialogueStore().index_name)
]?.is_disturb
) {
handleFindWebview(`updateUnreadMsgNumReduce('${unReadNum}')`)
}
export const ServeClearTalkUnreadNum = (data) => {
return request({
url: '/api/v1/talk/unread/clear',
method: 'POST',
@ -68,7 +58,6 @@ export const ServeTalkRecords = (data) => {
// 获取转发会话记录详情列表服务接口
export const ServeGetForwardRecords = (data) => {
return request({
// url: '/api/v1/talk/records/forward/v2',
url: '/api/v1/talk/records/forward',
method: 'GET',
data,
@ -180,77 +169,13 @@ export const ServeConfirmVoteHandle = (data) => {
})
}
export const uploadImg = (data, onProgressFn) => {
export const uploadImg = (data,onProgressFn) => {
return request({
url: '/upload/img',
method: 'POST',
data: data,
baseURL: import.meta.env.VITE_EPR_BASEURL,
isFormData: true,
onUploadProgress: (progressEvent) =>
onProgressFn(progressEvent, data.get('file')),
data:data,
baseURL:import.meta.env.VITE_EPR_BASEURL,
isFormData:true,
onUploadProgress:(progressEvent)=>onProgressFn(progressEvent,data.get('file'))
})
}
// 根据msg_id获取消息
export const detailGetRecordsContext = (data) => {
return request({
url: '/api/v1/talk/record/detail',
method: 'GET',
data,
})
}
// 获取自己消息已读回执列表
export const ServeReadConditionList = (data) => {
return request({
url: '/api/v1/talk/my-records/read/condition',
method: 'POST',
data,
})
}
// 获取消息已读未读详情
export const ServeMessageReadDetail = (data) => {
return request({
url: '/api/v1/talk/my-records/read/condition',
method: 'POST',
data,
})
}
// 语音转文字
export const ServeConvertText = (data) => {
return request({
url: '/api/v1/talk/message/voice-to-text',
method: 'POST',
data,
})
}
// 获取用户所在群聊列表(我的群聊)
export const ServeUserGroupChatList = (data) => {
return request({
url: '/api/v1/group/user/list',
method: 'POST',
data,
})
}
//查询聊天助手是否开启
export const ServeContactRobotQuery = (data) => {
return request({
url: '/api/v1/contact/robot/query',
method: 'POST',
data,
})
}
//开关聊天助手
export const ServeContactRobotUpdate = (data) => {
return request({
url: '/api/v1/contact/robot/update',
method: 'POST',
data,
})
}

View File

@ -26,22 +26,6 @@ export const departmentV2TreeAll = (data) => {
data,
})
}
// 通讯录过滤测试部门
export const departmentV2TreeAll2 = (data) => {
return request({
url: '/api/v1/contact/department/v2/tree/all',
method: 'POST',
data,
})
}
// 查询是否有权限
export const userHasPermission = (data) => {
return request({
url: '/api/v1/contact/check/erp/rule',
method: 'POST',
data,
})
}
//获取指定部门下的所有岗位
export const v2TreePositionByDepartment = (data) => {
@ -61,14 +45,6 @@ export const userV2List = (data) => {
data,
})
}
// 过滤测试数据的erp用户接口
export const userV2List2 = (data) => {
return request({
url: '/api/v1/group/erp/users',
method: 'POST',
data,
})
}
export const groupCreateDept = (data) => {
return request({

View File

@ -4,7 +4,6 @@ import qs from 'qs'
// ES搜索聊天记录-主页搜索什么都有
export const ServeSeachQueryAll = (data) => {
return request({
// url: '/api/v1/elasticsearch/query-all/v2',
url: '/api/v1/elasticsearch/query-all',
method: 'POST',
data,
@ -14,7 +13,6 @@ export const ServeSeachQueryAll = (data) => {
// ES搜索用户数据
export const ServeQueryUser = (data) => {
return request({
// url: '/api/v1/elasticsearch/query-user/v2',
url: '/api/v1/elasticsearch/query-user',
method: 'POST',
data,
@ -47,12 +45,3 @@ export const ServeTalkDate = (data) => {
data,
})
}
//获取会话Id
export const ServeGetSessionId = (data) => {
return request({
url: '/api/v1/talk/session/getId',
method: 'POST',
data,
})
}

View File

@ -10,9 +10,10 @@ export const ServeGetUserSetting = (data) => {
export const userInfoApi = (data) => {
return request({
url: '/api/v1/users/erp/info',
url: '/user/info',
method: 'POST',
data,
baseURL:import.meta.env.VITE_EPR_BASEURL,
})
}

View File

@ -1,89 +0,0 @@
<template>
<div class="chat-app-error-page">
<div class="error-container">
<div class="error-icon">
<i class="iconfont icon-wifi"></i>
</div>
<div class="error-message">
<span>您的网络好像波动了一下~</span>
</div>
<div class="reload-button" @click="handleReload">
<span>重新加载</span>
</div>
</div>
</div>
</template>
<script setup>
import { onMounted, ref, computed, nextTick } from 'vue'
const handleReload = () => {
location.reload(true);
}
onMounted(() => {
if (typeof plus !== 'undefined') {
} else {
document.addEventListener('plusready', () => {
})
}
})
</script>
<style scoped lang="scss">
.chat-app-error-page {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100vh;
background-image: url('@/static/image/clockIn/z3280@3x.png');
background-size: cover;
background-position: bottom center;
.error-container {
display: flex;
flex-direction: column;
align-items: center;
padding: 32px;
background: #ffffff;
border-radius: 16px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
max-width: 80%;
width: 320px;
.error-icon {
font-size: 48px;
color: #ff6b6b;
margin-bottom: 16px;
}
.error-message {
font-size: 16px;
color: #333333;
margin-bottom: 24px;
text-align: center;
}
.reload-button {
padding: 12px 32px;
background: $theme-primary;
color: #ffffff;
border-radius: 24px;
font-size: 16px;
cursor: pointer;
transition: all 0.3s ease;
&:hover {
opacity: 0.9;
transform: translateY(-1px);
}
&:active {
transform: translateY(0);
}
}
}
}
</style>

View File

@ -1,81 +0,0 @@
<script setup>
import { computed } from 'vue';
</script>
<template>
<div class="loader">
<p class="heading">加载中</p>
<div class="loading">
<div class="load"></div>
<div class="load"></div>
<div class="load"></div>
<div class="load"></div>
</div>
</div>
</template>
<style scoped lang="scss">
.loader {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100vh;
background-image: url('@/static/image/clockIn/z3280@3x.png');
background-size: cover;
background-position: bottom center;
}
.heading {
color: black;
letter-spacing: 0.2em;
margin-bottom: 1em;
}
.loading {
display: flex;
width: 5em;
align-items: center;
justify-content: center;
}
.load {
width: 23px;
height: 3px;
background-color: limegreen;
animation: 1s move_5011 infinite;
border-radius: 5px;
margin: 0.1em;
}
.load:nth-child(1) {
animation-delay: 0.2s;
}
.load:nth-child(2) {
animation-delay: 0.4s;
}
.load:nth-child(3) {
animation-delay: 0.6s;
}
@keyframes move_5011 {
0% {
width: 0.2em;
}
25% {
width: 0.7em;
}
50% {
width: 1.5em;
}
100% {
width: 0.2em;
}
}
</style>

View File

@ -1,5 +1,5 @@
<template>
<div class="avatar-module" :style="[customStyle, { background: avatar ? '#fff' : '' }]">
<div class="avatar-module" :style="customStyle">
<img :src="avatar" v-if="avatar" />
<span v-else :style="customTextStyle">{{ text_avatar }}</span>
</div>
@ -89,7 +89,6 @@ const text_avatar = computed(() => {
img {
width: 100%;
height: 100%;
object-fit: cover;
}
}
</style>

View File

@ -90,6 +90,8 @@ const onSubmit = () => {
ServeUploadAvatar(form).then((res) => {
if (res.code == 200) {
emit('success', res.data.avatar)
} else {
message.warning(res.message)
}
})
})

View File

@ -6,11 +6,7 @@
props.subBtnText ? 'apposition-btn-style' : '',
]"
>
<wd-button
custom-class="custom-sub-btn-class"
v-if="props.subBtnText"
@click="clickSubBtn"
>
<wd-button custom-class="custom-sub-btn-class" v-if="props.subBtnText">
{{ props.subBtnText }}
</wd-button>
<wd-button
@ -19,7 +15,6 @@
:disabled="props?.disabled"
:class="[props?.disabled ? 'custom-btn-class-disabled' : '']"
:plain="props?.plain"
:loading="props?.isLoading"
>
{{ props.btnText }}
</wd-button>
@ -29,22 +24,16 @@
import { reactive } from 'vue'
import { defineProps, defineEmits } from 'vue'
const state = reactive({})
const emits = defineEmits(['clickSubBtn', 'clickBtn'])
const emits = defineEmits(['clickBtn'])
const props = defineProps({
isBottom: false, //
btnText: '', //
subBtnText: '', //
disabled: false, //
plain: false, //
isLoading: false, //
})
//
const clickSubBtn = () => {
emits('clickSubBtn')
}
//
//
const clickBtn = () => {
emits('clickBtn')
}

View File

@ -21,20 +21,9 @@
<script setup>
import { defineProps, defineEmits, reactive, watch } from 'vue'
const props = defineProps({
searchText: {
type: String,
default: ''
},
first_talk_record_infos: {
type: Object,
default(){
return {}
}
},
disabled: {
type: Boolean,
default: false
},
searchText: String,
first_talk_record_infos: Object,
disabled: Boolean,
})
const state = reactive({
searchText: '', //

View File

@ -1,7 +1,7 @@
<template>
<tm-navbar
:hideBack="props.hideBack"
:hideHome="props.hideHome"
hideHome
:title="props.title"
:shadow="props.shadowNum"
:fontSize="34"
@ -38,10 +38,6 @@ const props = defineProps({
type: Number,
default: 1,
},
hideHome: {
type: Boolean,
default: false,
},
})
</script>

View File

@ -25,18 +25,9 @@
class="flex flex-col items-center justify-center"
>
<tm-image :width="40" :height="40" :src="copy07"></tm-image>
<div class="mt-1">复制</div>
<div>复制</div>
</div>
<div
v-if="props.isShowConvertText"
@click="() => itemClick('convertText')"
class="flex flex-col items-center justify-center"
>
<tm-image :width="40" :height="40" :src="copy07"></tm-image>
<div class="mt-1">转文字</div>
</div>
<div
v-if="props.isShowMultipleChoose && !props.isChatRobot"
@click="() => itemClick('multipleChoose')"
class="flex flex-col items-center justify-center"
>
@ -45,15 +36,15 @@
:height="40"
:src="multipleChoices"
></tm-image>
<div class="mt-1">多选</div>
<div>多选</div>
</div>
<div
v-if="props.isShowCite && !props.isChatRobot"
v-if="props.isShowCite"
@click="() => itemClick('actionCite')"
class="flex flex-col items-center justify-center"
>
<tm-image :width="40" :height="40" :src="cite"></tm-image>
<div class="mt-1">引用</div>
<div>引用</div>
</div>
<div
v-if="props.isShowWithdraw"
@ -61,15 +52,14 @@
class="flex flex-col items-center justify-center"
>
<tm-image :width="40" :height="40" :src="withdraw"></tm-image>
<div class="mt-1">撤回</div>
<div>撤回</div>
</div>
<div
:class="{ 'w-full': props.isChatRobot }"
@click="() => itemClick('actionDelete')"
class="flex flex-col items-center justify-center"
>
<tm-image :width="40" :height="40" :src="delete07"></tm-image>
<div class="mt-1">删除</div>
<div>删除</div>
</div>
</div>
<div :style="data.iconStyle" class="icon"></div>
@ -88,8 +78,8 @@
//
// uniapp & vue
import { onLoad, onReady } from '@dcloudio/uni-app'
import { defineEmits, defineProps } from 'vue'
import { onLoad, onReady } from "@dcloudio/uni-app";
import { defineEmits, defineProps } from "vue";
import {
reactive,
ref,
@ -98,152 +88,134 @@ import {
nextTick,
getCurrentInstance,
onMounted,
onBeforeUnmount,
} from 'vue'
import copy07 from '@/static/image/chatList/copy07@2x.png'
import multipleChoices from '@/static/image/chatList/multipleChoices@2x.png'
import cite from '@/static/image/chatList/cite@2x.png'
import withdraw from '@/static/image/chatList/withdraw@2x.png'
import delete07 from '@/static/image/chatList/delete@2x.png'
onBeforeUnmount
} from "vue";
import copy07 from "@/static/image/chatList/copy07@2x.png";
import multipleChoices from "@/static/image/chatList/multipleChoices@2x.png";
import cite from "@/static/image/chatList/cite@2x.png";
import withdraw from "@/static/image/chatList/withdraw@2x.png";
import delete07 from "@/static/image/chatList/delete@2x.png";
// pinia
const systemInfo = uni.getSystemInfoSync()
const bubbleRef = ref(null)
const systemInfo = uni.getSystemInfoSync();
const bubbleRef = ref(null);
const props = defineProps({
isShowCopy: {
//
type: Boolean,
default: true,
},
isShowCite: {
//
type: Boolean,
default: true,
},
isShowWithdraw: {
//
type: Boolean,
default: true,
},
isShowConvertText: {
//
type: Boolean,
default: false,
},
isShowMultipleChoose: {
//
type: Boolean,
default: true,
},
isChatRobot: {
//
type: Boolean,
default: false,
}
})
});
const emits = defineEmits(['clickMenu'])
const emits = defineEmits(["clickMenu"]);
/**
* @name 生成UUID
*/
const uuid = () => {
const reg = /[xy]/g
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'
const reg = /[xy]/g;
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx"
.replace(reg, function (c) {
var r = (Math.random() * 16) | 0,
v = c == 'x' ? r : (r & 0x3) | 0x8
return v.toString(16)
v = c == "x" ? r : (r & 0x3) | 0x8;
return v.toString(16);
})
.replace(/-/g, '')
}
.replace(/-/g, "");
};
const popoverBoxId = `ID${uuid()}`
const popoverContentId = `ID${uuid()}`
const instance = getCurrentInstance()
const popoverBoxId = `ID${uuid()}`;
const popoverContentId = `ID${uuid()}`;
const instance = getCurrentInstance();
const data = reactive({
popoverShow: false,
defaultStyle: {},
showStyle: {
left: 0,
right: '',
transform: '',
right: "",
transform: "",
},
iconStyle: {
left: '',
right: '',
transform: '',
left: "",
right: "",
transform: "",
},
})
});
/**
* @name 获取DOM
*/
const getDom = (dom) => {
return new Promise((resolve, reject) => {
const query = uni.createSelectorQuery().in(instance)
let select = query.select(dom)
const query = uni.createSelectorQuery().in(instance);
let select = query.select(dom);
const boundingClientRect = select.boundingClientRect((data) => {
resolve(data)
})
boundingClientRect.exec()
})
}
resolve(data);
});
boundingClientRect.exec();
});
};
const itemClick = (item) => {
emits('clickMenu', item)
}
emits("clickMenu", item);
};
// 5
let pressDownTime = 0
let time = null
let pressDownTime = 0;
let time = null;
const onTouchstart = () => {
time && clearTimeout(time)
time = setTimeout(open, 500)
}
time && clearTimeout(time);
time = setTimeout(open, 500);
};
const onTouchend = () => {
time && clearTimeout(time)
}
time && clearTimeout(time);
};
const open = async () => {
let popoverContent = await getDom(`#${popoverContentId}`)
let popoverBox = await getDom(`#${popoverBoxId}`)
let popoverContent = await getDom(`#${popoverContentId}`);
let popoverBox = await getDom(`#${popoverBoxId}`);
//
let originX = popoverBox.width / 2
let originX = popoverBox.width / 2;
//
let isTop = popoverBox.top - 50 > popoverContent.height
let isTop = popoverBox.top - 50 > popoverContent.height;
//
data.defaultStyle = {
top: isTop ? '60rpx' : 'auto',
bottom: !isTop ? '-20rpx' : 'auto',
transform: `translateY(${isTop ? '-100%' : '100%'}) scale(.8)`,
}
top: isTop ? "60rpx" : "auto",
bottom: !isTop ? "-20rpx" : "auto",
transform: `translateY(${isTop ? "-100%" : "100%"}) scale(.8)`,
};
//
if (popoverBox.left > systemInfo.windowWidth - popoverBox.right) {
data.defaultStyle.right = 0
data.defaultStyle.right = 0;
//
data.defaultStyle['transform-origin'] = `${
data.defaultStyle["transform-origin"] = `${
popoverContent.width - originX
}px ${isTop ? '100%' : '0%'}`
}px ${isTop ? "100%" : "0%"}`;
} else {
data.defaultStyle.left = 0
data.defaultStyle.left = 0;
//
data.defaultStyle['transform-origin'] = `${originX}px ${
isTop ? '100%' : '0%'
}`
data.defaultStyle["transform-origin"] = `${originX}px ${
isTop ? "100%" : "0%"
}`;
}
data.showStyle = { ...data.defaultStyle }
data.showStyle = { ...data.defaultStyle };
// icon
let iconDefsultStyle = {
transform: `translate(0%, ${isTop ? '20%' : '-20%'})`,
'border-top-color': isTop ? '#333333' : '',
'border-bottom-color': !isTop ? '#333333' : '',
top: !isTop ? '-20rpx' : 'auto',
bottom: isTop ? '-20rpx' : 'auto',
}
transform: `translate(0%, ${isTop ? "20%" : "-20%"})`,
"border-top-color": isTop ? "#333333" : "",
"border-bottom-color": !isTop ? "#333333" : "",
top: !isTop ? "-20rpx" : "auto",
bottom: isTop ? "-20rpx" : "auto",
};
setTimeout(() => {
if (popoverBox.left > systemInfo.windowWidth - popoverBox.right) {
@ -252,55 +224,55 @@ const open = async () => {
...data.defaultStyle,
//
opacity: 1,
transform: `translateY(${isTop ? '-100%' : '100%'}) scale(1)`,
'pointer-events': 'auto',
}
transform: `translateY(${isTop ? "-100%" : "100%"}) scale(1)`,
"pointer-events": "auto",
};
data.iconStyle = {
right: `${originX}px`,
left: 'auto',
left: "auto",
...iconDefsultStyle,
}
};
} else {
data.showStyle = {
//
...data.defaultStyle,
//
opacity: 1,
transform: `translateY(${isTop ? '-100%' : '100%'}) scale(1)`,
'pointer-events': 'auto',
}
transform: `translateY(${isTop ? "-100%" : "100%"}) scale(1)`,
"pointer-events": "auto",
};
data.iconStyle = {
left: `${originX}px`,
right: 'auto',
right: "auto",
...iconDefsultStyle,
}
};
}
if (!data.popoverShow) data.popoverShow = true
}, 200)
}
if (!data.popoverShow) data.popoverShow = true;
}, 200);
};
const close = (time) => {
setTimeout(() => {
data.popoverShow = false
data.showStyle = data.defaultStyle
}, time || 0)
}
data.popoverShow = false;
data.showStyle = data.defaultStyle;
}, time || 0);
};
const handleClickOutside = (event) => {
if ((data.popoverShow = false)) {
if (data.popoverShow = false) {
return false
}
if (bubbleRef.value && !bubbleRef.value.contains(event.target)) {
close()
close();
}
}
};
onMounted(() => {
document.addEventListener('click', handleClickOutside)
})
document.addEventListener('click', handleClickOutside);
});
onBeforeUnmount(() => {
document.removeEventListener('click', handleClickOutside)
})
document.removeEventListener('click', handleClickOutside);
});
</script>
<style lang="scss" scoped>

View File

@ -1,155 +1,109 @@
<script lang="ts" setup>
import { ref, reactive, onMounted, onUnmounted, computed } from 'vue'
import { PlayOne, PauseOne } from '@icon-park/vue-next'
import { ITalkRecordExtraAudio, ITalkRecord } from '@/types/chat'
import { onHide } from '@dcloudio/uni-app'
import aTrumpet from '@/uni_modules/a-trumpet/components/a-trumpet/a-trumpet.vue'
import { useUserStore } from '@/store'
import { ref, reactive, onMounted } from "vue";
import { PlayOne, PauseOne } from "@icon-park/vue-next";
import { ITalkRecordExtraAudio, ITalkRecord } from "@/types/chat";
const props = defineProps<{
extra: ITalkRecordExtraAudio
data: ITalkRecord
maxWidth?: Boolean
}>()
extra: ITalkRecordExtraAudio;
data: ITalkRecord;
maxWidth?: Boolean;
}>();
const userStore = useUserStore()
const talkParams = reactive({
uid: computed(() => userStore.uid),
})
const audioRef = ref();
const audioContext = ref<any>(null);
const durationDesc = ref("-");
const audioContext = ref<any>(null)
const durationDesc = ref('-')
const state = reactive({
isAudioPlay: false,
progress: 0,
duration: 0,
currentTime: 0,
loading: true,
})
//
function onOtherAudioPlay(e) {
if (e.detail !== props.data.msg_id && state.isAudioPlay) {
audioContext.value.pause()
state.isAudioPlay = false
}
}
});
onMounted(() => {
audioContext.value = uni.createInnerAudioContext()
audioContext.value.src = props.extra.url
// 使uni-app
audioContext.value = uni.createInnerAudioContext();
audioContext.value.src = props.extra.url;
audioContext.value.onCanplay(() => {
state.duration = audioContext.value.duration
durationDesc.value = formatTime(parseInt(audioContext.value.duration))
state.loading = false
})
state.duration = audioContext.value.duration;
durationDesc.value = formatTime(parseInt(audioContext.value.duration));
state.loading = false;
});
audioContext.value.onTimeUpdate(() => {
if (audioContext.value.duration == 0) {
state.progress = 0
state.progress = 0;
} else {
state.currentTime = audioContext.value.currentTime
state.currentTime = audioContext.value.currentTime;
state.progress =
(audioContext.value.currentTime / audioContext.value.duration) * 100
(audioContext.value.currentTime / audioContext.value.duration) * 100;
}
})
});
audioContext.value.onEnded(() => {
state.isAudioPlay = false
state.progress = 0
})
state.isAudioPlay = false;
state.progress = 0;
});
audioContext.value.onError((e) => {
console.log('音频播放异常===>', e)
})
window.addEventListener('audio-play', onOtherAudioPlay)
})
onUnmounted(() => {
window.removeEventListener('audio-play', onOtherAudioPlay)
audioContext.value &&
audioContext.value.destroy &&
audioContext.value.destroy()
})
onHide(() => {
if (audioContext.value && audioContext.value.pause) {
audioContext.value.pause()
state.isAudioPlay = false
}
})
console.log("音频播放异常===>", e);
});
});
const onPlay = () => {
if (state.isAudioPlay) {
audioContext.value.pause()
state.isAudioPlay = false
audioContext.value.pause();
} else {
//
window.dispatchEvent(
new CustomEvent('audio-play', { detail: props.data.msg_id }),
)
audioContext.value.play()
state.isAudioPlay = true
audioContext.value.play();
}
}
state.isAudioPlay = !state.isAudioPlay;
};
const onPlayEnd = () => {
state.isAudioPlay = false;
state.progress = 0;
};
const formatTime = (value: number = 0) => {
if (value == 0) {
return '-'
return "-";
}
const minutes = Math.floor(value / 60)
let seconds = value
const minutes = Math.floor(value / 60);
let seconds = value;
if (minutes > 0) {
seconds = Math.floor(value - minutes * 60)
seconds = Math.floor(value - minutes * 60);
}
return `${minutes}'${seconds}"`
}
return `${minutes}'${seconds}"`;
};
</script>
<template>
<div
class="audio-message"
@click.stop="onPlay"
:class="
props?.data?.user_id == talkParams.uid
? 'justify-end py-[22rpx] pl-[30rpx] pr-[16rpx]'
: 'justify-start py-[22rpx] pr-[30rpx] pl-[16rpx]'
"
>
<a-trumpet
v-if="props?.data?.user_id != talkParams.uid"
:isPlay="state.isAudioPlay"
color="#C1C1C1"
:size="30"
></a-trumpet>
<div
:class="
props?.data?.user_id == talkParams.uid ? 'mr-[8rpx]' : 'ml-[8rpx]'
"
>
{{ Math.ceil(props?.extra?.duration / 1000) }}s
<div class="im-message-audio">
<div class="play">
<div class="btn pointer" @click.stop="onPlay">
<n-icon
:size="18"
:component="state.isAudioPlay ? PauseOne : PlayOne"
/>
</div>
</div>
<a-trumpet
v-if="props?.data?.user_id == talkParams.uid"
:isPlay="state.isAudioPlay"
color="#C1C1C1"
:size="30"
direction="left"
></a-trumpet>
<div class="desc">
<span class="line" v-for="i in 23" :key="i"></span>
<span
class="indicator"
:style="{ left: state.progress + '%' }"
v-show="state.progress > 0"
></span>
</div>
<div class="time">{{ durationDesc }}</div>
</div>
</template>
<style lang="less" scoped>
.audio-message {
display: flex;
flex-direction: row;
align-items: center;
width: 100%;
height: 100%;
background-color: #fff;
border-radius: 10px;
}
.im-message-audio {
--audio-bg-color: #f5f5f5;
--audio-btn-bg-color: #ffffff;
@ -291,7 +245,7 @@ const formatTime = (value: number = 0) => {
}
}
html[theme-mode='dark'] {
html[theme-mode="dark"] {
.im-message-audio {
--audio-bg-color: #2c2c32;
--audio-btn-bg-color: rgb(78, 75, 75);

View File

@ -25,130 +25,69 @@ const getFileTypeIMG = computed(() => {
let objT = {
finishedImg: '',
blankImg: '',
progressColor: '',
}
progressColor: ''
};
switch (suffix) {
case 'pdf':
objT.finishedImg = filePaperPDF
objT.blankImg = filePaperPDFBlank
objT.progressColor = '#DE4E4E'
break
break;
case 'doc':
case 'docx':
objT.finishedImg = filePaperWord
objT.blankImg = filePaperWordBlank
objT.progressColor = '#2750B2'
break
break;
case 'xls':
case 'xlsx':
objT.finishedImg = filePaperExcel
objT.blankImg = filePaperExcelBlank
objT.progressColor = '#3C7F4B'
break
break;
case 'ppt':
case 'pptx':
objT.finishedImg = filePaperPPT
objT.blankImg = filePaperPPTBlank
objT.progressColor = '#B74B2B'
break
break;
default:
objT.finishedImg = filePaperOther
objT.blankImg = filePaperOtherBlank
objT.progressColor = '#46299d'
objT.progressColor = '#747474'
}
return objT
})
const previewPDF = () => {
if (typeof plus !== 'undefined') {
downloadAndOpenFile()
} else {
document.addEventListener('plusready', () => {
downloadAndOpenFile()
})
}
}
const downloadAndOpenFile = () => {
uni.showLoading({ title: '加载中...', mask: true })
const downloadUrl = props?.extra?.path
if (!downloadUrl) {
uni.hideLoading()
uni.showToast({ title: '文件路径无效', icon: 'none' })
return
}
const options = {
filename: '_doc/downloads/', //
}
const dtask = plus.downloader.createDownload(downloadUrl, options, function (
d,
status,
) {
if (status === 200) {
uni.hideLoading()
const filePath = d.filename
if (filePath) {
plus.runtime.openFile(filePath, {}, function () {})
} else {
uni.showToast({ title: '文件路径无效', icon: 'none' })
}
} else {
uni.hideLoading()
}
})
dtask.start()
}
</script>
<template>
<section
class="file-message"
@click="previewPDF"
:class="{ left: data.float === 'left', right: data.float === 'right' }"
>
<div class="flex justify-between">
<div
class="w-[228rpx] text-[32rpx] text-[#1A1A1A] h-[88rpx] leading-[44rpx] textEllipsis file_name"
>
<div class="w-[228rpx] text-[32rpx] text-[#1A1A1A] h-[88rpx] leading-[44rpx] textEllipsis">
{{ extra.name }}
</div>
<div
v-if="data.uploadStatus === 2 || !data.uploadStatus"
class="w-[95rpx]"
>
<tm-image
:width="95"
:height="95"
:src="getFileTypeIMG.finishedImg"
></tm-image>
<div v-if="data.uploadStatus === 2 || !data.uploadStatus" class="w-[95rpx]">
<tm-image :width="95" :height="95" :src="getFileTypeIMG.finishedImg"></tm-image>
</div>
<div
v-if="data.uploadStatus === 1 || data.uploadStatus === 3"
class="w-[95rpx]"
>
<tm-image
:width="95"
:height="95"
:src="getFileTypeIMG.blankImg"
></tm-image>
<wd-circle v-if="data.uploadStatus === 1"
customClass="circleProgress"
:modelValue="data.uploadCurrent"
layerColor="#E3E3E3"
:color="getFileTypeIMG.progressColor"
:strokeWidth="3"
:size="20"
></wd-circle>
<div class="upload-failed" v-if="data.uploadStatus === 3">
<tm-icon :font-size="20" name="tmicon-times" color="#fff"></tm-icon>
</div>
<div v-if="data.uploadStatus === 1 || data.uploadStatus === 3" class="w-[95rpx]">
<tm-image :width="95" :height="95" :src="getFileTypeIMG.blankImg"></tm-image>
<wd-circle
customClass="circleProgress"
:modelValue="data.uploadCurrent"
layerColor="#E3E3E3"
:color="getFileTypeIMG.progressColor"
:strokeWidth="3"
:size="20"
></wd-circle>
</div>
</div>
<div class="divider mt-[28rpx]"></div>
<div class="text-[24rpx] text-[#747474] mt-[10rpx]">
{{ fileFormatSize(extra.size) }}
</div>
<div class="text-[24rpx] text-[#747474] mt-[10rpx]">{{ fileFormatSize(extra.size) }}</div>
<!-- <div class="main">
<div class="ext">{{ getFileNameSuffix(extra.name) }}</div>
<div class="file-box">
@ -185,11 +124,6 @@ const downloadAndOpenFile = () => {
border-radius: 16rpx 0 16rpx 16rpx;
}
.file_name {
word-break: break-all; /* 在任意字符间断行 */
word-wrap: break-word; /* 允许长单词换行到下一行 */
}
.main {
height: 45px;
display: flex;
@ -278,7 +212,7 @@ const downloadAndOpenFile = () => {
}
.divider {
background-color: #e7e7e7;
background-color: #E7E7E7;
height: 1rpx;
}
@ -291,20 +225,4 @@ const downloadAndOpenFile = () => {
width: 40rpx !important;
height: 40rpx !important;
}
.upload-failed {
position: absolute;
top: 120rpx;
right: 52rpx;
transform: translate(-50%, -50%);
z-index: 1;
width: 40rpx;
height: 40rpx;
display: flex;
align-items: center;
justify-content: center;
color: #ff4d4f;
background: #ff4d4f;
border-radius: 50%;
}
</style>

View File

@ -12,25 +12,19 @@ const props = defineProps<{
const isShowRecord = ref(false)
const title = computed(() => {
const uniqueNames = [...new Set(props.extra.records.map(v => v.nickname))];
if (uniqueNames.length <= 2) {
return uniqueNames.join('和');
} else {
return uniqueNames.slice(0, 2).join('和') + '等';
}
// return [...new Set(props.extra.records.map((v) => v.nickname))].join('')
return [...new Set(props.extra.records.map((v) => v.nickname))].join('、')
})
console.log(props.extra.records)
const onClick = () => {
// isShowRecord.value = true
uni.navigateTo({
url: '/pages/forwardRecord/index?msgId=' + props.data.msg_id + '&created_at=' + props.data?.created_at
url: '/pages/forwardRecord/index?msgId=' + props.data.msg_id
})
}
</script>
<template>
<section class="im-message-forward pointer" :class="{ left: data.float === 'left' }" @click="onClick">
<div class="title">{{ extra.forward_name || title}}的会话记录</div>
<div class="title">{{ title }} 的会话记录</div>
<div class="list" v-for="(record, index) in extra.records" :key="index">
<p>
<span>{{ record.nickname }}: </span>
@ -65,14 +59,13 @@ const onClick = () => {
}
.title {
max-height: 88rpx;
height: 44rpx;
line-height: 44rpx;
font-size: 32rpx;
color: #1A1A1A;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-weight: 400;
margin-bottom: 8rpx;
}

View File

@ -12,40 +12,40 @@ const img = computed(() => {
// console.log(props.extra);
let info = {
width: 0,
height: 0,
height: 0
}
if (props.extra.url.includes('blob:http://')) {
info = {
width: props.extra.width,
height: props.extra.height,
height: props.extra.height
}
} else {
}else {
info = getImageInfo(props.extra.url)
}
if (info.width == 0 || info.height == 0) {
return {
width: 450,
height: 298,
height: 298
}
}
if (info.width < 300) {
if(info.width<300){
return {
width: 300,
height: info.height / (info.width / 300),
height: info.height / (info.width / 300)
}
}
if (info.width < 350) {
return {
width: info.width,
height: info.height,
height: info.height
}
}
return {
width: 350,
height: info.height / (info.width / 350),
height: info.height / (info.width / 350)
}
})
</script>
@ -54,30 +54,13 @@ const img = computed(() => {
class="im-message-image"
:class="{
left: data.float === 'left',
right: data.float === 'right',
right: data.float === 'right'
}"
>
<div class="image-container">
<div class="relative">
<tm-image
preview
:width="img.width"
:height="img.height"
:src="extra.url"
model="aspectFill"
/>
<wd-circle
custom-class="circleProgress"
v-if="data.uploadStatus === 1"
v-model="props.data.uploadCurrent"
color="#46299d"
layer-color="#E3E3E3"
></wd-circle>
<div class="upload-failed" v-if="data.uploadStatus === 3">
<tm-icon :font-size="20" name="tmicon-times" color="#fff"></tm-icon>
</div>
</div>
</div>
<div class="image-container">
<tm-image preview :width="img.width" :height="img.height" :src="extra.url" />
<wd-circle custom-class="circleProgress" v-if="props.data.uploadCurrent && props.data.uploadCurrent<100" v-model="props.data.uploadCurrent" color="#ffffff" layer-color="#E3E3E3"></wd-circle>
</div>
</section>
</template>
<style lang="less" scoped>
@ -96,12 +79,12 @@ const img = computed(() => {
}
&.right {
background-color: #46299d;
background-color: #46299D;
border-radius: 16rpx 0 16rpx 16rpx;
}
}
.image-container {
position: relative;
position: relative;
.circleProgress {
position: absolute;
@ -111,18 +94,4 @@ const img = computed(() => {
z-index: 1;
}
}
.upload-failed {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 1;
width: 40rpx;
height: 40rpx;
display: flex;
align-items: center;
justify-content: center;
background: #ff4d4f;
border-radius: 50%;
}
</style>

View File

@ -4,97 +4,34 @@ import { formatTime } from '@/utils/datetime'
defineProps({
login_uid: {
type: Number,
default: 0,
default: 0
},
user_id: {
type: Number,
default: 0,
default: 0
},
talk_type: {
type: Number,
default: 0,
default: 0
},
nickname: {
type: String,
default: '',
default: ''
},
datetime: {
type: String,
default: '',
},
msg_id: {
type: String,
default: '',
},
revokeInfo: {
type: Object,
default() {
return {}
},
},
extra:{
type: String,
default: '',
default: ''
}
})
</script>
<template>
<div class="im-message-revoke">
<div class="content" v-if="JSON.stringify(revokeInfo) !== '{}'">
<span v-if="talk_type === 1 && login_uid === revokeInfo.withdraw_id">
你撤回了一条消息 | {{ formatTime(datetime) }}
<slot></slot>
<!-- 添加插槽用于放置重新编辑按钮 -->
</span>
<span v-if="talk_type === 1 && login_uid !== revokeInfo.withdraw_id">
{{revokeInfo.withdraw_name}}撤回了一条消息 | {{ formatTime(datetime) }}
</span>
<span v-if="talk_type === 2 && login_uid === revokeInfo.withdraw_id && login_uid === revokeInfo.retracted_id">
你撤回了一条消息 |
<div class="content">
<span v-if="login_uid == user_id"> 你撤回了一条消息 | {{ formatTime(datetime) }} </span>
<span v-else-if="talk_type == 1"> 对方撤回了一条消息 | {{ formatTime(datetime) }} </span>
<span v-else>
"{{ nickname }}" 撤回了一条消息 |
{{ formatTime(datetime) }}
<slot></slot>
</span>
<span v-if="talk_type === 2 && login_uid === revokeInfo.withdraw_id && login_uid !== revokeInfo.retracted_id">
你撤回了{{revokeInfo.retracted_name}}一条消息 |
{{ formatTime(datetime) }}
</span>
<span v-if="talk_type === 2 && login_uid !== revokeInfo.withdraw_id && revokeInfo.withdraw_id === revokeInfo.retracted_id">
{{revokeInfo.withdraw_name}}撤回了一条消息 |
{{ formatTime(datetime) }}
</span>
<span v-if="talk_type === 2 && login_uid !== revokeInfo.withdraw_id && login_uid === revokeInfo.retracted_id && revokeInfo.withdraw_id !== revokeInfo.retracted_id">
{{revokeInfo.withdraw_name}}撤回了你一条消息 |
{{ formatTime(datetime) }}
</span>
<span v-if="talk_type === 2 && login_uid !== revokeInfo.withdraw_id && login_uid !== revokeInfo.retracted_id && revokeInfo.withdraw_id !== revokeInfo.retracted_id">
{{revokeInfo.withdraw_name}}撤回了{{revokeInfo.retracted_name}}一条消息 |
{{ formatTime(datetime) }}
</span>
<!-- <span v-if="login_uid == user_idA"> 你撤回B了一条消息 | {{ formatTime(datetime) }} </span>
<span v-else-if="login_uid == user_idB"> A撤回你了一条消息 | {{ formatTime(datetime) }} </span>
<span v-else> A撤回B了一条消息 | {{ formatTime(datetime) }} </span> -->
</div>
<div class="content" v-if="JSON.stringify(revokeInfo) === '{}'">
<span v-if="talk_type === 1 && login_uid === user_id">
你撤回了一条消息 | {{ formatTime(datetime) }}
<slot></slot>
<!-- 添加插槽用于放置重新编辑按钮 -->
</span>
<span v-if="talk_type === 1 && login_uid !== user_id">
{{nickname}}撤回了一条消息 | {{ formatTime(datetime) }}
</span>
<span v-if="talk_type === 2 && !extra && login_uid === user_id">
你撤回了一条消息 | {{ formatTime(datetime) }}
<slot></slot>
<!-- 添加插槽用于放置重新编辑按钮 -->
</span>
<span v-if="talk_type === 2 && !extra && login_uid !== user_id">
{{nickname}}撤回了一条消息 | {{ formatTime(datetime) }}
</span>
<span v-if="talk_type === 2 && extra">
{{extra}} | {{ formatTime(datetime) }}
</span>
</div>
</div>

View File

@ -2,7 +2,6 @@
import { textReplaceEmoji } from '@/utils/emojis'
import { textReplaceLink, textReplaceMention } from '@/utils/strings'
import { ITalkRecordExtraText, ITalkRecord } from '@/types/chat'
import { computed } from 'vue'
const props = defineProps<{
extra: ITalkRecordExtraText
@ -13,14 +12,15 @@ const props = defineProps<{
const float = props.data.float
const textContent = computed(() => {
let text = props.extra?.content || ''
// text = textReplaceLink(text)
if (props.data.talk_type == 2) {
text = textReplaceMention(text, '#1890ff')
}
return textReplaceEmoji(text)
})
let textContent = props.extra?.content || ''
textContent = textReplaceLink(textContent)
if (props.data.talk_type == 2) {
textContent = textReplaceMention(textContent, '#1890ff')
}
textContent = textReplaceEmoji(textContent)
</script>
<template>

View File

@ -1,8 +1,8 @@
<script lang="ts" setup>
import { ref, nextTick, getCurrentInstance, computed, onMounted } from 'vue'
import { getImageInfo } from '@/utils/functions'
import playCircle from '@/static/image/chatList/playCircle@2x.png'
import { useStatus } from '@/store/status'
import playCircle from "@/static/image/chatList/playCircle@2x.png";
import { useStatus } from "@/store/status";
const { statusBarHeight } = useStatus()
const instance = getCurrentInstance()
@ -20,12 +20,12 @@ const open = ref(false)
const img = computed(() => {
let info = {
width: 0,
height: 0,
height: 0
}
if (props.extra.url.includes('blob:http://')) {
info = {
width: props.extra.width,
height: props.extra.height,
height: props.extra.height
}
} else {
info = getImageInfo(props.extra.url)
@ -34,26 +34,26 @@ const img = computed(() => {
if (info.width == 0 || info.height == 0) {
return {
width: 450,
height: 298,
height: 298
}
}
if (info.width < 300) {
return {
width: 300,
height: info.height / (info.width / 300),
height: info.height / (info.width / 300)
}
}
if (info.width < 350) {
return {
width: info.width,
height: info.height,
height: info.height
}
}
return {
width: 350,
height: info.height / (info.width / 350),
height: info.height / (info.width / 350)
}
})
@ -67,98 +67,57 @@ const fullscreenchange = (e) => {
/* 视频播放 获取第一帧 */
const canplay = (e) => {
console.log('Video can play:', e)
console.log('Video can play:', e);
if (e.target) {
setTimeout(() => {
e.target.pause()
}, 200)
e.target.pause();
}, 200);
}
}
};
async function onPlay() {
videoContext.value = uni.createVideoContext(props.extra.url, instance);
videoContext.value.requestFullScreen({ direction: 2 });
videoContext.value.play()
open.value = true
await nextTick()
videoContext.value = uni.createVideoContext(props.extra.url, instance)
setTimeout(() => {
//
videoContext.value.requestFullScreen({ direction: 2 })
//
setTimeout(() => {
videoContext.value.play()
}, 100)
}, 200)
}
onMounted(() => {
videoRef.value = uni.createVideoContext(props.data.msg_id)
videoRef.value = uni.createVideoContext(props.data.msg_id);
videoRef.value.play()
setTimeout(() => {
videoRef.value.pause()
}, 200)
}, 200);
})
</script>
<template>
<section
class="im-message-video"
:class="{ left: data.float === 'left' }"
@click="onPlay"
>
<div
class="coverVideo"
:style="{
width: img.width + 'rpx',
height: img.height + 'rpx',
}"
v-if="props.extra.url.includes('blob:http://')"
>
<video
:id="data.msg_id"
:autoplay="false"
disablepictureinpicture
muted
:src="props.extra.url"
width="100%"
height="100%"
playsinline
preload="auto"
controls="false"
x5-playsinline
webkit-playsinline
style="object-fit: cover; pointer-events: none;"
></video>
<section class="im-message-video" :class="{ left: data.float === 'left' }" @click="onPlay">
<div class="coverVideo" :style="{
width: img.width + 'rpx',
height: img.height + 'rpx'
}" v-if="props.extra.url.includes('blob:http://')">
<video :id="data.msg_id" :autoplay="false" disablepictureinpicture muted :src="props.extra.url" width="100%"
height="100%" playsinline preload="auto" controls="false" x5-playsinline
webkit-playsinline style="object-fit: cover; pointer-events: none;">
</video>
</div>
<wd-img
v-else
:width="`${img.width}rpx`"
:height="`${img.height}rpx`"
:src="data.extra.cover"
/>
<div
v-if="data.uploadStatus === 2 || !data.uploadStatus"
class="btn-video"
:style="{
width: img.width + 'rpx',
height: img.height + 'rpx',
}"
>
<wd-img v-else :width="`${img.width}rpx`" :height="`${img.height}rpx`" :src="data.extra.cover" />
<div v-if="data.uploadStatus === 2 || !data.uploadStatus" class="btn-video" :style="{
width: img.width + 'rpx',
height: img.height + 'rpx'
}">
<tm-image :src="playCircle" :width="80" :height="80" />
</div>
<div
v-else
class="btn-video"
:style="{
width: img.width + 'rpx',
height: img.height + 'rpx',
}"
>
<div v-else class="btn-video" :style="{
width: img.width + 'rpx',
height: img.height + 'rpx'
}" >
<wd-circle
v-if="data.uploadStatus === 1"
v-model="props.data.uploadCurrent"
customClass="circleProgress"
color="#46299d"
layer-color="#E3E3E3"
layerColor="#E3E3E3"
color="#FFFFFF"
:strokeWidth="6"
:size="40"
></wd-circle>
@ -172,32 +131,18 @@ onMounted(() => {
:width="70"
:percent="props.data.uploadCurrent">
</tm-progress> -->
<div class="upload-failed" v-if="data.uploadStatus === 3">
<tm-icon :font-size="20" name="tmicon-times" color="#fff"></tm-icon>
</div>
</div>
<div v-show="open">
<video :src="props.extra.url" controls @fullscreenchange="fullscreenchange" :id="props.extra.url">
</video>
</div>
</section>
<teleport to="body">
<div v-show="open" class="video-container">
<video
:src="props.extra.url"
controls
@fullscreenchange="fullscreenchange"
:id="props.extra.url"
playsinline
webkit-playsinline
x5-playsinline
class="fullscreen-video"
></video>
</div>
</teleport>
</template>
<style lang="less" scoped>
.im-message-video {
overflow: hidden;
padding: 20rpx 18rpx;
background: #46299d;
background: #46299D;
min-width: 30rpx;
min-height: 30rpx;
display: inline-flex;
@ -255,43 +200,10 @@ onMounted(() => {
:deep(.uni-video-bar) {
display: none;
}
}
.circleProgress {
width: 80rpx !important;
height: 80rpx !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;
}
.upload-failed {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 1;
width: 40rpx;
height: 40rpx;
display: flex;
align-items: center;
justify-content: center;
background: #ff4d4f;
border-radius: 50%;
}
</style>

View File

@ -1,25 +0,0 @@
<script setup>
import './sys-message.less'
import { useInject } from '@/hooks'
defineProps({
extra: Object,
data: Object
})
const { showUserInfoModal } = useInject()
</script>
<template>
<div class="im-message-sys-text">
<div class="sys-text">
<template v-for="(user, index) in extra.members" :key="index">
<a @click="showUserInfoModal(user.user_id)">{{ user.nickname }}</a>
<em v-show="index < extra.members.length - 1"></em>
</template>
<span>已成为管理员</span>
</div>
</div>
</template>

View File

@ -1,19 +0,0 @@
<script setup>
import './sys-message.less'
import { useInject } from '@/hooks'
const { showUserInfoModal } = useInject()
defineProps({
extra: Object,
data: Object
})
</script>
<template>
<div class="im-message-sys-text">
<div class="sys-text">
<span>{{ extra.content }}</span>
</div>
</div>
</template>

View File

@ -1,25 +0,0 @@
<script setup>
import './sys-message.less'
import { useInject } from '@/hooks'
const { showUserInfoModal } = useInject()
defineProps({
extra: Object,
data: Object
})
</script>
<template>
<div class="im-message-sys-text">
<div class="sys-text">
<a @click="showUserInfoModal(data.user_id)">
<!-- {{ data.nickname }} -->
管理员
</a>
<!-- <span>修改群名为</span>
<span>"{{ extra.group_name }}"</span> -->
<span>修改了群信息</span>
</div>
</div>
</template>

View File

@ -24,7 +24,7 @@ const { showUserInfoModal } = useInject()
<em v-show="index < extra.members.length - 1"></em>
</template>
<span>出群聊</span>
<span>出群聊</span>
</div>
</div>
</template>

View File

@ -1,23 +0,0 @@
<script setup>
import './sys-message.less'
import { useInject } from '@/hooks'
const { showUserInfoModal } = useInject()
defineProps({
extra: Object,
data: Object,
})
</script>
<template>
<div class="im-message-sys-text">
<div class="sys-text">
<template v-for="(user, index) in extra?.members" :key="index">
<a @click="showUserInfoModal(user.user_id)">{{ user.nickname }}</a>
<em v-show="index < extra.members.length - 1"></em>
</template>
<span>已离开此群</span>
</div>
</div>
</template>

View File

@ -1,153 +0,0 @@
<script setup>
import { handleFindWebview } from '@/utils/common'
import noClockAfterWork from '@/static/image/chatBotMessageCard/noClockAfterWork.png'
import noClockBeforeWork from '@/static/image/chatBotMessageCard/noClockBeforeWork.png'
import copy from '@/static/image/chatBotMessageCard/copy.png'
import errorClock from '@/static/image/chatBotMessageCard/errorClock.png'
import prompt from '@/static/image/chatBotMessageCard/prompt.png'
const emits = defineEmits(['openPromptDrawer'])
const props = defineProps({
extra: Object,
data: Object,
})
//
const getMessageCard = computed(() => {
switch (props.data.msg_type) {
case 1117: //
return prompt
case 1118: //
return noClockBeforeWork
case 1119: //
return noClockAfterWork
case 1120: //
return prompt
case 1121: //
return copy
case 1122: //
return errorClock
case 1123: //
return errorClock
case 1124: //退
return errorClock
default:
return ''
}
})
//
const getTextColor = computed(() => {
const msgType = props.data.msg_type
//
if ([1117, 1120].includes(msgType)) return '#2668BF'
//
if ([1118, 1119].includes(msgType)) return '#46299D'
//
if (msgType === 1121) return '#2099BE'
//退
if ([1122, 1123, 1124].includes(msgType)) return '#933BA3'
return ''
})
//
const getTextContent = computed(() => {
const msgType = props.data.msg_type
//
if ([1117, 1120].includes(msgType)) return '立即审批'
//
if ([1118, 1119].includes(msgType)) return '立即打卡'
//退
if ([1121, 1122, 1123, 1124].includes(msgType)) return '立即查看'
return ''
})
//webview
const handleSysMessagePush = () => {
if (props?.extra?.url) {
//
emits('openPromptDrawer', encodeURIComponent(props?.extra?.url))
} else {
handleFindWebview(
`handleChatRobotSysMessagePush('${encodeURIComponent(
JSON.stringify({
url: props?.extra?.url,
msg_type: props?.data?.msg_type,
}),
)}')`,
)
uni.reLaunch({
url: '/pages/index/index',
})
}
}
</script>
<template>
<div class="sys-message-push" @click="handleSysMessagePush">
<div class="sys-message-push-card">
<span class="sys-message-push-card-title">{{ props.extra.title }}</span>
<span
class="sys-message-push-card-message"
v-html="props.extra.message"
></span>
<img :src="getMessageCard" alt="" />
</div>
<div class="sys-message-push-card-btn">
<span :style="{ color: getTextColor }">{{ getTextContent }}</span>
</div>
</div>
</template>
<style lang="less" scoped>
.sys-message-push {
border-radius: 0 16rpx 16rpx 16rpx;
overflow: hidden;
box-shadow: 0 6px 12px 2px rgba(188, 188, 188, 0.08);
.sys-message-push-card {
position: relative;
width: 486rpx;
height: 270rpx;
.sys-message-push-card-title {
position: absolute;
top: 28rpx;
left: 30rpx;
font-size: 24rpx;
line-height: 34rpx;
color: rgba(255, 255, 255, 0.73);
font-weight: 500;
}
.sys-message-push-card-message {
position: absolute;
bottom: 44rpx;
left: 30rpx;
font-size: 40rpx;
line-height: 58rpx;
color: #fff;
font-weight: bold;
min-width: 326rpx;
max-width: 100%;
}
img {
width: 100%;
height: 100%;
}
}
.sys-message-push-card-btn {
background-color: #fff;
width: 486rpx;
height: 78rpx;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
text {
color: #000;
font-size: 28rpx;
line-height: 40rpx;
font-weight: 500;
}
}
}
</style>

View File

@ -30,7 +30,6 @@ class Connect {
},
// Websocket 连接成功回调方法
onOpen: () => {
console.log("socket已连接")
// 更新 WebSocket 连接状态
useUserStore().updateSocketStatus(true)
// online.value = true;
@ -38,7 +37,6 @@ class Connect {
},
// Websocket 断开连接回调方法
onClose: () => {
console.log("socket已断开")
// 更新 WebSocket 连接状态
useUserStore().updateSocketStatus(false)
// online.value = false

View File

@ -17,7 +17,7 @@ export const ChatMsgSysText = 1000 // 系统文本消息
export const ChatMsgSysGroupCreate = 1101 // 创建群聊消息
export const ChatMsgSysGroupMemberJoin = 1102 // 加入群聊消息
export const ChatMsgSysGroupMemberQuit = 1103 // 群成员退出群消息
export const ChatMsgSysGroupMemberKicked = 1104 // 移出群成员消息(普通群、项目群被踢)
export const ChatMsgSysGroupMemberKicked = 1104 // 踢出群成员消息
export const ChatMsgSysGroupMessageRevoke = 1105 // 管理员撤回成员消息
export const ChatMsgSysGroupDismissed = 1106 // 群解散
export const ChatMsgSysGroupMuted = 1107 // 群禁言
@ -26,17 +26,6 @@ export const ChatMsgSysGroupMemberMuted = 1109 // 群成员禁言
export const ChatMsgSysGroupMemberCancelMuted = 1110 // 群成员解除禁言
export const ChatMsgSysGroupNotice = 1111 // 编辑群公告
export const ChatMsgSysGroupTransfer = 1113 // 变更群主
export const ChatMsgSysGroupAdmin = 1114 // 设置管理员
export const ChatMsgSysGroupMemberRemoved = 1115 // 移出群成员消息(部门群、公司群自动移出)
export const ChatMsgSysGroupInfoChange = 1116 // 管理员更新了群信息
export const ChatMsgSysPush_PromptReminder = 1117 // 系统推送消息-催办提醒
export const ChatMsgSysPush_NoClockReminderBeforeWork = 1118 // 系统推送消息-上班未打卡
export const ChatMsgSysPush_NoClockReminderAfterWork = 1119 // 系统推送消息-下班未打卡
export const ChatMsgSysPush_ApprovalReminder = 1120 // 系统推送消息-待审批提醒
export const ChatMsgSysPush_CopyReminder = 1121 // 系统推送消息-抄送提醒
export const ChatMsgSysPush_AbsentReminder = 1122 // 系统推送消息-缺卡提醒
export const ChatMsgSysPush_LateReminder = 1123 // 系统推送消息-迟到提醒
export const ChatMsgSysPush_EarlyReminder = 1124 // 系统推送消息-早退提醒
export const ChatMsgTypeMapping = {
[ChatMsgTypeText]: '[文本消息]',
@ -57,26 +46,14 @@ export const ChatMsgTypeMapping = {
[ChatMsgSysGroupCreate]: '[创建群消息]',
[ChatMsgSysGroupMemberJoin]: '[加入群消息]',
[ChatMsgSysGroupMemberQuit]: '[退出群消息]',
[ChatMsgSysGroupMemberKicked]: '[出群消息]',
[ChatMsgSysGroupMemberKicked]: '[出群消息]',
[ChatMsgSysGroupMessageRevoke]: '[撤回消息]',
[ChatMsgSysGroupDismissed]: '[群解散消息]',
[ChatMsgSysGroupMuted]: '[群禁言消息]',
[ChatMsgSysGroupCancelMuted]: '[群解除禁言消息]',
[ChatMsgSysGroupMemberMuted]: '[群成员禁言消息]',
[ChatMsgSysGroupMemberCancelMuted]: '[群成员解除禁言消息]',
[ChatMsgSysGroupNotice]: '[群公告]',
[ChatMsgSysGroupTransfer]: '[转让群主]',
[ChatMsgSysGroupAdmin]: '[设置管理员]',
[ChatMsgSysGroupMemberRemoved]: '[移出群成员消息]',
[ChatMsgSysGroupInfoChange]: '[群信息更新]',
[ChatMsgSysPush_PromptReminder]: '[催办提醒]',
[ChatMsgSysPush_NoClockReminderBeforeWork]: '[未打卡提醒]',
[ChatMsgSysPush_NoClockReminderAfterWork]: '[未打卡提醒]',
[ChatMsgSysPush_ApprovalReminder]: '[审批提醒]',
[ChatMsgSysPush_CopyReminder]: '[抄送提醒]',
[ChatMsgSysPush_AbsentReminder]: '[异常卡提醒]',
[ChatMsgSysPush_LateReminder]: '[异常卡提醒]',
[ChatMsgSysPush_EarlyReminder]: '[异常卡提醒]'
[ChatMsgSysGroupNotice]: '[群公告]'
}
// 消息类型 - 消息组件 映射关系
@ -101,24 +78,12 @@ export const MessageComponents = {
[ChatMsgSysGroupMemberQuit]: 'sys-group-member-quit-message',
[ChatMsgSysGroupMemberKicked]: 'sys-group-member-kicked-message',
// [ChatMsgSysGroupMessageRevoke]: '[撤回消息]',
[ChatMsgSysGroupDismissed]: 'sys-group-dismissed',
// [ChatMsgSysGroupDismissed]: '[群解散消息]',
[ChatMsgSysGroupMuted]: 'sys-group-muted-message',
[ChatMsgSysGroupCancelMuted]: 'sys-group-cancel-muted-message',
[ChatMsgSysGroupMemberMuted]: 'sys-group-member-muted-message',
[ChatMsgSysGroupMemberCancelMuted]: 'sys-group-member-cancel-muted-message',
[ChatMsgSysGroupTransfer]: 'sys-group-transfer-message',
[ChatMsgSysGroupAdmin]:'sys-group-admin-message',
[ChatMsgSysGroupMemberRemoved]:'sys-group-member-removed-message',
[ChatMsgSysGroupInfoChange]:'sys-group-info-change-message',
// 智能助手推送的系统消息采用相同的模版
[ChatMsgSysPush_PromptReminder]:'sys-push-message',
[ChatMsgSysPush_NoClockReminderBeforeWork]:'sys-push-message',
[ChatMsgSysPush_NoClockReminderAfterWork]:'sys-push-message',
[ChatMsgSysPush_ApprovalReminder]:'sys-push-message',
[ChatMsgSysPush_CopyReminder]:'sys-push-message',
[ChatMsgSysPush_AbsentReminder]:'sys-push-message',
[ChatMsgSysPush_LateReminder]:'sys-push-message',
[ChatMsgSysPush_EarlyReminder]:'sys-push-message'
[ChatMsgSysGroupTransfer]: 'sys-group-transfer-message'
}
// 可转发的消息类型
@ -126,11 +91,10 @@ export const ForwardableMessageType = [
ChatMsgTypeText,
ChatMsgTypeCode,
ChatMsgTypeImage,
// ChatMsgTypeAudio,
ChatMsgTypeAudio,
ChatMsgTypeVideo,
ChatMsgTypeFile,
ChatMsgTypeLocation,
ChatMsgTypeCard,
ChatMsgTypeLink,
ChatMsgTypeForward
ChatMsgTypeLink
]

View File

@ -68,38 +68,20 @@ class Revoke extends Base {
}
handle() {
const { updateDialogueRecord } = useDialogueListStore()
const {updateDialogueRecord} = useDialogueListStore()
useTalkStore().updateItem({
index_name: this.getIndexName(),
msg_text: this.resource.text,
revokeInfo: {
retracted_id: this.resource.retracted_id,
retracted_name: this.resource.retracted_name,
withdraw_id: this.resource.withdraw_id,
withdraw_name: this.resource.withdraw_name,
},
updated_at: parseTime(new Date()),
updated_at: parseTime(new Date())
})
useDialogueStore().updateDialogueRecord({
msg_id: this.msg_id,
revokeInfo: {
retracted_id: this.resource.retracted_id,
retracted_name: this.resource.retracted_name,
withdraw_id: this.resource.withdraw_id,
withdraw_name: this.resource.withdraw_name,
},
is_revoke: 1,
is_revoke: 1
})
updateDialogueRecord({
msg_id: this.msg_id,
revokeInfo: {
retracted_id: this.resource.retracted_id,
retracted_name: this.resource.retracted_name,
withdraw_id: this.resource.withdraw_id,
withdraw_name: this.resource.withdraw_name,
},
is_revoke: 1,
is_revoke: 1
})
}
}

View File

@ -5,17 +5,8 @@ import { parseTime } from '@/utils/datetime'
import * as message from '@/constant/message'
import { formatTalkItem, palyMusic, formatTalkRecord } from '@/utils/talk'
// import { isElectronMode } from '@/utils/common'
import {
ServeClearTalkUnreadNum,
ServeCreateTalkList,
} from '@/api/chat/index.js'
import {
useTalkStore,
useDialogueStore,
useDialogueListStore,
useGroupStore,
} from '@/store'
import { handleFindWebview } from '@/utils/common'
import { ServeClearTalkUnreadNum, ServeCreateTalkList } from '@/api/chat/index.js'
import { useTalkStore, useDialogueStore,useDialogueListStore } from '@/store'
/**
* 好友状态事件
@ -41,11 +32,6 @@ class Talk extends Base {
*/
talk_type = 0
/**
* 文件上传唯一随机值
*/
fileNum = ''
/**
* 初始化构造方法
*
@ -57,10 +43,6 @@ class Talk extends Base {
this.sender_id = resource.sender_id
this.receiver_id = resource.receiver_id
this.talk_type = resource.talk_type
// this.fileNum = resource.file_num
if (resource.file_num) {
resource.data.file_num = resource.file_num
}
this.resource = resource.data
this.handle()
@ -94,11 +76,7 @@ class Talk extends Base {
*/
getTalkText() {
let text = ''
if (
this.resource.msg_type != message.ChatMsgTypeText &&
//智能助手发送的系统消息,也直接显示内容
this.resource.user_id !== 2
) {
if (this.resource.msg_type != message.ChatMsgTypeText) {
text = message.ChatMsgTypeMapping[this.resource.msg_type]
} else {
text = this.resource.extra.content.replace(/<img .*?>/g, '')
@ -111,10 +89,11 @@ class Talk extends Base {
play() {
// 客户端有消息提示
// if (isElectronMode()) return
// useSettingsStore().isPromptTone && palyMusic()
}
async handle() {
handle() {
// 不是自己发送的消息则需要播放提示音
if (!this.isCurrSender()) {
this.play()
@ -122,21 +101,7 @@ class Talk extends Base {
// 判断会话列表是否存在,不存在则创建
if (useTalkStore().findTalkIndex(this.getIndexName()) == -1) {
if (this.resource.msg_type == 1102) {
//被邀请进入群聊时,需要热更新会话列表
await useTalkStore().loadTalkList()
} else if (this.resource.msg_type == 1106) {
//群解散时,需要热更新会话列表
await useTalkStore().loadTalkList()
} else if (
this.resource.msg_type == 1104 ||
this.resource.msg_type == 1115
) {
//群成员被移出时,需要热更新会话列表
await useTalkStore().loadTalkList()
} else {
return this.addTalkItem()
}
return this.addTalkItem()
}
// 判断当前是否正在和好友对话
@ -144,24 +109,9 @@ class Talk extends Base {
this.insertTalkRecord()
} else {
this.updateTalkItem()
if (
!useTalkStore().items[useTalkStore().findTalkIndex(this.getIndexName())]
?.is_disturb &&
!(
useTalkStore().findTalkIndex(this.getIndexName()) == -1 &&
(this.resource.msg_type == 1104 || this.resource.msg_type == 1115)
)
) {
this.updateUnreadMsgNumAdd()
}
}
}
//更新未读数量+1
updateUnreadMsgNumAdd() {
handleFindWebview(`updateUnreadMsgNumAdd()`)
}
/**
* 显示消息提示
* @returns
@ -173,6 +123,7 @@ class Talk extends Base {
// lang: 'zh-CN',
// body: '您有新的消息请注意查收'
// })
// notification.onclick = () => {
// notification.close()
// }
@ -200,16 +151,12 @@ class Talk extends Base {
ServeCreateTalkList({
talk_type,
receiver_id,
}).then(async ({ code, data }) => {
receiver_id
}).then(({ code, data }) => {
if (code == 200) {
let item = formatTalkItem(data)
if (!item?.is_disturb) {
item.unread_num = 1
this.updateUnreadMsgNumAdd()
}
item.unread_num = 1
useTalkStore().addItem(item)
await useTalkStore().loadTalkList()
}
})
}
@ -219,76 +166,24 @@ class Talk extends Base {
*/
insertTalkRecord() {
let record = this.resource
let newRecord = formatTalkRecord(this.getAccountId(), this.resource)
const { addDialogueRecord, addChatRecord } = useDialogueListStore()
let newRecord = formatTalkRecord(this.getAccountId(), this.resource);
const {addDialogueRecord,addChatRecord} = useDialogueListStore()
// 群成员变化的消息,需要更新群成员列表
if ([1102, 1103, 1104, 1115].includes(record.msg_type)) {
if ([1102, 1103, 1104].includes(record.msg_type)) {
useDialogueStore().updateGroupMembers()
}
//群解散时,需要更新群成员权限
if ([1106].includes(record.msg_type)) {
useDialogueStore().updateDismiss()
}
//群成员被移出时,需要更新群成员权限
if ([1104, 1115].includes(record.msg_type)) {
console.error(this.resource.extra.members, 'this.resource.extra.members')
if (this.resource?.extra?.members?.length > 0) {
const isMeQuit = this.resource.extra.members.find(
(item) => item.user_id === this.getAccountId(),
)
if (isMeQuit) {
useDialogueStore().updateQuit()
}
}
}
//群禁言变化时,需要更新群禁言状态——即更新群成员列表
if ([1107, 1108, 1109, 1110].includes(record.msg_type)) {
useDialogueStore().updateGroupMembers()
}
//群公告变化时,需要更新群公告(新增和修改有热更新,删除没有)
if ([13].includes(record.msg_type)) {
useGroupStore().ServeGetGroupNotices()
}
//群管理员变化时,需要更新群管理员列表——即更新群成员列表,同时更新群信息
if ([1114].includes(record.msg_type)) {
useDialogueStore().updateGroupMembers()
useGroupStore().ServeGroupDetail()
}
if ([1116].includes(record.msg_type)) {
// 更新会话信息
useDialogueStore().setDialogue({
name: record.extra.group_name,
talk_type: record.talk_type,
receiver_id: record.receiver_id,
})
// 更新群聊信息
useGroupStore().updateGroupInfo({
group_name: record.extra.group_name,
avatar: record.extra.group_avatar,
})
// 更新会话列表中的会话信息
const dialogue = useDialogueListStore().getDialogueList(
`${record.talk_type}_${record.receiver_id}`,
)
if (dialogue) {
dialogue.talk.username = record.extra.group_name
}
}
addDialogueRecord([newRecord], 'add')
addChatRecord(this.getIndexName(), newRecord)
addDialogueRecord([newRecord],'add')
addChatRecord(this.getIndexName(),newRecord)
useDialogueStore().addDialogueRecord(newRecord)
if (!this.isCurrSender()) {
// 推送已读消息
// setTimeout(() => {
// ws.emit('im.message.read', {
// receiver_id: this.sender_id,
// msg_ids: [this.resource.msg_id],
// })
// }, 1000)
setTimeout(() => {
ws.emit('im.message.read', {
receiver_id: this.sender_id,
msg_ids: [this.resource.msg_id]
})
}, 1000)
}
// 获取聊天面板元素节点
@ -296,8 +191,7 @@ class Talk extends Base {
if (!el) return
// 判断的滚动条是否在底部
const isBottom =
Math.ceil(el.scrollTop) + el.clientHeight >= el.scrollHeight
const isBottom = Math.ceil(el.scrollTop) + el.clientHeight >= el.scrollHeight
if (isBottom || record.user_id == this.getAccountId()) {
nextTick(() => {
@ -310,15 +204,14 @@ class Talk extends Base {
useTalkStore().updateItem({
index_name: this.getIndexName(),
msg_text: this.getTalkText(),
updated_at: parseTime(new Date()),
updated_at: parseTime(new Date())
})
if (this.talk_type == 1 && this.getAccountId() !== this.sender_id) {
//不在此处维护未读消息数量
// ServeClearTalkUnreadNum({
// talk_type: 1,
// receiver_id: this.sender_id,
// })
ServeClearTalkUnreadNum({
talk_type: 1,
receiver_id: this.sender_id
})
}
}
@ -329,31 +222,8 @@ class Talk extends Base {
useTalkStore().updateMessage({
index_name: this.getIndexName(),
msg_text: this.getTalkText(),
updated_at: parseTime(new Date()),
updated_at: parseTime(new Date())
})
//收到新消息时,同时判断是否有人@我
if (
this.resource.msg_type === 1 &&
this.resource?.extra?.mentions?.length > 0
) {
const findMention = this.resource?.extra?.mentions?.find(
(mention) => mention === this.getAccountId(),
)
//有人@我或者@所有人,则更新会话列表
if (findMention || this.resource?.extra?.mentions?.includes(0)) {
useTalkStore().loadTalkList()
}
}
if (this.resource.msg_type == 1116) {
// 更新会话列表中的会话信息
const dialogue = useDialogueListStore().getDialogueList(
`${this.resource.talk_type}_${this.resource.receiver_id}`,
)
if (dialogue) {
dialogue.talk.username = this.resource.extra.group_name
}
useTalkStore().loadTalkList()
}
}
}

View File

@ -11,12 +11,7 @@ import { reactive, nextTick, computed, h, inject } from 'vue'
// EditTwo,
// IdCard
// } from '@icon-park/vue-next'
import {
ServeTopTalkList,
ServeDeleteTalkList,
ServeSetNotDisturb,
ServeClearTalkUnreadNum,
} from '@/api/chat'
import { ServeTopTalkList, ServeDeleteTalkList, ServeSetNotDisturb } from '@/api/chat'
import { useDialogueStore, useTalkStore, useDialogueListStore } from '@/store'
import { ServeSecedeGroup } from '@/api/group'
// import { ServeDeleteContact, ServeEditContactRemark } from '@/api/contact'
@ -28,7 +23,7 @@ export function useSessionMenu() {
show: false,
x: 0,
y: 0,
item: {},
item: {}
})
const dialogueStore = useDialogueStore()
@ -123,22 +118,10 @@ export function useSessionMenu() {
// 移除会话
const onRemoveTalk = (item) => {
ServeDeleteTalkList({
list_id: item.id,
list_id: item.id
}).then(({ code }) => {
if (code == 200) {
onDeleteTalk(item.index_name)
console.error(item, 'item')
if (item.unread_num > 0) {
//同时已读
ServeClearTalkUnreadNum(
{
talk_type: item.talk_type,
receiver_id: item.receiver_id,
},
item.unread_num,
).then(() => {
})
}
}
})
}
@ -148,13 +131,13 @@ export function useSessionMenu() {
ServeSetNotDisturb({
talk_type: item.talk_type,
receiver_id: item.receiver_id,
is_disturb: item.is_disturb == 0 ? 1 : 0,
is_disturb: item.is_disturb == 0 ? 1 : 0
}).then(({ code, message }) => {
if (code == 200) {
message.success('设置成功!')
talkStore.updateItem({
index_name: item.index_name,
is_disturb: item.is_disturb == 0 ? 1 : 0,
is_disturb: item.is_disturb == 0 ? 1 : 0
})
} else {
message.error(message)
@ -170,12 +153,12 @@ export function useSessionMenu() {
ServeTopTalkList({
list_id: item.id,
type: item.is_top == 0 ? 1 : 2,
type: item.is_top == 0 ? 1 : 2
}).then(({ code, message }) => {
if (code == 200) {
talkStore.updateItem({
index_name: item.index_name,
is_top: item.is_top == 0 ? 1 : 0,
is_top: item.is_top == 0 ? 1 : 0
})
} else {
message.error(message)
@ -218,7 +201,7 @@ export function useSessionMenu() {
negativeText: '取消',
onPositiveClick: () => {
ServeSecedeGroup({
group_id: item.receiver_id,
group_id: item.receiver_id
}).then(({ code, message }) => {
if (code == 200) {
message.success('已退出群聊')
@ -227,7 +210,7 @@ export function useSessionMenu() {
message.error(message)
}
})
},
}
})
}
@ -276,18 +259,12 @@ export function useSessionMenu() {
disturb: onSetDisturb,
signout_group: onSignOutGroup,
delete_contact: onDeleteContact,
remark: onChangeRemark,
remark: onChangeRemark
}
dropdown.show = false
evnets[key] && evnets[key](dropdown.item)
}
return {
dropdown,
onCloseContextMenu,
onContextMenuTalkHandle,
onToTopTalk,
onRemoveTalk,
}
return { dropdown, onCloseContextMenu, onContextMenuTalkHandle, onToTopTalk, onRemoveTalk }
}

View File

@ -10,24 +10,13 @@ import tmui from '@/uni_modules/tmui'
import { config } from '@/config/tmui/index.js'
import 'dayjs/locale/zh-cn'
import xLoaderror from '@/components/x-loaderror/index.vue'
import asyncLoading from '@/components/async-loading/index.vue'
import asyncError from '@/components/async-error/index.vue'
import { vLoading } from '@/components/x-loading/index.js'
import messagePopup from '@/components/x-message/useMessagePopup'
import pageAnimation from '@/components/page-animation/index.vue'
import * as plugins from './plugins'
import {
useDialogueStore,
useTalkStore,
useUserStore,
useDialogueListStore,
} from '@/store'
import { uniStorage } from '@/utils/uniStorage.js'
import { handleFindWebview } from '@/utils/common'
const { showMessage } = messagePopup()
dayjs.locale('zh-cn')
if (import.meta.env.VITE_SHOW_CONSOLE === 'true') {
if (import.meta.env.VITE_SHOW_CONSOLE) {
new VConsole()
}
export function createApp() {
@ -39,8 +28,6 @@ export function createApp() {
app.mixin(pageAnimation)
app.component('customNavbar', customNavbar)
app.component('x-loaderror', xLoaderror)
app.component('AsyncLoading', asyncLoading)
app.component('AsyncError', asyncError)
app.directive('no-space', {
mounted(el) {
el.addEventListener('input', (e) => {
@ -53,94 +40,6 @@ export function createApp() {
})
},
})
//获取当前聊天页面所在页面并通过当前的receiver_id判断是否要创建本地通知栏消息
window.getCurrentChatRoute = (msg) => {
let pushMsg = JSON.parse(decodeURIComponent(msg))
//当前所在聊天会话的receiver_id
const receiver_id = pushMsg?.payload?.receiver_id
// 获取当前页面路径
const pages = getCurrentPages()
const page = pages[pages.length - 1]
console.log(page.route)
const dialogueStore = useDialogueStore()
console.log(dialogueStore?.talk?.receiver_id)
if (
page?.route === 'pages/dialog/index' &&
receiver_id === dialogueStore?.talk?.receiver_id
) {
return
}
console.log('===准备创建本地通知栏消息')
handleFindWebview(`doCreatePushMessage('${msg}')`)
}
//处理聊天推送弹窗点开
window.openUniPushMsg = (msg) => {
console.log('=====点击通知栏消息')
let pushMsg = JSON.parse(decodeURIComponent(msg))
console.log('=====pushMsg', pushMsg)
//由于弹窗前处理了不该弹窗的场景,因此这里弹窗可以一并处理
//也就是都跳转到聊天页面
const talkStore = useTalkStore()
talkStore.toTalk(pushMsg?.payload?.talk_type, pushMsg?.payload?.receiver_id)
}
//处理当用户信息发生变化时,更新用户信息
window.updateUserInfo = () => {
useUserStore().loadSetting()
}
// 通讯录跳转
window.handleContacts = () => {
// 旧版本-按组织架构树的通讯录
uni.navigateTo({
url: '/pages/chooseByDeps/index?chooseMode=3&type=true'
});
// 新版本-按公司别、好友、群组的通讯录
// uni.navigateTo({
// url: '/pages/addressBook/index?type=true',
// })
}
//处理OA、墨册强制刷新时聊天同步强制刷新
window.doLocationRefresh = () => {
uniStorage.removeItem('dialogueList')
uniStorage.removeItem('dialogue')
useUserStore().loadSetting()
useDialogueListStore().dialogueList.value = []
// location.reload(true)
}
//检查聊天页面是否可用
window.checkChatWebviewAvailable = () => {
handleFindWebview(`doneCheckChatWebviewAvailable()`)
}
//获取从base传来的多选视频列表
window.getBaseMulVideo = (videoList) => {
const videos = JSON.parse(decodeURIComponent(videoList))
console.error('=====videos', videos)
if (videos.length > 0) {
const videoUri = videos[0]
console.error('=====videoUri', videoUri)
}
}
//检查是否是特殊测试用户,开启控制台
const checkTestUser = () => {
const userStore = useUserStore()
if (
import.meta.env.VITE_SHOW_CONSOLE === 'false' &&
userStore.mobile == '18100591363'
) {
new VConsole()
}
}
checkTestUser()
window.message = ['success', 'error', 'warning'].reduce((acc, type) => {
acc[type] = (message) => {
if (typeof message === 'string') {

View File

@ -5,7 +5,8 @@
"^tm-(.*)": "@/tmui/components/tm-$1/tm-$1.vue"
}
},
"pages": [{
"pages": [
{
"path": "pages/index/index",
"type": "page",
"style": {
@ -177,38 +178,6 @@
"navigationStyle": "custom",
"enablePullDownRefresh": false
}
},
{
"path": "pages/chatSettings/groupManage/manageGroupDeps",
"type": "page",
"style": {
"navigationStyle": "custom",
"enablePullDownRefresh": false
}
},
{
"path": "pages/complaintReport/index",
"type": "page",
"style": {
"navigationStyle": "custom",
"enablePullDownRefresh": false
}
},
{
"path": "pages/addressBook/index",
"type": "page",
"style": {
"navigationStyle": "custom",
"enablePullDownRefresh": false
}
},
{
"path": "pages/addressBook/addFriend/index",
"type": "page",
"style": {
"navigationStyle": "custom",
"enablePullDownRefresh": false
}
}
],
"globalStyle": {

View File

@ -1,398 +0,0 @@
<template>
<div class="add-friend-page">
<zPaging ref="zPaging" :show-scrollbar="false" @scrolltolower="doLoadMore">
<template #top>
<div :class="'top_bg'">
<customNavbar
:class="'index_top_navbar'"
:title="$t('addFriend.pageTitle')"
></customNavbar>
<div class="pl-[32rpx] pr-[32rpx] pt-[32rpx] pb-[32rpx]">
<customInput
:searchText="searchVal"
@inputSearchText="inputSearchText"
></customInput>
</div>
</div>
</template>
<div class="add-friend">
<div class="add-friend-list" v-if="state.friendsList.length > 0">
<div
class="members-list-each"
v-for="(item, index) in state.friendsList"
:key="index"
@click="toUserDetail(item)"
>
<div class="members-info">
<avatarModule
:mode="1"
:avatar="item.avatar"
:groupType="0"
:userName="item.nickname"
:customStyle="{ width: '72rpx', height: '72rpx' }"
:customTextStyle="{
fontSize: '32rpx',
fontWeight: 'bold',
color: '#fff',
lineHeight: '44rpx',
}"
></avatarModule>
<div class="members-info-area">
<div
class="members-info-basic"
:style="{ padding: item.company_name ? 0 : '0 0 24rpx' }"
>
<span class="members-name">
{{ item.nickname }}
</span>
<span class="members-jobNum">
{{ item.job_num }}
</span>
<!-- <span class="members-company">{{ item.company_name }}</span> -->
</div>
<div class="members-positions-area">
<tm-popover position="bc">
<tm-scrolly :refresher="false" :height="84">
<div class="members-positions">
<div
class="members-positions-each"
v-for="(postionItem,
positionIndex) in item.user_position"
:key="positionIndex"
>
{{ postionItem.position_name }}
</div>
</div>
</tm-scrolly>
<template v-slot:label>
<tm-scrolly
:refresher="false"
:height="
item.user_position.length >= 4
? 180
: item.user_position.length === 3
? 140
: item.user_position.length === 2
? 100
: item.user_position.length === 1
? 60
: 0
"
>
<div class="members-positions-popover-box">
<div
class="members-positions-popover"
v-for="(postionItem,
positionIndex) in item.user_position"
:key="positionIndex"
>
{{ postionItem.position_name }}
</div>
</div>
</tm-scrolly>
</template>
</tm-popover>
</div>
</div>
</div>
<div class="company-infos" v-if="item.company_name">
<div class="company-each">
<span>{{ item.company_name }}</span>
</div>
</div>
</div>
</div>
<div class="addressBook-noData" v-if="state.friendsList.length === 0">
<img src="@/static/image/search/search-no-data.png" />
<span>
{{
searchVal
? $t('addFriend.message.notFindData')
: $t('search.hint')
}}
</span>
</div>
</div>
</zPaging>
</div>
</template>
<script setup>
import customInput from '@/components/custom-input/custom-input.vue'
import zPaging from '@/uni_modules/z-paging/components/z-paging/z-paging.vue'
import avatarModule from '@/components/avatar-module/index.vue'
import { ServeFriendSearch } from '@/api/addressBook/index'
import { handleSetWebviewStyle } from '@/utils/common'
import { ref, onMounted, reactive } from 'vue'
import { useI18n } from 'vue-i18n'
const { t } = useI18n()
const searchVal = ref('')
const state = reactive({
friendsListPage: 1, //
friendsListPageSize: 10, //
friendsList: [], //
hasMoreFriends: true, //
})
onMounted(() => {
getFriendsList()
handleSetWebviewStyle()
})
//
const inputSearchText = (e) => {
searchVal.value = e
}
//
const getFriendsList = () => {
let params = {
name: searchVal.value,
}
ServeFriendSearch(params)
.then((res) => {
console.error(res)
if (res?.code === 200) {
if (state.friendsListPage === 1) {
state.friendsList = res.data?.user_list || []
} else {
state.friendsList = state.friendsList.concat(
res.data?.user_list || [],
)
}
if (state.friendsList.length < res.data?.count) {
state.hasMoreFriends = true
} else {
state.hasMoreFriends = false
}
}
})
.catch((err) => {
console.log(err)
})
}
//
const toUserDetail = (userInfo) => {
uni.navigateTo({
url:
'/pages/dialog/dialogDetail/userDetail??erpUserId=' +
userInfo.erp_user_id,
})
}
//
const doLoadMore = (e) => {
state.friendsListPage += 1
getFriendsList()
}
watch(
() => searchVal.value,
(newVal) => {
state.friendsListPage = 1
getFriendsList()
},
)
</script>
<style scoped lang="scss">
::v-deep .zp-paging-container-content {
height: 100%;
display: flex;
}
::v-deep .index_top_navbar .tmicon-angle-left {
color: #fff !important;
}
::v-deep .index_top_navbar .text-weight-b {
color: #fff !important;
}
::v-deep .index_top_navbar .statusHeightTop > .noNvueBorder:first-child {
background: transparent !important;
border: none !important;
}
.top_bg {
background: url('@/static/image/mine/page_top.png') no-repeat;
background-size: cover;
background-position: bottom center;
}
:deep(.animateAll_tabs_tmui) {
width: 120rpx !important;
height: 10rpx !important;
}
.add-friend-page {
.add-friend {
flex: 1;
display: flex;
flex-direction: column;
background-image: url('@/static/image/clockIn/z3280@3x.png');
background-size: cover;
background-position: bottom center;
background-attachment: fixed;
width: 100%;
.add-friend-list {
margin: 30rpx 24rpx;
overflow: hidden;
flex: 1;
gap: 30rpx 0;
.members-list-each {
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: center;
background-color: #fff;
width: 100%;
border-radius: 8rpx;
.swipe-action {
width: 100%;
overflow: visible !important;
:deep(.wd-swipe-action__right) {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
background-color: #cf3050;
padding: 0 48rpx;
border-radius: 0 8rpx 8rpx 0;
span {
color: #fff;
line-height: 1;
font-size: 30rpx;
font-weight: 400;
}
}
}
.members-info {
display: flex;
flex-direction: row;
align-items: flex-start;
justify-content: flex-start;
gap: 16rpx;
padding: 24rpx 24rpx 0;
.members-info-area {
display: flex;
flex-direction: row;
align-items: flex-start;
justify-content: flex-start;
gap: 16rpx;
.members-info-basic {
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: center;
.members-name {
font-size: 30rpx;
font-weight: 500;
width: 150rpx;
word-break: break-all;
}
.members-jobNum {
font-size: 26rpx;
font-weight: 400;
color: #999;
word-break: break-all;
margin: 10rpx 0 0;
}
.members-company {
font-size: 24rpx;
font-weight: 400;
line-height: 1;
color: #999;
}
}
.members-positions-area {
.members-positions {
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-start;
flex-wrap: wrap;
gap: 10rpx;
padding: 4rpx 0 0;
max-height: 84rpx;
overflow: hidden;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
line-clamp: 2;
.members-positions-each {
background-color: #eee9f8;
line-height: 1;
font-size: 24rpx;
padding: 4rpx 16rpx;
color: #46299d;
}
}
.members-positions-popover-box {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 10rpx 0;
.members-positions-popover {
background-color: #eee9f8;
line-height: 1;
font-size: 24rpx;
padding: 8rpx 16rpx;
color: #46299d;
width: 100%;
}
}
}
}
}
.company-infos {
margin: 0 0 0 88rpx;
padding: 0 0 24rpx 24rpx;
.company-each {
span {
font-size: 24rpx;
font-weight: 400;
line-height: 1;
color: #999;
}
}
}
}
}
.addressBook-noData {
margin: 30rpx 24rpx;
overflow: hidden;
flex: 1;
gap: 30rpx 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
img {
width: 500rpx;
}
span {
font-size: 30rpx;
font-weight: 400;
line-height: 1;
color: #999;
}
}
}
}
</style>

View File

@ -1,944 +0,0 @@
<template>
<div class="address-book-page">
<zPaging ref="zPaging" :show-scrollbar="false" @scrolltolower="doLoadMore">
<template #top>
<div :class="'top_bg'">
<customNavbar
:class="'index_top_navbar'"
:title="$t('index.mine.addressBook')"
:hideHome="navshow"
:hideBack="navshow"
>
<template #left>
<tm-icon
@click="goWebHome"
v-if="navshow"
name="tmicon-angle-left"
style="padding-left: 30rpx;"
></tm-icon>
</template>
</customNavbar>
<div class="pl-[32rpx] pr-[32rpx] pt-[32rpx] pb-[32rpx]">
<customInput
:searchText="searchVal"
@inputSearchText="inputSearchText"
></customInput>
</div>
</div>
<tm-tabs
:list="state.addressBookTabs"
align="center"
:width="750"
:height="300"
:itemWidth="250"
default-name="company"
activeColor="#46299d"
activeFontColor="#46299d"
tabs-line-ani-color="#46299d"
:showTabsLineAni="true"
:showTabsLine="false"
:activeFontSize="32"
:itemFontSize="30"
@update:activeName="updateAddressBookTab"
></tm-tabs>
</template>
<div class="address-book">
<div class="address-book-tabs-panes-list">
<div
class="tabs-panes-each address-book-company"
v-if="
state.addressBookActiveTab === 'company' &&
state.myContractList.length > 0
"
>
<div class="address-book-company-name">
<span>{{ state.myCompany }}</span>
</div>
<div
class="members-list-each"
v-for="(item, index) in state.myContractList"
:key="index"
>
<div class="members-info" @click="toUserDetail(item)">
<avatarModule
:mode="1"
:avatar="item.avatar"
:groupType="0"
:userName="item.nickname"
:customStyle="{ width: '72rpx', height: '72rpx' }"
:customTextStyle="{
fontSize: '32rpx',
fontWeight: 'bold',
color: '#fff',
lineHeight: '44rpx',
}"
></avatarModule>
<div class="members-info-area">
<div class="members-info-basic" style="padding: 0 0 24rpx;">
<span class="members-name">
{{ item.nickname }}
</span>
<span class="members-jobNum">
{{ item.job_num }}
</span>
</div>
<div class="members-positions-area">
<tm-popover position="bc">
<tm-scrolly :refresher="false" :height="84">
<div class="members-positions">
<div
class="members-positions-each"
v-for="(postionItem,
positionIndex) in item.user_position"
:key="positionIndex"
>
{{ postionItem.position_name }}
</div>
</div>
</tm-scrolly>
<template v-slot:label>
<tm-scrolly
:refresher="false"
:height="
item.user_position.length >= 4
? 180
: item.user_position.length === 3
? 140
: item.user_position.length === 2
? 100
: item.user_position.length === 1
? 60
: 0
"
>
<div class="members-positions-popover-box">
<div
class="members-positions-popover"
v-for="(postionItem,
positionIndex) in item.user_position"
:key="positionIndex"
>
{{ postionItem.position_name }}
</div>
</div>
</tm-scrolly>
</template>
</tm-popover>
</div>
</div>
</div>
</div>
</div>
<div
class="tabs-panes-each address-book-friends"
v-if="
state.addressBookActiveTab === 'friends' &&
state.myFriendsList.length > 0
"
>
<div
class="members-list-each"
v-for="(item, index) in state.myFriendsList"
:key="index"
>
<wd-swipe-action
class="swipe-action"
@click="showDeleteModal(item, index)"
v-if="
!item.company_name ||
(item.company_name && item.company_name !== state.myCompany)
"
>
<div class="members-info" @click="toUserDetail(item)">
<avatarModule
:mode="1"
:avatar="item.avatar"
:groupType="0"
:userName="item.nickname"
:customStyle="{ width: '72rpx', height: '72rpx' }"
:customTextStyle="{
fontSize: '32rpx',
fontWeight: 'bold',
color: '#fff',
lineHeight: '44rpx',
}"
></avatarModule>
<div class="members-info-area">
<div
class="members-info-basic"
:style="{ padding: item.company_name ? 0 : '0 0 24rpx' }"
>
<span class="members-name">
{{ item.nickname }}
</span>
<span class="members-jobNum">
{{ item.job_num }}
</span>
<!-- <span class="members-company">{{ item.company_name }}</span> -->
</div>
<div class="members-positions-area">
<tm-popover position="bc">
<tm-scrolly :refresher="false" :height="84">
<div class="members-positions">
<div
class="members-positions-each"
v-for="(postionItem,
positionIndex) in item.user_position"
:key="positionIndex"
>
{{ postionItem.position_name }}
</div>
</div>
</tm-scrolly>
<template v-slot:label>
<tm-scrolly
:refresher="false"
:height="
item.user_position.length >= 4
? 180
: item.user_position.length === 3
? 140
: item.user_position.length === 2
? 100
: item.user_position.length === 1
? 60
: 0
"
>
<div class="members-positions-popover-box">
<div
class="members-positions-popover"
v-for="(postionItem,
positionIndex) in item.user_position"
:key="positionIndex"
>
{{ postionItem.position_name }}
</div>
</div>
</tm-scrolly>
</template>
</tm-popover>
</div>
</div>
</div>
<div class="company-infos" v-if="item.company_name">
<div class="company-each">
<span>{{ item.company_name }}</span>
</div>
</div>
<template #right>
<div
v-for="(swipeActionItem,
swipeActionIndex) in state.swipeAction"
:key="swipeActionIndex"
>
<span>{{ swipeActionItem.text }}</span>
</div>
</template>
</wd-swipe-action>
</div>
</div>
<div
class="tabs-panes-each address-book-groups"
v-if="
state.addressBookActiveTab === 'groups' &&
state.myGroupsList.length > 0
"
>
<div
class="groups-list-each"
v-for="(item, index) in state.myGroupsList"
:key="index"
>
<div class="groups-info">
<avatarModule
:mode="2"
:avatar="item?.avatar"
:groupType="Number(item?.group_type)"
:customStyle="{ width: '72rpx', height: '72rpx' }"
></avatarModule>
<span class="groups-name">
{{ item?.group_name }}
</span>
<span
class="groups-type"
:style="{
color:
groupTypeMapping[item?.group_type]?.result_type_color,
border:
'1px solid' +
groupTypeMapping[item?.group_type]?.result_type_color,
}"
>
{{ groupTypeMapping[item?.group_type]?.result_type }}
</span>
</div>
<div class="groups-btns">
<div class="groups-btns-each" @click="toGroupChat(item)">
<span>{{ $t('addressBook.btns.enterGroup') }}</span>
</div>
</div>
</div>
</div>
<div
class="addressBook-noData"
v-if="
!state.isLoadingData &&
((state.addressBookActiveTab === 'company' &&
state.myContractList.length === 0) ||
(state.addressBookActiveTab === 'friends' &&
state.myFriendsList.length === 0) ||
(state.addressBookActiveTab === 'groups' &&
state.myGroupsList.length === 0))
"
>
<img src="@/static/image/search/search-no-data.png" />
<span>
{{
searchVal
? $t('addFriend.message.notFindData')
: $t('search.hint')
}}
</span>
</div>
</div>
</div>
<tm-modal
class="friendDeleteModal"
ref="friendDeleteModalRef"
:mask="true"
:okText="$t('ok')"
okColor="#46299d"
cancelColor="#999"
@ok="doDeleteFriend"
@cancel="hideDeleteModal"
:teleport="false"
titleStyle="font-size: 40rpx;font-weight: 600;line-height: 1;"
:height="300"
:round="4"
>
<div class="friendDeleteModal-content">
<span>{{ $t('addressBook.message.doOrNotDeleteFriend') }}</span>
</div>
</tm-modal>
</zPaging>
</div>
</template>
<script setup>
import customInput from '@/components/custom-input/custom-input.vue'
import zPaging from '@/uni_modules/z-paging/components/z-paging/z-paging.vue'
import avatarModule from '@/components/avatar-module/index.vue'
import { ref, onMounted, reactive, watch } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
import { handleSetWebviewStyle, handleFindWebview } from '@/utils/common'
import { ServeUserGroupChatList, ServeCreateTalkList } from '@/api/chat/index'
import { ServeGetSessionId } from '@/api/search/index'
import { formatTalkItem } from '@/utils/talk'
import { useDialogueStore, useTalkStore } from '@/store'
import {
ServeQueryFriendsList,
ServeDeleteFriend,
} from '@/api/addressBook/index'
import { useI18n } from 'vue-i18n'
const { t } = useI18n()
const navshow = ref(false)
const searchVal = ref('')
const friendDeleteModalRef = ref(null)
const state = reactive({
addressBookTabs: [], //tab
addressBookActiveTab: 'company', //tab
myContractListPage: 1, //
myContractListPageSize: 10, //
myContractList: [], //
hasMoreContracts: true, //
myFriendsListPage: 1, //
myFriendsListPageSize: 10, //
myFriendsList: [], //
hasMoreFriends: true, //
myGroupsListPage: 1, //
myGroupsListPageSize: 10, //
myGroupsList: [], //
hasMoreGroups: true, //
myCompany: '', //
swipeAction: [], //
isLoadingData: true, //
})
onLoad((options) => {
if (options.type) {
navshow.value = true
}
})
const goWebHome = () => {
uni.navigateBack()
handleFindWebview(`handleBackHost()`)
}
onMounted(() => {
state.swipeAction = [
{
text: t('addressBook.btns.delete'), //,
},
]
state.addressBookTabs = [
{
key: 'company',
title: t('addressBook.tabs.company'),
},
{
key: 'friends',
title: t('addressBook.tabs.friends'),
},
{
key: 'groups',
title: t('addressBook.tabs.groups'),
},
]
handleSetWebviewStyle()
getMyContractList()
})
//
const inputSearchText = (e) => {
searchVal.value = e
}
//tab
const updateAddressBookTab = (e) => {
state.addressBookActiveTab = e
}
//
const getMyContractList = () => {
let params = {
type: 'addressBook', //addressBook
page: state.myContractListPage,
page_size: state.myContractListPageSize,
name: searchVal.value,
}
// console.log(params)
state.isLoadingData = true
ServeQueryFriendsList(params)
.then((res) => {
// console.log(res)
state.isLoadingData = false
if (res?.code === 200) {
state.myCompany = res.data?.company_name
if (state.myContractListPage === 1) {
state.myContractList = res.data?.user_list || []
} else {
state.myContractList = state.myContractList.concat(
res.data?.user_list || [],
)
}
if (state.myContractList.length < res.data?.count) {
state.hasMoreContracts = true
} else {
state.hasMoreContracts = false
}
}
})
.catch((err) => {
state.isLoadingData = false
})
}
//
const getMyFriendsList = () => {
let params = {
type: 'myFriends', //myFriends
page: state.myFriendsListPage,
page_size: state.myFriendsListPageSize,
name: searchVal.value,
}
console.error(params)
state.isLoadingData = true
ServeQueryFriendsList(params)
.then((res) => {
console.log(res)
state.isLoadingData = false
if (res?.code === 200) {
if (state.myFriendsListPage === 1) {
state.myFriendsList = res.data?.user_list || []
} else {
state.myFriendsList = state.myFriendsList.concat(
res.data?.user_list || [],
)
}
if (state.myFriendsList.length < res.data?.count) {
state.hasMoreFriends = true
} else {
state.hasMoreFriends = false
}
}
})
.catch((err) => {
state.isLoadingData = false
})
}
//
const toUserDetail = (userInfo) => {
uni.navigateTo({
url:
'/pages/dialog/dialogDetail/userDetail??erpUserId=' +
userInfo.erp_user_id,
})
}
//
const getMyGroupsList = () => {
let params = {
page: state.myGroupsListPage,
page_size: state.myGroupsListPageSize,
group_name: searchVal.value,
}
state.isLoadingData = true
ServeUserGroupChatList(params)
.then((res) => {
console.log(res)
state.isLoadingData = false
if (res?.code === 200) {
if (state.myGroupsListPage === 1) {
state.myGroupsList = res.data?.items || []
} else {
state.myGroupsList = state.myGroupsList.concat(res.data?.items || [])
}
if (state.myGroupsList.length < res.data?.total) {
state.hasMoreGroups = true
} else {
state.hasMoreGroups = false
}
}
})
.catch((err) => {
state.isLoadingData = false
})
}
//
const doLoadMore = (e) => {
if (state.addressBookActiveTab === 'company' && state.hasMoreContracts) {
state.myContractListPage += 1
getMyContractList()
} else if (state.addressBookActiveTab === 'friends' && state.hasMoreFriends) {
state.myFriendsListPage += 1
getMyFriendsList()
} else if (state.addressBookActiveTab === 'groups' && state.hasMoreGroups) {
state.myGroupsListPage += 1
getMyGroupsList()
}
}
//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 toGroupChat = async (groupInfo) => {
let talk_type = 2
let receiver_id = groupInfo.id
const sessionId = await getSessionId(talk_type, receiver_id)
if (useTalkStore().findTalkIndex(`${talk_type}_${receiver_id}`) === -1) {
ServeCreateTalkList({
talk_type,
receiver_id,
}).then(async ({ code, data }) => {
if (code == 200) {
let item = formatTalkItem(data)
useTalkStore().addItem(item)
}
})
}
useDialogueStore().setDialogue({
name: groupInfo.group_name,
talk_type: 2,
receiver_id: receiver_id,
})
uni.navigateTo({
url: '/pages/dialog/index?sessionId=' + sessionId,
})
}
//
const showDeleteModal = (userInfo, friendIndex) => {
friendDeleteModalRef.value.open({
userInfo,
friendIndex,
})
}
//
const hideDeleteModal = () => {
friendDeleteModalRef.value.close()
}
//
const doDeleteFriend = (args) => {
console.log(args.userInfo, args.friendIndex)
let params = {
receiver_id: args.userInfo.id, //id
talk_type: 1,
}
ServeDeleteFriend(params).then((res) => {
console.log(res)
if (res?.code === 200) {
message.success(t('addressBook.message.deleteSuccess') + ' !')
state.myFriendsList.splice(args.friendIndex, 1)
}
})
}
// -groupType
const groupTypeMapping = {
0: {},
1: {},
2: {
result_type: t('index.mine.department'),
result_type_color: '#377EC6',
},
3: {
result_type: t('index.mine.project'),
result_type_color: '#C1681C',
},
4: {
result_type: t('index.type.company'),
result_type_color: '#7A58DE',
},
}
watch(
() => state.addressBookActiveTab,
(newVal) => {
if (newVal === 'company') {
state.myContractListPage = 1
getMyContractList()
} else if (newVal === 'friends') {
state.myFriendsListPage = 1
getMyFriendsList()
} else if (newVal === 'groups') {
state.myGroupsListPage = 1
getMyGroupsList()
}
},
)
watch(
() => searchVal.value,
(newVal) => {
if (state.addressBookActiveTab === 'company') {
state.myContractListPage = 1
getMyContractList()
} else if (state.addressBookActiveTab === 'friends') {
state.myFriendsListPage = 1
getMyFriendsList()
} else if (state.addressBookActiveTab === 'groups') {
state.myGroupsListPage = 1
getMyGroupsList()
}
},
)
</script>
<style scoped lang="scss">
::v-deep .zp-paging-container-content {
height: 100%;
display: flex;
}
::v-deep .index_top_navbar .tmicon-angle-left {
color: #fff !important;
}
::v-deep .index_top_navbar .text-weight-b {
color: #fff !important;
}
::v-deep .index_top_navbar .statusHeightTop > .noNvueBorder:first-child {
background: transparent !important;
border: none !important;
}
.top_bg {
background: url('@/static/image/mine/page_top.png') no-repeat;
background-size: cover;
background-position: bottom center;
}
:deep(.animateAll_tabs_tmui) {
width: 120rpx !important;
height: 10rpx !important;
}
.address-book-page {
.address-book {
flex: 1;
display: flex;
flex-direction: column;
background-image: url('@/static/image/clockIn/z3280@3x.png');
background-size: cover;
background-position: bottom center;
background-attachment: fixed;
width: 100%;
.address-book-tabs-panes-list {
margin: 30rpx 24rpx;
overflow: hidden;
flex: 1;
display: flex;
flex-direction: column;
.tabs-panes-each {
gap: 30rpx 0;
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: center;
}
.address-book-company,
.address-book-friends {
.address-book-company-name {
margin: 0 0 -16rpx;
span {
font-size: 26rpx;
font-weight: 400;
line-height: 1;
color: #999;
}
}
.members-list-each {
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: center;
background-color: #fff;
width: 100%;
border-radius: 8rpx;
.swipe-action {
width: 100%;
overflow: visible !important;
:deep(.wd-swipe-action__right) {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
background-color: #cf3050;
padding: 0 48rpx;
border-radius: 0 8rpx 8rpx 0;
span {
color: #fff;
line-height: 1;
font-size: 30rpx;
font-weight: 400;
}
}
}
.members-info {
display: flex;
flex-direction: row;
align-items: flex-start;
justify-content: flex-start;
gap: 16rpx;
padding: 24rpx 24rpx 0;
width: 100%;
.members-info-area {
display: flex;
flex-direction: row;
align-items: flex-start;
justify-content: flex-start;
gap: 16rpx;
.members-info-basic {
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: center;
.members-name {
font-size: 30rpx;
font-weight: 500;
width: 150rpx;
word-break: break-all;
}
.members-jobNum {
font-size: 26rpx;
font-weight: 400;
color: #999;
word-break: break-all;
margin: 10rpx 0 0;
}
.members-company {
font-size: 24rpx;
font-weight: 400;
line-height: 1;
color: #999;
}
}
.members-positions-area {
.members-positions {
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-start;
flex-wrap: wrap;
gap: 10rpx;
padding: 4rpx 0 0;
max-height: 84rpx;
overflow: hidden;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
line-clamp: 2;
.members-positions-each {
background-color: #eee9f8;
line-height: 1;
font-size: 24rpx;
padding: 4rpx 16rpx;
color: #46299d;
}
}
.members-positions-popover-box {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 10rpx 0;
.members-positions-popover {
background-color: #eee9f8;
line-height: 1;
font-size: 24rpx;
padding: 8rpx 16rpx;
color: #46299d;
width: 100%;
}
}
}
}
}
.company-infos {
margin: 0 0 0 88rpx;
padding: 0 0 24rpx 24rpx;
.company-each {
span {
font-size: 24rpx;
font-weight: 400;
line-height: 1;
color: #999;
}
}
}
}
}
.address-book-groups {
.groups-list-each {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
background-color: #fff;
width: 100%;
padding: 24rpx;
border-radius: 8rpx;
.groups-info {
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-start;
gap: 16rpx;
.groups-name {
font-size: 30rpx;
font-weight: 500;
}
.groups-type {
font-size: 24rpx;
border-radius: 6rpx;
font-weight: bold;
padding: 6rpx 12rpx;
flex-shrink: 0;
}
}
.groups-btns {
flex-shrink: 0;
margin: 0 0 0 16rpx;
.groups-btns-each {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
background-color: #46299d;
padding: 16rpx;
border-radius: 8rpx;
span {
color: #fff;
font-size: 24rpx;
font-weight: 400;
line-height: 1;
}
}
}
}
}
.addressBook-noData {
margin: 30rpx 24rpx;
overflow: hidden;
flex: 1;
gap: 30rpx 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
img {
width: 500rpx;
}
span {
font-size: 30rpx;
font-weight: 400;
line-height: 1;
color: #999;
}
}
}
}
.friendDeleteModal {
:deep(.overlay) {
height: 100vh !important;
}
.friendDeleteModal-content {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
}
}
}
</style>

View File

@ -9,7 +9,7 @@
? ''
: '0',
}"
v-for="(memberItem, memberIndex) in sortedMemberList"
v-for="(memberItem, memberIndex) in props?.memberList"
@click="toUserDetailPage(memberItem)"
>
<div
@ -53,7 +53,7 @@
>
<div class="group-member-each">
<div class="group-member-avatar" :style="{ background: 'unset' }">
<img src="@/static/image/chatSettings/add-member.png" />
<img src="/src/static/image/chatSettings/add-member.png" />
</div>
<div class="group-member-name">
<span class="text-[24rpx] font-regular">添加</span>
@ -71,7 +71,7 @@
>
<div class="group-member-each">
<div class="group-member-avatar" :style="{ background: 'unset' }">
<img src="@/static/image/chatSettings/remove-member.png" />
<img src="/src/static/image/chatSettings/remove-member.png" />
</div>
<div class="group-member-name">
<span class="text-[24rpx] font-regular">移除</span>
@ -81,7 +81,7 @@
</div>
</template>
<script setup>
import { defineProps, computed } from 'vue'
import { defineProps } from 'vue'
const props = defineProps({
memberList: Array, //
memberListsLimit: Number, //
@ -90,36 +90,6 @@ const props = defineProps({
hideAddRemoveBtns: Boolean, //
})
//
const sortedMemberList = computed(() => {
if (!props.memberList || props.memberList.length === 0) return [];
//
const indexedList = props.memberList.map((item, index) => ({ item, index }));
// leader
return indexedList.sort((a, b) => {
const leaderA = a.item.leader || 0;
const leaderB = b.item.leader || 0;
// leaderleader
if ((leaderA === 1 || leaderA === 2) && (leaderB !== 1 && leaderB !== 2)) {
return -1; // a
}
if ((leaderB === 1 || leaderB === 2) && (leaderA !== 1 && leaderA !== 2)) {
return 1; // b
}
// leader
if ((leaderA === 1 || leaderA === 2) && (leaderB === 1 || leaderB === 2)) {
return leaderB - leaderA; // leader
}
//
return a.index - b.index;
}).map(item => item.item); //
})
//
const toUserDetailPage = (userItem) => {
console.log(userItem)

View File

@ -49,7 +49,7 @@
props?.memberItem?.is_mine && props?.manageType === 'removeMembers'
"
>
<img src="@/static/image/chatSettings/is-mine.png" />
<img src="/src/static/image/chatSettings/is-mine.png" />
</div>
<div
class="is-admin-tag"

View File

@ -1,803 +0,0 @@
<template>
<div class="select-member-by-alphabet">
<ZPaging ref="zPaging" :show-scrollbar="false" :use-virtual-list="true" :virtual-list-col="5"
:refresher-enabled="false" :loading-more-enabled="false" @scroll="onScroll" :fixed="false"
:height="props?.selectAreaHeight">
<div class="select-members">
<div class="search-member" v-if="
props?.manageType !== 'removeMembers' &&
!(
props?.manageType === 'admin' &&
(groupParams.groupInfo.group_type === 2 ||
groupParams.groupInfo.group_type === 4)
)
">
<customInput :searchText="state.searchText" @inputSearchText="inputSearchText"></customInput>
</div>
<div v-show="props?.manageType == 'removeMembers'" class="my-self">
<div class="my-self-left">
<image style="width: 72rpx;border-radius: 50%;height: 72rpx;" :src="mySelfMember.avatar" mode=""></image>
<div style="padding: 0 20rpx;">{{mySelfMember.nickname}}</div>
<img style="width: 45rpx;" src="@/static/image/chatSettings/is-mine.png" />
</div>
<div class="my-self-right">
{{ $t('group.identify.admin') }}
</div>
</div>
<div class="member-list" :style="{
padding: props?.manageType === 'searchRecord' ? '20rpx 0 0' : '',
}">
<div class="member-list-alphabet-anchor-point" :style="{
top: props?.manageType === 'mention' ? '90rpx' : '',
}">
<div class="member-list-alphabet-anchor-point-each" v-for="(alphabetItem, alphabetIndex) in state?.alphabet"
:key="alphabetIndex" :style="{
margin: state?.alphabet?.length > 17 ? '0' : '',
}" @click.stop="scrollToView(alphabetItem)">
<span class="text-[32rpx] font-regular" :style="{
color:
state.currentAlphabet === alphabetItem ? '#7A58DE' : '',
}">
{{ alphabetItem }}
</span>
</div>
</div>
<div class="member-list-alphabet" v-for="(alphabetItem, alphabetIndex) in state.resultMemberList"
:key="alphabetIndex">
<div class="member-list-alphabet-key" :style="{
padding:
props?.manageType === 'searchRecord' ||
props?.manageType === 'removeMembers' ||
props?.manageType === 'mention'
? '10rpx 30rpx'
: '',
}" v-if="
alphabetItem?.memberList?.length > 0 &&
alphabetItem?.key !== '0'
" :id="alphabetItem.key === '#' ? 'special-hash' : alphabetItem.key" :ref="
(el) => {
if (el) alphabetElementRefs[alphabetIndex] = el
}
">
<span class="text-[32rpx] font-regular">
{{ alphabetItem.key }}
</span>
</div>
<div v-if="alphabetItem?.memberList?.length > 0">
<div class="member-list-each" v-for="(item, index) in alphabetItem?.memberList" :key="index">
<tm-checkbox-group v-model="item.checkArr">
<selectMemberItem :groupType="groupParams.groupInfo.group_type" :memberItem="item"
@clickItem="handleClickItem(item)" :manageType="props?.manageType" :itemStyle="
props?.manageType === 'searchRecord' ||
props?.manageType === 'removeMembers' ||
props?.manageType === 'mention'
? 'list'
: 'card'
">
<template #left v-if="props?.manageType !== 'searchRecord'">
<div v-if="
props?.manageType === 'removeMembers' && item?.is_mine
">
<tm-checkbox color="#fff" :transprent="true" :border="0" :disabled="true"></tm-checkbox>
</div>
<tm-checkbox v-if="
!(
props?.manageType === 'removeMembers' &&
item?.is_mine
) && props?.isMulSelect
" :round="10" :color="
item?.checkArr?.length > 0 ? '#46299d' : '#B4B4B4'
" :outlined="
item?.checkArr?.length > 0 ||
(props?.manageType === 'silence' &&
item.is_mute === 1) ||
(props?.manageType === 'admin' &&
(item.leader === 1 || item.leader === 2))
? false
: true
" :value="item.id" :disabled="
(props?.manageType === 'silence' &&
item.is_mute === 1) ||
(props?.manageType === 'admin' &&
(item.leader === 1 || item.leader === 2))
" @change="checkBoxChange"></tm-checkbox>
</template>
</selectMemberItem>
</tm-checkbox-group>
</div>
</div>
</div>
</div>
</div>
</ZPaging>
</div>
</template>
<script setup>
import customInput from '@/components/custom-input/custom-input.vue'
import selectMemberItem from '../components/select-member-item.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 groupAllMember from '@/static/image/chatList/groupAllMember.png'
import {
computed,
onMounted,
reactive,
ref,
watch,
nextTick,
defineProps,
defineEmits,
} from 'vue'
import {
ServeGroupNoSpeak,
ServeEditGroupAdmin,
ServeGroupAssignAdmin,
ServeRemoveMembersGroup,
} from '@/api/group/index.js'
import {
useDialogueStore,
useGroupStore,
useGroupTypeStore
} from '@/store'
const emits = defineEmits([
'updateSelectedMembersNum',
'getSelectResult',
'getMentionSelectLists',
])
const zPaging = ref()
useZPaging(zPaging)
const groupStore = useGroupStore()
const groupParams = reactive({
groupInfo: computed(() => groupStore.groupInfo),
})
const groupTypeStore = useGroupTypeStore()
const groupTypeParams = reactive({
departmentAllPositions: computed(() => groupTypeStore.departmentAllPositions),
})
const dialogueStore = useDialogueStore()
const dialogueParams = reactive({
memberList: computed(() => {
const lowerCaseSearchText = state?.searchText.toLowerCase()
return dialogueStore.members.filter((item) =>
state?.searchText ?
item.nickname.toLowerCase().includes(lowerCaseSearchText) :
true,
)
}),
receiverId: computed(() => dialogueStore.talk.receiver_id),
})
const props = defineProps({
manageType: {
//
type: String,
default: '',
},
isCreateDepGroup: {
//
type: Number,
default: 0,
},
selectAreaHeight: {
//
type: String,
default: '',
},
isMulSelect: {
//
type: Boolean,
default: true,
},
})
const state = reactive({
searchText: '', //
alphabet: [], //A-Z
currentAlphabet: 'A', //A-Z
resultMemberList: [], //A-Z
isAssign: false, //view
scrollDirection: '', //
})
watch(
() => dialogueParams?.memberList,
(newMemberList) => {
assembleAlphabetMemberList(newMemberList)
}, {
deep: true
},
)
watch(
() => groupParams?.groupInfo,
(newGroupInfo) => {
assembleAlphabetMemberList(dialogueParams?.memberList)
}, {
deep: true
},
)
watch(
() => props?.isMulSelect,
(newIsMulSelect) => {
if (props?.manageType === 'mention') {
if (!newIsMulSelect) {
state.resultMemberList.unshift({
key: '0',
memberList: [{
avatar: groupAllMember,
erp_user_id: 0,
gender: 0,
is_mute: 0,
key: '0',
leader: 0,
nickname: '所有人',
remark: '',
user_id: 0,
}, ],
})
} else {
if (state.resultMemberList[0].key === '0') {
state.resultMemberList.splice(0, 1)
}
}
}
}, {
deep: true,
immediate: true
},
)
//A-Z tag
const alphabetElementRefs = ref([])
//
let observer
onMounted(() => {
if (props?.manageType) {
assembleAlphabetMemberList(dialogueParams?.memberList)
}
if (props?.isCreateDepGroup) {
assembleAlphabetMemberList()
}
dialogueParams.memberList.forEach((ele) => {
ele.checkArr = []
})
const options = {
root: null, // 使
threshold: 1.0, // 100%
}
observer = new IntersectionObserver(handleIntersection, options)
nextTick(() => {
watch(
alphabetElementRefs,
(newAlphabetElementRefs) => {
if (Array.isArray(newAlphabetElementRefs)) {
newAlphabetElementRefs.forEach((el, index) => {
observeElement(el, index)
})
}
}, {
immediate: true,
deep: true
},
)
if (alphabetElementRefs.value.length > 0) {
alphabetElementRefs.value.forEach((el, index) =>
observeElement(el, index),
)
}
})
})
//
const observeElement = (el, index) => {
if (el && observer) {
observer.observe(el)
}
}
//
const handleIntersection = (entries) => {
if (state.isAssign) {
state.isAssign = false
return
}
entries.forEach((entry) => {
if (!entry.isIntersecting && state.scrollDirection === 'down') {
state.currentAlphabet = entry.target.id
} else if (entry.isIntersecting && state.scrollDirection === 'up') {
if (state?.alphabet?.length > 1) {
state?.alphabet.forEach((item, index) => {
if (item === entry.target.id && index > 0) {
state.currentAlphabet = state?.alphabet[index - 1]
}
})
} else {
state.currentAlphabet = entry.target.id
}
}
})
}
//
const inputSearchText = (e) => {
// console.log(e)
state.searchText = e
}
//item
const handleClickItem = (item) => {
if (
(props?.manageType === 'silence' && item.is_mute === 1) ||
(props?.manageType === 'admin' &&
(item.leader === 1 || item.leader === 2)) ||
(props?.manageType === 'removeMembers' && item.is_mine) ||
(props?.manageType === 'mention' && !props?.isMulSelect)
) {
if (props?.manageType === 'mention' && !props?.isMulSelect) {
emits('getMentionSelectLists', [item])
}
return
}
if (props?.manageType === 'searchRecord') {
uni.navigateTo({
url: '/pages/search/searchByCondition/index?condition=member&groupMemberId=' +
item.id,
})
return
}
let itemList = dialogueParams.memberList
if (
props?.manageType === 'admin' &&
(groupParams.groupInfo.group_type == 2 ||
groupParams.groupInfo.group_type == 4 ||
props?.isCreateDepGroup === 1)
) {
itemList = state.resultMemberList[0].memberList
}
itemList.forEach((ele) => {
if (ele.id == item.id) {
ele.checkArr = ele.checkArr?.length > 0 ? [] : [item.id]
if (ele.checkArr?.length > 0) {
emits('updateSelectedMembersNum', 1)
} else {
emits('updateSelectedMembersNum', -1)
}
}
})
}
const mySelfMember = ref({})
//A-Z
const assembleAlphabetMemberList = async (newMemberList) => {
if (props?.manageType === 'removeMembers' && Array.isArray(newMemberList)) {
// is_mine true
for (let i = 0; i < newMemberList.length; i++) {
const item = newMemberList[i];
if (item?.is_mine === true) {
mySelfMember.value = item;
newMemberList.splice(i, 1); //
break; //
}
}
}
if (
props?.manageType === 'searchRecord' ||
props?.manageType === 'removeMembers' ||
props?.manageType === 'mention'
) {
const resultMemberList = ref([])
const alphabet = Array.from({
length: 26
}, (_, i) =>
String.fromCharCode(i + 65),
)
let tempAlphabet = []
//
let groupedData = {
'#': [],
}
alphabet.forEach((letter) => {
groupedData[letter] = []
})
//
if (newMemberList) {
newMemberList.forEach((item) => {
const key = item.key?.toUpperCase()
if (alphabet.includes(key)) {
groupedData[key].push(item)
} else {
groupedData['#'].push(item)
}
})
}
//
alphabet.forEach((letter) => {
if (groupedData[letter].length > 0) {
tempAlphabet.push(letter)
}
resultMemberList.value.push({
key: letter,
memberList: groupedData[letter],
})
})
// #
if (groupedData['#'].length > 0) {
tempAlphabet.push('#')
resultMemberList.value.push({
key: '#',
memberList: groupedData['#'],
})
}
state.alphabet = tempAlphabet
if (props?.manageType === 'mention' && !props?.isMulSelect) {
resultMemberList.value.unshift({
key: '0',
memberList: [{
avatar: groupAllMember,
erp_user_id: 0,
gender: 0,
is_mute: 0,
key: '0',
leader: 0,
nickname: '所有人',
remark: '',
user_id: 0,
}, ],
})
}
state.resultMemberList = resultMemberList
} else {
if (
(groupParams.groupInfo.group_type == 2 ||
groupParams.groupInfo.group_type == 4) &&
props?.manageType === 'admin'
) {
let departmentIdsArr = []
if (groupParams?.groupInfo?.deptInfos?.length > 0) {
groupParams.groupInfo.deptInfos.forEach((item) => {
departmentIdsArr.push(item.dept_id)
})
}
getPosiByDep(departmentIdsArr)
} else if (props?.isCreateDepGroup === 1) {
let departmentIdsArr = []
if (groupTypeStore?.depCheckedKeys?.value?.length > 0) {
groupTypeStore.depCheckedKeys.value.forEach((item) => {
departmentIdsArr.push(item.ID)
})
}
getPosiByDep(departmentIdsArr)
} else {
state.resultMemberList = [{
key: '',
memberList: newMemberList,
}, ]
}
}
}
//
const getPosiByDep = async (departmentIdsArr) => {
await groupTypeStore.getPositionByDepartment({
IDs: departmentIdsArr,
})
let departmentAllPositions = []
if (groupTypeParams?.departmentAllPositions?.value?.length > 0) {
groupTypeParams?.departmentAllPositions?.value?.forEach((item) => {
item?.AllPositions?.forEach((positionItem) => {
departmentAllPositions.push({
nickname: item.name + '-' + positionItem.name,
id: item.ID + '-' + positionItem.ID,
checkArr: [],
positionInfo: {
dept_id: item.ID,
dept_name: item.name,
position_id: positionItem.ID,
position_name: positionItem.name,
},
})
})
})
}
if (groupParams?.groupInfo?.groupAdminList?.length > 0) {
groupParams?.groupInfo?.groupAdminList.forEach((item) => {
departmentAllPositions.forEach((idsItem) => {
if (item.dept_id + '-' + item.position_id == idsItem.id) {
idsItem.leader = 1
}
})
})
}
if (
props?.isCreateDepGroup === 1 &&
groupTypeStore?.groupAdmins?.value?.length > 0
) {
departmentAllPositions.forEach((allPos) => {
groupTypeStore.groupAdmins.value.forEach((admin) => {
if (allPos.id === admin.id) {
allPos.checkArr = [allPos.id]
}
})
})
}
if (state?.searchText) {
const lowerCaseSearchText = state?.searchText.toLowerCase()
departmentAllPositions = departmentAllPositions.filter((item) =>
state?.searchText ?
item.nickname.toLowerCase().includes(lowerCaseSearchText) :
true,
)
}
state.resultMemberList = [{
key: '',
memberList: departmentAllPositions,
}, ]
}
//view
const scrollToView = (alphabet) => {
state.currentAlphabet = alphabet
state.isAssign = true
//
const offsetHeight = document.getElementById('topArea')?.clientHeight ?
document.getElementById('topArea').clientHeight - 1 :
props?.manageType === 'mention' ?
140 :
80
// 使scrollIntoViewById
const targetId = alphabet === '#' ? 'special-hash' : alphabet
zPaging.value?.scrollIntoViewById(targetId, offsetHeight)
}
//
const onScroll = (e) => {
if (e.detail.deltaY < 0) {
state.scrollDirection = 'down'
} else if (e.detail.deltaY > 0) {
state.scrollDirection = 'up'
} else {
state.scrollDirection = ''
}
}
//
const checkBoxChange = (e) => {
if (e) {
emits('updateSelectedMembersNum', 1)
} else {
emits('updateSelectedMembersNum', -1)
}
}
//
const confirmSelectMembers = () => {
let selectedUserIds = ''
let itemList = dialogueStore.members
let positionInfos = []
let selectUserInfos = []
if (
props?.manageType === 'admin' &&
(groupParams.groupInfo.group_type == 2 ||
groupParams.groupInfo.group_type == 4 ||
props?.isCreateDepGroup === 1)
) {
itemList = state.resultMemberList[0].memberList
}
itemList.forEach((ele) => {
if (ele.checkArr?.length > 0) {
if (!selectedUserIds) {
selectedUserIds = String(ele.checkArr[0])
} else {
selectedUserIds += ',' + ele.checkArr[0]
}
selectUserInfos.push(ele)
}
if (
ele.checkArr?.length > 0 ||
(ele.leader && (ele.leader == 1 || ele.leader == 2))
) {
if (props?.isCreateDepGroup === 1) {
let posInfo = Object.assign({}, ele.positionInfo, {
name: ele.nickname,
id: ele.id,
})
positionInfos.push(posInfo)
} else {
positionInfos.push(ele.positionInfo)
}
}
})
console.log(selectedUserIds)
if (selectedUserIds) {
if (props?.manageType === 'silence') {
let params = {
mode: 1, //12
group_id: dialogueParams.receiverId, //id
user_ids: selectedUserIds, //ids
}
console.log(params)
const resp = ServeGroupNoSpeak(params)
resp.then(({
code,
data
}) => {
console.log(data)
if (code == 200) {
useDialogueStore().updateGroupMembers()
} else {}
})
resp.catch(() => {})
} else if (props?.manageType === 'admin') {
if (props?.isCreateDepGroup === 1) {
// console.log(positionInfos)
groupTypeStore.groupAdmins.value = positionInfos
uni.navigateBack({
delta: 1,
})
} else {
if (
groupParams.groupInfo.group_type == 1 ||
groupParams.groupInfo.group_type == 3
) {
let params = {
mode: 1, //12
group_id: dialogueParams.receiverId, //id
user_ids: selectedUserIds,
}
console.log(params)
const resp = ServeGroupAssignAdmin(params)
resp.then(({
code,
data
}) => {
console.log(data)
if (code == 200) {
useDialogueStore().updateGroupMembers()
} else {}
})
resp.catch(() => {})
} else if (
groupParams.groupInfo.group_type == 2 ||
groupParams.groupInfo.group_type == 4
) {
let params = {
source: 'app',
id: dialogueParams.receiverId,
deptInfos: groupParams.groupInfo.deptInfos,
positionInfos: positionInfos,
}
console.log(params)
const resp = ServeEditGroupAdmin(params)
resp.then(({
code,
data
}) => {
console.log(data)
if (code == 200) {
groupStore.ServeGroupDetail()
} else {}
})
resp.catch(() => {})
}
}
} else if (props?.manageType === 'removeMembers') {
let params = {
group_id: dialogueParams.receiverId, //id
members_ids: selectedUserIds, //id
}
console.log(params)
const resp = ServeRemoveMembersGroup(params)
resp.then(({
code,
data
}) => {
console.log(data)
if (code == 200) {
// console.error(-selectedUserIds.split(',').length)
emits('updateSelectedMembersNum', -selectedUserIds.split(',').length)
useDialogueStore().updateGroupMembers()
groupStore.ServeGroupDetail()
} else {}
})
resp.catch(() => {})
} else if (props?.manageType === 'mention') {
emits('getSelectResult', selectUserInfos)
}
}
}
//
defineExpose({
confirmSelectMembers,
})
</script>
<style lang="scss" scoped>
.my-self {
padding: 10rpx 60rpx;
display: flex;
justify-content: space-between;
align-items: center;
background-color: #fff;
.my-self-left {
display: flex;
align-items: center;
margin-left: 8%;
}
.my-self-right {
color: #b4b4b4;
padding: 0.1875rem 0.375rem;
border: #b4b4b4 1px solid;
border-radius: 8rpx;
font-size: 0.875rem;
}
}
.select-member-by-alphabet {
.select-members {
padding: 20rpx 32rpx;
.search-member {
padding: 22rpx 16rpx;
background-color: #fff;
}
.member-list {
.member-list-alphabet-anchor-point {
position: fixed;
right: 32rpx;
top: 0;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
.member-list-alphabet-anchor-point-each {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
margin: 0 0 14rpx;
span {
width: 52rpx;
text-align: center;
line-height: 44rpx;
color: $theme-text;
}
}
}
.member-list-alphabet {
.member-list-alphabet-key {
background-color: #f3f3f3;
span {
line-height: 44rpx;
color: $theme-text;
}
}
}
}
}
}
</style>

View File

@ -16,10 +16,9 @@
props?.item?.hasPointer &&
(props?.isManager ||
(!props?.isManager &&
(props?.item?.label !== $t('chat.settings.groupName') ||
props?.item?.label !== $t('chat.settings.groupType'))))
props?.item?.label !== $t('chat.settings.groupName')))
"
src="@/static/image/chatSettings/pointer.png"
src="/src/static/image/chatSettings/pointer.png"
/>
<tm-switch
:width="88"
@ -28,8 +27,8 @@
barIcon=""
color="#46299D"
unCheckedColor="#EEEEEE"
:modelValue="props?.item?.label === t('chat.settings.openReminder') ? openReminderStatus : modelValue"
:defaultValue="props?.item?.label === t('chat.settings.openReminder') ? openReminderStatus : modelValue"
:modelValue="modelValue"
:defaultValue="modelValue"
@change="changeSwitch($event, props?.item)"
></tm-switch>
</div>
@ -46,8 +45,8 @@
</div>
</template>
<script setup>
import { defineProps, defineEmits, computed, ref, onMounted, watch } from 'vue'
import { ServeContactRobotQuery } from '@/api/chat'
import { defineProps, defineEmits, computed } from 'vue'
import { useI18n } from 'vue-i18n'
const { t } = useI18n()
@ -103,32 +102,6 @@ const modelValue = computed(() => {
return switchStatus
})
//
const openReminderStatus = ref(false)
const loadRefuseStatus = async () => {
try {
const res = await ServeContactRobotQuery()
openReminderStatus.value = !!(res?.data?.Result)
} catch (err) {
openReminderStatus.value = false
}
}
onMounted(() => {
if (props?.item?.label === t('chat.settings.openReminder')) {
loadRefuseStatus()
}
})
watch(
() => props?.item?.label,
(newLabel) => {
if (newLabel === t('chat.settings.openReminder')) {
loadRefuseStatus()
}
}
)
//
const changeSwitch = (e, item) => {
emits('changeSwitch', e, item.label)

View File

@ -22,7 +22,6 @@
<customBtn
:btnText="$t('button.text.edit')"
@click="editAvatar"
:isLoading="state.isLoading"
></customBtn>
</ZPaging>
</div>
@ -50,10 +49,6 @@ const dialogueParams = reactive({
receiver_id: computed(() => dialogueStore.talk.receiver_id),
})
const state = reactive({
isLoading: false, //
})
const onProgressFn = (progress, id) => {
console.log((progress.loaded / progress.total) * 100, 'progress')
@ -74,7 +69,6 @@ const editAvatar = () => {
count: 1,
success: async (res) => {
console.log(res, 'res')
state.isLoading = true
res.tempFiles.forEach(async (file) => {
console.log(file)
let image = new Image()
@ -99,7 +93,6 @@ const editAvatar = () => {
const resp = ServeEditGroup(params)
resp.then(({ code }) => {
if (code == 200) {
state.isLoading = false
groupStore.updateGroupInfo({
avatar: data.ori_url,
})
@ -107,18 +100,13 @@ const editAvatar = () => {
// delta: 1,
// })
} else {
state.isLoading = false
}
})
resp.catch(() => {
state.isLoading = false})
resp.catch(() => {})
} else {
state.isLoading = false
}
},
).catch(() => {
state.isLoading = false
})
)
}
})
},

View File

@ -29,13 +29,10 @@
:placeholder="$t('edit.groupName.placeholder')"
placeholder-style="color:#B4B4B4;font-size:28rpx;font-weight:500;line-height:40rpx;"
v-model="state.groupName"
@input="handleGroupNameInput"
maxlength="20"
/>
<img
v-if="state.groupName"
class="groupName-input-clearBtn"
src="@/static/image/chatSettings/clear-btn.png"
src="/src/static/image/chatSettings/clear-btn.png"
@click="clearGroupNameInput"
/>
</div>
@ -85,12 +82,7 @@ onLoad((options) => {
const clearGroupNameInput = () => {
state.groupName = ''
}
const handleGroupNameInput = (e) => {
if (state.groupName.length > 20) {
// 20
state.groupName = state.groupName.slice(0, 20)
}
}
//
const confirmEdit = () => {
console.log(state.groupName)
@ -154,7 +146,7 @@ const confirmEdit = () => {
height: 110rpx;
box-shadow: 0 6px 12px 2px rgba(188, 188, 188, 0.08);
padding: 0 74rpx 0 32rpx;
color: #747474;
color: #B747474;
font-size: 28rpx;
font-weight: 500;
line-height: 40rpx;

View File

@ -46,7 +46,7 @@
<div class="group-admin-list-each-btns">
<img
v-if="item.is_mine"
src="@/static/image/chatSettings/is-mine.png"
src="/src/static/image/chatSettings/is-mine.png"
/>
<div
class="group-admin-list-each-btns-each"
@ -67,7 +67,7 @@
margin: state?.groupAdminList.length == 0 ? '20rpx 0 0' : '',
}"
>
<img src="@/static/image/chatSettings/add-btn.png" />
<img src="/src/static/image/chatSettings/add-btn.png" />
<span class="text-[28rpx] font-medium">
{{ $t('chat.manage.addAdmin') }}
</span>

View File

@ -1,76 +0,0 @@
<template>
<div class="outer-layer manage-group-deps-page">
<div class="root">
<ZPaging
ref="zPaging"
:show-scrollbar="false"
:use-virtual-list="true"
:virtual-list-col="5"
:auto="false"
:refresher-enabled="false"
:loading-more-enabled="false"
>
<template #top>
<customNavbar :title="$t('pageTitle.view.deps')"></customNavbar>
</template>
<div class="group-deps-list">
<div
class="group-deps-list-each"
v-for="item in groupParams.groupInfo.deptInfos"
:key="item.dept_id"
>
<span>{{ item.dept_name }}</span>
</div>
</div>
</ZPaging>
</div>
</div>
</template>
<script setup>
import settingFormItem from '../components/settingFormItem.vue'
import ZPaging from '@/uni_modules/z-paging/components/z-paging/z-paging.vue'
import { onLoad } from '@dcloudio/uni-app'
import { computed, onMounted, reactive } from 'vue'
import { useGroupStore } from '@/store'
import { useI18n } from 'vue-i18n'
const { t } = useI18n()
const groupStore = useGroupStore()
const groupParams = reactive({
groupInfo: computed(() => groupStore.groupInfo),
})
const state = reactive({})
onLoad((options) => {
console.log(options)
})
onMounted(() => {
console.log(groupParams.groupInfo.deptInfos)
})
</script>
<style scoped lang="scss">
.outer-layer {
flex: 1;
background-image: url('@/static/image/clockIn/z3280@3x.png');
background-size: cover;
background-repeat: no-repeat;
}
.group-deps-list {
margin: 20rpx 32rpx;
background-color: #fff;
.group-deps-list-each {
padding: 34rpx 32rpx;
border-bottom: 1px solid $theme-border-color;
span {
font-size: 28rpx;
font-weight: 500;
color: #333;
line-height: 40rpx;
}
}
}
</style>

View File

@ -42,7 +42,7 @@
class="add-silence-member-btn chat-settings-card"
@click="toSelectMembersPage"
>
<img src="@/static/image/chatSettings/add-btn.png" />
<img src="/src/static/image/chatSettings/add-btn.png" />
<span class="text-[28rpx] font-medium">
{{ $t('chat.manage.addSilenceMember') }}
</span>

View File

@ -14,7 +14,7 @@
</span>
</div>
</template>
<template #right v-if="state.isManager === 'true'">
<template #right>
<div
v-if="state.editMode !== 3"
class="nav-bar-done-btn"
@ -39,7 +39,7 @@
</template>
<div class="notice-text-area">
<div class="notice-view-area">
<div class="notice-view-info" v-if="state.editMode !== 1 && state?.groupNoticeObj?.content">
<div class="notice-view-info" v-if="state.editMode !== 1">
<div class="notice-creater-avatar">
<img :src="state?.groupNoticeObj?.avatar" />
</div>
@ -53,12 +53,9 @@
</div>
</div>
<div class="notice-view-content" v-if="state.editMode === 3">
<span class="text-[32rpx] font-regular" v-if="state?.groupNoticeObj?.content">
<span class="text-[32rpx] font-regular">
{{ state?.groupNoticeObj?.content }}
</span>
<span class="text-[32rpx] font-regular color-[#898989]" v-else>
{{ $t('groupNotice.notice.empty') }}
</span>
</div>
<wd-textarea
size="large"
@ -80,7 +77,6 @@ import useConfirm from '@/components/x-confirm/useConfirm.js'
import ZPaging from '@/uni_modules/z-paging/components/z-paging/z-paging.vue'
import { reactive, computed, onMounted } from 'vue'
import { useGroupStore, useDialogueStore } from '@/store'
import { onLoad } from '@dcloudio/uni-app'
import {
ServeEditGroupNotice,
ServeDeleteGroupNotice,
@ -102,8 +98,7 @@ const state = reactive({
groupNoticeObj: null, //
groupNotice: '', //
canDoComplete: false, //
editMode: 3, // 123
isManager: 'false', //
editMode: 1, // 123
})
onMounted(() => {
@ -112,14 +107,6 @@ onMounted(() => {
state.editMode = 3
state.groupNoticeObj = groupParams.groupNotice[0]
inputGroupNotice(groupParams?.groupNotice[0]?.content)
} else if( state.isManager === 'true'){
state.editMode = 1
}
})
onLoad((options) => {
console.log(options)
if (options?.is_manager) {
state.isManager = options?.is_manager
}
})

View File

@ -11,45 +11,170 @@
@scroll="onScroll"
>
<template #top>
<div id="topArea">
<customNavbar :title="pageTitle"></customNavbar>
</div>
<customNavbar :title="pageTitle" id="topArea"></customNavbar>
</template>
<selectMemberByAlphabet
:manageType="state.manageType"
:isCreateDepGroup="state.isCreateDepGroup"
ref="selectMemberByAlphabetRef"
:selectAreaHeight="state.selectAreaHeight"
@updateSelectedMembersNum="updateSelectedMembersNum"
></selectMemberByAlphabet>
<template #bottom v-if="state.manageType !== 'searchRecord'">
<div id="footArea">
<customBtn
v-if="state.manageType !== 'removeMembers'"
:isBottom="true"
:btnText="$t('ok')"
@clickBtn="confirmSelectMembers"
></customBtn>
<div
class="confirm-btn-area"
v-if="state.manageType === 'removeMembers'"
>
<div class="confirm-btn-area-statistic-text">
<span class="text-[28rpx] font-medium">
{{
$t('select.member.num') +
'' +
state.selectedMembersNum +
$t('statistic.unit.person')
}}
<div class="select-members">
<div
class="search-member"
v-if="state.manageType !== 'removeMembers'"
>
<customInput
:searchText="state.searchText"
@inputSearchText="inputSearchText"
></customInput>
</div>
<div
class="member-list"
:style="{
padding: state.manageType === 'searchRecord' ? '20rpx 0 0' : '',
}"
>
<div class="member-list-alphabet-anchor-point">
<div
class="member-list-alphabet-anchor-point-each"
v-for="(alphabetItem, alphabetIndex) in state?.alphabet"
:key="alphabetIndex"
:style="{
margin: state?.alphabet?.length > 17 ? '0' : '',
}"
@click.stop="scrollToView(alphabetItem)"
>
<span
class="text-[32rpx] font-regular"
:style="{
color:
state.currentAlphabet === alphabetItem ? '#7A58DE' : '',
}"
>
{{ alphabetItem }}
</span>
</div>
<customBtn
:btnText="$t('ok')"
@clickBtn="confirmSelectMembers"
:disabled="state.selectedMembersNum == 0 ? true : false"
></customBtn>
</div>
<div
class="member-list-alphabet"
v-for="(alphabetItem, alphabetIndex) in state.resultMemberList"
:key="alphabetIndex"
>
<div
class="member-list-alphabet-key"
:style="{
padding:
state.manageType === 'searchRecord' ||
state.manageType === 'removeMembers'
? '10rpx 30rpx'
: '',
}"
v-if="alphabetItem?.memberList?.length > 0"
:id="alphabetItem.key"
:ref="
(el) => {
if (el) alphabetElementRefs[alphabetIndex] = el
}
"
>
<span class="text-[32rpx] font-regular">
{{ alphabetItem.key }}
</span>
</div>
<div v-if="alphabetItem?.memberList?.length > 0">
<div
class="member-list-each"
v-for="(item, index) in alphabetItem?.memberList"
:key="index"
>
<tm-checkbox-group v-model="item.checkArr">
<selectMemberItem
:groupType="groupParams.groupInfo.group_type"
:memberItem="item"
@clickItem="handleClickItem(item)"
:manageType="state.manageType"
:itemStyle="
state.manageType === 'searchRecord' ||
state.manageType === 'removeMembers'
? 'list'
: 'card'
"
>
<template
#left
v-if="state.manageType !== 'searchRecord'"
>
<div
v-if="
state.manageType === 'removeMembers' &&
item?.is_mine
"
>
<tm-checkbox
color="#fff"
:transprent="true"
:border="0"
:disabled="true"
></tm-checkbox>
</div>
<tm-checkbox
v-if="
!(
state.manageType === 'removeMembers' &&
item?.is_mine
)
"
:round="10"
:color="
item?.checkArr?.length > 0 ? '#46299d' : '#B4B4B4'
"
:outlined="
item?.checkArr?.length > 0 ||
(state.manageType === 'silence' &&
item.is_mute === 1) ||
(state.manageType === 'admin' &&
(item.leader === 1 || item.leader === 2))
? false
: true
"
:value="item.id"
:disabled="
(state.manageType === 'silence' &&
item.is_mute === 1) ||
(state.manageType === 'admin' &&
(item.leader === 1 || item.leader === 2))
"
@change="checkBoxChange"
></tm-checkbox>
</template>
</selectMemberItem>
</tm-checkbox-group>
</div>
</div>
</div>
</div>
</div>
<template #bottom v-if="state.manageType !== 'searchRecord'">
<customBtn
v-if="state.manageType !== 'removeMembers'"
:isBottom="true"
:btnText="$t('ok')"
@clickBtn="confirmSelectMembers"
></customBtn>
<div
class="confirm-btn-area"
v-if="state.manageType === 'removeMembers'"
>
<div class="confirm-btn-area-statistic-text">
<span class="text-[28rpx] font-medium">
{{
$t('select.member.num') +
'' +
state.selectedMembersNum +
$t('statistic.unit.person')
}}
</span>
</div>
<customBtn
:btnText="$t('ok')"
@clickBtn="confirmSelectMembers"
:disabled="state.selectedMembersNum == 0 ? true : false"
></customBtn>
</div>
</template>
</ZPaging>
@ -57,63 +182,120 @@
</div>
</template>
<script setup>
import selectMemberByAlphabet from '../components/selectMemberByAlphabet.vue'
import customInput from '@/components/custom-input/custom-input.vue'
import selectMemberItem from '../components/select-member-item.vue'
import customBtn from '@/components/custom-btn/custom-btn.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 { computed, reactive, ref, onMounted, nextTick } from 'vue'
import { computed, onMounted, reactive, ref, watch, nextTick } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
import {
ServeGroupNoSpeak,
ServeEditGroupAdmin,
ServeGroupAssignAdmin,
ServeRemoveMembersGroup,
} from '@/api/group/index.js'
import { useDialogueStore, useGroupStore, useGroupTypeStore } from '@/store'
import { useI18n } from 'vue-i18n'
const { t } = useI18n()
const selectMemberByAlphabetRef = ref(null)
const zPaging = ref()
useZPaging(zPaging)
const state = reactive({
manageType: '', //
selectedMembersNum: 0, //
isCreateDepGroup: 0, //
selectAreaHeight: 0, //
const groupStore = useGroupStore()
const groupParams = reactive({
groupInfo: computed(() => groupStore.groupInfo),
})
const groupTypeStore = useGroupTypeStore()
const groupTypeParams = reactive({
departmentAllPositions: computed(() => groupTypeStore.departmentAllPositions),
})
const dialogueStore = useDialogueStore()
const dialogueParams = reactive({
memberList: computed(() => {
const lowerCaseSearchText = state?.searchText.toLowerCase()
return dialogueStore.members.filter((item) =>
state?.searchText
? item.nickname.toLowerCase().includes(lowerCaseSearchText)
: true,
)
}),
receiverId: computed(() => dialogueStore.talk.receiver_id),
})
const state = reactive({
searchText: '', //
manageType: '', //
alphabet: [], //A-Z
resultMemberList: [], //A-Z
currentAlphabet: 'A', //A-Z
scrollDirection: '', //
isAssign: false, //view
selectedMembersNum: 0, //
isCreateDepGroup: 0, //
})
watch(
() => dialogueParams?.memberList,
(newMemberList) => {
assembleAlphabetMemberList(newMemberList)
},
{ deep: true },
)
watch(
() => groupParams?.groupInfo,
(newGroupInfo) => {
assembleAlphabetMemberList(dialogueParams?.memberList)
},
{ deep: true },
)
//A-Z tag
const alphabetElementRefs = ref([])
//
let observer
onLoad((options) => {
// console.log(options)
console.log(options)
if (options.manageType) {
state.manageType = options.manageType
assembleAlphabetMemberList(dialogueParams?.memberList)
}
if (options.isCreateDepGroup) {
state.isCreateDepGroup = Number(options.isCreateDepGroup)
assembleAlphabetMemberList()
}
})
onMounted(() => {
dialogueParams.memberList.forEach((ele) => {
ele.checkArr = []
})
const options = {
root: null, // 使
threshold: 1.0, // 100%
}
observer = new IntersectionObserver(handleIntersection, options)
nextTick(() => {
let selectAreaHeight = uni.getSystemInfoSync().windowHeight
// console.log(':', uni.getSystemInfoSync().windowHeight)
const topAreaQuery = uni.createSelectorQuery()
topAreaQuery
.select('#topArea')
.boundingClientRect((res) => {
if (res) {
// console.log(':', res.height)
selectAreaHeight = selectAreaHeight - res.height
watch(
alphabetElementRefs,
(newAlphabetElementRefs) => {
if (Array.isArray(newAlphabetElementRefs)) {
newAlphabetElementRefs.forEach((el, index) => {
observeElement(el, index)
})
}
})
.exec()
const footAreaQuery = uni.createSelectorQuery()
footAreaQuery
.select('#footArea')
.boundingClientRect((res) => {
if (res) {
// console.log(':', res.height)
selectAreaHeight = selectAreaHeight - res.height
}
})
.exec()
// console.log(selectAreaHeight)
state.selectAreaHeight = selectAreaHeight + 'px'
},
{ immediate: true, deep: true },
)
if (alphabetElementRefs.value.length > 0) {
alphabetElementRefs.value.forEach((el, index) =>
observeElement(el, index),
)
}
})
})
@ -132,16 +314,341 @@ const pageTitle = computed(() => {
return page_title
})
//
const confirmSelectMembers = () => {
if (selectMemberByAlphabetRef.value) {
selectMemberByAlphabetRef.value.confirmSelectMembers()
//
const handleIntersection = (entries) => {
if (state.isAssign) {
state.isAssign = false
return
}
entries.forEach((entry) => {
if (!entry.isIntersecting && state.scrollDirection === 'down') {
state.currentAlphabet = entry.target.id
} else if (entry.isIntersecting && state.scrollDirection === 'up') {
if (state?.alphabet?.length > 1) {
state?.alphabet.forEach((item, index) => {
if (item === entry.target.id && index > 0) {
state.currentAlphabet = state?.alphabet[index - 1]
}
})
} else {
state.currentAlphabet = entry.target.id
}
}
})
}
//
const observeElement = (el, index) => {
if (el && observer) {
observer.observe(el)
}
}
//
const updateSelectedMembersNum = (numChange) => {
state.selectedMembersNum = state.selectedMembersNum + numChange
//
const inputSearchText = (e) => {
// console.log(e)
state.searchText = e
}
//item
const handleClickItem = (item) => {
if (
(state.manageType === 'silence' && item.is_mute === 1) ||
(state.manageType === 'admin' &&
(item.leader === 1 || item.leader === 2)) ||
(state.manageType === 'removeMembers' && item.is_mine)
) {
return
}
let itemList = dialogueParams.memberList
if (
state.manageType === 'admin' &&
(groupParams.groupInfo.group_type == 2 ||
groupParams.groupInfo.group_type == 4 ||
state.isCreateDepGroup === 1)
) {
itemList = state.resultMemberList[0].memberList
}
itemList.forEach((ele) => {
if (ele.id == item.id) {
ele.checkArr = ele.checkArr?.length > 0 ? [] : [item.id]
if (ele.checkArr?.length > 0) {
state.selectedMembersNum += 1
} else {
state.selectedMembersNum -= 1
}
}
})
}
//
const confirmSelectMembers = () => {
let selectedUserIds = ''
let itemList = dialogueParams.memberList
let positionInfos = []
if (
state.manageType === 'admin' &&
(groupParams.groupInfo.group_type == 2 ||
groupParams.groupInfo.group_type == 4 ||
state.isCreateDepGroup === 1)
) {
itemList = state.resultMemberList[0].memberList
}
itemList.forEach((ele) => {
if (ele.checkArr?.length > 0) {
if (!selectedUserIds) {
selectedUserIds = String(ele.checkArr[0])
} else {
selectedUserIds += ',' + ele.checkArr[0]
}
}
if (
ele.checkArr?.length > 0 ||
(ele.leader && (ele.leader == 1 || ele.leader == 2))
) {
if (state.isCreateDepGroup === 1) {
let posInfo = Object.assign({}, ele.positionInfo, {
name: ele.nickname,
id: ele.id,
})
positionInfos.push(posInfo)
} else {
positionInfos.push(ele.positionInfo)
}
}
})
console.log(selectedUserIds)
if (selectedUserIds) {
if (state.manageType === 'silence') {
let params = {
mode: 1, //12
group_id: dialogueParams.receiverId, //id
user_ids: selectedUserIds, //ids
}
console.log(params)
const resp = ServeGroupNoSpeak(params)
resp.then(({ code, data }) => {
console.log(data)
if (code == 200) {
useDialogueStore().updateGroupMembers()
} else {
}
})
resp.catch(() => {})
} else if (state.manageType === 'admin') {
if (state.isCreateDepGroup === 1) {
// console.log(positionInfos)
groupTypeStore.groupAdmins.value = positionInfos
uni.navigateBack({
delta: 1,
})
} else {
if (
groupParams.groupInfo.group_type == 1 ||
groupParams.groupInfo.group_type == 3
) {
let params = {
mode: 1, //12
group_id: dialogueParams.receiverId, //id
user_ids: selectedUserIds,
}
console.log(params)
const resp = ServeGroupAssignAdmin(params)
resp.then(({ code, data }) => {
console.log(data)
if (code == 200) {
useDialogueStore().updateGroupMembers()
} else {
}
})
resp.catch(() => {})
} else if (
groupParams.groupInfo.group_type == 2 ||
groupParams.groupInfo.group_type == 4
) {
let params = {
source: 'app',
id: dialogueParams.receiverId,
deptInfos: groupParams.groupInfo.deptInfos,
positionInfos: positionInfos,
}
console.log(params)
const resp = ServeEditGroupAdmin(params)
resp.then(({ code, data }) => {
console.log(data)
if (code == 200) {
groupStore.ServeGroupDetail()
} else {
}
})
resp.catch(() => {})
}
}
} else if (state.manageType === 'removeMembers') {
let params = {
group_id: dialogueParams.receiverId, //id
members_ids: selectedUserIds, //id
}
console.log(params)
const resp = ServeRemoveMembersGroup(params)
resp.then(({ code, data }) => {
console.log(data)
if (code == 200) {
useDialogueStore().updateGroupMembers()
groupStore.ServeGroupDetail()
} else {
}
})
resp.catch(() => {})
}
}
}
//A-Z
const assembleAlphabetMemberList = async (newMemberList) => {
if (
state.manageType === 'searchRecord' ||
state.manageType === 'removeMembers'
) {
const resultMemberList = ref([])
const alphabet = Array.from({ length: 26 }, (_, i) =>
String.fromCharCode(i + 65),
)
let tempAlphabet = []
alphabet.forEach((letter) => {
const matchedItems = newMemberList.filter((item) => item.key === letter)
if (matchedItems.length > 0) {
tempAlphabet.push(letter)
}
resultMemberList.value.push({
key: letter,
memberList: matchedItems.length ? matchedItems : [],
})
})
state.alphabet = tempAlphabet
state.resultMemberList = resultMemberList
} else {
if (
(groupParams.groupInfo.group_type == 2 ||
groupParams.groupInfo.group_type == 4) &&
state.manageType === 'admin'
) {
let departmentIdsArr = []
if (groupParams?.groupInfo?.deptInfos?.length > 0) {
groupParams.groupInfo.deptInfos.forEach((item) => {
departmentIdsArr.push(item.dept_id)
})
}
getPosiByDep(departmentIdsArr)
} else if (state.isCreateDepGroup === 1) {
let departmentIdsArr = []
if (groupTypeStore?.depCheckedKeys?.value?.length > 0) {
groupTypeStore.depCheckedKeys.value.forEach((item) => {
departmentIdsArr.push(item.ID)
})
}
getPosiByDep(departmentIdsArr)
} else {
state.resultMemberList = [
{
key: '',
memberList: newMemberList,
},
]
}
}
}
const getPosiByDep = async (departmentIdsArr) => {
await groupTypeStore.getPositionByDepartment({
IDs: departmentIdsArr,
})
let departmentAllPositions = []
if (groupTypeParams?.departmentAllPositions?.value?.length > 0) {
groupTypeParams?.departmentAllPositions?.value?.forEach((item) => {
item?.AllPositions?.forEach((positionItem) => {
departmentAllPositions.push({
nickname: item.name + '-' + positionItem.name,
id: item.ID + '-' + positionItem.ID,
checkArr: [],
positionInfo: {
dept_id: item.ID,
dept_name: item.name,
position_id: positionItem.ID,
position_name: positionItem.name,
},
})
})
})
}
if (groupParams?.groupInfo?.groupAdminList?.length > 0) {
groupParams?.groupInfo?.groupAdminList.forEach((item) => {
departmentAllPositions.forEach((idsItem) => {
if (item.dept_id + '-' + item.position_id == idsItem.id) {
idsItem.leader = 1
}
})
})
}
if (
state.isCreateDepGroup === 1 &&
groupTypeStore?.groupAdmins?.value?.length > 0
) {
departmentAllPositions.forEach((allPos) => {
groupTypeStore.groupAdmins.value.forEach((admin) => {
if (allPos.id === admin.id) {
allPos.checkArr = [allPos.id]
}
})
})
}
if(state?.searchText){
const lowerCaseSearchText = state?.searchText.toLowerCase()
departmentAllPositions = departmentAllPositions.filter((item) =>
state?.searchText
? item.nickname.toLowerCase().includes(lowerCaseSearchText)
: true,
)
}
state.resultMemberList = [
{
key: '',
memberList: departmentAllPositions,
},
]
}
//view
const scrollToView = (alphabet) => {
state.currentAlphabet = alphabet
state.isAssign = true
console.log()
zPaging.value?.scrollIntoViewById(
alphabet,
document.getElementById('topArea').clientHeight
? document.getElementById('topArea').clientHeight - 1
: 80,
)
}
//
const onScroll = (e) => {
if (e.detail.deltaY < 0) {
state.scrollDirection = 'down'
} else if (e.detail.deltaY > 0) {
state.scrollDirection = 'up'
} else {
state.scrollDirection = ''
}
}
//
const checkBoxChange = (e) => {
if (e) {
state.selectedMembersNum += 1
} else {
state.selectedMembersNum -= 1
}
}
</script>
<style scoped lang="scss">
@ -152,6 +659,48 @@ const updateSelectedMembersNum = (numChange) => {
background-repeat: no-repeat;
}
.select-members {
padding: 20rpx 32rpx;
.search-member {
padding: 22rpx 16rpx;
background-color: #fff;
}
.member-list {
.member-list-alphabet-anchor-point {
position: fixed;
right: 32rpx;
top: 0;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
.member-list-alphabet-anchor-point-each {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
margin: 0 0 14rpx;
span {
width: 52rpx;
text-align: center;
line-height: 44rpx;
color: $theme-text;
}
}
}
.member-list-alphabet {
.member-list-alphabet-key {
background-color: #f3f3f3;
span {
line-height: 44rpx;
color: $theme-text;
}
}
}
}
}
.confirm-btn-area {
background-color: #fff;
padding: 14rpx 32rpx 72rpx;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -17,17 +17,10 @@
</div>
<div class="avatarImg">
<avatarModule
:mode="props?.data?.group_type === 0 ? 1 : 2"
:mode="2"
:avatar="props?.data?.avatar"
:groupType="props?.data?.group_type"
:userName="props?.data?.name"
:customStyle="{ width: '96rpx', height: '96rpx' }"
:customTextStyle="{
fontSize: '32rpx',
fontWeight: 'bold',
color: '#fff',
lineHeight: '44rpx',
}"
></avatarModule>
</div>
<div class="chatInfo">
@ -37,18 +30,7 @@
class="text-[#171717] text-[32rpx] font-medium leading-[44rpx]"
>
<span>{{ props.data.name }}</span>
<span v-if="props.data.talk_type === 2">
{{ props.data.group_member_num }}
</span>
<span v-if="props.data.group_type === 2" class="depTag tag">
部门
</span>
<span v-if="props.data.group_type === 3" class="projectTag tag">
项目
</span>
<span v-if="props.data.group_type === 4" class="companyTag tag">
公司
</span>
<span>{{ props.data.group_member_num }}</span>
</div>
</div>
</div>
@ -136,31 +118,16 @@ const cellClick = () => {
opacity: 40%;
}
.tag {
display: inline-flex;
align-items: center;
text-align: center;
// margin-left: 10rpx;
margin-top: 4rpx;
vertical-align: top;
height: 38rpx;
line-height: 38rpx;
padding: 0 10rpx;
font-size: 24rpx;
border-radius: 6rpx;
font-weight: bold;
}
.companyTag {
width: 76rpx;
height: 38rpx;
border: 1px solid #7a58de;
font-size: 24rpx;
text-align: center;
border-radius: 6rpx;
color: #7a58de;
}
.depTag {
border: 1px solid #377ec6;
color: #377ec6;
}
.projectTag {
border: 1px solid #c1681c;
color: #c1681c;
font-weight: bold;
margin-left: 12rpx;
}
.textEllipsis {

View File

@ -58,7 +58,6 @@
okColor="#FFFFFF"
@ok="handleOk"
@cancel="handleCancel"
:okText="'发送'"
>
<template v-slot:title>
<div
@ -87,7 +86,7 @@
<div
class="mt-[8rpx] text-[#666666] text-[24rpx] w-[94rpx] truncate"
>
<span>{{ item.name }}</span>
{{ item.name }}
</div>
</div>
<div v-else class="w-full flex items-center justify-start">
@ -99,16 +98,6 @@
></avatarModule>
<div class="ml-[16rpx] text-[#161616] text-[32rpx] font-bold">
{{ selectItemsModal[0].name }}
<span v-if="selectItemsModal[0].talk_type === 2">{{ selectItemsModal[0].group_member_num }}</span>
<span v-if="selectItemsModal[0].group_type === 2" class="depTag tag">
部门
</span>
<span v-if="selectItemsModal[0].group_type === 3" class="projectTag tag">
项目
</span>
<span v-if="selectItemsModal[0].group_type === 4" class="companyTag tag">
公司
</span>
</div>
</div>
</div>
@ -219,7 +208,7 @@ watch(
onMounted(() => {
talkStore.loadTalkList()
console.log(talkStore.talkItems)
items.value = lodash.cloneDeep(talkStore.talkItems).filter(item=>item.is_dismiss === 0 && item.is_quit === 0 && ((item.talk_type === 1 && item.receiver_id !== 2) || item.talk_type !== 1))
items.value = lodash.cloneDeep(talkStore.talkItems)
})
onUnmounted(() => {
dialogueStore.setForwardType('')
@ -252,32 +241,7 @@ onUnmounted(() => {
margin-top: 20rpx;
background-color: #fff;
}
.tag{
display: inline-flex;
align-items: center;
text-align: center;
// margin-left: 10rpx;
margin-top: 4rpx;
vertical-align: top;
height: 38rpx;
line-height: 38rpx;
padding: 0 10rpx;
font-size: 24rpx;
border-radius: 6rpx;
font-weight: bold;
}
.companyTag {
border: 1px solid #7a58de;
color: #7a58de;
}
.depTag {
border: 1px solid #377ec6;
color: #377ec6;
}
.projectTag {
border: 1px solid #c1681c;
color: #c1681c;
}
.btnBox {
::v-deep .custom-btn-class {
padding: 6rpx 24rpx !important;

View File

@ -15,7 +15,7 @@
</div>
<div class="mt-[54rpx] w-full h-[872rpx]">
<div
@click="groupActiveIndex = 0;depCheckedKeys = []"
@click="groupActiveIndex = 0"
class="groupCard firstPanel"
:class="groupActiveIndex === 0 ? 'activePanel' : ''"
>
@ -33,7 +33,6 @@
</div>
</div>
<div
v-if="isHasPermission"
@click="groupActiveIndex = 1"
class="groupCard secondPanel"
:class="groupActiveIndex === 1 ? 'activePanel' : ''"
@ -96,8 +95,7 @@
</div>
</div>
<div
v-if="isCreateProjecy"
@click="groupActiveIndex = 2;depCheckedKeys = [];"
@click="groupActiveIndex = 2"
class="groupCard thirdPanel"
:class="groupActiveIndex === 2 ? 'activePanel' : ''"
>
@ -133,36 +131,13 @@
import ZPaging from '@/uni_modules/z-paging/components/z-paging/z-paging.vue'
import customBtn from '@/components/custom-btn/custom-btn.vue'
import { ref, watch, computed } from 'vue'
import { onShow, onLoad, onUnload } from '@dcloudio/uni-app'
import { onShow, onLoad } from '@dcloudio/uni-app'
import { useChatList } from '@/store/chatList/index.js'
import { useAuth } from '@/store/auth'
import { useTalkStore, useUserStore } from '@/store'
import { useGroupTypeStore } from '@/store/groupType'
import { userHasPermission } from '@/api/deps/index.js'
const { groupActiveIndex, depCheckedKeys } = useGroupTypeStore()
const { userInfo } = useAuth()
onUnload(()=> {
})
const isHasPermission = ref(false)
const isCreateProjecy = ref(false)
onShow( async() =>{
const isHasRes = await userHasPermission({
erpUserId: userInfo?.value?.ID,
ruleUrl: [
"auth_chat_app_create_all_dept",
"auth_chat_app_create_limit_dept"
]
})
if (isHasRes.code === 200) {
if (isHasRes.data.auth_chat_app_create_all_dept || isHasRes.data.auth_chat_app_create_limit_dept) {
isHasPermission.value = true
} else{
isHasPermission.value = false
}
isCreateProjecy.value = isHasRes.data.btn_rule_create_project_group
}
})
const { groupActiveIndex, depCheckedKeys } = useGroupTypeStore()
const confirmBtnStatus = computed(() => {
let disabledT = false
@ -233,10 +208,10 @@ const handleConfirm = () => {
border-radius: 12rpx;
&.firstPanel {
background-image: url('@/static/image/chatList/zu6033@2x.png');
margin-bottom: 28rpx;
}
&.secondPanel {
background-image: url('@/static/image/chatList/zu6031@2x.png');
margin-top: 28rpx;
margin-bottom: 28rpx;
}
&.thirdPanel {

View File

@ -404,7 +404,7 @@ const getCurrentMembers = async (depItem) => {
departmentId: depItem.ID,
status: 'notactive',
})
if (res.code === 200) {
if (res.status === 0) {
currentMembers.value = res.data.data.length
? res.data.data.map((v) => {
return {
@ -628,7 +628,7 @@ const handleConfirm = async () => {
departmentIds: allCheckedList.value.map((v) => v.ID),
status: 'notactive',
})
if (res.code == 200 && res.data?.data?.length) {
if (res.status == 0 && res.data?.data?.length) {
res.data?.data.forEach((v) => {
listT.push(v)
})

View File

@ -1,366 +0,0 @@
<template>
<div class="outer-layer user-detail-page">
<div class="root">
<ZPaging ref="zPaging" :show-scrollbar="false">
<template #top>
<customNavbar :title="$t('complaint.title')"></customNavbar>
</template>
<!-- 投诉主体内容 -->
<view class="complaint-container">
<!-- 投诉类型选择 -->
<view class="form-item">
<text class="form-label">{{ $t('complaint.selectType') }}</text>
<picker mode="selector" :range="complaintTypes" range-key="label" @change="handleTypeChange">
<view class="picker">
{{ selectedType.label || $t('complaint.selectPlaceholder') }}
<uni-icons type="arrowright" size="16" color="#999"></uni-icons>
</view>
</picker>
</view>
<!-- 图片证据上传 -->
<view class="form-item">
<text class="form-label">{{ $t('complaint.imageEvidence') }}</text>
<view class="upload-area">
<view v-for="(img, index) in imageList" :key="index" class="image-wrapper">
<image :src="img" mode="aspectFill" class="uploaded-image" @click="previewImage(index)" />
<uni-icons type="close" size="18" color="#fff" class="delete-icon"
@click="removeImage(index)"></uni-icons>
</view>
<view v-if="imageList.length < 9" class="upload-btn" @click="chooseImage">
<uni-icons type="plusempty" size="28" color="#999"></uni-icons>
<text class="upload-text">{{ $t('complaint.addImage') }}</text>
</view>
</view>
</view>
<!-- 投诉内容 -->
<view class="form-item">
<text class="form-label">{{ $t('complaint.complaintContent') }}</text>
<textarea v-model="complaintContent" :placeholder="$t('complaint.contentPlaceholder')"
class="content-textarea"></textarea>
</view>
<!-- 投诉须知 -->
<view class="notice-box">
<view class="notice-header" @click="toggleNotice">
<text class="notice-title">{{ $t('complaint.noticeTitle') }}</text>
<text class="toggle-btn">
{{ isNoticeExpanded ? $t('complaint.collapse') : $t('complaint.expand') }}
</text>
</view>
<!-- 折叠状态只显示前两项 -->
<view class="notice-content" v-if="!isNoticeExpanded">
<view class="notice-item">
<text>{{ $t('complaint.noticeone') }}</text>
</view>
<view class="notice-item">
<text>{{ $t('complaint.noticetwo') }}</text>
</view>
</view>
<!-- 展开状态显示全部 -->
<view class="notice-content" v-else>
<view class="notice-item">
<text>{{ $t('complaint.noticeone') }}</text>
</view>
<view class="notice-item">
<text>{{ $t('complaint.noticetwo') }}</text>
</view>
<view class="notice-item">
<text>{{ $t('complaint.noticethree') }}</text>
</view>
<view class="notice-item">
<text>{{ $t('complaint.noticefour') }}</text>
</view>
<view class="notice-item">
<text>{{ $t('complaint.noticefive') }}</text>
</view>
<view class="notice-item">
<text>{{ $t('complaint.noticenoticeContent') }}</text>
</view>
</view>
</view>
<button class="submit-btn" :disabled="!selectedType.value" @click="handleSubmit">
{{ $t('complaint.submit') }}
</button>
</view>
</ZPaging>
<!-- 固定在底部的提交按钮 -->
</div>
</div>
</template>
<script setup>
import ZPaging from '@/uni_modules/z-paging/components/z-paging/z-paging.vue'
import {
ref
} from 'vue';
import {
onLoad
} from '@dcloudio/uni-app';
import {
useI18n
} from 'vue-i18n';
const {
t
} = useI18n();
//
const complaintTypes = computed(() => [{
label: t('complaint.typeOptions.porn'),
value: 'porn'
},
{
label: t('complaint.typeOptions.illegal'),
value: 'illegal'
},
{
label: t('complaint.typeOptions.gambling'),
value: 'gambling'
},
{
label: t('complaint.typeOptions.violence'),
value: 'violence'
},
{
label: t('complaint.typeOptions.selfHarm'),
value: 'selfHarm'
},
{
label: t('complaint.typeOptions.other'),
value: 'other'
}
]);
//
const selectedType = ref({});
const imageList = ref([]);
const complaintContent = ref('');
const isNoticeExpanded = ref(false);
//
const toggleNotice = () => {
isNoticeExpanded.value = !isNoticeExpanded.value;
};
//
const handleTypeChange = (e) => {
selectedType.value = complaintTypes.value[e.detail.value];
};
//
const chooseImage = () => {
uni.chooseImage({
count: 9 - imageList.value.length,
sizeType: ['compressed'],
sourceType: ['album', 'camera'],
success: (res) => {
imageList.value = [...imageList.value, ...res.tempFilePaths];
if (imageList.value.length > 9) {
uni.showToast({
title: `最多只能选择9张图片`,
icon: 'none'
});
}
}
});
};
//
const removeImage = (index) => {
imageList.value.splice(index, 1);
};
//
const previewImage = (index) => {
uni.previewImage({
current: index,
urls: imageList.value
});
};
//
const handleSubmit = () => {
const formData = {
type: selectedType.value,
images: imageList.value,
content: complaintContent.value
};
uni.showLoading({
title: t('complaint.toast.submitting') // 使
});
setTimeout(() => {
uni.hideLoading();
uni.showToast({
title: t('complaint.toast.success'),
icon: 'success'
});
selectedType.value = {};
imageList.value = [];
complaintContent.value = '';
//
setTimeout(() => {
uni.navigateBack();
}, 1500);
}, 2000);
};
onLoad(() => {
//
});
</script>
<style scoped lang="scss">
::v-deep .uni-picker-action-confirm {
color: #452aa1 !important;
}
.outer-layer {
flex: 1;
background-image: url('@/static/image/mine/1111.png');
background-size: cover;
background-repeat: no-repeat;
}
.complaint-container {
padding: 20rpx 30rpx;
background-color: rgba(255, 255, 255, 0.9);
margin: 20rpx;
border-radius: 16rpx;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.05);
}
.form-item {
margin-bottom: 40rpx;
}
.form-label {
display: block;
font-size: 28rpx;
color: #333;
font-weight: 500;
margin-bottom: 20rpx;
}
.picker {
display: flex;
align-items: center;
justify-content: space-between;
padding: 24rpx;
background-color: #f7f7f7;
border-radius: 12rpx;
font-size: 28rpx;
color: #333;
}
.upload-area {
display: flex;
flex-wrap: wrap;
gap: 20rpx;
}
.upload-btn {
width: 160rpx;
height: 160rpx;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background-color: #f7f7f7;
border-radius: 12rpx;
border: 1rpx dashed #ddd;
}
.upload-text {
font-size: 24rpx;
color: #999;
margin-top: 10rpx;
}
.image-wrapper {
width: 160rpx;
height: 160rpx;
position: relative;
border-radius: 12rpx;
overflow: hidden;
}
.uploaded-image {
width: 100%;
height: 100%;
}
.delete-icon {
position: absolute;
top: 8rpx;
right: 8rpx;
background-color: rgba(0, 0, 0, 0.5);
border-radius: 50%;
padding: 4rpx;
}
.content-textarea {
width: 100%;
height: 200rpx;
padding: 20rpx;
background-color: #f7f7f7;
border-radius: 12rpx;
font-size: 28rpx;
color: #333;
}
.notice-box {
padding: 20rpx;
background-color: #f0f7ff;
border-radius: 12rpx;
margin: 40rpx 0;
}
.notice-title {
font-size: 28rpx;
color: #452aa1;
font-weight: bold;
display: block;
margin-bottom: 10rpx;
}
.notice-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20rpx 0;
}
.toggle-btn {
color: #452aa1;
font-size: 24rpx;
}
.notice-content {
font-size: 24rpx;
color: #666;
line-height: 1.6;
padding-bottom: 20rpx;
}
.submit-btn {
margin-top: 40rpx;
background-color: #452aa1;
color: white;
border-radius: 8rpx;
height: 90rpx;
line-height: 90rpx;
font-size: 32rpx;
&[disabled] {
background-color: #f0f0f0;
}
}
</style>

View File

@ -8,8 +8,12 @@
<div class="group-avatar flex items-center justify-center">
<div class="avatar-placeholder" v-if="groupActiveIndex === -1"></div>
<div v-else>
<avatarModule :mode="2" :avatar="avatarImg" :groupType="groupType"
:customStyle="{ width: '192rpx', height: '192rpx' }"></avatarModule>
<avatarModule
:mode="2"
:avatar="avatarImg"
:groupType="groupType"
:customStyle="{ width: '192rpx', height: '192rpx' }"
></avatarModule>
</div>
</div>
<div class="input-group flex items-center justify-between">
@ -17,12 +21,25 @@
群名称
</div>
<div class="input-box">
<tm-input v-model="groupName" :followTheme="false" fontColor="#747474" placeholderStyle="color: #B4B4B4"
focusColor="#FFF" :fontSize="28" :maxlength="20" :height="40" :transprent="true"
placeholder="请输入群名称1~20个字" :padding="[0, 0]" align="right"></tm-input>
<tm-input
v-model="groupName"
:followTheme="false"
fontColor="#747474"
placeholderStyle="color: #B4B4B4"
focusColor="#FFF"
:fontSize="28"
:maxlength="20"
:height="40"
:transprent="true"
placeholder="请输入群名称1~20个字"
:padding="[0, 0]"
align="right"
></tm-input>
</div>
</div>
<div class="input-group w-full flex flex-col mt-[20rpx] leading-[40rpx]">
<div
class="input-group w-full flex flex-col mt-[20rpx] leading-[40rpx]"
>
<div class="flex items-center justify-between">
<div class="input-item">
群类型
@ -35,310 +52,344 @@
<span v-else-if="groupActiveIndex === 2">项目群</span>
</div>
<div class="ml-[32rpx]">
<tm-icon :font-size="22" color="#747474" name="tmicon-angle-right"></tm-icon>
<tm-icon
:font-size="22"
color="#747474"
name="tmicon-angle-right"
></tm-icon>
</div>
</div>
</div>
<div v-if="depCheckedKeys.length && groupActiveIndex === 1" class="mt-[32rpx]">
<div v-for="(item, index) in depCheckedKeys" class="text-[#747474] text-[28rpx] leading-[40rpx] font-bold"
<div
v-if="depCheckedKeys.length && groupActiveIndex === 1"
class="mt-[32rpx]"
>
<div
v-for="(item, index) in depCheckedKeys"
class="text-[#747474] text-[28rpx] leading-[40rpx] font-bold"
:class="[
index !== 0 ? 'mt-[10rpx]' : '',
depsNoExpanded_1 && index > 4 ? 'hidden' : '',
]">
depsNoExpanded && index > 4 ? 'hidden' : '',
]"
>
{{ item.name }}
</div>
<div class="text-[#46299D] text-[28rpx] mt-[20rpx] font-bold flex justify-center">
<div v-if="depCheckedKeys.length > 5" @click="depsNoExpanded_1 = !depsNoExpanded_1" class="w-[100rpx]">
{{ depsNoExpanded_1 ? '展开' : '收起' }}
<div
class="text-[#46299D] text-[28rpx] mt-[20rpx] font-bold flex justify-center"
>
<div
v-if="depCheckedKeys.length > 5"
@click="depsNoExpanded = !depsNoExpanded"
class="w-[100rpx]"
>
{{ depsNoExpanded ? '展开' : '收起' }}
</div>
</div>
</div>
</div>
<div v-if="groupActiveIndex === 0 || groupActiveIndex === 2"
class="input-group w-full flex flex-col mt-[20rpx] leading-[40rpx]">
<div
v-if="groupActiveIndex === 0 || groupActiveIndex === 2"
class="input-group w-full flex flex-col mt-[20rpx] leading-[40rpx]"
>
<div class="flex items-center justify-between">
<div class="input-item">
群成员
</div>
<div @click="chooseMembers" class="left-box">
<div class="ml-[32rpx] flex items-center">
<div class="text-[#B4B4B4] text-[28rpx] font-bold mr-[32rpx]">
<div
class="text-[#B4B4B4] text-[28rpx] font-bold mr-[32rpx]"
>
全部({{ allChooseMembers?.length || 0 }})
</div>
<tm-icon :font-size="22" color="#747474" name="tmicon-angle-right"></tm-icon>
<tm-icon
:font-size="22"
color="#747474"
name="tmicon-angle-right"
></tm-icon>
</div>
</div>
</div>
<groupMemberList :groupType="3" :is_manager="true" :memberList="allChooseMembers" :memberListsLimit="15"
:hideAddRemoveBtns="true"></groupMemberList>
<groupMemberList
:groupType="3"
:is_manager="true"
:memberList="allChooseMembers"
:memberListsLimit="15"
:hideAddRemoveBtns="true"
></groupMemberList>
</div>
<div v-if="groupActiveIndex === 1" class="input-group w-full flex flex-col mt-[20rpx] leading-[40rpx]">
<div
v-if="groupActiveIndex === 1"
class="input-group w-full flex flex-col mt-[20rpx] leading-[40rpx]"
>
<div class="flex items-center justify-between">
<div class="input-item">
群管理员
</div>
<div @click="chooseGroupAdmin" class="left-box">
<div class="ml-[32rpx] flex items-center">
<div v-if="!groupAdmins.length" class="text-[#B4B4B4] text-[28rpx] font-bold mr-[32rpx]">
<div
v-if="!groupAdmins.length"
class="text-[#B4B4B4] text-[28rpx] font-bold mr-[32rpx]"
>
请选择群管理员
</div>
<tm-icon :font-size="22" color="#747474" name="tmicon-angle-right"></tm-icon>
<tm-icon
:font-size="22"
color="#747474"
name="tmicon-angle-right"
></tm-icon>
</div>
</div>
</div>
<div v-if="groupAdmins.length" class="mt-[32rpx]">
<div v-for="(item, index) in groupAdmins" class="text-[#747474] text-[28rpx] leading-[40rpx] font-bold"
<div
v-for="(item, index) in groupAdmins"
class="text-[#747474] text-[28rpx] leading-[40rpx] font-bold"
:class="[
index !== 0 ? 'mt-[10rpx]' : '',
depsNoExpanded_2 && index > 4 ? 'hidden' : '',
]">
depsNoExpanded && index > 4 ? 'hidden' : '',
]"
>
{{ item.name }}
</div>
<div class="text-[#46299D] text-[28rpx] mt-[20rpx] font-bold flex justify-center">
<div v-if="groupAdmins.length > 5" @click="depsNoExpanded_2 = !depsNoExpanded_2" class="w-[100rpx]">
{{ depsNoExpanded_2 ? '展开' : '收起' }}
<div
class="text-[#46299D] text-[28rpx] mt-[20rpx] font-bold flex justify-center"
>
<div
v-if="groupAdmins.length > 5"
@click="depsNoExpanded = !depsNoExpanded"
class="w-[100rpx]"
>
{{ depsNoExpanded ? '展开' : '收起' }}
</div>
</div>
</div>
</div>
</div>
<template #bottom>
<customBtn :isBottom="true" :btnText="$t('pageTitle.create.group')" @click="handleConfirm"
:isLoading="isLoading" :disabled="confirmBtnStatus || isLoading"></customBtn>
<customBtn
:isBottom="true"
:btnText="$t('pageTitle.create.group')"
@click="handleConfirm"
:disabled="confirmBtnStatus"
></customBtn>
</template>
</zPaging>
</div>
</template>
<script setup>
import zPaging from '@/uni_modules/z-paging/components/z-paging/z-paging.vue'
import customBtn from '@/components/custom-btn/custom-btn.vue'
import groupMemberList from '../chatSettings/components/groupMembersList.vue'
import avatarModule from '@/components/avatar-module/index.vue'
import zPaging from '@/uni_modules/z-paging/components/z-paging/z-paging.vue'
import customBtn from '@/components/custom-btn/custom-btn.vue'
import groupMemberList from '../chatSettings/components/groupMembersList.vue'
import avatarModule from '@/components/avatar-module/index.vue'
import {
ref,
watch,
computed,
onMounted
} from 'vue'
import {
onShow,
onLoad,
onUnload
} from '@dcloudio/uni-app'
import {
useChatList
} from '@/store/chatList/index.js'
import {
useAuth
} from '@/store/auth'
import {
useTalkStore,
useUserStore,
useGroupStore
} from '@/store'
import addCircle from '@/static/image/chatList/addCircle.png'
import cahtPopover from '@/static/image/chatList/cahtPopover.png'
import {
ServeCreateGroup
} from '@/api/group/index'
import {
useGroupTypeStore
} from '@/store/groupType'
import {
handleSetWebviewStyle
} from '@/utils/common'
import { ref, watch, computed } from 'vue'
import { onShow, onLoad } from '@dcloudio/uni-app'
import { useChatList } from '@/store/chatList/index.js'
import { useAuth } from '@/store/auth'
import { useTalkStore, useUserStore, useGroupStore } from '@/store'
import addCircle from '@/static/image/chatList/addCircle.png'
import cahtPopover from '@/static/image/chatList/cahtPopover.png'
import { ServeCreateGroup } from '@/api/group/index'
import { useGroupTypeStore } from '@/store/groupType'
const {
groupName,
groupActiveIndex,
depCheckedKeys,
groupAdmins,
createDepGroup,
resetGroupInfo,
allChooseMembers,
} = useGroupTypeStore()
const talkStore = useTalkStore()
const userStore = useUserStore()
const groupStore = useGroupStore()
const {
userInfo
} = useAuth()
const {
groupName,
groupActiveIndex,
depCheckedKeys,
groupAdmins,
createDepGroup,
resetGroupInfo,
allChooseMembers,
} = useGroupTypeStore()
const talkStore = useTalkStore()
const userStore = useUserStore()
const groupStore = useGroupStore()
const { userInfo } = useAuth()
const groupChatType = ref('')
const depsNoExpanded_1 = ref(true)
const depsNoExpanded_2 = ref(true)
const groupChatType = ref('')
const depsNoExpanded = ref(true)
onLoad(()=> {
groupStore.$reset()
})
onLoad(() => {
groupStore.$reset()
})
onUnload(() => {
resetGroupInfo();
})
onMounted(() => {
handleSetWebviewStyle()
//
const groupType = computed(() => {
let group_type = ''
switch (groupActiveIndex.value) {
case 0:
group_type = 1
break
case 1:
group_type = 2
break
case 2:
group_type = 3
break
default:
group_type = ''
}
return group_type
})
//
const chooseGroupType = () => {
uni.navigateTo({
url: '/pages/chooseGroupType/index',
})
}
//
const groupType = computed(() => {
let group_type = ''
switch (groupActiveIndex.value) {
case 0:
group_type = 1
break
case 1:
group_type = 2
break
case 2:
group_type = 3
break
default:
group_type = ''
const chooseGroupAdmin = () => {
uni.navigateTo({
url:
'/pages/chatSettings/groupManage/selectMembers?manageType=admin&isCreateDepGroup=1',
})
}
const chooseMembers = () => {
uni.navigateTo({
url: '/pages/chooseByDeps/index?chooseMode=2',
})
}
//
const handleConfirm = async () => {
// console.log(allChooseMembers.value)
let erp_ids = ''
if (allChooseMembers?.value?.length > 0) {
allChooseMembers?.value?.forEach((ele) => {
if (!erp_ids) {
erp_ids = String(ele.ID)
} else {
erp_ids += ',' + ele.ID
}
})
}
if (groupActiveIndex.value === 0) {
//
let params = {
avatar: '',
name: groupName.value,
erp_ids: erp_ids,
type: 1,
profile: '',
}
return group_type
})
//
const chooseGroupType = () => {
uni.navigateTo({
url: '/pages/chooseGroupType/index',
})
}
const chooseGroupAdmin = () => {
uni.navigateTo({
url: '/pages/chatSettings/groupManage/selectMembers?manageType=admin&isCreateDepGroup=1',
})
}
const chooseMembers = () => {
uni.navigateTo({
url: '/pages/chooseByDeps/index?chooseMode=2',
})
}
const isLoading = ref(false)
//
const handleConfirm = async () => {
if (isLoading.value) return
isLoading.value = true
try {
let erp_ids = ''
if (allChooseMembers?.value?.length > 0) {
allChooseMembers.value.forEach((ele) => {
if (!erp_ids) {
erp_ids = String(ele.ID)
} else {
erp_ids += ',' + ele.ID
}
})
}
let res = null
if (groupActiveIndex.value === 0) {
//
const params = {
avatar: '',
name: groupName.value,
erp_ids: erp_ids,
type: 1,
profile: '',
}
console.log('普通群参数:', params)
res = await ServeCreateGroup(params)
} else if (groupActiveIndex.value === 1) {
//
res = await createDepGroup()
} else if (groupActiveIndex.value === 2) {
//
const params = {
avatar: '',
name: groupName.value,
erp_ids: erp_ids,
type: 3,
profile: '',
}
console.log('项目群参数:', params)
res = await ServeCreateGroup(params)
}
if (res?.code === 200) {
resetGroupInfo()
uni.navigateBack()
}
} catch (err) {
console.error(err)
} finally {
isLoading.value = false
console.log(params)
const res = await ServeCreateGroup(params)
if (res.code === 200) {
resetGroupInfo()
uni.navigateBack()
}
} else if (groupActiveIndex.value === 1) {
//
const res = await createDepGroup()
if (res.code === 200) {
resetGroupInfo()
uni.navigateBack()
}
} else if (groupActiveIndex.value === 2) {
//
let params = {
avatar: '',
name: groupName.value,
erp_ids: erp_ids,
type: 3,
profile: '',
}
console.log(params)
const res = await ServeCreateGroup(params)
if (res.code === 200) {
resetGroupInfo()
uni.navigateBack()
}
} else {
}
//
const confirmBtnStatus = computed(() => {
return groupActiveIndex.value === -1;
});
}
onShow(() => {
depsNoExpanded_1.value = true;
depsNoExpanded_2.value = true;
})
//
const confirmBtnStatus = computed(() => {
let disabledT = false
console.log(groupActiveIndex.value !== -1)
if (
groupName.value === '' ||
(groupActiveIndex.value && groupActiveIndex.value === -1) ||
(!groupActiveIndex.value && groupActiveIndex.value !== 0)
) {
return true
}
switch (groupActiveIndex.value) {
case 0:
break
case 1:
if (!depCheckedKeys.value.length) {
disabledT = true
}
if (!groupAdmins.value.length) {
disabledT = true
}
break
case 2:
break
default:
break
}
return disabledT
})
onShow(() => {
depsNoExpanded.value = true
})
</script>
<style scoped lang="scss">
::v-deep .zp-paging-container-content {
height: 100%;
display: flex;
}
::v-deep .zp-paging-container-content {
height: 100%;
display: flex;
}
.create-group-chat {
background-image: url('@/static/image/clockIn/z3280@3x.png');
background-size: cover;
background-position: center bottom;
width: 100%;
padding: 0 32rpx 20rpx;
.group-avatar {
padding: 60rpx 0;
.avatar-placeholder {
width: 192rpx;
height: 192rpx;
background-color: #e0e0e0;
border-radius: 50%;
}
.create-group-chat {
background-image: url('@/static/image/clockIn/z3280@3x.png');
background-size: cover;
background-position: center bottom;
width: 100%;
padding: 0 32rpx 20rpx;
.group-avatar {
padding: 60rpx 0;
.avatar-placeholder {
width: 192rpx;
height: 192rpx;
background-color: #e0e0e0;
border-radius: 50%;
}
}
}
.divider {
height: 1rpx;
background-color: #7c7c7c;
opacity: 0.6;
}
.divider {
height: 1rpx;
background-color: #7c7c7c;
opacity: 0.6;
}
.input-group {
background-color: #fff;
padding: 38rpx 40rpx 32rpx 32rpx;
}
.input-item {
line-height: 40rpx;
font-size: 28rpx;
color: #000;
font-weight: bold;
}
.input-box {
margin-left: 84rpx;
line-height: 40rpx;
width: 404rpx;
font-weight: bold;
}
.left-box {
display: flex;
align-items: center;
}
.input-group {
background-color: #fff;
padding: 38rpx 40rpx 32rpx 32rpx;
}
.input-item {
line-height: 40rpx;
font-size: 28rpx;
color: #000;
font-weight: bold;
}
.input-box {
margin-left: 84rpx;
line-height: 40rpx;
width: 404rpx;
font-weight: bold;
}
.left-box {
display: flex;
align-items: center;
}
</style>

View File

@ -1,34 +1,25 @@
<template>
<div>
<zPaging :fixed="false" :height="'210px'" :show-scrollbar="false">
<div class="emojiRoot">
<div
v-for="(img, key) in emojis"
v-html="img"
:key="key"
@click="onSendEmoticon(1, key, img)"
class="option pointer flex-center"
/>
</div>
</zPaging>
<div class="emojiRoot">
<div v-for="(img, key) in emojis" v-html="img" :key="key" @click="onSendEmoticon(1, key, img)"
class="option pointer flex-center" />
</div>
</template>
<script setup>
import zPaging from '@/uni_modules/z-paging/components/z-paging/z-paging.vue'
import { ref, reactive, defineProps, defineEmits } from 'vue'
import dayjs from 'dayjs'
import { ref, reactive, defineProps,defineEmits } from "vue"
import dayjs from "dayjs";
import { beautifyTime } from '@/utils/datetime'
import { useTalkStore } from '@/store'
import { useSessionMenu } from '@/hooks'
import { emojis } from '@/utils/emojis'
const props = defineProps({
data: {
type: Object,
default: {},
required: false,
},
})
});
const emit = defineEmits(['on-select'])
const onSendEmoticon = (type, value, img = '') => {
@ -42,15 +33,21 @@ const onSendEmoticon = (type, value, img = '') => {
emit('on-select', { type, value, img })
}
}
</script>
<style lang="scss" scoped>
.emojiRoot {
width: 100%;
height: 420rpx;
padding: 5rpx 32rpx;
display: flex;
justify-content: flex-start;
align-items: center;
flex-wrap: wrap;
overflow: auto;
}
.option {

View File

@ -1,39 +1,25 @@
<template>
<div class="emojiRoot">
<div
@click="() => photoActionsSelect(0)"
class="flex flex-col items-center"
>
<div
class="w-[106rpx] h-[106rpx] bg-[#F9F9F9] rounded-[60rpx] flex-center"
>
<div @click="()=>photoActionsSelect(0)" class="flex flex-col items-center">
<div class="w-[106rpx] h-[106rpx] bg-[#F9F9F9] rounded-[60rpx] flex-center">
<tm-image :width="53" :height="44" :src="photoAlbum"></tm-image>
</div>
<div class="mt-[6rpx] text-[#666666] text-[24rpx]">照片</div>
</div>
<div
@click="() => photoActionsSelect(1)"
class="flex flex-col items-center"
>
<div
class="w-[106rpx] h-[106rpx] bg-[#F9F9F9] rounded-[60rpx] flex-center"
>
<div @click="()=>photoActionsSelect(1)" class="flex flex-col items-center">
<div class="w-[106rpx] h-[106rpx] bg-[#F9F9F9] rounded-[60rpx] flex-center">
<tm-image :width="53" :height="44" :src="videoImg"></tm-image>
</div>
<div class="mt-[6rpx] text-[#666666] text-[24rpx]">视频</div>
</div>
<div @click="takePhoto" class="flex flex-col items-center">
<div
class="w-[106rpx] h-[106rpx] bg-[#F9F9F9] rounded-[60rpx] flex-center"
>
<div class="w-[106rpx] h-[106rpx] bg-[#F9F9F9] rounded-[60rpx] flex-center">
<tm-image :width="53" :height="44" :src="photoGraph"></tm-image>
</div>
<div class="mt-[6rpx] text-[#666666] text-[24rpx]">拍摄</div>
</div>
<div @click="chooseFile" class="flex flex-col items-center">
<div
class="w-[106rpx] h-[106rpx] bg-[#F9F9F9] rounded-[60rpx] flex-center"
>
<div class="w-[106rpx] h-[106rpx] bg-[#F9F9F9] rounded-[60rpx] flex-center">
<tm-image :width="53" :height="44" :src="folder"></tm-image>
</div>
<div class="mt-[6rpx] text-[#666666] text-[24rpx]">文件</div>
@ -41,15 +27,10 @@
</div>
</template>
<script setup>
import { ref, reactive, defineProps, defineEmits } from 'vue'
import dayjs from 'dayjs'
import { ref, reactive, defineProps, defineEmits } from "vue"
import dayjs from "dayjs";
import { beautifyTime } from '@/utils/datetime'
import {
useDialogueListStore,
useDialogueStore,
useUserStore,
useUploadsStore,
} from '@/store'
import { useDialogueListStore, useDialogueStore, useUserStore,useUploadsStore } from '@/store'
import { useSessionMenu } from '@/hooks'
import photoAlbum from '@/static/image/chatList/photoAlbum.png'
import photoGraph from '@/static/image/chatList/photoGraph.png'
@ -57,168 +38,87 @@ import videoImg from '@/static/image/chatList/video@2x.png'
import folder from '@/static/image/chatList/folder.png'
import { uploadImg } from '@/api/chat'
import { uniqueId } from '@/utils'
import { handleFindWebview } from '@/utils/common'
const props = defineProps({
sendUserInfo: {
type: Object,
default: {},
required: true,
required: true
},
talkParams: {
type: Object,
default: {},
required: true,
},
})
const state = reactive({
base64Url: '',
})
required: true
}
});
const uploadsStore = useUploadsStore()
const {
addDialogueRecord,
virtualList,
updateUploadProgress,
} = useDialogueListStore()
const { addDialogueRecord, virtualList, updateUploadProgress } = useDialogueListStore()
const dialogueStore = useDialogueStore()
const userStore = useUserStore()
const emit = defineEmits(['selectImg'])
const onProgressFn = (progress, id) => {
console.log((progress.loaded / progress.total) * 100, 'progress')
console.log(progress.loaded / progress.total * 100, 'progress');
updateUploadProgress(id, (progress.loaded / progress.total) * 100)
updateUploadProgress(id, progress.loaded / progress.total * 100)
}
const photoActionsSelect = (index) => {
if (index === 0) {
if (typeof plus === 'undefined') {
uni.chooseImage({
sourceType: ['album'],
count: 9,
success: async (res) => {
console.log(res, 'res')
res.tempFiles.forEach(async (file) => {
const fileSizeInMB = (file.size / (1024 * 1024)).toFixed(2)
if (fileSizeInMB > 100) {
plus.nativeUI.toast('图片大小不能超过100MB')
return
}
const result = await onUploadImageVideo(file, 'image')
if (result) {
emit('selectImg', result, result.file_num)
}
})
},
})
} else {
plus?.gallery.pick(
(res) => {
console.log(res, 'res')
res.files.reverse()
res.files.forEach(async (filePath) => {
plus?.io?.resolveLocalFileSystemURL(
filePath,
async (entry) => {
entry.file((file) => {
const fileReader = new plus.io.FileReader()
fileReader.readAsDataURL(file)
fileReader.onloadend = async (e) => {
const base64Url = e.target.result
const fileObj = base64ToFile(base64Url)
const fileSizeInMB = (fileObj.size / (1024 * 1024)).toFixed(
2,
)
if (fileSizeInMB > 100) {
plus.nativeUI.toast('图片大小不能超过100MB')
return
}
let data = await onUploadImageVideo(fileObj, 'image')
if (data) {
emit('selectImg', data, data.file_num)
}
}
})
},
(err) => {
console.log(err)
},
)
})
},
(err) => {
console.log(err)
},
{
filter: 'image',
maximum: 9,
multiple: true,
onmaxed: () => {
plus.nativeUI.toast('最多只能选择9张图片')
},
},
)
}
} else {
// handleFindWebview(`getPlusVideoPicker()`)
// return
uni.chooseImage({
sourceType: ['album'],
count: 9,
success: async (res) => {
console.log(res,'res');
res.tempFiles.forEach(async (file) => {
let data = await onUploadImageVideo(file, 'image')
emit('selectImg', data)
})
}
})
}else{
uni.chooseVideo({
sourceType: ['album'],
compressed: true,
maxDuration: 60,
success: async (res) => {
console.log(res, 'res')
const fileSizeInMB = (res.tempFile.size / (1024 * 1024)).toFixed(2)
if (fileSizeInMB > 100) {
plus.nativeUI.toast('视频大小不能超过100MB')
return
}
let data = await onUploadImageVideo(
res.tempFile,
'video',
res.tempFilePath,
)
if (data) {
emit('selectImg', data, data.file_num)
}
},
console.log(res,'res');
let data = await onUploadImageVideo(res.tempFile, 'video',res.tempFilePath)
emit('selectImg', data)
}
})
}
}
const onUploadImageVideo = async (file, type = 'image', fileUrl) => {
console.log('开始上传文件:', file.name)
uploadsStore.updateUploadStatus(true)
const onUploadImageVideo = async (file, type = 'image',fileUrl) => {
console.log(file, 'file');
return new Promise(async (resolve) => {
if (type === 'image') {
let image = new Image()
image.src = URL.createObjectURL(file)
image.onload = async () => {
image.onload = () => {
const form = new FormData()
form.append('file', file)
form.append('source', 'fonchain-chat')
form.append('urlParam', `width=${image.width}&height=${image.height}`)
form.append("source", "fonchain-chat");
form.append("urlParam", `width=${image.width}&height=${image.height}`);
let randomId = uniqueId()
let newItem = {
avatar: userStore.avatar,
created_at: dayjs().format('YYYY-MM-DD HH:mm:ss'),
extra: {
height: image.height,
name: '',
name: "",
size: 0,
url: image.src,
width: image.width,
width: image.width
},
float: 'right',
float: "right",
isCheck: false,
is_mark: 0,
is_read: 0,
is_revoke: 0,
msg_id: randomId,
file_num: randomId,
msg_type: 3,
nickname: userStore.nickname,
receiver_id: dialogueStore.talk.receiver_id,
@ -226,372 +126,210 @@ const onUploadImageVideo = async (file, type = 'image', fileUrl) => {
talk_type: dialogueStore.talk.talk_type,
user_id: userStore.uid,
uploadCurrent: 0,
uploadStatus: 1, // 1 2 3
uploadStatus: 1, // 1 2 3
}
virtualList.value.unshift(newItem)
try {
const result = await uploadImg(form, (e) => onProgressFn(e, randomId))
console.log('上传完成,结果:', result)
if (result.status === 0) {
//
const index = virtualList.value.findIndex(
(item) => item.file_num === randomId,
)
if (index !== -1) {
virtualList.value[index].uploadStatus = 2
virtualList.value[index].uploadCurrent = 100
}
//
uploadImg(form, (e) => onProgressFn(e, randomId)).then(({ status, data, msg }) => {
if (status == 0) {
resolve({
type: 'image',
url: result.data.ori_url,
url: data.ori_url,
size: file.size,
width: image.width,
height: image.height,
file_num: randomId,
height: image.height
})
} else {
uploadsStore.updateUploadStatus(false)
//
const index = virtualList.value.findIndex(
(item) => item.file_num === randomId,
)
if (index !== -1) {
virtualList.value[index].uploadStatus = 3
}
message.error(result.msg)
resolve('')
message.error(msg)
}
} catch (error) {
console.error('上传出错:', error)
uploadsStore.updateUploadStatus(false)
//
const index = virtualList.value.findIndex(
(item) => item.file_num === randomId,
)
if (index !== -1) {
virtualList.value[index].uploadStatus = 3
}
message.error('上传失败')
resolve('')
}
})
}
} else {
uni.getVideoInfo({
src: fileUrl,
success: async (resp) => {
console.log('视频信息:', resp)
const form = new FormData()
form.append('file', file)
form.append('source', 'fonchain-chat')
form.append('type', 'video')
form.append('urlParam', `width=${resp.width}&height=${resp.height}`)
let randomId = uniqueId()
let newItem = {
avatar: userStore.avatar,
created_at: dayjs().format('YYYY-MM-DD HH:mm:ss'),
extra: {
src:fileUrl,
success:(resp)=>{
console.log(resp);
form.append('file', file)
form.append("source", "fonchain-chat");
form.append("type", "video");
form.append("urlParam", `width=${resp.width}&height=${resp.height}`);
let randomId = uniqueId()
let newItem = {
avatar: userStore.avatar,
created_at: dayjs().format('YYYY-MM-DD HH:mm:ss'),
extra: {
duration: parseInt(resp.duration),
height: resp.height,
name: "",
url: fileUrl,
width: resp.width
},
float: "right",
isCheck: false,
is_mark: 0,
is_read: 0,
is_revoke: 0,
msg_id: randomId,
msg_type: 5,
nickname: userStore.nickname,
receiver_id: dialogueStore.talk.receiver_id,
sequence: -1,
talk_type: dialogueStore.talk.talk_type,
user_id: userStore.uid,
uploadCurrent: 0,
uploadStatus: 1, // 1 2 3
}
virtualList.value.unshift(newItem)
uploadImg(form, (e) => onProgressFn(e, randomId)).then(({ status, data, msg }) => {
if (status == 0) {
console.log(data);
resolve({
type: 'video',
url: data.ori_url,
cover: data.cover_url,
duration: parseInt(resp.duration),
height: resp.height,
name: '',
url: fileUrl,
width: resp.width,
},
float: 'right',
isCheck: false,
is_mark: 0,
is_read: 0,
is_revoke: 0,
msg_id: randomId,
file_num: randomId,
msg_type: 5,
nickname: userStore.nickname,
receiver_id: dialogueStore.talk.receiver_id,
sequence: -1,
talk_type: dialogueStore.talk.talk_type,
user_id: userStore.uid,
uploadCurrent: 0,
uploadStatus: 1,
size: file.size
})
} else {
// resolve('')
// message.error(msg)
}
virtualList.value.unshift(newItem)
try {
const result = await uploadImg(form, (e) =>
onProgressFn(e, randomId),
)
console.log('视频上传完成,结果:', result)
if (result.status === 0) {
//
const index = virtualList.value.findIndex(
(item) => item.file_num === randomId,
)
if (index !== -1) {
virtualList.value[index].uploadStatus = 2
virtualList.value[index].uploadCurrent = 100
}
resolve({
type: 'video',
url: result.data.ori_url,
cover: result.data.cover_url,
duration: parseInt(resp.duration),
size: file.size,
file_num: randomId,
})
} else {
uploadsStore.updateUploadStatus(false)
//
const index = virtualList.value.findIndex(
(item) => item.file_num === randomId,
)
if (index !== -1) {
virtualList.value[index].uploadStatus = 3
}
message.error(result.msg)
resolve('')
}
} catch (error) {
console.error('视频上传出错:', error)
uploadsStore.updateUploadStatus(false)
//
const index = virtualList.value.findIndex(
(item) => item.file_num === randomId,
)
if (index !== -1) {
virtualList.value[index].uploadStatus = 3
}
message.error('上传失败')
resolve('')
}
},
fail: (error) => {
console.error('获取视频信息失败:', error)
uploadsStore.updateUploadStatus(false)
message.error('获取视频信息失败')
resolve('')
},
})
}
})
const form = new FormData()
}
})
}
const base64ToFile = (base64) => {
if (!base64) {
message.warning('您的系统暂不支持发送原图哦')
}
// base64file
const [header, base64String] = base64.split(';base64,')
const imageType = header.split(':')[1]
const byteCharacters = atob(base64String)
const [header, base64String] = base64.split(";base64,");
const imageType = header.split(":")[1];
const byteCharacters = atob(base64String);
const byteArray = new Uint8Array(
Array.from(byteCharacters, (char) => char.charCodeAt(0)),
)
return new File([new Blob([byteArray], { type: imageType })], 'example.png', {
type: imageType,
})
Array.from(byteCharacters, (char) => char.charCodeAt(0))
);
return new File(
[new Blob([byteArray], { type: imageType })],
"example.png",
{ type: imageType }
);
}
const choosePhoto = (filter = 'none', maximum = 9, multiple = true) => {
window.plus?.gallery.pick(
(res) => {
console.log(res)
res.files.reverse()
res.files.forEach(async (filePath) => {
const suffix = filePath.split('.').pop()?.toLowerCase() || ''
if (['jpg', 'png'].includes(suffix)) {
console.log('进入图片')
window.plus?.io?.resolveLocalFileSystemURL(
filePath,
async (entry) => {
entry.file((file) => {
const fileReader = new plus.io.FileReader()
fileReader.readAsDataURL(file)
fileReader.onloadend = async (e) => {
const base64Url = e.target.result
const fileObj = base64ToFile(base64Url)
let data = await onUploadImageVideo(fileObj, 'image')
emit('selectImg', data)
}
})
},
(err) => {
console.log(err)
},
)
}
if (['mp4', 'flv'].includes(suffix)) {
console.log(filePath, '进入视频')
// const localUrl = plus.io.convertLocalFileSystemURL(filePath)
// console.log(localUrl);
plus.io.getVideoInfo({
filePath: filePath,
success: (event) => {
console.log(event)
},
fail: (err) => {
console.log(err)
},
window.plus?.gallery.pick((res) => {
console.log(res);
res.files.reverse()
res.files.forEach(async (filePath) => {
const suffix = filePath.split('.').pop()?.toLowerCase() || ''
if (['jpg', 'png'].includes(suffix)) {
console.log("进入图片")
window.plus?.io?.resolveLocalFileSystemURL(filePath, async (entry) => {
entry.file((file) => {
const fileReader = new plus.io.FileReader();
fileReader.readAsDataURL(file);
fileReader.onloadend = async (e) => {
const base64Url = e.target.result;
const fileObj = base64ToFile(base64Url);
let data = await onUploadImageVideo(fileObj, 'image')
emit('selectImg', data)
};
})
// window.plus?.io?.resolveLocalFileSystemURL(localUrl, async (entry) => {
// entry.file((file) => {
// console.log(file,'file');
// const fileReader = new plus.io.FileReader();
// fileReader.readAsDataURL(file);
// fileReader.onloadend = async (e) => {
// const base64Url = e.target.result;
// const fileObj = base64ToFile(base64Url);
// let data = await onUploadImageVideo(fileObj, 'video')
// emit('selectImg', data)
// };
// })
// },
// (err) => {
// console.log(err);
// }
// )
},
(err) => {
console.log(err);
}
)
}
if (['mp4', 'flv'].includes(suffix)) {
console.log(filePath,"进入视频")
// const localUrl = plus.io.convertLocalFileSystemURL(filePath)
// console.log(localUrl);
plus.io.getVideoInfo({
filePath:filePath,
success:(event)=>{
console.log(event);
},
fail:(err)=>{
console.log(err);
}
})
},
(err) => {
console.log(err)
},
});
// window.plus?.io?.resolveLocalFileSystemURL(localUrl, async (entry) => {
// entry.file((file) => {
// console.log(file,'file');
// const fileReader = new plus.io.FileReader();
// fileReader.readAsDataURL(file);
// fileReader.onloadend = async (e) => {
// const base64Url = e.target.result;
// const fileObj = base64ToFile(base64Url);
// let data = await onUploadImageVideo(fileObj, 'video')
// emit('selectImg', data)
// };
// })
// },
// (err) => {
// console.log(err);
// }
// )
}
})
}, (err) => {
console.log(err);
},
{
filter: filter,
maximum: maximum,
multiple: multiple,
},
}
)
}
const takePhoto = () => {
if (typeof plus !== 'undefined') {
getCamera()
} else {
document.addEventListener('plusready', () => {
getCamera()
})
}
}
const getCamera = () => {
const cmr = plus.camera.getCamera()
cmr.captureImage(
(p) => {
plus.io.resolveLocalFileSystemURL(
p,
(entry) => {
compressAndShowImage(entry.toLocalURL(), entry.name)
},
(err) => {
console.log(err)
},
)
},
() => {},
{ index: '2' },
)
}
const compressAndShowImage = (url, filename) => {
const dst = `_doc/upload/${filename}`
plus.zip.compressImage(
{ src: url, dst, quality: 10, overwrite: true },
(zip) => displayImage(zip.target),
(err) => {
console.log(err)
},
)
}
const displayImage = (url) => {
plus.io.resolveLocalFileSystemURL(url, (entry) => {
entry.file((file) => {
const fileReader = new plus.io.FileReader()
fileReader.readAsDataURL(file)
fileReader.onloadend = async (e) => {
state.base64Url = e.target.result
const imageFile = base64ToFile(state.base64Url)
let data = await onUploadImageVideo(imageFile, 'image')
emit('selectImg', data, data.file_num)
}
})
})
}
const chooseFile = () => {
uni.chooseFile({
count: 1,
extension: [''],
extension:[''],
success: (res) => {
const fileSizeInMB = (res.tempFiles[0].size / (1024 * 1024)).toFixed(2)
if (fileSizeInMB > 100) {
plus.nativeUI.toast('文件大小不能超过100MB')
return
}
let randomId = uniqueId()
let newItem = {
avatar: userStore.avatar,
created_at: dayjs().format('YYYY-MM-DD HH:mm:ss'),
extra: {
drive: 3,
name: res.tempFiles[0].name,
size: res.tempFiles[0].size,
path: res.tempFilePaths[0],
},
float: 'right',
isCheck: false,
is_mark: 0,
is_read: 0,
is_revoke: 0,
msg_id: randomId,
file_num: randomId,
msg_type: 6,
nickname: userStore.nickname,
receiver_id: dialogueStore.talk.receiver_id,
sequence: -1,
talk_type: dialogueStore.talk.talk_type,
user_id: userStore.uid,
uploadCurrent: 0,
uploadStatus: 1, // 1 2 3
}
let newItem = {
avatar: userStore.avatar,
created_at: dayjs().format('YYYY-MM-DD HH:mm:ss'),
extra: {
drive: 3,
name: res.tempFiles[0].name,
size: res.tempFiles[0].size,
path: res.tempFilePaths[0],
},
float: "right",
isCheck: false,
is_mark: 0,
is_read: 0,
is_revoke: 0,
msg_id: randomId,
msg_type: 6,
nickname: userStore.nickname,
receiver_id: dialogueStore.talk.receiver_id,
sequence: -1,
talk_type: dialogueStore.talk.talk_type,
user_id: userStore.uid,
uploadCurrent: 0,
uploadStatus: 1, // 1 2 3
}
virtualList.value.unshift(newItem)
uploadsStore.updateUploadStatus(true)
uploadsStore.initUploadFile(
res.tempFiles[0],
props.talkParams,
randomId,
(status, data, msg) => {
if (status === 0) {
//
const index = virtualList.value.findIndex(
(item) => item.file_num === randomId,
)
if (index !== -1) {
virtualList.value[index].uploadStatus = 2
virtualList.value[index].uploadCurrent = 100
}
} else {
uploadsStore.updateUploadStatus(false)
//
const index = virtualList.value.findIndex(
(item) => item.file_num === randomId,
)
if (index !== -1) {
virtualList.value[index].uploadStatus = 3
}
message.error(msg)
}
},
)
},
uploadsStore.initUploadFile(res.tempFiles[0], props.talkParams,randomId)
}
})
}
</script>
<style lang="scss" scoped>
.emojiRoot {

View File

@ -37,7 +37,7 @@
</div>
<div class="user-info-main user-info-card">
<div class="user-info-main-title">
<img src="@/static/image/mine/ming001@3x.png" />
<img src="/src/static/image/mine/ming001@3x.png" />
<span class="text-[28rpx] font-medium">
{{ $t('index.mine.basic') }}
</span>
@ -61,48 +61,13 @@
<template #bottom>
<customBtn
:isBottom="true"
:btnText="
state.canSendMsg
? $t('user.detail.sendMsg')
: $t('addressBook.btns.addFriend')
"
:subBtnText="
!state.canSendMsg || state.userInfo.sys_id === state.uid
? ''
: $t('user.detail.ringBell')
"
@clickBtn="checkSendPermission"
@clickSubBtn="handleCall"
:btnText="$t('user.detail.sendMsg')"
:subBtnText="$t('user.detail.ringBell')"
@clickBtn="toTalkUser"
></customBtn>
</template>
</ZPaging>
</div>
<tm-drawer
placement="bottom"
v-model:show="state.isShowPhoneCall"
:hideHeader="true"
:height="416"
:round="6"
>
<div class="do-phone-call">
<div class="do-phone-call-header">
<span>{{ $t('popup.title.phone') }}</span>
<img
src="@/static/image/login/check-circle-filled@3x.png"
@click="hidePhoneCallPopup"
/>
</div>
<div class="do-phone-call-number">
<span>{{ state.phoneNumber }}</span>
</div>
<div class="do-phone-call-btn">
<customBtn
:btnText="$t('do.phone.call')"
@clickBtn="doPhoneCall"
></customBtn>
</div>
</div>
</tm-drawer>
</div>
</template>
<script setup>
@ -111,12 +76,10 @@ import ZPaging from '@/uni_modules/z-paging/components/z-paging/z-paging.vue'
import { onLoad } from '@dcloudio/uni-app'
import { reactive } from 'vue'
import { useTalkStore, useUserStore } from '@/store'
import { useTalkStore } from '@/store'
const talkStore = useTalkStore()
const userStore = useUserStore()
import { getUserInfoByClickAvatar } from '@/api/user/index'
import { ServeCheckFriend, ServeAddFriend } from '@/api/addressBook/index'
import { useI18n } from 'vue-i18n'
const { t } = useI18n()
@ -125,10 +88,6 @@ const state = reactive({
erpUserId: '', //erpid
userInfo: null, //
userBasicInfos: [], //
isShowPhoneCall: false, //
phoneNumber: '', //
uid: computed(() => userStore.uid), //id
canSendMsg: false, //
})
onLoad((options) => {
@ -151,7 +110,6 @@ const getUserInfo = () => {
console.log(data)
if (code == 200) {
state.userInfo = data
checkNeedAddFriend()
let department = ''
let post = ''
if (data?.erp_dept_position?.length > 0) {
@ -209,7 +167,6 @@ const getUserInfo = () => {
value: data.enter_date,
},
]
state.phoneNumber = data.tel_num
} else {
}
})
@ -217,72 +174,10 @@ const getUserInfo = () => {
resp.catch(() => {})
}
//
const handleCall = () => {
state.isShowPhoneCall = true
}
//
const hidePhoneCallPopup = () => {
state.isShowPhoneCall = false
}
//
const toTalkUser = () => {
talkStore.toTalk(1, state.userInfo.sys_id, state.erpUserId)
}
//
const doPhoneCall = () => {
uni.makePhoneCall({
phoneNumber: state.phoneNumber,
success: () => {
console.log('拨号成功')
},
fail: (err) => {
console.error('失败:', err)
},
})
}
//
const checkSendPermission = () => {
if (state.canSendMsg) {
toTalkUser()
} else {
doAddFriend()
}
}
//
const checkNeedAddFriend = () => {
state.canSendMsg = true
// let params = {
// receiver_id: state.userInfo.sys_id, //id
// talk_type: 1,
// }
// ServeCheckFriend(params).then((res) => {
// console.log(res)
// if (res?.code === 200) {
// state.canSendMsg = res.data?.is_friend || false
// }
// })
}
//
const doAddFriend = () => {
let params = {
receiver_id: state.userInfo.sys_id, //id
talk_type: 1,
}
ServeAddFriend(params).then((res) => {
console.log(res)
if (res?.code === 200) {
message.success(t('addressBook.message.addSuccess') + ' !')
state.canSendMsg = true
}
})
}
</script>
<style scoped lang="scss">
.outer-layer {
@ -325,7 +220,6 @@ const doAddFriend = () => {
flex-direction: row;
align-items: center;
justify-content: center;
flex-shrink: 0;
img {
width: 100%;
height: 100%;
@ -396,58 +290,4 @@ const doAddFriend = () => {
}
}
}
.do-phone-call {
.do-phone-call-header {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
padding: 42rpx 0;
position: relative;
span {
font-size: 28rpx;
line-height: 40rpx;
color: #747474;
font-weight: 400;
}
img {
position: absolute;
top: 44rpx;
right: 30rpx;
width: 36rpx;
height: 36rpx;
}
}
.do-phone-call-number {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
padding: 32rpx 0;
border-top: 2rpx solid #e7e7e7;
border-bottom: 2rpx solid #e7e7e7;
span {
font-size: 32rpx;
line-height: 44rpx;
color: #1a1a1a;
font-weight: 400;
}
}
.do-phone-call-btn {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
padding: 50rpx 0;
:deep(.custom-btn-class) {
width: 690rpx;
font-size: 32rpx;
line-height: 44rpx;
padding: 18rpx 0;
height: 80rpx;
background: linear-gradient(to right, #674bbc, #46299d);
}
}
}
</style>

File diff suppressed because it is too large Load Diff

View File

@ -1,17 +1,14 @@
<script lang="ts" setup>
import zPaging from '@/uni_modules/z-paging/components/z-paging/z-paging.vue'
import { ref, onMounted } from 'vue'
import { ServeGetForwardRecords } from '@/api/chat'
import { MessageComponents } from '@/constant/message'
import { ITalkRecord } from '@/types/chat'
import WdLoading from "@/uni_modules/wot-design-uni/components/wd-loading/wd-loading.vue";
import { useInject } from '@/hooks'
import { parseTime } from '@/utils/datetime'
const emit = defineEmits(['close'])
const msgId = ref(null)
const createdAt = ref(null)
const { showUserInfoModal } = useInject()
const isShow = ref(true)
const items = ref<ITalkRecord[]>([])
@ -23,18 +20,12 @@ const onMaskClick = () => {
const onLoadData = () => {
ServeGetForwardRecords({
msg_id: msgId.value,
biz_date: createdAt.value
msg_id: msgId.value
}).then((res) => {
if (res.code == 200) {
items.value = res.data.items || []
//
const uniqueNames = [...new Set(items.value.map(v => v.nickname))];
if (uniqueNames.length <= 2) {
title.value = uniqueNames.join('和');
} else {
title.value = uniqueNames.slice(0, 2).join('和') + '等';
}
title.value = [...new Set(items.value.map((v) => v.nickname))].join('、')
}
})
}
@ -44,7 +35,6 @@ onMounted(() => {
const page = pages[pages.length - 1]
const options = page.$page.options
msgId.value = options.msgId
createdAt.value = parseTime(new Date((options.created_at as any)), '{y}{m}')
console.log(msgId.value,'msgId.value');
onLoadData()
@ -52,12 +42,12 @@ onMounted(() => {
</script>
<template>
<div class="forward-record-page">
<zPaging ref="zPaging" :show-scrollbar="false">
<template #top>
<customNavbar :title="`${title}的会话记录`"></customNavbar>
</template>
<div class="main-box">
<div class="outer-layer">
<div>
<tm-navbar :hideBack="false" hideHome :title="`${title}的会话记录`" >
</tm-navbar>
</div>
<div class="main-box">
<div v-if="items.length === 0" class="flex justify-center items-center w-full mt-[200rpx]">
<wd-loading />
</div>
@ -81,15 +71,16 @@ onMounted(() => {
</div>
</div>
</div>
</zPaging>
</div>
</template>
<style lang="less" scoped>
.forward-record-page {
.outer-layer {
overflow-y: auto;
flex: 1;
background-image: url("@/static/image/clockIn/z3280@3x.png");
background-size: cover;
padding: 0 66rpx 20rpx 50rpx;
display: flex;
flex-direction: column;
}
@ -100,7 +91,7 @@ onMounted(() => {
display: flex;
flex-direction: column;
overflow: auto;
padding: 28rpx 66rpx 20rpx 50rpx;
padding-top: 28rpx;
}
.message-item {

View File

@ -1,50 +1,40 @@
<template>
<div>
<wd-swipe-action class="swipe_action">
<div
@click="cellClick"
:class="['chatItem', props.data.is_top === 1 ? 'isTop' : '']"
>
<div class="avatarImg">
<tm-badge
:count="props.data.is_disturb === 0 ? props.data.unread_num : ''"
:dot="
props.data.is_disturb === 1 && props.data.unread_num
? true
: false
"
:maxCount="99"
class="badge"
color="#D03050"
>
<avatarModule
:mode="props?.data?.group_type === 0 ? 1 : 2"
:avatar="props?.data?.avatar"
:groupType="props?.data?.group_type"
:userName="props?.data?.name"
:customStyle="{
width: '96rpx',
height: '96rpx',
}"
:customTextStyle="{
fontSize: '32rpx',
fontWeight: 'bold',
color: '#fff',
lineHeight: '44rpx',
}"
></avatarModule>
</tm-badge>
</div>
<div class="chatInfo">
<div class="chatInfo_1">
<div class="name_center">
<div
class="text-[#000000] text-[32rpx] font-bold opacity-90 name_text"
>
<div>
<wd-swipe-action class="swipe_action">
<div
@click="cellClick"
:class="['chatItem', props.data.is_top === 1 ? 'isTop' : '']"
>
<div class="avatarImg">
<tm-badge
:count="props.data.unread_num"
:maxCount="99"
color="#D03050"
>
<avatarModule
:mode="props?.data?.group_type === 0 ? 1 : 2"
:avatar="props?.data?.avatar"
:groupType="props?.data?.group_type"
:userName="props?.data?.name"
:customStyle="{
width: '96rpx',
height: '96rpx',
}"
:customTextStyle="{
fontSize: '32rpx',
fontWeight: 'bold',
color: '#fff',
lineHeight: '44rpx',
}"
></avatarModule>
</tm-badge>
</div>
<div class="chatInfo">
<div class="chatInfo_1">
<div class="name_center">
<div class="text-[#000000] text-[32rpx]
font-bold opacity-90 name_text">
{{ formatNameText(props.data.name) }}
<span v-if="props.data.talk_type === 2">
{{ props.data.group_member_num }}
</span>
<span v-if="props.data.group_type === 2" class="depTag tag">
部门
</span>
@ -55,167 +45,153 @@
公司
</span>
</div>
</div>
<div
style="flex-shrink: 0;"
class="text-[#000000] text-[28rpx] font-medium opacity-26 ml-[24rpx] time_right"
>
{{ beautifyTime(props.data.updated_at) }}
</div>
</div>
<div class="chatInfo_2 w-full mr-[6rpx]">
<div class="w-full chatInfo_2_1 textEllipsis">
<span v-if="props.data.atsign_num" style="color: red;">
[有人@]
</span>
{{ props.data.msg_text }}
</div>
</div>
</div>
</div>
<template #right>
<div class="flex flex-row flex-row-center-end">
<!-- 样式占位 -->
<div style="width: 1px;"></div>
<div
@click="handleTop"
class="w-[156rpx] h-[154rpx] text-[#ffffff] bg-[#F09F1F] flex items-center justify-center"
>
{{ props.data.is_top === 1 ? '取消置顶' : '置顶' }}
</div>
<div
@click="handleDelete"
class="w-[156rpx] h-[154rpx] text-[#ffffff] bg-[#CF3050] flex items-center justify-center"
>
删除
</div>
</div>
</template>
</wd-swipe-action>
<div
v-if="props.index !== talkStore.talkItems.length - 1"
class="divider"
></div>
</div>
</div>
<div style="flex-shrink: 0;"
class="text-[#000000] text-[28rpx] font-medium opacity-26 ml-[24rpx] time_right"
>
{{ beautifyTime(props.data.updated_at) }}
</div>
</div>
<div class="chatInfo_2 w-full mr-[6rpx]">
<div class="w-full chatInfo_2_1 textEllipsis">
{{ props.data.msg_text }}
</div>
</div>
</div>
</div>
<template #right>
<div class="flex flex-row flex-row-center-end">
<!-- 样式占位 -->
<div style="width: 1px"></div>
<div
@click="handleTop"
class="w-[156rpx] h-[154rpx] text-[#ffffff] bg-[#F09F1F] flex items-center justify-center"
>
{{ props.data.is_top === 1 ? "取消置顶" : "置顶" }}
</div>
<div
@click="handleDelete"
class="w-[156rpx] h-[154rpx] text-[#ffffff] bg-[#CF3050] flex items-center justify-center"
>
删除
</div>
</div>
</template>
</wd-swipe-action>
<div
v-if="props.index !== talkStore.talkItems.length - 1"
class="divider"
></div>
</div>
</template>
<script setup>
import avatarModule from '@/components/avatar-module/index.vue'
import { ref, reactive, defineProps, computed } from 'vue'
import dayjs from 'dayjs'
import { beautifyTime } from '@/utils/datetime'
import { ServeClearTalkUnreadNum } from '@/api/chat'
import { useTalkStore, useDialogueStore } from '@/store'
import { useSessionMenu } from '@/hooks'
import avatarModule from "@/components/avatar-module/index.vue";
import { ref, reactive, defineProps, computed } from "vue";
import dayjs from "dayjs";
import { beautifyTime } from "@/utils/datetime";
import { ServeClearTalkUnreadNum } from "@/api/chat";
import { useTalkStore, useDialogueStore } from "@/store";
import { useSessionMenu } from "@/hooks";
const talkStore = useTalkStore()
const { onToTopTalk, onRemoveTalk } = useSessionMenu()
const dialogueStore = useDialogueStore()
const dialogueParams = reactive({
unReadNum: computed(() => dialogueStore.unreadNum),
})
const talkStore = useTalkStore();
const { onToTopTalk, onRemoveTalk } = useSessionMenu();
const dialogueStore = useDialogueStore();
const props = defineProps({
data: {
type: Object,
default: {},
required: true,
},
index: {
type: Number,
default: -1,
required: true,
},
})
data: {
type: Object,
default: {},
required: true,
},
index: {
type: Number,
default: -1,
required: true,
},
});
//
const formatNameText = (text, maxLength = 16) => {
return text.length > maxLength ? `${text.slice(0, maxLength - 1)}...` : text
}
const formatNameText = (text, maxLength = 19) => {
return text.length > maxLength ? `${text.slice(0, maxLength - 1)}...` : text;
};
const cellClick = () => {
console.log(props.data)
//
dialogueStore.setDialogue(props.data)
console.log(props.data);
//
dialogueStore.setDialogue(props.data);
//
if (props.data.unread_num > 0) {
ServeClearTalkUnreadNum(
{
talk_type: props.data.talk_type,
receiver_id: props.data.receiver_id,
},
dialogueParams.unReadNum,
).then(() => {
talkStore.updateItem({
index_name: props.data.index_name,
unread_num: 0,
})
dialogueStore.clearUnreadNum()
})
}
uni.navigateTo({
url: `/pages/dialog/index?sessionId=${props.data.id}`,
})
}
//
if (props.data.unread_num > 0) {
ServeClearTalkUnreadNum({
talk_type: props.data.talk_type,
receiver_id: props.data.receiver_id,
}).then(() => {
talkStore.updateItem({
index_name: props.data.index_name,
unread_num: 0,
});
});
}
uni.navigateTo({
url: "/pages/dialog/index?sessionId=" + props.data.id,
});
};
const handleTop = () => {
console.log(props.data, 1)
onToTopTalk(props.data)
}
console.log(props.data, 1);
onToTopTalk(props.data);
};
//
const handleDelete = () => {
console.log(props.data)
onRemoveTalk(props.data)
}
console.log(props.data);
onRemoveTalk(props.data);
};
</script>
<style lang="scss" scoped>
::v-deep .swipe_action {
// border: 1px solid #fff;
// transform: translate3d(1px, 0px, 0px) !important;
}
::v-deep .badge .round-6 {
min-width: 22rpx;
min-height: 22rpx;
// border: 1px solid #fff;
// transform: translate3d(1px, 0px, 0px) !important;
}
.chatItem {
width: 100%;
height: 154rpx;
padding: 30rpx 16rpx;
display: flex;
align-items: center;
width: 100%;
height: 154rpx;
padding: 30rpx 16rpx;
display: flex;
align-items: center;
&.isTop {
background-color: #f3f3f3;
}
&.isTop {
background-color: #f3f3f3;
}
}
.chatInfo {
flex: 1;
margin-left: 20rpx;
flex: 1;
margin-left: 20rpx;
}
.chatInfo_1 {
display: flex;
align-items: center;
justify-content: space-between;
display: flex;
align-items: center;
justify-content: space-between;
}
.chatInfo_2 {
display: flex;
align-items: center;
justify-content: space-between;
margin-top: 6rpx;
display: flex;
align-items: center;
justify-content: space-between;
margin-top: 6rpx;
}
.chatInfo_2_1 {
font-size: 28rpx;
color: rgba($color: #000000, $alpha: 0.4);
font-size: 28rpx;
color: rgba($color: #000000, $alpha: 0.4);
}
.tag {
.tag{
display: inline-flex;
align-items: center;
text-align: center;
// margin-left: 10rpx;
margin-left: 10rpx;
margin-top: 4rpx;
vertical-align: top;
height: 38rpx;
@ -226,22 +202,22 @@ const handleDelete = () => {
font-weight: bold;
}
.companyTag {
border: 1px solid #7a58de;
color: #7a58de;
border: 1px solid #7a58de;
color: #7a58de;
}
.depTag {
border: 1px solid #377ec6;
color: #377ec6;
border: 1px solid #377ec6;
color: #377ec6;
}
.projectTag {
border: 1px solid #c1681c;
color: #c1681c;
border: 1px solid #c1681c;
color: #c1681c;
}
.name_center {
.name_center{
flex: 1;
min-width: 0;
}
.name_text {
.name_text{
display: inline-block;
max-height: 88rpx; //
line-height: 44rpx;
@ -253,24 +229,23 @@ const handleDelete = () => {
word-break: break-all;
}
.time_right {
/* white-space: nowrap;
/* white-space: nowrap;
max-width: 146rpx; */
flex: 0 0 auto; /* 不伸缩,保持内容宽度 */
white-space: nowrap;
}
.textEllipsis {
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 1;
-webkit-box-orient: vertical;
word-break: break-all;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 1;
-webkit-box-orient: vertical;
}
.divider {
background-color: rgba(243, 243, 243, 1);
height: 1rpx;
margin: 0 18rpx;
background-color: rgba(243, 243, 243, 1);
height: 1rpx;
margin: 0 18rpx;
}
</style>

View File

@ -31,25 +31,12 @@
>
<template v-slot:left>
<div class="flex items-center ml-[48rpx]">
<!-- <image
<image
class="w-[72rpx] h-[72rpx]"
style="border-radius: 50%;"
:src="userStore.avatar"
mode="scaleToFill"
/> -->
<avatarModule
:mode="1"
:avatar="userStore.avatar"
:groupType="0"
:userName="userStore.nickname"
:customStyle="{ width: '72rpx', height: '72rpx' }"
:customTextStyle="{
fontSize: '32rpx',
fontWeight: 'bold',
color: '#fff',
lineHeight: '44rpx',
}"
></avatarModule>
/>
<div class="ml-[24rpx] text-[36rpx] font-bold">
{{ userStore.nickname }}
</div>
@ -66,11 +53,11 @@
/>
<template v-slot:label>
<div
class="w-full px-[14rpx]"
class="w-full h-[208rpx] pt-[22rpx] pb-[32rpx] pl-[14rpx] pr-[12rpx]"
>
<div
@click="creatGroupChat"
class="flex items-center pl-[22rpx] py-[32rpx]"
class="flex items-center pl-[22rpx] mb-[32rpx]"
>
<div class="mr-[26rpx] flex items-center">
<tm-image
@ -86,27 +73,9 @@
</div>
</div>
<div class="divider"></div>
<!-- <div
@click="toAddFriendPage"
class="flex items-center pl-[22rpx] py-[32rpx]"
>
<div class="mr-[26rpx] flex items-center">
<tm-image
:width="40"
:height="44"
:src="addFriend"
></tm-image>
</div>
<div
class="leading-[54rpx] text-[32rpx] text-[#FFFFFF] font-bold"
>
添加好友
</div>
</div>
<div class="divider"></div> -->
<div
@click="toAddressBookPage"
class="flex items-center pl-[22rpx] py-[32rpx]"
class="flex items-center pl-[22rpx] mt-[32rpx]"
>
<div class="mr-[26rpx] flex items-center">
<tm-image
@ -156,23 +125,17 @@ import { ref, watch, computed } from 'vue'
import { onShow, onLoad } from '@dcloudio/uni-app'
import { useChatList } from '@/store/chatList/index.js'
import { useAuth } from '@/store/auth'
import { ServeClearTalkUnreadNum } from '@/api/chat'
import { useTalkStore, useUserStore, useDialogueStore } from '@/store'
import chatItem from './components/chatItem.vue'
import addCircle from '@/static/image/chatList/addCircle.png'
import cahtPopover from '@/static/image/chatList/cahtPopover.png'
import zu3289 from '@/static/image/chatList/zu3289@2x.png'
import addFriend from '@/static/image/chatList/addFriend.png'
import ZPaging from '@/uni_modules/z-paging/components/z-paging/z-paging.vue'
import { handleSetWebviewStyle } from '@/utils/common'
const paging = ref()
const isEmptyViewShow = ref(false)
const talkStore = useTalkStore()
const userStore = useUserStore()
const dialogueStore = useDialogueStore()
const dialogueParams = reactive({
unReadNum: computed(() => dialogueStore.unreadNum),
})
const { userInfo } = useAuth()
const topItems = computed(() => talkStore.topItems)
@ -235,24 +198,11 @@ const toSearchPage = () => {
})
}
//
const toAddFriendPage = () => {
uni.navigateTo({
url: '/pages/addressBook/addFriend/index',
})
}
//
const toAddressBookPage = () => {
// -
uni.navigateTo({
url: '/pages/chooseByDeps/index?chooseMode=3',
})
// -
// uni.navigateTo({
// url: '/pages/addressBook/index',
// })
}
/* watch(
@ -264,7 +214,6 @@ const toAddressBookPage = () => {
); */
onShow(() => {
handleSetWebviewStyle(true)
//
talkStore
.loadTalkList()
@ -286,27 +235,21 @@ onLoad((options) => {
if (items?.value?.length > 0) {
items.value.forEach((openSession) => {
if (openSession.index_name === options?.openSessionIndexName) {
setTimeout(() => {
dialogueStore.setDialogue(openSession)
if (openSession.unread_num > 0) {
ServeClearTalkUnreadNum(
{
talk_type: openSession.talk_type,
receiver_id: openSession.receiver_id,
},
dialogueParams.unReadNum,
).then(() => {
talkStore.updateItem({
index_name: openSession.index_name,
unread_num: 0,
})
dialogueStore.clearUnreadNum()
dialogueStore.setDialogue(openSession)
if (openSession.unread_num > 0) {
ServeClearTalkUnreadNum({
talk_type: openSession.talk_type,
receiver_id: openSession.receiver_id,
}).then(() => {
talkStore.updateItem({
index_name: openSession.index_name,
unread_num: 0,
})
}
uni.navigateTo({
url: `/pages/dialog/index?sessionId=${openSession.id}`,
})
}, 500)
}
uni.navigateTo({
url: '/pages/dialog/index?sessionId=' + openSession.id,
})
}
})
}

View File

@ -96,30 +96,14 @@ import {
import HighlightText from './highLightText.vue'
import { useI18n } from 'vue-i18n'
import { beautifyTime } from '@/utils/datetime'
import { ChatMsgTypeMapping } from '@/constant/message'
const { t } = useI18n()
const props = defineProps({
searchItem: Object | Number,
searchResultKey: {
type: String,
default: '',
},
searchText: {
type: String,
default: '',
}, //
searchRecordDetail: {
type: Boolean,
default: false,
}, //
pointerIconSrc: {
type: String,
default: '',
}, //
conditionType: {
type: Number,
default: 0,
}, //
searchResultKey: String,
searchText: String, //
searchRecordDetail: Boolean, //
pointerIconSrc: String, //
conditionType: Number, //
})
// -
const keyMapping = {
@ -176,13 +160,6 @@ const keyMapping = {
name: 'receiver_name',
group_num: 'group_num',
},
search_by_member_condition: {
avatar: 'avatar',
name: 'nickname',
created_at: 'created_at',
msg_type: 'msg_type',
detailKey: 'chatMessageType',
},
}
//key
const getKeyValue = (keys) => {
@ -216,8 +193,10 @@ const imgText = computed(() => {
})
// -groupType
const groupTypeMapping = {
0: {},
1: {},
0: {
},
1: {
},
2: {
result_type: t('index.mine.department'),
result_type_color: '#377EC6',
@ -262,12 +241,6 @@ const resultDetail = computed(() => {
case 'extra':
result_detail = props.searchItem?.extra
break
case 'chatMessageType':
result_detail =
props.searchItem?.msg_type === 1
? props.searchItem?.extra?.content
: ChatMsgTypeMapping[props.searchItem?.msg_type]
break
default:
result_detail = ''
}
@ -302,7 +275,6 @@ const resultDetail = computed(() => {
padding: 2rpx 14rpx;
border: 2rpx solid #000;
border-radius: 6rpx;
flex-shrink: 0;
span {
line-height: 34rpx;
}
@ -327,7 +299,6 @@ const resultDetail = computed(() => {
.info-detail-searchRecordDetail {
span {
color: $theme-text;
word-break: break-all;
}
}
}

View File

@ -9,7 +9,7 @@
:default-page-size="props.searchResultPageSize"
:loading-more-default-as-loading="true"
:inside-more="true"
:empty-view-img="searchNoData"
:empty-view-img="'/src/static//image/search/search-no-data.png'"
:empty-view-text="$t('search.hint')"
:empty-view-img-style="{ width: '476rpx', height: '261rpx' }"
:empty-view-title-style="{
@ -19,7 +19,6 @@
'font-size': '28rpx',
'font-weight': 400,
}"
:refresher-enabled="false"
>
<template #top>
<div class="searchRoot">
@ -36,10 +35,7 @@
</span>
</div>
</template>
<div
class="search-record-detail"
v-if="props.searchRecordDetail && !props?.hideFirstRecord"
>
<div class="search-record-detail" v-if="props.searchRecordDetail">
<searchItem
@click="
clickSearchItem(
@ -113,7 +109,6 @@
</div>
</template>
<script setup>
import searchNoData from '@/static/image/search/search-no-data.png'
import customInput from '@/components/custom-input/custom-input.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'
@ -122,10 +117,6 @@ import { useI18n } from 'vue-i18n'
import { ref, reactive, defineEmits, defineProps, onMounted } from 'vue'
import pointerIconSrc from '@/static/image/search/search-item-pointer.png'
import lodash from 'lodash'
import { useUserStore } from '@/store'
const userStore = useUserStore()
const zPaging = ref()
useZPaging(zPaging)
@ -140,45 +131,17 @@ const state = reactive({
searchResultList: [], //
searchResult: null, //
pageNum: 1, //
uid: computed(() => userStore.uid), //id
})
const props = defineProps({
searchResultPageSize: {
type: Number,
default: 0,
}, //
listLimit: {
type: Boolean,
default: false,
}, //
apiParams: {
type: String,
default: '',
}, //
searchResultPageSize: Number, //
listLimit: Boolean, //
apiParams: String, //
apiRequest: Function, //
searchText: {
type: String,
default: '',
}, //
isPagination: {
type: Boolean,
default: false,
}, //
searchRecordDetail: {
type: Boolean,
default: false,
}, //
first_talk_record_infos: {
type: Object,
default() {
return {}
},
}, //
hideFirstRecord: {
type: Boolean,
default: false,
}, ///
searchText: String, //
isPagination: Boolean, //
searchRecordDetail: Boolean, //
first_talk_record_infos: Object, //
})
const { t } = useI18n()
@ -191,14 +154,13 @@ onMounted(() => {
//
const inputSearchText = (e) => {
// console.log(e)
if (e.trim() != state.searchText.trim()) {
state.pageNum = 1
state.searchResult = null //
emits('lastIdChange', 0, 0, 0)
}
state.searchText = e.trim()
if (!e.trim()) {
state.searchResult = null //
emits('lastIdChange', 0, 0, 0)
}
zPaging.value?.reload()
@ -235,35 +197,15 @@ const queryAllSearch = (pageNum, searchResultPageSize) => {
})
}
if ((data.talk_record_infos || []).length > 0) {
let receiverInfo = lodash.cloneDeep(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[0],
)
;(data.talk_record_infos || []).forEach((item) => {
item.group_type = 0
})
}
let tempGeneral_infos = Array.isArray(data.general_infos)
? [...data.general_infos]
: data.general_infos
@ -272,8 +214,6 @@ const queryAllSearch = (pageNum, searchResultPageSize) => {
data.group_member_infos || [],
)
data.general_infos = tempGeneral_infos
//
let isEmpty = true
let dataKeys = Object.keys(data)
let paginationKey = ''
@ -283,147 +223,58 @@ const queryAllSearch = (pageNum, searchResultPageSize) => {
isEmpty = false
}
})
if (isEmpty) {
if (pageNum === 1) {
//
state.searchResult = null
if (pageNum == 1) {
zPaging.value?.complete([])
} else {
//
zPaging.value?.complete(
state.searchResult ? [state.searchResult] : [],
)
data = state.searchResult
zPaging.value?.complete([data])
}
} 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
if (
paginationKey &&
Array.isArray(
(state?.searchResult && state?.searchResult[paginationKey]) || [],
) &&
((state?.searchResult && state?.searchResult[paginationKey]) || [])
.length > 0
) {
data[paginationKey] = state.searchResult[paginationKey].concat(
data[paginationKey],
)
}
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
}
let noMoreSearchResultRecord = true
if (
Object.keys(data).includes('talk_record_infos') &&
state.searchResult['talk_record_infos']?.length > 0
) {
//
if (state.searchResult['talk_record_infos']?.length < total) {
noMoreSearchResultRecord = false
}
}
zPaging.value?.completeByNoMore([data], noMoreSearchResultRecord)
} else {
let noMoreSearchResultUser = true
let noMoreSearchResultGroup = true
let noMoreSearchResultGeneral = true
if (
Object.keys(data).includes('user_infos') &&
state.searchResult['user_infos']?.length > 0
) {
//
if (state.searchResult['user_infos']?.length < total) {
noMoreSearchResultUser = false
}
}
if (
Object.keys(data).includes(
'group_member_infos' || 'group_infos',
) &&
state.searchResult['combinedGroup']?.length > 0
) {
//
if (state.searchResult['combinedGroup']?.length < total) {
noMoreSearchResultGroup = false
}
}
if (
Object.keys(data).includes('general_infos') &&
state.searchResult['general_infos']?.length > 0
) {
//
if (state.searchResult['general_infos']?.length < total) {
noMoreSearchResultGeneral = false
}
}
// zPaging.value?.completeByTotal([data], total)
zPaging.value?.completeByNoMore(
[data],
noMoreSearchResultUser &&
noMoreSearchResultGroup &&
noMoreSearchResultGeneral,
)
total = data.group_record_count
}
zPaging.value?.completeByTotal([data], total)
} else {
state.searchResult = data
zPaging.value?.complete([data])
}
}
state.searchResult = data
} else {
if (pageNum === 1) {
//
state.searchResult = null
zPaging.value?.complete([])
} else {
//
zPaging.value?.complete(state.searchResult ? [state.searchResult] : [])
}
zPaging.value?.complete([])
}
})
resp.catch(() => {
if (pageNum === 1) {
//
state.searchResult = null
zPaging.value?.complete([])
} else {
//
zPaging.value?.complete(state.searchResult ? [state.searchResult] : [])
}
zPaging.value?.complete([])
})
}
//
const cancelSearch = () => {
const pages = getCurrentPages()
if (pages.length > 1) {
uni.navigateBack({
delta: 1,
})
} else {
uni.reLaunch({
url: '/pages/index/index',
})
}
uni.navigateBack({
delta: 1,
})
}
//key
@ -491,12 +342,6 @@ const getHasMoreResult = (searchResultKey) => {
}
break
case 'general_infos':
if (
state.searchResult['record_count'] &&
state.searchResult['record_count'] >= 3
) {
has_more_result = t('has_more') + t('chat.type.record')
}
break
default:
}
@ -510,41 +355,12 @@ const toMoreResultPage = (searchResultKey) => {
//
const clickSearchItem = (searchResultKey, searchItem) => {
console.log(searchResultKey, searchItem)
let talk_type = searchItem.talk_type
let receiver_id = searchItem.receiver_id
if (searchResultKey === 'user_infos') {
talk_type = 1
receiver_id = searchItem.id
} else if (searchResultKey === 'combinedGroup') {
talk_type = searchItem.type || 2
receiver_id = searchItem.group_id || searchItem.id
} else if (searchResultKey === 'general_infos') {
if (searchItem.talk_type === 1) {
if (searchItem.user_id === state.uid) {
//
}
if (searchItem.receiver_id === state.uid) {
//
let temp_id = searchItem.receiver_id
let temp_name = searchItem.receiver_name
let temp_avatar = searchItem.receiver_avatar
searchItem.receiver_id = searchItem.user_id
searchItem.receiver_name = searchItem.user_name
searchItem.receiver_avatar = searchItem.user_avatar
searchItem.user_id = temp_id
searchItem.user_name = temp_name
searchItem.user_avatar = temp_avatar
}
}
}
emits(
'clickSearchItem',
state.searchText,
searchResultKey,
talk_type,
receiver_id,
encodeURIComponent(JSON.stringify(searchItem)),
searchItem.talk_type,
searchItem.receiver_id,
)
}
</script>

View File

@ -13,19 +13,7 @@
</template>
<script setup>
import searchList from './components/searchList.vue'
import { ServeSeachQueryAll, ServeGetSessionId } from '@/api/search/index'
import { onMounted } from 'vue'
import { handleSetWebviewStyle } from '@/utils/common'
import { useDialogueStore, useTalkStore } from '@/store'
import { ServeCreateTalkList } from '@/api/chat/index.js'
import { formatTalkItem } from '@/utils/talk'
const dialogueStore = useDialogueStore()
onMounted(() => {
handleSetWebviewStyle()
})
import { ServeSeachQueryAll } from '@/api/search/index'
//
const toMoreResultPage = (searchResultKey, searchText) => {
@ -39,49 +27,14 @@ const toMoreResultPage = (searchResultKey, searchText) => {
}
//
const clickSearchItem = async (
const clickSearchItem = (
searchText,
searchResultKey,
talk_type,
receiver_id,
res,
) => {
console.log(searchResultKey)
const result = JSON.parse(decodeURIComponent(res))
console.log(result)
console.log(talk_type, receiver_id)
const sessionId = await getSessionId(talk_type, receiver_id)
if (searchResultKey === 'user_infos') {
if (useTalkStore().findTalkIndex(`${talk_type}_${receiver_id}`) === -1) {
ServeCreateTalkList({
talk_type,
receiver_id,
erp_user_id: result.erp_user_id,
}).then(async ({ code, data }) => {
if (code == 200) {
let item = formatTalkItem(data)
useTalkStore().addItem(item)
}
})
}
dialogueStore.setDialogue({
name: result.nickname,
talk_type: 1,
receiver_id: receiver_id,
})
uni.navigateTo({
url: '/pages/dialog/index?sessionId=' + sessionId,
})
} else if (searchResultKey === 'combinedGroup') {
dialogueStore.setDialogue({
name: result.name || result.group_name,
talk_type: result.type || 2,
receiver_id: result.group_id || result.id,
})
uni.navigateTo({
url: '/pages/dialog/index?sessionId=' + sessionId,
})
} else if (searchResultKey === 'general_infos') {
if (searchResultKey === 'general_infos') {
uni.navigateTo({
url:
'/pages/search/moreResult/moreResultDetail?searchText=' +
@ -93,25 +46,5 @@ const clickSearchItem = async (
})
}
}
//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(() => {})
})
}
</script>
<style scoped lang="scss"></style>

View File

@ -21,21 +21,14 @@ import {
ServeQueryUser,
ServeQueryGroup,
ServeTalkRecord,
ServeGetSessionId,
} from '@/api/search/index'
import { reactive } from 'vue'
import { useDialogueStore, useTalkStore } from '@/store'
import { ServeCreateTalkList } from '@/api/chat/index.js'
import { formatTalkItem } from '@/utils/talk'
const dialogueStore = useDialogueStore()
const state = reactive({
apiRequest: Function,
apiParams: '',
searchText: '',
searchResultKey: '',
apiParams: String,
searchText: String,
searchResultKey: String,
})
onLoad((options) => {
@ -64,8 +57,6 @@ onLoad((options) => {
receiver_id: 0, //
last_group_id: 0, //id
last_member_id: 0, //id
last_receiver_user_name: '', //
last_receiver_group_name: '', //
}),
)
state.apiRequest = ServeTalkRecord
@ -77,19 +68,11 @@ onLoad((options) => {
})
//id
const lastIdChange = (
last_id,
last_group_id,
last_member_id,
last_receiver_user_name,
last_receiver_group_name,
) => {
const lastIdChange = (last_id, last_group_id, last_member_id) => {
let idChanges = {
last_id,
last_group_id,
last_member_id,
last_receiver_user_name,
last_receiver_group_name,
}
state.apiParams = encodeURIComponent(
JSON.stringify(
@ -103,79 +86,12 @@ const lastIdChange = (
}
//
const clickSearchItem = async (
searchText,
searchResultKey,
talk_type,
receiver_id,
res,
) => {
console.log(state.searchResultKey)
const result = JSON.parse(decodeURIComponent(res))
console.log(result)
console.log(talk_type, receiver_id)
const sessionId = await getSessionId(talk_type, receiver_id)
if (state.searchResultKey === 'user_infos') {
if (useTalkStore().findTalkIndex(`${talk_type}_${receiver_id}`) === -1) {
ServeCreateTalkList({
talk_type,
receiver_id,
erp_user_id: result.erp_user_id,
}).then(async ({ code, data }) => {
if (code == 200) {
let item = formatTalkItem(data)
useTalkStore().addItem(item)
}
})
}
dialogueStore.setDialogue({
name: result.nickname,
talk_type: 1,
receiver_id: receiver_id,
})
const clickSearchItem = (searchText) => {
if (state.searchResultKey === 'general_infos') {
uni.navigateTo({
url: '/pages/dialog/index?sessionId=' + sessionId,
})
} else if (state.searchResultKey === 'combinedGroup') {
dialogueStore.setDialogue({
name: result.name || result.group_name,
talk_type: result.type || 2,
receiver_id: result.group_id || result.id,
})
uni.navigateTo({
url: '/pages/dialog/index?sessionId=' + sessionId,
})
} else if (state.searchResultKey === 'general_infos') {
uni.navigateTo({
url:
'/pages/search/moreResult/moreResultDetail?searchText=' +
searchText +
'&talk_type=' +
talk_type +
'&receiver_id=' +
receiver_id,
url: '/pages/search/moreResult/moreResultDetail?searchText=' + searchText,
})
}
}
//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(() => {})
})
}
</script>
<style scoped lang="scss"></style>

View File

@ -7,11 +7,9 @@
:apiRequest="ServeTalkRecord"
:apiParams="state.apiParams"
:searchText="state.searchText"
:hideFirstRecord="state.hideFirstRecord"
:isPagination="true"
:searchRecordDetail="true"
@lastIdChange="lastIdChange"
@clickSearchItem="clickSearchItem"
></searchList>
</div>
</div>
@ -21,17 +19,10 @@ import searchList from '../components/searchList.vue'
import { onLoad } from '@dcloudio/uni-app'
import { ServeTalkRecord } from '@/api/search/index'
import { reactive } from 'vue'
import { useDialogueStore, useUserStore } from '@/store'
import lodash from 'lodash'
const dialogueStore = useDialogueStore()
const userStore = useUserStore()
const state = reactive({
apiParams: '',
searchText: '',
uid: computed(() => userStore.uid), //id
hideFirstRecord: false, ///
apiParams: String,
searchText: String,
})
onLoad((options) => {
@ -57,10 +48,6 @@ onLoad((options) => {
}
console.log(JSON.parse(decodeURIComponent(state.apiParams)))
if (options.hideFirstRecord) {
state.hideFirstRecord = options.hideFirstRecord === '1' ? true : false
}
})
//id
@ -80,51 +67,5 @@ const lastIdChange = (last_id, last_group_id, last_member_id) => {
),
)
}
//
const clickSearchItem = (
searchText,
searchResultKey,
talk_type,
receiver_id,
res,
) => {
console.log(searchResultKey)
let result = JSON.parse(decodeURIComponent(res))
console.log(result)
let receiverInfo = lodash.cloneDeep(result)
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
}
}
dialogueStore.setDialogue({
name: receiverInfo.receiver_name,
talk_type: talk_type,
receiver_id: receiverInfo.receiver_id,
})
if (searchResultKey === 'talk_record_infos_receiver') {
uni.navigateTo({
url: '/pages/dialog/index',
})
} else {
uni.navigateTo({
url: '/pages/dialog/index?msgInfo=' + res + '&keepDialogInfo=1',
})
}
}
</script>
<style scoped lang="scss"></style>

View File

@ -9,7 +9,6 @@
: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>
@ -42,7 +41,7 @@
<span class="text-[28rpx] font-regular">
{{ state.selectedMonth }}
</span>
<img src="@/static/image/search/down-pointer.png" />
<img src="/src/static/image/search/down-pointer.png" />
</div>
</tm-time-picker>
<tm-calendar-view
@ -65,8 +64,7 @@
v-if="
state.condition === 'imgAndVideo' ||
state.condition === 'file' ||
state.condition === 'link' ||
state.condition === 'member'
state.condition === 'link'
"
:style="{
padding: state.condition === 'imgAndVideo' ? '0 27rpx' : '',
@ -114,27 +112,11 @@
v-for="(item, index) in conditionItem.monthResultList"
:key="index"
:style="{
border:
state.condition === 'imgAndVideo' ||
state.condition === 'member'
? '0'
: '',
border: state.condition === 'imgAndVideo' ? '0' : '',
padding:
state.condition === 'imgAndVideo' ? '0 0 10rpx' : '',
}"
>
<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'"
@ -160,27 +142,17 @@
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>
<tm-image
preview
:src="
item?.msg_type === 3
? item?.extra?.url
: item?.msg_type === 5
? item?.extra?.cover
: ''
"
model="aspectFill"
/>
</div>
</div>
<div
@ -201,7 +173,6 @@
</div>
<div
class="condition-each-result-attachments"
@click="previewPDF(item)"
v-if="
state.condition === 'file' || state.condition === 'link'
"
@ -212,7 +183,7 @@
v-if="state.condition === 'file'"
/>
<img
src="@/static/image/search/result-link-icon.png"
src="/src/static/image/search/result-link-icon.png"
v-if="state.condition === 'link'"
/>
</div>
@ -267,20 +238,6 @@
</div>
</div>
</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>
@ -290,16 +247,15 @@ 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 playCircle from '@/static/image/chatList/playCircle@2x.png'
import { fileFormatSize, fileSuffix } from '@/utils/strings'
import searchItem from '../components/searchItem.vue'
import customInput from '@/components/custom-input/custom-input.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 { parseTime } from '@/utils/datetime'
import { onMounted, reactive, computed, ref, nextTick } from 'vue'
import { onMounted, reactive, computed, ref } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
import { ServeTalkDate, ServeGetSessionId } from '@/api/search/index'
import { ServeTalkDate } from '@/api/search/index'
import { ServeFindTalkRecords } from '@/api/chat/index'
import { useDialogueStore } from '@/store'
import { useI18n } from 'vue-i18n'
@ -317,6 +273,7 @@ const dialogueParams = reactive({
let nowDay = new Date().setHours(0, 0, 0, 0)
const state = reactive({
receiver_id: '', //id
pageTitle: '', //
dateStyle: [], //
nowDate: new Date(nowDay), //
@ -332,53 +289,16 @@ const state = reactive({
searchResultList: [], //
cursor: 0, //
msg_type: 0, //
group_member_id: 0, //id
flatList: [], //
})
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)
}
onLoad((options) => {
console.log(options)
if (options.receiver_id) {
state.receiver_id = Number(options.receiver_id)
}
if (options.condition) {
state.condition = options.condition
if (options.condition === 'member') {
state.showPageTitle = true
state.pageTitle = t('search.condition.member')
state.group_member_id = options.groupMemberId
queryAllSearch()
} else if (options.condition === 'date') {
if (options.condition === 'date') {
state.showPageTitle = true
state.pageTitle = t('search.condition.date')
ServeQueryTalkDate(parseTime(state.nowDate, '{y}{m}'))
@ -431,8 +351,8 @@ onMounted(() => {
const ServeQueryTalkDate = (month) => {
let params = {
month: month,
talk_type: dialogueParams.talk_type, //12
receiver_id: dialogueParams.receiver_id, //id
talk_type: 2, //12
receiver_id: state.receiver_id, //id
}
const resp = ServeTalkDate(params)
console.log(resp)
@ -467,7 +387,7 @@ const ServeQueryTalkDate = (month) => {
}
//
const selectDate = async (e) => {
const selectDate = (e) => {
if (e == parseTime(state.nowDate, '{y}/{m}/{d}')) {
console.log('==今日')
state.dateStyle = [
@ -493,38 +413,6 @@ const selectDate = async (e) => {
},
]
}
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(() => {})
})
}
//
@ -555,22 +443,13 @@ const getDArray = (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',
})
}
uni.navigateBack({
delta: 1,
})
}
//
@ -585,8 +464,7 @@ const queryAllSearch = () => {
direction: 'up', //downup
start_time: '',
end_time: '',
group_member_user_id: state.group_member_id, //id
file_name: state.msg_type === 6 ? state.searchText : '',
group_member_user_id: 0, //id
}
console.log(params)
const resp = ServeFindTalkRecords(params)
@ -594,20 +472,17 @@ const queryAllSearch = () => {
resp.then(({ code, data }) => {
console.log(data)
if (code == 200) {
// cursor0searchResultList
let dateList = state.cursor === 0 ? [] : state.searchResultList
let dateList = 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)
}
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 =
@ -640,35 +515,15 @@ const queryAllSearch = () => {
} 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)
}
console.log(dateList)
state.cursor = data?.cursor
zPaging.value?.completeByNoMore(dateList, noMore)
} 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([])
})
}
@ -691,59 +546,6 @@ const fileTypeAvatar = (fileType) => {
}
return file_type_avatar
}
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 {
@ -850,7 +652,6 @@ body::v-deep .round-3 {
span {
line-height: 40rpx;
color: $theme-text;
word-break: break-all;
}
}
.attachment-sub-info {
@ -897,26 +698,6 @@ body::v-deep .round-3 {
width: 164rpx !important;
height: 164rpx !important;
}
.video-preview {
position: relative;
width: 164rpx;
height: 164rpx;
cursor: pointer;
.play-icon {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
display: flex;
align-items: center;
justify-content: center;
img {
width: 80rpx !important;
height: 80rpx !important;
}
}
}
}
}
}
@ -925,23 +706,4 @@ body::v-deep .round-3 {
}
}
}
.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

@ -23,7 +23,7 @@ class WsSocket {
lockReconnect: false,
setTimeout: null, // 计时器对象
time: 3000, // 重连间隔时间
number: 20 // 重连次数
number: 10000000 // 重连次数
}
}

View File

@ -1,21 +1,5 @@
import Request from '@/service/request/index.js'
import {useAuth} from "@/store/auth";
import { createApp } from 'vue';
import XMessage from '@/components/x-message/index.vue';
// 创建消息提示实例
const messageInstance = (() => {
const messageNode = document.createElement('div');
document.body.appendChild(messageNode);
const app = createApp(XMessage);
const instance = app.mount(messageNode);
return {
warning: (msg) => instance.showMessage({ type: 'warning', message: msg }),
error: (msg) => instance.showMessage({ type: 'error', message: msg }),
success: (msg) => instance.showMessage({ type: 'success', message: msg }),
info: (msg) => instance.showMessage({ type: 'info', message: msg })
};
})();
const { token ,refreshToken,userInfo}=useAuth()
let isRefreshing = false;
let refreshSubscribers = [];
@ -40,25 +24,19 @@ const request = new Request({
},
responseInterceptors: async (res) => {
if(res.data.status===1){
// message.warning(res.data.msg)
messageInstance.warning(res.data.msg)
message.warning(res.data.msg)
}
if (res.data.status === 401) {
return
// return getRefreshToken(res);
return getRefreshToken(res);
// uni.navigateTo({
// url:'/pages/login/index'
// })
}
if ([200, 201, 204].includes(res.status)) {
if(res.data.code !== 200 && res.data.code!== 0) {
messageInstance.error(res.data.message || res.data.msg || 'An error occurred.');
}
return res.config.responseType === 'blob' ? res : res;
} else {
/* message.error(res.data.msg || 'An error occurred.');*/
messageInstance.error(res.data.message || res.data.msg || 'An error occurred.');
return Promise.reject(new Error(res.data.message || res.data.msg || 'An error occurred.'));
return Promise.reject(new Error(res.data.msg || 'An error occurred.'));
}
}
}
@ -81,14 +59,13 @@ async function getRefreshToken(response) {
})
return request.request(response.config);
} else {
messageInstance.error(res.message || res.msg);
message.error(res.message || res.msg);
throw new Error(res.message || res.msg);
}
} catch (error) {
// uni.navigateTo({
// url:'/pages/login/index'
// })
uni.navigateTo({
url:'/pages/login/index'
})
throw error
} finally {
isRefreshing = false;
@ -96,9 +73,9 @@ async function getRefreshToken(response) {
refreshSubscribers = [];
}
} else {
// uni.navigateTo({
// url:'/pages/login/index'
// })
uni.navigateTo({
url:'/pages/login/index'
})
throw new Error('No refresh token available.');
}
} else {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 654 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 752 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 208 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -5,7 +5,7 @@ import { userInfoApi } from "@/api/user";
import {ref} from 'vue'
export const useAuth = createGlobalState(() => {
const token = useStorage('token', '', uniStorage)
// const token = ref("2046c3941ed4959f6d988d3d4a0fe40d4b52f33f3f5fc1001406064554641d9406bb13cacb92939b0ca223c17e2c2f2fe70212ef017dbae8965d5cf86bad48ce4316e605ca187bd9ffd4aa6b56865be4ad4e422701d330b52d60cfe649cd48cf3a21a2a6e9a9cabafff364ee9c311ec634b0afc09db0d3215bedce561e9d50e5a8da6092062e2ebe35f747d77d72a68ad492a4ab218c07887c9cd4867f2c2d28e4ae1fd671144cc20ef0632f9ce067289004d67f6adf41b20d6ef5cdbfb74aadc2d2736ececf07254f1a76552bde4f1161a0fca7bfe32a29685ce1e76366116b81ae2195b3713dbb04285e5ddfd36184fe671c5524d20b4fe74a555db755f8d939b0bc46fb0cb998323d54c9925729d7ca835b7925999a677faa0cbe1cbc67b5203d85317653883aec81d3e71d865b326376bea726cc66d9f7f5a160d43f671c")
// const token = ref('79b5c732d96d2b27a48a99dfd4a5566c43aaa5796242e854ebe3ffc198d6876b9628e7b764d9af65ab5dbb2d517ced88170491b74b048c0ba827c0d3741462cb89dc59ed46653a449af837a8262941caaef1334d640773710f8cd96473bacfb190cba595a5d6a9c87d70f0999a3ebb41147213b31b4bdccffca66a56acf3baab5af0154f0dce360079f37709f78e13711036899344bddb0fb4cf0f2890287cb62c3fcbe33368caa5e213624577be8b8420ab75b1f50775ee16142a4321c5d56995f37354a66a969da98d95ba6e65d142ed097e04b411c1ebad2f62866d0ec7e1838420530a9941dbbcd00490199f8b897937e719454eda6de1352a14497a54063c2ae13c2b1418f2689268102faffee874777ce1312eb7d9399eaa8cf58674aa86c9b85ad9300293c0a3369ed429536bbea4fcb092b78466ea53a44a2b2b1c1a')
const refreshToken = useStorage('refreshToken', '', uniStorage)
const userInfo = useStorage('userInfo', {}, uniStorage)
const leaderList = useStorage('leaderList', [], uniStorage)

View File

@ -10,14 +10,10 @@ import {
departmentV2AllPosition,
groupCreateDept,
departmentV2TreeAll,
departmentV2TreeAll2,
userHasPermission,
userV2List,
userV2List2,
v2TreePositionByDepartment,
} from '@/api/deps/index.js'
import { useAuth } from '@/store/auth'
const { userInfo } = useAuth()
export const useGroupTypeStore = createGlobalState(() => {
const groupName = ref('')
const groupActiveIndex = ref(-1) // 当前激活的分组索引
@ -38,40 +34,6 @@ export const useGroupTypeStore = createGlobalState(() => {
depTreeMyList.value = res.data.nodes
}
}
// userInfo?.value?.ID
const getDepsTreeMy2 = async (chooseMode) => {
let params = { nowUserId: 0 }
if (chooseMode === 1) {
const isHasRes = await userHasPermission({
erpUserId: userInfo?.value?.ID,
ruleUrl: [
'auth_chat_app_create_all_dept',
'auth_chat_app_create_limit_dept',
],
})
if (isHasRes.code === 200) {
if (isHasRes.data.auth_chat_app_create_all_dept) {
params = {
nowUserId: 0,
}
} else {
if (isHasRes.data.auth_chat_app_create_limit_dept) {
params = {
nowUserId: userInfo?.value?.ID,
}
} else {
params = {
nowUserId: 0,
}
}
}
}
}
const res = await departmentV2TreeAll2(params)
if (res.status === 0) {
depTreeMyList.value = res.data.nodes
}
}
//获取指定部门下的所有岗位
const getPositionByDepartment = async (params) => {
@ -89,7 +51,7 @@ export const useGroupTypeStore = createGlobalState(() => {
}
const getDepMembers = async (param) => {
const res = await userV2List2(param)
const res = await userV2List(param)
return res
}
@ -162,7 +124,6 @@ export const useGroupTypeStore = createGlobalState(() => {
postTreeList,
departmentAllPositions,
getDepsTreeMy,
getDepsTreeMy2,
getPositionByDepartment,
getPositionsTree,
crumbs,

View File

@ -15,10 +15,6 @@ import { useAuth } from '../auth/index'
// let keyboardTimeout = null
export const useDialogueStore = defineStore('dialogue', {
// 添加持久化配置
persist: {
paths: ['talk.talk_type', 'talk.receiver_id']
},
state: () => {
return {
// 对话索引(聊天对话的唯一索引)
@ -52,15 +48,6 @@ export const useDialogueStore = defineStore('dialogue', {
// 是否显示会话列表
isShowSessionList: true,
//是否已被解散
isDismiss: false,
//是否退群/移出群
isQuit: false,
//未读消息数量
unreadNum:0,
// 群成员列表
members: [],
@ -97,21 +84,6 @@ export const useDialogueStore = defineStore('dialogue', {
this.online = status
},
// 更新未读消息数量-清空未读
clearUnreadNum() {
this.unreadNum = 0
},
// 更新群解散状态
updateDismiss() {
this.isDismiss = true
},
// 更新群成员退出状态
updateQuit() {
this.isQuit = true
},
// 更新对话信息
setDialogue(data = {}) {
this.online = data.is_online == 1
@ -126,13 +98,8 @@ export const useDialogueStore = defineStore('dialogue', {
this.unreadBubble = 0
this.isShowEditor = data?.is_robot === 0
this.isDismiss = data?.is_dismiss === 1 ? true : false
this.isQuit = data?.is_quit === 1 ? true : false
this.unreadNum = data?.unread_num || 0
this.members = []
if (data.talk_type == 2 && !this.isDismiss && !this.isQuit) {
if (data.talk_type == 2) {
this.updateGroupMembers()
}
},
@ -242,6 +209,8 @@ export const useDialogueStore = defineStore('dialogue', {
if (res.code == 200) {
this.batchDelDialogueRecord(msgIds)
batchDelDialogueRecord(msgIds)
} else {
message.warning(res.message)
}
})
},
@ -265,6 +234,8 @@ export const useDialogueStore = defineStore('dialogue', {
ServeRevokeRecords({ msg_id }).then((res) => {
if (res.code == 200) {
this.updateDialogueRecord({ msg_id, is_revoke: 1 })
} else {
message.warning(res.message)
}
})
},

View File

@ -5,111 +5,9 @@ import lodash from 'lodash'
import { ref } from 'vue'
import { createGlobalState, useStorage } from '@vueuse/core'
import { uniStorage } from '@/utils/uniStorage.js'
import { handleFindWebview } from '@/utils/common'
export const useDialogueListStore = createGlobalState(() => {
const testDatabase = async () => {
// 初始化数据库
let chatDatabase = {
eventType: 'openDatabase',
eventParams: {
name: 'chat',
path: '_doc/chat.db',
},
}
let chatDBexecuteSql = {
eventType: 'executeSql',
eventParams: {
name: 'chat',
sql: `CREATE TABLE IF NOT EXISTS talk_records (
id INTEGER PRIMARY KEY AUTOINCREMENT,
msg_id TEXT NOT NULL,
sequence INTEGER NOT NULL,
talk_type INTEGER NOT NULL DEFAULT 1,
msg_type INTEGER NOT NULL DEFAULT 1,
user_id INTEGER NOT NULL DEFAULT 0,
receiver_id INTEGER NOT NULL DEFAULT 0,
is_revoke INTEGER NOT NULL DEFAULT 0,
is_mark INTEGER NOT NULL DEFAULT 0,
quote_id TEXT NOT NULL,
extra TEXT NOT NULL,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
biz_date TEXT
)`,
},
}
const content = {
content: '我试试传送文件和图片是不是一个接口',
name: '测试excel1.xlsx',
path:
'https://cdn-test.szjixun.cn/fonchain-chat/chat/file/multipart/20250307/727a2371-ffc4-46da-b953-a7d449ff82ff-测试excel1.xlsx',
size: 9909,
drive: 3,
}
const extra = JSON.stringify(content)
let chatDBexecuteSql2 = {
eventType: 'executeSql',
eventParams: {
name: 'chat',
sql:
'INSERT INTO talk_records (msg_id, sequence, talk_type, msg_type, user_id, receiver_id, is_revoke, is_mark, quote_id, extra, created_at, updated_at, biz_date) VALUES ("' +
'77b715fb30f54f739a255a915ef72445' +
'", 166, 2, 1, 1774, 888890, 0, 0, "' +
'' +
'", "' +
extra +
'", "' +
'2025-03-06T15:57:07.000Z' +
'", "' +
'2025-03-06T15:57:07.000Z' +
'", "' +
'20250306' +
'")',
},
}
let chatDBSelectSql = {
eventType: 'selectSql',
eventParams: {
name: 'chat',
sql: `SELECT * FROM talk_records ORDER BY sequence DESC LIMIT 20`,
},
}
let chatDBIsOpenDatabase = {
eventType: 'isOpenDatabase',
eventParams: {
name: 'chat',
path: '_doc/chat.db',
},
}
// handleFindWebview(
// `operateSQLite('${encodeURIComponent(JSON.stringify(chatDatabase))}')`,
// )
// handleFindWebview(
// `operateSQLite('${encodeURIComponent(
// JSON.stringify(chatDBexecuteSql),
// )}')`,
// )
// handleFindWebview(
// `operateSQLite('${encodeURIComponent(
// JSON.stringify(chatDBexecuteSql2),
// )}')`,
// )
// handleFindWebview(
// `operateSQLite('${encodeURIComponent(JSON.stringify(chatDBSelectSql))}')`,
// )
// handleFindWebview(
// `operateSQLite('${encodeURIComponent(
// JSON.stringify(chatDBIsOpenDatabase),
// )}')`,
// )
}
// testDatabase()
const dialogueList = useStorage('dialogueList', [], uniStorage)
// const dialogueList = ref([])
const zpagingRef = ref()
const virtualList = ref([])
@ -120,49 +18,18 @@ export const useDialogueListStore = createGlobalState(() => {
const addDialogueRecord = (newRecords, type = 'add') => {
console.log(newRecords)
const dialogue = useDialogueStore()
const dialogue = lodash.cloneDeep(useDialogueStore())
if (!dialogue || typeof dialogue !== 'object') return
// 检查是否已存在相同 index_name 的对话
const existingIndex = dialogueList.value.findIndex(
(item) => item.index_name === dialogue.index_name,
)
if (existingIndex === -1) {
// 如果不存在,创建新对话,只保存需要的属性
const newDialogue = {
index_name: dialogue.index_name,
talk: {
username: dialogue.talk.username,
talk_type: dialogue.talk.talk_type,
receiver_id: dialogue.talk.receiver_id,
},
online: dialogue.online,
records: dialogue.records || [],
unreadBubble: dialogue.unreadBubble,
isOpenMultiSelect: dialogue.isOpenMultiSelect,
isShowEditor: dialogue.isShowEditor,
isShowSessionList: dialogue.isShowSessionList,
isDismiss: dialogue.isDismiss,
isQuit: dialogue.isQuit,
unreadNum: dialogue.unreadNum,
members: dialogue.members.map((member) => ({
id: member.id,
nickname: member.nickname,
avatar: member.avatar,
gender: member.gender,
leader: member.leader,
remark: member.remark,
online: member.online,
value: member.value,
key: member.key,
erp_user_id: member.erp_user_id,
is_mute: member.is_mute,
is_mine: member.is_mine,
})),
forwardType: dialogue.forwardType,
}
dialogueList.value.push(newDialogue)
// 如果不存在,直接添加
dialogueList.value.push(dialogue)
} else {
// 如果对话存在,处理 records 数组
const { records = [] } = dialogue
newRecords.forEach((newRecord) => {
const recordIndex = dialogueList.value[existingIndex].records.findIndex(
(record) => record.msg_id === newRecord.msg_id,
@ -177,11 +44,18 @@ export const useDialogueListStore = createGlobalState(() => {
}
}
})
// 更新除 records 和 index_name 外的其他属性
const { index_name, records: _, ...updateProps } = dialogue
dialogueList.value[existingIndex] = {
...dialogueList.value[existingIndex],
...updateProps,
}
}
}
const updateDialogueRecord = (record) => {
const dialogue = useDialogueStore()
const dialogue = lodash.cloneDeep(useDialogueStore())
const item = getDialogueList(dialogue.index_name)
const recordIndex = item.records.findIndex(
(item) => item.msg_id === record.msg_id,
@ -204,7 +78,7 @@ export const useDialogueListStore = createGlobalState(() => {
}
const deleteDialogueRecord = (record) => {
const dialogue = useDialogueStore()
const dialogue = lodash.cloneDeep(useDialogueStore())
const item = getDialogueList(dialogue.index_name)
const recordIndex = item.records.findIndex(
(item) => item.msg_id === record.msg_id,
@ -230,29 +104,14 @@ export const useDialogueListStore = createGlobalState(() => {
}
const addChatRecord = (indexName, item) => {
const dialogue = useDialogueStore()
const dialogue = lodash.cloneDeep(useDialogueStore())
if (dialogue?.index_name === indexName) {
if (item?.file_num) {
const index = virtualList.value.findIndex(
(v) => v?.file_num === item?.file_num,
)
if (index > -1) {
// 保持响应性的同时替换整个对象
virtualList.value.splice(index, 1, {
...virtualList.value[index], // 保留原有不需要修改的字段
...item, // 覆盖需要更新的字段
})
} else {
zpagingRef.value?.addChatRecordData(item, false, false)
}
} else {
zpagingRef.value?.addChatRecordData(item, false, false)
}
zpagingRef.value?.addChatRecordData(item, false, false)
}
}
const batchDelDialogueRecord = (msgIds) => {
const dialogue = useDialogueStore()
const dialogue = lodash.cloneDeep(useDialogueStore())
const item = getDialogueList(dialogue.index_name)
item.records = item.records.filter((item) => !msgIds.includes(item.msg_id))
}
@ -270,7 +129,7 @@ export const useDialogueListStore = createGlobalState(() => {
//清空聊天记录时,同时清空本地保存的聊天记录
const clearDialogueRecord = () => {
const dialogue = useDialogueStore()
const dialogue = lodash.cloneDeep(useDialogueStore())
const item = getDialogueList(dialogue.index_name)
item.records = []
virtualList.value = []

View File

@ -41,6 +41,8 @@ export const useEditorStore = defineStore('editor', {
}).then((res) => {
if (res.code == 200) {
this.loadUserEmoticon()
} else {
message.warning(res.message)
}
})
},
@ -53,6 +55,8 @@ export const useEditorStore = defineStore('editor', {
ServeUploadEmoticon(data).then((res) => {
if (res.code == 200) {
this.emoticon.items[1].children.unshift(res.data)
} else {
message.warning(res.message)
}
})
},
@ -65,6 +69,8 @@ export const useEditorStore = defineStore('editor', {
if (res.code == 200) {
this.emoticon.items[1].children.splice(resoure.index, 1)
message.success('删除成功')
} else {
message.warning(res.message)
}
})
}

View File

@ -2,7 +2,6 @@ import { defineStore } from 'pinia'
import { ServeGetTalkList, ServeCreateTalkList } from '@/api/chat/index'
import { formatTalkItem, ttime, KEY_INDEX_NAME } from '@/utils/talk'
import { useEditorDraftStore } from './editor-draft'
import { handleFindWebview } from '@/utils/common'
// import { ISession } from '@/types/chat'
export const useTalkStore = defineStore('talk', {
@ -103,8 +102,6 @@ export const useTalkStore = defineStore('talk', {
// 返回 Promise 对象,使调用方可以使用 then/catch
return resp.then(({ code, data }) => {
if (code == 200) {
//向OA的webview通信改变未读消息数量
handleFindWebview(`doUpdateUnreadNum('${data.unread_num}')`)
this.items = data.items.map((item) => {
const value = formatTalkItem(item)
@ -147,7 +144,6 @@ export const useTalkStore = defineStore('talk', {
ServeCreateTalkList({
talk_type,
receiver_id,
erp_user_id,
}).then(({ code, data, message }) => {
if (code == 200) {

Some files were not shown because too many files have changed in this diff Show More