Compare commits
200 Commits
Author | SHA1 | Date | |
---|---|---|---|
580439fec8 | |||
454b9ddbbd | |||
079db90021 | |||
3719c162c3 | |||
eb2473df6f | |||
f056ebd176 | |||
033cf16ae7 | |||
f19660c03c | |||
e0e3cfdd12 | |||
0a15ae5463 | |||
d5df575aac | |||
bc99d083c9 | |||
68c867eee9 | |||
d137f39471 | |||
dd31ad21be | |||
c139828e71 | |||
11bb78cc5e | |||
b97219565e | |||
65dc8002db | |||
dd42b26d1f | |||
eb1523516c | |||
02999dc782 | |||
dd0ccb9818 | |||
71d97f069f | |||
7be32e930c | |||
885707f328 | |||
f76198b674 | |||
66828a254c | |||
3898c637b4 | |||
29dcfac775 | |||
e4aa6181a8 | |||
676ad46ab5 | |||
3b70cafff6 | |||
9e2daf8b2e | |||
651c920b7a | |||
4090ee0547 | |||
251cf7d66f | |||
b7eea81200 | |||
df1cc68b59 | |||
a5d9009910 | |||
bbb5dd99fc | |||
2953d50e4b | |||
4381520f3c | |||
4a5d005600 | |||
ec17da2e2c | |||
041692afe8 | |||
9536ce98a6 | |||
30412b109e | |||
87ca206b2b | |||
d494d514b5 | |||
f6038e95a0 | |||
3183ff5049 | |||
e0305ab496 | |||
21ebbf92d5 | |||
f8c3689588 | |||
|
31dcd0ebca | ||
|
c374154f0f | ||
|
dd3e217bb1 | ||
|
96c4fbaad9 | ||
e7ec387735 | |||
6140c625e4 | |||
99ffb6ec05 | |||
f17250f236 | |||
e904dae8a0 | |||
66e95da62e | |||
fea7504a91 | |||
2676e70c0c | |||
f9f7e6e45d | |||
|
fdf71eb4da | ||
1f4c5ba6df | |||
|
687cc0ec38 | ||
3642d3e2fa | |||
8ed2bf7113 | |||
17d9ed737d | |||
04dcbdf331 | |||
fa2098c565 | |||
383abed2e8 | |||
3a15ec6c00 | |||
|
2fa381bea1 | ||
|
988912de2f | ||
|
47d5cab23a | ||
|
ed2d6dfbd5 | ||
|
e780d44255 | ||
|
f6d66fa71c | ||
|
5822c80f6a | ||
|
fe857f1e81 | ||
|
4a670201c6 | ||
a06f0b2489 | |||
84cca43b52 | |||
|
b771a3c910 | ||
|
0b542e2e63 | ||
c2a9676b09 | |||
7595af1d33 | |||
d781a94a89 | |||
|
d0063bf11f | ||
|
11ff75f0d3 | ||
|
8d82024a1e | ||
|
982adc3f4b | ||
|
efc9ffea12 | ||
|
d66aa108d8 | ||
|
165f960d14 | ||
d13ae03355 | |||
ce9f977efd | |||
|
32e8428f46 | ||
|
cb123c6331 | ||
2f64cda0d4 | |||
9e8e3aeba3 | |||
|
1f64ffd8aa | ||
8034a303e7 | |||
|
8eb0f0b4f2 | ||
92a7d74c6c | |||
d7c813977d | |||
2642a885f0 | |||
|
ba52f9a576 | ||
|
33106bfa80 | ||
026e4fa3e6 | |||
|
92cda98ca9 | ||
|
a9febca5e5 | ||
|
b8fd50394c | ||
a6810d4f12 | |||
|
b4b614b545 | ||
b0abca9bf2 | |||
|
505bb9b9f8 | ||
|
ad2b7d42e3 | ||
|
16300b68bf | ||
|
4b24e7a269 | ||
|
0e640c7ad6 | ||
|
010528e52e | ||
|
baa05faa24 | ||
380b0120bc | |||
894bb9bf28 | |||
11ef845e9c | |||
cba592b87d | |||
3fe60616a5 | |||
|
28625c7943 | ||
|
566bba5308 | ||
|
feee532366 | ||
|
67f923b516 | ||
|
4abd2d8047 | ||
98e3a0710a | |||
747653f54a | |||
|
e6075849fe | ||
|
c7343a012a | ||
|
2d28872a12 | ||
|
659c2c3e12 | ||
2304ddfc45 | |||
3414fb63b8 | |||
55e13e6024 | |||
|
be047f30c1 | ||
|
f6ce053787 | ||
|
382c3fed39 | ||
e4c2b7cdcb | |||
adb57bda88 | |||
|
d303c8e94e | ||
4b3062eefa | |||
36342b4dd9 | |||
|
0cb90fcc3b | ||
|
7d2cde3b65 | ||
|
4be1c7c9a4 | ||
|
c408b97b1e | ||
ae2a447fa4 | |||
f2abedd88e | |||
9b904b4fda | |||
8b61369006 | |||
|
93428be5df | ||
8c9b9d0f2c | |||
ada0bc0ee9 | |||
|
ff5a23b18e | ||
78756431b7 | |||
226a24b06b | |||
c4b4ec323a | |||
e782b72b74 | |||
ba0a5384e0 | |||
ea73fa8b9b | |||
c9eb55ed5f | |||
|
f1fa0208c1 | ||
|
b151f90fa6 | ||
|
37c5b604c9 | ||
11aacea856 | |||
aa9adb6211 | |||
69fd096c4d | |||
82ba6ddbd7 | |||
|
ccc8c41b2d | ||
|
cef9d08356 | ||
|
69c5934ef4 | ||
|
160788a107 | ||
d7e8df5386 | |||
b58f25945e | |||
bdfd84bd35 | |||
a0578c57ad | |||
e9bd2bf6cd | |||
2c063f3d4f | |||
5c55411aa3 | |||
df07c953bf | |||
dafe65bb72 | |||
78ca543946 | |||
2e3b0b994b | |||
8704691821 | |||
4f57419c5d | |||
489fb71be5 |
75
auto-imports.d.ts
vendored
Normal file
@ -0,0 +1,75 @@
|
||||
/* eslint-disable */
|
||||
/* prettier-ignore */
|
||||
// @ts-nocheck
|
||||
// noinspection JSUnusedGlobalSymbols
|
||||
// Generated by unplugin-auto-import
|
||||
// biome-ignore lint: disable
|
||||
export {}
|
||||
declare global {
|
||||
const EffectScope: typeof import('vue')['EffectScope']
|
||||
const computed: typeof import('vue')['computed']
|
||||
const createApp: typeof import('vue')['createApp']
|
||||
const customRef: typeof import('vue')['customRef']
|
||||
const defineAsyncComponent: typeof import('vue')['defineAsyncComponent']
|
||||
const defineComponent: typeof import('vue')['defineComponent']
|
||||
const effectScope: typeof import('vue')['effectScope']
|
||||
const getCurrentInstance: typeof import('vue')['getCurrentInstance']
|
||||
const getCurrentScope: typeof import('vue')['getCurrentScope']
|
||||
const h: typeof import('vue')['h']
|
||||
const inject: typeof import('vue')['inject']
|
||||
const isProxy: typeof import('vue')['isProxy']
|
||||
const isReactive: typeof import('vue')['isReactive']
|
||||
const isReadonly: typeof import('vue')['isReadonly']
|
||||
const isRef: typeof import('vue')['isRef']
|
||||
const markRaw: typeof import('vue')['markRaw']
|
||||
const nextTick: typeof import('vue')['nextTick']
|
||||
const onActivated: typeof import('vue')['onActivated']
|
||||
const onBeforeMount: typeof import('vue')['onBeforeMount']
|
||||
const onBeforeUnmount: typeof import('vue')['onBeforeUnmount']
|
||||
const onBeforeUpdate: typeof import('vue')['onBeforeUpdate']
|
||||
const onDeactivated: typeof import('vue')['onDeactivated']
|
||||
const onErrorCaptured: typeof import('vue')['onErrorCaptured']
|
||||
const onMounted: typeof import('vue')['onMounted']
|
||||
const onRenderTracked: typeof import('vue')['onRenderTracked']
|
||||
const onRenderTriggered: typeof import('vue')['onRenderTriggered']
|
||||
const onScopeDispose: typeof import('vue')['onScopeDispose']
|
||||
const onServerPrefetch: typeof import('vue')['onServerPrefetch']
|
||||
const onUnmounted: typeof import('vue')['onUnmounted']
|
||||
const onUpdated: typeof import('vue')['onUpdated']
|
||||
const onWatcherCleanup: typeof import('vue')['onWatcherCleanup']
|
||||
const provide: typeof import('vue')['provide']
|
||||
const reactive: typeof import('vue')['reactive']
|
||||
const readonly: typeof import('vue')['readonly']
|
||||
const ref: typeof import('vue')['ref']
|
||||
const resolveComponent: typeof import('vue')['resolveComponent']
|
||||
const shallowReactive: typeof import('vue')['shallowReactive']
|
||||
const shallowReadonly: typeof import('vue')['shallowReadonly']
|
||||
const shallowRef: typeof import('vue')['shallowRef']
|
||||
const toRaw: typeof import('vue')['toRaw']
|
||||
const toRef: typeof import('vue')['toRef']
|
||||
const toRefs: typeof import('vue')['toRefs']
|
||||
const toValue: typeof import('vue')['toValue']
|
||||
const triggerRef: typeof import('vue')['triggerRef']
|
||||
const unref: typeof import('vue')['unref']
|
||||
const useAttrs: typeof import('vue')['useAttrs']
|
||||
const useCssModule: typeof import('vue')['useCssModule']
|
||||
const useCssVars: typeof import('vue')['useCssVars']
|
||||
const useDialog: typeof import('naive-ui')['useDialog']
|
||||
const useId: typeof import('vue')['useId']
|
||||
const useLoadingBar: typeof import('naive-ui')['useLoadingBar']
|
||||
const useMessage: typeof import('naive-ui')['useMessage']
|
||||
const useModel: typeof import('vue')['useModel']
|
||||
const useNotification: typeof import('naive-ui')['useNotification']
|
||||
const useSlots: typeof import('vue')['useSlots']
|
||||
const useTemplateRef: typeof import('vue')['useTemplateRef']
|
||||
const watch: typeof import('vue')['watch']
|
||||
const watchEffect: typeof import('vue')['watchEffect']
|
||||
const watchPostEffect: typeof import('vue')['watchPostEffect']
|
||||
const watchSyncEffect: typeof import('vue')['watchSyncEffect']
|
||||
}
|
||||
// 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'
|
||||
import('vue')
|
||||
}
|
72
components.d.ts
vendored
Normal file
@ -0,0 +1,72 @@
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
// Generated by unplugin-vue-components
|
||||
// Read more: https://github.com/vuejs/core/pull/3399
|
||||
// biome-ignore lint: disable
|
||||
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']
|
||||
AvatarModule: typeof import('./src/components/avatar-module/index.vue')['default']
|
||||
CheckBox: typeof import('./src/components/checkBox/index.vue')['default']
|
||||
CodeMessage: typeof import('./src/components/talk/message/CodeMessage.vue')['default']
|
||||
CustomBtn: typeof import('./src/components/custom-btn/custom-btn.vue')['default']
|
||||
CustomInput: typeof import('./src/components/custom-input/custom-input.vue')['default']
|
||||
CustomNavbar: typeof import('./src/components/custom-navbar/index.vue')['default']
|
||||
CustomRefresher: typeof import('./src/components/custom-refresher/custom-refresher.vue')['default']
|
||||
DeepBubble: typeof import('./src/components/deep-bubble/deep-bubble.vue')['default']
|
||||
FileMessage: typeof import('./src/components/talk/message/FileMessage.vue')['default']
|
||||
ForwardMessage: typeof import('./src/components/talk/message/ForwardMessage.vue')['default']
|
||||
ForwardRecord: typeof import('./src/components/talk/ForwardRecord.vue')['default']
|
||||
GroupNoticeMessage: typeof import('./src/components/talk/message/GroupNoticeMessage.vue')['default']
|
||||
HistoryRecord: typeof import('./src/components/talk/HistoryRecord.vue')['default']
|
||||
ImageMessage: typeof import('./src/components/talk/message/ImageMessage.vue')['default']
|
||||
LinkMessage: typeof import('./src/components/talk/message/LinkMessage.vue')['default']
|
||||
Loading: typeof import('./src/components/base/Loading.vue')['default']
|
||||
LoginMessage: typeof import('./src/components/talk/message/LoginMessage.vue')['default']
|
||||
Message: typeof import('./src/components/x-message/message/index.vue')['default']
|
||||
MixedMessage: typeof import('./src/components/talk/message/MixedMessage.vue')['default']
|
||||
NButton: typeof import('naive-ui')['NButton']
|
||||
NIcon: typeof import('naive-ui')['NIcon']
|
||||
PageAnimation: typeof import('./src/components/page-animation/index.vue')['default']
|
||||
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']
|
||||
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']
|
||||
UnknownMessage: typeof import('./src/components/talk/message/UnknownMessage.vue')['default']
|
||||
UploadsModal: typeof import('./src/components/base/UploadsModal.vue')['default']
|
||||
VideoMessage: typeof import('./src/components/talk/message/VideoMessage.vue')['default']
|
||||
VoteMessage: typeof import('./src/components/talk/message/VoteMessage.vue')['default']
|
||||
XCalendar: typeof import('./src/components/x-calendar/index.vue')['default']
|
||||
XCaptcha: typeof import('./src/components/x-captcha/index.vue')['default']
|
||||
XConfirm: typeof import('./src/components/x-confirm/index.vue')['default']
|
||||
XDateSelect: typeof import('./src/components/x-date-select/index.vue')['default']
|
||||
XLoaderror: typeof import('./src/components/x-loaderror/index.vue')['default']
|
||||
XLoading: typeof import('./src/components/x-loading/index.vue')['default']
|
||||
XMessage: typeof import('./src/components/x-message/index.vue')['default']
|
||||
XPaging: typeof import('./src/components/x-paging/index.vue')['default']
|
||||
XTabbar: typeof import('./src/components/x-tabbar/index.vue')['default']
|
||||
Xtime: typeof import('./src/components/base/Xtime.vue')['default']
|
||||
}
|
||||
}
|
6
env/.env.dev
vendored
@ -5,4 +5,8 @@ VITE_SHOW_CONSOLE = true
|
||||
# 是否开启sourcemap
|
||||
VITE_SHOW_SOURCEMAP = true
|
||||
# baseUrl
|
||||
VITE_BASEURL = 'http://warehouse.szjixun.cn/oa_backend'
|
||||
VITE_BASEURL = 'http://172.16.100.93:8503'
|
||||
#VITE_SOCKET_API
|
||||
VITE_SOCKET_API = 'ws://172.16.100.93:8504'
|
||||
# EPRAPI baseUrl
|
||||
VITE_EPR_BASEURL = 'http://114.218.158.24:9020'
|
||||
|
19
env/.env.prod
vendored
@ -1,8 +1,21 @@
|
||||
# 变量必须以 VITE_ 为前缀才能暴露给外部读取
|
||||
NODE_ENV = 'prod'
|
||||
# 是否显示console
|
||||
VITE_SHOW_CONSOLE = true
|
||||
VITE_SHOW_CONSOLE = false
|
||||
# 是否开启sourcemap
|
||||
VITE_SHOW_SOURCEMAP = true
|
||||
VITE_SHOW_SOURCEMAP = false
|
||||
|
||||
# baseUrl
|
||||
VITE_BASEURL = 'https://oa-a.szjixun.cn/api'
|
||||
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' #体制内
|
||||
|
||||
|
12
env/.env.test
vendored
@ -4,10 +4,16 @@ 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 = 'https://warehouse.szjixun.cn/oa_backend'
|
||||
VITE_BASEURL = 'http://172.16.100.93:9503'
|
||||
# VITE_BASEURL = 'http://192.168.88.21:9503'
|
||||
#VITE_SOCKET_API
|
||||
VITE_SOCKET_API = 'ws://172.16.100.93:9504'
|
||||
# VITE_SOCKET_API = 'ws://192.168.88.21:9504'
|
||||
|
||||
# EPRAPI baseUrl
|
||||
VITE_EPR_BASEURL = 'http://114.218.158.24:9020'
|
||||
|
16
package.json
@ -8,7 +8,9 @@
|
||||
"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"
|
||||
"build:h5:prod": "uni build --mode prod",
|
||||
"preview:h5": "uni preview --mode test",
|
||||
"preview:h5:prod": "uni preview --mode prod"
|
||||
},
|
||||
"dependencies": {
|
||||
"@dcloudio/uni-app": "3.0.0-alpha-4000020240111001",
|
||||
@ -25,6 +27,7 @@
|
||||
"@dcloudio/uni-mp-weixin": "3.0.0-alpha-4000020240111001",
|
||||
"@dcloudio/uni-mp-xhs": "3.0.0-alpha-4000020240111001",
|
||||
"@dcloudio/uni-quickapp-webview": "3.0.0-alpha-4000020240111001",
|
||||
"@icon-park/vue-next": "^1.4.2",
|
||||
"@uni-helper/axios-adapter": "^1.5.2",
|
||||
"@uni-helper/localforage-adapter": "^1.0.2",
|
||||
"@uni-helper/uni-use": "^0.19.12",
|
||||
@ -36,7 +39,9 @@
|
||||
"lodash": "^4.17.21",
|
||||
"nzh": "^1.0.13",
|
||||
"pinia-plugin-persistedstate": "^4.1.3",
|
||||
"quill-mention": "^6.0.2",
|
||||
"quill": "^1.3.7",
|
||||
"quill-mention": "^4.1.0",
|
||||
"recorder-core": "^1.3.25011100",
|
||||
"vconsole": "^3.15.1",
|
||||
"vue": "^3.3.8",
|
||||
"vue-i18n": "11.0.0-rc.1"
|
||||
@ -58,13 +63,16 @@
|
||||
"@vue/runtime-core": "^3.3.8",
|
||||
"@vue/tsconfig": "^0.5.1",
|
||||
"lint-staged": "^15.2.0",
|
||||
"naive-ui": "^2.41.0",
|
||||
"pinia": "2.0.36",
|
||||
"sass": "^1.77.8",
|
||||
"sass": "1.62.1",
|
||||
"simple-git-hooks": "^2.9.0",
|
||||
"typescript": "^5.3.3",
|
||||
"unocss": "^0.58.9",
|
||||
"unocss-applet": "^0.8.2",
|
||||
"vite": "^5.0.11",
|
||||
"unplugin-auto-import": "^19.1.1",
|
||||
"unplugin-vue-components": "^28.4.1",
|
||||
"vite": "4.5.1",
|
||||
"vue-tsc": "^1.8.27"
|
||||
}
|
||||
}
|
||||
|
5887
pnpm-lock.yaml
56
src/App.vue
@ -1,43 +1,65 @@
|
||||
<script setup>
|
||||
import {useStatus} from "@/store/status";
|
||||
import { useUserStore } from '@/store'
|
||||
import {
|
||||
useProvideUserModal,
|
||||
} from '@/hooks'
|
||||
import { useStatus } from '@/store/status'
|
||||
import { useUserStore, useDialogueListStore } from '@/store'
|
||||
import { useProvideUserModal } from '@/hooks'
|
||||
import {useAuth} from "@/store/auth";
|
||||
const {token} = useAuth()
|
||||
import ws from '@/connect'
|
||||
const {statusBarHeight}= useStatus()
|
||||
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 init = () => {
|
||||
root.style.setProperty('--statusBarHeight', `${statusBarHeight.value}px`)
|
||||
const handleWebview = () => {
|
||||
let statusBarHeight_ = window?.plus?.navigator?.getStatusbarHeight()
|
||||
const webview = plus.webview.currentWebview()
|
||||
// webview.setStyle({
|
||||
// top: statusBarHeight_,
|
||||
// bottom: 0,
|
||||
// })
|
||||
console.log("webview", webview)
|
||||
token.value = webview.token
|
||||
if(webview?.doClearDialogueList){
|
||||
useDialogueListStore().dialogueList.value = []
|
||||
uniStorage.removeItem('dialogueList')
|
||||
}
|
||||
userStore.loadSetting()
|
||||
ws.connect()
|
||||
}
|
||||
const init = () => {
|
||||
if (typeof plus !== 'undefined') {
|
||||
handleWebview()
|
||||
} else {
|
||||
document.addEventListener('plusready', () => {
|
||||
handleWebview()
|
||||
})
|
||||
}
|
||||
}
|
||||
init()
|
||||
</script>
|
||||
<style lang="scss">
|
||||
@import "@/static/css/color.scss";
|
||||
@import "@/static/css/font.scss";
|
||||
<style lang="scss">
|
||||
@import '@/static/css/color.scss';
|
||||
@import '@/static/css/font.scss';
|
||||
/* #ifdef APP-NVUE */
|
||||
@import '@/uni_modules/tmui/scss/nvue.css';
|
||||
/* #endif */
|
||||
/* #ifndef APP-NVUE */
|
||||
@import '@/uni_modules/tmui/scss/noNvue.css';
|
||||
/* #endif */
|
||||
*{
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
/*解决阅览图片关闭按钮会显示在状态栏区域的问题*/
|
||||
#u-a-p>div>div{
|
||||
margin-top:var(--statusBarHeight)
|
||||
#u-a-p > div > div {
|
||||
margin-top: var(--statusBarHeight);
|
||||
}
|
||||
/*不显示滚动条的类*/
|
||||
.no-scroll {
|
||||
-ms-overflow-style: none; /* IE 和 Edge */
|
||||
scrollbar-width: none; /* Firefox */
|
||||
-ms-overflow-style: none; /* IE 和 Edge */
|
||||
scrollbar-width: none; /* Firefox */
|
||||
}
|
||||
.no-scroll::-webkit-scrollbar {
|
||||
display: none; /* Webkit 浏览器 */
|
||||
display: none; /* Webkit 浏览器 */
|
||||
}
|
||||
</style>
|
||||
|
@ -1,5 +1,6 @@
|
||||
import request from '@/service/index.js'
|
||||
import qs from 'qs'
|
||||
import { useTalkStore, useDialogueStore } from '@/store'
|
||||
|
||||
// 获取聊天列表服务接口
|
||||
export const ServeGetTalkList = (data) => {
|
||||
@ -38,7 +39,31 @@ export const ServeTopTalkList = (data) => {
|
||||
}
|
||||
|
||||
// 清除聊天消息未读数服务接口
|
||||
export const ServeClearTalkUnreadNum = (data) => {
|
||||
export const ServeClearTalkUnreadNum = (data, unReadNum) => {
|
||||
console.log('=======chatApp==UnreadNum', unReadNum)
|
||||
if (
|
||||
!useTalkStore().items[
|
||||
useTalkStore().findTalkIndex(useDialogueStore().index_name)
|
||||
]?.is_disturb
|
||||
) {
|
||||
if (typeof plus !== 'undefined') {
|
||||
let OAWebView = plus.webview.all()
|
||||
OAWebView.forEach((webview) => {
|
||||
if (webview.id === 'webviewId1') {
|
||||
webview.evalJS(`updateUnreadMsgNumReduce('${unReadNum}')`)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
document.addEventListener('plusready', () => {
|
||||
let OAWebView = plus.webview.all()
|
||||
OAWebView.forEach((webview) => {
|
||||
if (webview.id === 'webviewId1') {
|
||||
webview.evalJS(`updateUnreadMsgNumReduce('${unReadNum}')`)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
return request({
|
||||
url: '/api/v1/talk/unread/clear',
|
||||
method: 'POST',
|
||||
@ -135,6 +160,15 @@ export const ServeRemoveRecords = (data) => {
|
||||
})
|
||||
}
|
||||
|
||||
//清空聊天记录
|
||||
export const ServeEmptyMessage = (data) => {
|
||||
return request({
|
||||
url: '/api/v1/talk/message/empty',
|
||||
method: 'POST',
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
// 收藏表情包服务接口
|
||||
export const ServeCollectEmoticon = (data) => {
|
||||
return request({
|
||||
@ -160,13 +194,49 @@ 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,
|
||||
})
|
||||
}
|
||||
|
@ -26,6 +26,22 @@ 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) => {
|
||||
@ -45,6 +61,14 @@ 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({
|
||||
|
@ -45,3 +45,12 @@ export const ServeTalkDate = (data) => {
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
//获取会话Id
|
||||
export const ServeGetSessionId = (data) => {
|
||||
return request({
|
||||
url: '/api/v1/talk/session/getId',
|
||||
method: 'POST',
|
||||
data,
|
||||
})
|
||||
}
|
@ -10,10 +10,9 @@ export const ServeGetUserSetting = (data) => {
|
||||
|
||||
export const userInfoApi = (data) => {
|
||||
return request({
|
||||
url: '/user/info',
|
||||
url: '/api/v1/users/erp/info',
|
||||
method: 'POST',
|
||||
data,
|
||||
baseURL:import.meta.env.VITE_EPR_BASEURL,
|
||||
})
|
||||
}
|
||||
|
||||
|
89
src/components/async-error/index.vue
Normal file
@ -0,0 +1,89 @@
|
||||
<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>
|
81
src/components/async-loading/index.vue
Normal file
@ -0,0 +1,81 @@
|
||||
<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>
|
@ -1,29 +1,95 @@
|
||||
<script setup>
|
||||
import TmImage from "@/uni_modules/tmui/components/tm-image/tm-image.vue";
|
||||
import {useAuth} from "@/store/auth";
|
||||
import { useClockIn } from "@/store/clockIn/index.js";
|
||||
const {userInfo}=useAuth()
|
||||
const {workingTimeInfoData,actionTypeData} = useClockIn()
|
||||
</script>
|
||||
<template>
|
||||
<div class="flex-shrink-0 pl-[16rpx] pr-[40rpx] flex items-center rounded-[8rpx] w-[686rpx] h-[154rpx] bg-white">
|
||||
<div class="rounded-full overflow-hidden w-[96rpx] h-[96rpx]">
|
||||
<tm-image preview :width="96" :height="96" :src="userInfo.Avatar"></tm-image>
|
||||
</div>
|
||||
<div class="ml-[20rpx]">
|
||||
<div class="flex items-center">
|
||||
<div class="text-[32rpx] text-black">{{ userInfo.NickName }}</div>
|
||||
<div class="mx-[14rpx] h-[30rpx] w-[1rpx] bg-[#F7F7F7]"></div>
|
||||
<div class="w-[40rpx] h-[40rpx]">
|
||||
<img v-if="actionTypeData.isWorkDay ===1" class="w-[40rpx] h-[40rpx]" src="@/static/image/clockIn/zu3275@3x.png" alt="">
|
||||
<img v-else class="w-[40rpx] h-[40rpx]" src="@/static/image/clockIn/rest3275@2x.png" alt="">
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-[5rpx] flex">
|
||||
<div class="text-[24rpx] text-[#999999]">{{ workingTimeInfoData.WorkTimeTemplateName }}</div>
|
||||
<div class="text-[#46299D] text-[24rpx]">(考勤规则)</div>
|
||||
</div>
|
||||
</div>
|
||||
<slot name="right"></slot>
|
||||
<div class="avatar-module" :style="[customStyle, { background: avatar ? '#fff' : '' }]">
|
||||
<img :src="avatar" v-if="avatar" />
|
||||
<span v-else :style="customTextStyle">{{ text_avatar }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
//群聊默认头像
|
||||
import groupNormal from '@/static/image/chatList/groupNormal.png'
|
||||
import groupDepartment from '@/static/image/chatList/groupDepartment.png'
|
||||
import groupProject from '@/static/image/chatList/groupProject.png'
|
||||
import groupCompany from '@/static/image/chatList/groupCompany.png'
|
||||
import { computed, defineProps } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
mode: {
|
||||
//模式:1=人;2=群
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
avatar: {
|
||||
//头像
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
userName: {
|
||||
//用户名称
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
groupType: {
|
||||
//群类型:1=普通群;2=部门群;3=项目群;4=总群/公司群
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
customStyle: {
|
||||
//自定义样式
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
},
|
||||
},
|
||||
customTextStyle: {
|
||||
//自定义文字样式
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
//头像
|
||||
const avatar = computed(() => {
|
||||
let avatar_img = props?.avatar
|
||||
if (!avatar_img) {
|
||||
if (props?.mode === 1) {
|
||||
} else if (props?.mode === 2) {
|
||||
if (props?.groupType === 1) {
|
||||
avatar_img = groupNormal
|
||||
} else if (props?.groupType === 2) {
|
||||
avatar_img = groupDepartment
|
||||
} else if (props?.groupType === 3) {
|
||||
avatar_img = groupProject
|
||||
} else if (props?.groupType === 4) {
|
||||
avatar_img = groupCompany
|
||||
}
|
||||
}
|
||||
}
|
||||
return avatar_img
|
||||
})
|
||||
|
||||
//文字头像
|
||||
const text_avatar = computed(() => {
|
||||
return props?.userName.length >= 2
|
||||
? props?.userName.slice(-2)
|
||||
: props?.userName
|
||||
})
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.avatar-module {
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: linear-gradient(to right, #674bbc, #46299d);
|
||||
flex-shrink: 0;
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -90,8 +90,6 @@ const onSubmit = () => {
|
||||
ServeUploadAvatar(form).then((res) => {
|
||||
if (res.code == 200) {
|
||||
emit('success', res.data.avatar)
|
||||
} else {
|
||||
message.warning(res.message)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
@ -1,56 +1,63 @@
|
||||
<template>
|
||||
<div @click="onCheck" >
|
||||
<div @click.stop="onCheck">
|
||||
<tm-image :width="size" :height="size" :src="imageSrc"></tm-image>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { defineProps,computed,defineEmits } from "vue";
|
||||
import circle from "@/static/image/checkBox/circle@2x.png";
|
||||
import zu6039 from "@/static/image/checkBox/zu6039@2x.png";
|
||||
import zu6040 from "@/static/image/checkBox/zu6040@2x.png";
|
||||
import { defineProps, computed, defineEmits } from 'vue'
|
||||
import circle from '@/static/image/checkBox/circle@2x.png'
|
||||
import zu6039 from '@/static/image/checkBox/zu6039@2x.png'
|
||||
import zu6040 from '@/static/image/checkBox/zu6040@2x.png'
|
||||
import checkBoxDisabled from '@/static/image/checkBox/disabled.png'
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: String,
|
||||
default: "noChecked", // noChecked, checked, halfChecked
|
||||
default: 'noChecked', // noChecked, checked, halfChecked, disabled
|
||||
},
|
||||
size: {
|
||||
type: Number,
|
||||
default: 28,
|
||||
},
|
||||
});
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:modelValue','change']);
|
||||
const emit = defineEmits(['update:modelValue', 'change'])
|
||||
|
||||
const imageSrc = computed(() => {
|
||||
switch (props.modelValue) {
|
||||
case "noChecked":
|
||||
return circle;
|
||||
case "checked":
|
||||
return zu6039;
|
||||
case "halfChecked":
|
||||
return zu6040;
|
||||
if (props.disabled) {
|
||||
return checkBoxDisabled
|
||||
}
|
||||
});
|
||||
switch (props.modelValue) {
|
||||
case 'noChecked':
|
||||
return circle
|
||||
case 'checked':
|
||||
return zu6039
|
||||
case 'halfChecked':
|
||||
return zu6040
|
||||
}
|
||||
})
|
||||
|
||||
const onCheck = () => {
|
||||
if (props.disabled) return
|
||||
switch (props.modelValue) {
|
||||
case "noChecked":
|
||||
emit('update:modelValue', 'checked');
|
||||
emit('change', 'checked');
|
||||
break;
|
||||
case "checked":
|
||||
emit('update:modelValue', 'noChecked');
|
||||
emit('change', 'noChecked');
|
||||
break;
|
||||
case "halfChecked":
|
||||
emit('update:modelValue', 'checked');
|
||||
emit('change', 'checked');
|
||||
break;
|
||||
case 'noChecked':
|
||||
emit('update:modelValue', 'checked')
|
||||
emit('change', 'checked')
|
||||
break
|
||||
case 'checked':
|
||||
emit('update:modelValue', 'noChecked')
|
||||
emit('change', 'noChecked')
|
||||
break
|
||||
case 'halfChecked':
|
||||
emit('update:modelValue', 'checked')
|
||||
emit('change', 'checked')
|
||||
break
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
</script>
|
||||
<style scoped lang='less'>
|
||||
</style>
|
||||
<style scoped lang="less"></style>
|
||||
|
@ -6,10 +6,21 @@
|
||||
props.subBtnText ? 'apposition-btn-style' : '',
|
||||
]"
|
||||
>
|
||||
<wd-button custom-class="custom-sub-btn-class" v-if="props.subBtnText">
|
||||
<wd-button
|
||||
custom-class="custom-sub-btn-class"
|
||||
v-if="props.subBtnText"
|
||||
@click="clickSubBtn"
|
||||
>
|
||||
{{ props.subBtnText }}
|
||||
</wd-button>
|
||||
<wd-button custom-class="custom-btn-class" @click="clickBtn">
|
||||
<wd-button
|
||||
custom-class="custom-btn-class"
|
||||
@click="clickBtn"
|
||||
:disabled="props?.disabled"
|
||||
:class="[props?.disabled ? 'custom-btn-class-disabled' : '']"
|
||||
:plain="props?.plain"
|
||||
:loading="props?.isLoading"
|
||||
>
|
||||
{{ props.btnText }}
|
||||
</wd-button>
|
||||
</div>
|
||||
@ -18,14 +29,22 @@
|
||||
import { reactive } from 'vue'
|
||||
import { defineProps, defineEmits } from 'vue'
|
||||
const state = reactive({})
|
||||
const emits = defineEmits(['clickBtn'])
|
||||
const emits = defineEmits(['clickSubBtn', 'clickBtn'])
|
||||
const props = defineProps({
|
||||
isBottom: false, //是否底部按钮
|
||||
btnText: '', //按钮文字
|
||||
subBtnText: '', //次要按钮文字
|
||||
disabled: false, //是否禁用
|
||||
plain: false, //是否镂空
|
||||
isLoading: false, //是否正在加载中
|
||||
})
|
||||
|
||||
//点击
|
||||
//点击副按钮
|
||||
const clickSubBtn = () => {
|
||||
emits('clickSubBtn')
|
||||
}
|
||||
|
||||
//点击主按钮
|
||||
const clickBtn = () => {
|
||||
emits('clickBtn')
|
||||
}
|
||||
@ -38,28 +57,33 @@ const clickBtn = () => {
|
||||
justify-content: center;
|
||||
.custom-sub-btn-class {
|
||||
background-color: #eee9f8;
|
||||
padding: 18rpx 185rpx;
|
||||
padding: 18rpx 0;
|
||||
border-radius: 8rpx;
|
||||
box-shadow: 0 6px 12px 2px rgba(188, 188, 188, 0.08);
|
||||
font-size: 28rpx;
|
||||
font-weight: 500;
|
||||
line-height: 40rpx;
|
||||
color: $theme-primary;
|
||||
width: 426rpx;
|
||||
height: 76rpx;
|
||||
}
|
||||
.custom-btn-class {
|
||||
background-color: $theme-primary;
|
||||
padding: 18rpx 185rpx;
|
||||
padding: 18rpx 0;
|
||||
border-radius: 8rpx;
|
||||
box-shadow: 0 6px 12px 2px rgba(188, 188, 188, 0.08);
|
||||
font-size: 28rpx;
|
||||
font-weight: 500;
|
||||
line-height: 40rpx;
|
||||
width: 426rpx;
|
||||
height: 76rpx;
|
||||
}
|
||||
}
|
||||
.custom-btn-bottom {
|
||||
width: 100%;
|
||||
background-color: #fff;
|
||||
padding: 14rpx 0 72rpx;
|
||||
box-shadow: 0 1px 0 2px rgba(231, 231, 231, 1);
|
||||
}
|
||||
.apposition-btn-style {
|
||||
padding: 14rpx 30rpx 72rpx;
|
||||
@ -71,4 +95,8 @@ const clickBtn = () => {
|
||||
padding: 18rpx 124rpx;
|
||||
}
|
||||
}
|
||||
.custom-btn-class-disabled {
|
||||
background-color: #e6e6e6 !important;
|
||||
color: #bebebe !important;
|
||||
}
|
||||
</style>
|
||||
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="custom-search-input">
|
||||
<div class="custom-input">
|
||||
<tm-input
|
||||
class="search-input"
|
||||
placeholder="请输入…"
|
||||
@ -12,14 +12,29 @@
|
||||
@input="inputSearchText"
|
||||
:showClear="true"
|
||||
@clear="clearInput"
|
||||
placeholderStyle="color:#BABABA"
|
||||
:disabled="props?.disabled"
|
||||
></tm-input>
|
||||
<div v-if="props?.disabled" class="custom-input-disabled"></div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import { defineProps, defineEmits, reactive, watch } from 'vue'
|
||||
const props = defineProps({
|
||||
searchText: String,
|
||||
first_talk_record_infos: Object,
|
||||
searchText: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
first_talk_record_infos: {
|
||||
type: Object,
|
||||
default(){
|
||||
return {}
|
||||
}
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
})
|
||||
const state = reactive({
|
||||
searchText: '', //搜索内容
|
||||
@ -44,15 +59,27 @@ const inputSearchText = (e) => {
|
||||
}
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
.custom-search-input {
|
||||
.custom-input {
|
||||
width: 100%;
|
||||
position: relative;
|
||||
.search-input {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
::v-deep .noNvueBorder > .noNvueBorder > .noNvueBorder {
|
||||
background: #f9f9fd !important;
|
||||
border-radius: 8rpx !important;
|
||||
}
|
||||
.search-input::v-deep .tmicon-times-circle-fill::before {
|
||||
content: '\e82a';
|
||||
color: #d2d2d5;
|
||||
}
|
||||
|
||||
.custom-input-disabled {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
55
src/components/custom-navbar/index.vue
Normal file
@ -0,0 +1,55 @@
|
||||
<template>
|
||||
<tm-navbar
|
||||
:hideBack="props.hideBack"
|
||||
:hideHome="props.hideHome"
|
||||
:title="props.title"
|
||||
:shadow="props.shadowNum"
|
||||
:fontSize="34"
|
||||
:showStatusBar="false"
|
||||
>
|
||||
<template #left>
|
||||
<slot name="left"></slot>
|
||||
</template>
|
||||
<template #subTitle>
|
||||
<slot name="subTitle"></slot>
|
||||
</template>
|
||||
<template #right>
|
||||
<slot name="right"></slot>
|
||||
</template>
|
||||
</tm-navbar>
|
||||
</template>
|
||||
<script setup>
|
||||
import tmNavbar from '@/uni_modules/tmui/components/tm-navbar/tm-navbar.vue'
|
||||
import { defineProps } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
title: {
|
||||
//页面标题
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
hideBack: {
|
||||
//是否隐藏返回按钮
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
shadowNum: {
|
||||
//投影
|
||||
type: Number,
|
||||
default: 1,
|
||||
},
|
||||
hideHome: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
::v-deep .text-view {
|
||||
text {
|
||||
font-weight: 500;
|
||||
line-height: 48rpx;
|
||||
}
|
||||
}
|
||||
</style>
|
73
src/components/custom-refresher/custom-refresher.vue
Normal file
@ -0,0 +1,73 @@
|
||||
<!-- z-paging自定义的下拉刷新view -->
|
||||
<template>
|
||||
<view class="refresher-container">
|
||||
<!-- 这里的图片请换成自己项目的图片 -->
|
||||
<image
|
||||
class="refresh-image rotating"
|
||||
mode="aspectFit"
|
||||
style="width:48rpx;height:48rpx;"
|
||||
src="@/static/image/clockIn/loading@2x.png"
|
||||
></image>
|
||||
<text class="refresh-text">{{ refreshText }}</text>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
status: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
});
|
||||
|
||||
// 根据状态显示不同的文字
|
||||
const refreshText = computed(() => {
|
||||
const statusTextMap = {
|
||||
'default': '哎呀,用点力继续下拉!',
|
||||
'release-to-refresh': '拉疼我啦,松手刷新~~',
|
||||
'loading': '正在努力刷新中...',
|
||||
'complete': '刷新成功啦~'
|
||||
};
|
||||
return statusTextMap[props.status];
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.refresher-container {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
height: 150rpx;
|
||||
background: rgba(249, 249, 253, 1);
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.refresh-image {
|
||||
width: 48rpx;
|
||||
height: 48rpx;
|
||||
margin-bottom: 10rpx;
|
||||
margin-top: 10rpx;
|
||||
}
|
||||
|
||||
.rotating {
|
||||
animation: rotate 2s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes rotate {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
.refresh-text {
|
||||
font-size: 28rpx;
|
||||
color: rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
</style>
|
@ -25,7 +25,15 @@
|
||||
class="flex flex-col items-center justify-center"
|
||||
>
|
||||
<tm-image :width="40" :height="40" :src="copy07"></tm-image>
|
||||
<div>复制</div>
|
||||
<div class="mt-1">复制</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
|
||||
@click="() => itemClick('multipleChoose')"
|
||||
@ -36,7 +44,7 @@
|
||||
:height="40"
|
||||
:src="multipleChoices"
|
||||
></tm-image>
|
||||
<div>多选</div>
|
||||
<div class="mt-1">多选</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="props.isShowCite"
|
||||
@ -44,7 +52,7 @@
|
||||
class="flex flex-col items-center justify-center"
|
||||
>
|
||||
<tm-image :width="40" :height="40" :src="cite"></tm-image>
|
||||
<div>引用</div>
|
||||
<div class="mt-1">引用</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="props.isShowWithdraw"
|
||||
@ -52,14 +60,14 @@
|
||||
class="flex flex-col items-center justify-center"
|
||||
>
|
||||
<tm-image :width="40" :height="40" :src="withdraw"></tm-image>
|
||||
<div>撤回</div>
|
||||
<div class="mt-1">撤回</div>
|
||||
</div>
|
||||
<div
|
||||
@click="() => itemClick('actionDelete')"
|
||||
class="flex flex-col items-center justify-center"
|
||||
>
|
||||
<tm-image :width="40" :height="40" :src="delete07"></tm-image>
|
||||
<div>删除</div>
|
||||
<div class="mt-1">删除</div>
|
||||
</div>
|
||||
</div>
|
||||
<div :style="data.iconStyle" class="icon"></div>
|
||||
@ -78,8 +86,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,
|
||||
@ -87,18 +95,18 @@ import {
|
||||
computed,
|
||||
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";
|
||||
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'
|
||||
|
||||
// pinia
|
||||
const systemInfo = uni.getSystemInfoSync();
|
||||
const bubbleRef = ref(null);
|
||||
const systemInfo = uni.getSystemInfoSync()
|
||||
const bubbleRef = ref(null)
|
||||
|
||||
const props = defineProps({
|
||||
isShowCopy: {
|
||||
@ -113,109 +121,114 @@ const props = defineProps({
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
});
|
||||
isShowConvertText: {
|
||||
//是否显示转文字
|
||||
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) {
|
||||
@ -224,55 +237,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>
|
||||
|
@ -1,91 +1,95 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive } from 'vue'
|
||||
import { PlayOne, PauseOne } from '@icon-park/vue-next'
|
||||
import { ITalkRecordExtraAudio, ITalkRecord } from '@/types/chat'
|
||||
import { ref, reactive, onMounted } from "vue";
|
||||
import { PlayOne, PauseOne } from "@icon-park/vue-next";
|
||||
import { ITalkRecordExtraAudio, ITalkRecord } from "@/types/chat";
|
||||
|
||||
defineProps<{
|
||||
extra: ITalkRecordExtraAudio
|
||||
data: ITalkRecord
|
||||
maxWidth?: Boolean
|
||||
}>()
|
||||
const props = defineProps<{
|
||||
extra: ITalkRecordExtraAudio;
|
||||
data: ITalkRecord;
|
||||
maxWidth?: Boolean;
|
||||
}>();
|
||||
|
||||
const audioRef = ref()
|
||||
const audioRef = ref();
|
||||
const audioContext = ref<any>(null);
|
||||
|
||||
const durationDesc = ref('-')
|
||||
const durationDesc = ref("-");
|
||||
|
||||
const state = reactive({
|
||||
isAudioPlay: false,
|
||||
progress: 0,
|
||||
duration: 0,
|
||||
currentTime: 0,
|
||||
loading: true
|
||||
})
|
||||
loading: true,
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
// 使用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;
|
||||
});
|
||||
|
||||
audioContext.value.onTimeUpdate(() => {
|
||||
if (audioContext.value.duration == 0) {
|
||||
state.progress = 0;
|
||||
} else {
|
||||
state.currentTime = audioContext.value.currentTime;
|
||||
state.progress =
|
||||
(audioContext.value.currentTime / audioContext.value.duration) * 100;
|
||||
}
|
||||
});
|
||||
|
||||
audioContext.value.onEnded(() => {
|
||||
state.isAudioPlay = false;
|
||||
state.progress = 0;
|
||||
});
|
||||
|
||||
audioContext.value.onError((e) => {
|
||||
console.log("音频播放异常===>", e);
|
||||
});
|
||||
});
|
||||
|
||||
const onPlay = () => {
|
||||
if (state.isAudioPlay) {
|
||||
audioRef.value.pause()
|
||||
audioContext.value.pause();
|
||||
} else {
|
||||
audioRef.value.play()
|
||||
audioContext.value.play();
|
||||
}
|
||||
|
||||
state.isAudioPlay = !state.isAudioPlay
|
||||
}
|
||||
state.isAudioPlay = !state.isAudioPlay;
|
||||
};
|
||||
|
||||
const onPlayEnd = () => {
|
||||
state.isAudioPlay = false
|
||||
state.progress = 0
|
||||
}
|
||||
|
||||
const onCanplay = () => {
|
||||
state.duration = audioRef.value.duration
|
||||
durationDesc.value = formatTime(parseInt(audioRef.value.duration))
|
||||
state.loading = false
|
||||
}
|
||||
|
||||
const onError = (e: any) => {
|
||||
console.log('音频播放异常===>', e)
|
||||
}
|
||||
|
||||
const onTimeUpdate = () => {
|
||||
let audio = audioRef.value
|
||||
if (audio.duration == 0) {
|
||||
state.progress = 0
|
||||
} else {
|
||||
state.currentTime = audio.currentTime
|
||||
state.progress = (audio.currentTime / audio.duration) * 100
|
||||
}
|
||||
}
|
||||
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="im-message-audio">
|
||||
<audio
|
||||
ref="audioRef"
|
||||
preload="auto"
|
||||
type="audio/mp3,audio/wav"
|
||||
:src="extra.url"
|
||||
@timeupdate="onTimeUpdate"
|
||||
@ended="onPlayEnd"
|
||||
@canplay="onCanplay"
|
||||
@error="onError"
|
||||
/>
|
||||
|
||||
<div class="play">
|
||||
<div class="btn pointer" @click.stop="onPlay">
|
||||
<n-icon :size="18" :component="state.isAudioPlay ? PauseOne : PlayOne" />
|
||||
<n-icon
|
||||
:size="18"
|
||||
:component="state.isAudioPlay ? PauseOne : PlayOne"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="desc">
|
||||
@ -241,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);
|
||||
|
@ -1,33 +1,32 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue'
|
||||
import { NCode } from 'naive-ui'
|
||||
import { Copy, Stretching } from '@icon-park/vue-next'
|
||||
import { clipboard } from '@/utils/common'
|
||||
import { useUtil } from '@/hooks'
|
||||
import { ITalkRecordExtraCode, ITalkRecord } from '@/types/chat'
|
||||
import { ref } from "vue";
|
||||
import { NCode, useMessage } from "naive-ui";
|
||||
import { Copy, Stretching } from "@icon-park/vue-next";
|
||||
import { clipboard } from "@/utils/common";
|
||||
import { ITalkRecordExtraCode, ITalkRecord } from "@/types/chat";
|
||||
|
||||
const props = defineProps<{
|
||||
extra: ITalkRecordExtraCode
|
||||
data: ITalkRecord
|
||||
maxWidth?: Boolean
|
||||
}>()
|
||||
extra: ITalkRecordExtraCode;
|
||||
data: ITalkRecord;
|
||||
maxWidth?: Boolean;
|
||||
}>();
|
||||
|
||||
const { useMessage } = useUtil()
|
||||
const lineMumber = props.extra.code.trim().split('\n').length
|
||||
const full = ref(false)
|
||||
const message = useMessage();
|
||||
const lineMumber = props.extra.code.trim().split("\n").length;
|
||||
const full = ref(false);
|
||||
|
||||
const onClipboard = () => {
|
||||
clipboard(props.extra.code, () => {
|
||||
useMessage.success('复制成功')
|
||||
})
|
||||
}
|
||||
message.success("复制成功");
|
||||
});
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<section
|
||||
class="im-message-code el-container is-vertical"
|
||||
:class="{
|
||||
maxwidth: maxWidth,
|
||||
full: full
|
||||
full: full,
|
||||
}"
|
||||
>
|
||||
<header class="el-header tools">
|
||||
@ -37,9 +36,16 @@ const onClipboard = () => {
|
||||
<n-icon class="icon" :component="Copy" @click="onClipboard" />
|
||||
</p>
|
||||
</header>
|
||||
<main class="el-main me-scrollbar me-scrollbar-thumb" :lineMumber="lineMumber">
|
||||
<main
|
||||
class="el-main me-scrollbar me-scrollbar-thumb"
|
||||
:lineMumber="lineMumber"
|
||||
>
|
||||
<n-code :language="extra.lang" :code="extra.code" show-line-numbers />
|
||||
<div class="el-footer mask pointer" v-show="lineMumber > 20" @click="full = !full">
|
||||
<div
|
||||
class="el-footer mask pointer"
|
||||
v-show="lineMumber > 20"
|
||||
@click="full = !full"
|
||||
>
|
||||
查看更多
|
||||
</div>
|
||||
</main>
|
||||
@ -110,17 +116,25 @@ const onClipboard = () => {
|
||||
position: sticky;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
background: linear-gradient(to bottom, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 1) 100%);
|
||||
background: linear-gradient(
|
||||
to bottom,
|
||||
rgba(255, 255, 255, 0) 0%,
|
||||
rgba(255, 255, 255, 1) 100%
|
||||
);
|
||||
color: var(--im-text-color);
|
||||
}
|
||||
}
|
||||
|
||||
html[theme-mode='dark'] {
|
||||
html[theme-mode="dark"] {
|
||||
.im-message-code {
|
||||
background: var(--im-message-bg-color);
|
||||
|
||||
.mask {
|
||||
background: linear-gradient(to bottom, transparent 0%, var(--im-bg-color) 100%);
|
||||
background: linear-gradient(
|
||||
to bottom,
|
||||
transparent 0%,
|
||||
var(--im-bg-color) 100%
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -25,69 +25,130 @@ 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 = '#747474'
|
||||
objT.progressColor = '#46299d'
|
||||
}
|
||||
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">
|
||||
<div
|
||||
class="w-[228rpx] text-[32rpx] text-[#1A1A1A] h-[88rpx] leading-[44rpx] textEllipsis file_name"
|
||||
>
|
||||
{{ 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
|
||||
customClass="circleProgress"
|
||||
:modelValue="data.uploadCurrent"
|
||||
layerColor="#E3E3E3"
|
||||
:color="getFileTypeIMG.progressColor"
|
||||
:strokeWidth="3"
|
||||
:size="20"
|
||||
></wd-circle>
|
||||
<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>
|
||||
</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">
|
||||
@ -124,6 +185,11 @@ const getFileTypeIMG = computed(() => {
|
||||
border-radius: 16rpx 0 16rpx 16rpx;
|
||||
}
|
||||
|
||||
.file_name {
|
||||
word-break: break-all; /* 在任意字符间断行 */
|
||||
word-wrap: break-word; /* 允许长单词换行到下一行 */
|
||||
}
|
||||
|
||||
.main {
|
||||
height: 45px;
|
||||
display: flex;
|
||||
@ -212,7 +278,7 @@ const getFileTypeIMG = computed(() => {
|
||||
}
|
||||
|
||||
.divider {
|
||||
background-color: #E7E7E7;
|
||||
background-color: #e7e7e7;
|
||||
height: 1rpx;
|
||||
}
|
||||
|
||||
@ -225,4 +291,20 @@ const getFileTypeIMG = computed(() => {
|
||||
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>
|
||||
|
@ -12,9 +12,15 @@ const props = defineProps<{
|
||||
const isShowRecord = ref(false)
|
||||
|
||||
const title = computed(() => {
|
||||
return [...new Set(props.extra.records.map((v) => v.nickname))].join('、')
|
||||
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('和')
|
||||
})
|
||||
|
||||
console.log(props.extra.records)
|
||||
const onClick = () => {
|
||||
// isShowRecord.value = true
|
||||
uni.navigateTo({
|
||||
@ -24,7 +30,7 @@ const onClick = () => {
|
||||
</script>
|
||||
<template>
|
||||
<section class="im-message-forward pointer" :class="{ left: data.float === 'left' }" @click="onClick">
|
||||
<div class="title">{{ title }} 的会话记录</div>
|
||||
<div class="title">{{ extra.forward_name || title}}的会话记录</div>
|
||||
<div class="list" v-for="(record, index) in extra.records" :key="index">
|
||||
<p>
|
||||
<span>{{ record.nickname }}: </span>
|
||||
@ -59,13 +65,14 @@ const onClick = () => {
|
||||
}
|
||||
|
||||
.title {
|
||||
height: 44rpx;
|
||||
max-height: 88rpx;
|
||||
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;
|
||||
}
|
||||
|
@ -20,7 +20,8 @@ let show = ref(false)
|
||||
}"
|
||||
>
|
||||
<div class="title">
|
||||
<n-tag :bordered="false" size="small" type="primary">群公告</n-tag>
|
||||
<!-- <n-tag :bordered="false" size="small" type="primary">群公告</n-tag> -->
|
||||
<text>群公告</text>
|
||||
<!-- 《{{ extra.title }}》 -->
|
||||
</div>
|
||||
<div class="title" :class="{ ellipsis: !show }">
|
||||
|
@ -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,13 +54,30 @@ const img = computed(() => {
|
||||
class="im-message-image"
|
||||
:class="{
|
||||
left: data.float === 'left',
|
||||
right: data.float === 'right'
|
||||
right: data.float === 'right',
|
||||
}"
|
||||
>
|
||||
<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>
|
||||
<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>
|
||||
</section>
|
||||
</template>
|
||||
<style lang="less" scoped>
|
||||
@ -79,12 +96,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;
|
||||
@ -94,4 +111,18 @@ 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>
|
||||
|
95
src/components/talk/message/LinkMessage.vue
Normal file
@ -0,0 +1,95 @@
|
||||
<!-- 完全复制的textMessage组件,没有用处,仅兜底真有14类型时的场景。后续会单独做制作分享卡片功能,到时再根据分享卡片样式重做本页面 -->
|
||||
<!-- 完全复制的textMessage组件,没有用处,仅兜底真有14类型时的场景。后续会单独做制作分享卡片功能,到时再根据分享卡片样式重做本页面 -->
|
||||
<!-- 完全复制的textMessage组件,没有用处,仅兜底真有14类型时的场景。后续会单独做制作分享卡片功能,到时再根据分享卡片样式重做本页面 -->
|
||||
<!-- 完全复制的textMessage组件,没有用处,仅兜底真有14类型时的场景。后续会单独做制作分享卡片功能,到时再根据分享卡片样式重做本页面 -->
|
||||
<!-- 完全复制的textMessage组件,没有用处,仅兜底真有14类型时的场景。后续会单独做制作分享卡片功能,到时再根据分享卡片样式重做本页面 -->
|
||||
<!-- 完全复制的textMessage组件,没有用处,仅兜底真有14类型时的场景。后续会单独做制作分享卡片功能,到时再根据分享卡片样式重做本页面 -->
|
||||
<!-- 完全复制的textMessage组件,没有用处,仅兜底真有14类型时的场景。后续会单独做制作分享卡片功能,到时再根据分享卡片样式重做本页面 -->
|
||||
<!-- 完全复制的textMessage组件,没有用处,仅兜底真有14类型时的场景。后续会单独做制作分享卡片功能,到时再根据分享卡片样式重做本页面 -->
|
||||
<!-- 完全复制的textMessage组件,没有用处,仅兜底真有14类型时的场景。后续会单独做制作分享卡片功能,到时再根据分享卡片样式重做本页面 -->
|
||||
<!-- 完全复制的textMessage组件,没有用处,仅兜底真有14类型时的场景。后续会单独做制作分享卡片功能,到时再根据分享卡片样式重做本页面 -->
|
||||
<script lang="ts" setup>
|
||||
import { textReplaceEmoji } from '@/utils/emojis'
|
||||
import { textReplaceLink, textReplaceMention } from '@/utils/strings'
|
||||
import { ITalkRecordExtraText, ITalkRecord } from '@/types/chat'
|
||||
|
||||
const props = defineProps<{
|
||||
extra: ITalkRecordExtraText
|
||||
data: ITalkRecord
|
||||
maxWidth?: boolean
|
||||
source?: 'panel' | 'forward' | 'history'
|
||||
}>()
|
||||
|
||||
const float = props.data.float
|
||||
|
||||
let textContent = props.extra?.content || ''
|
||||
|
||||
textContent = textReplaceLink(textContent)
|
||||
|
||||
if (props.data.talk_type == 2) {
|
||||
textContent = textReplaceMention(textContent, '#1890ff')
|
||||
}
|
||||
|
||||
textContent = textReplaceEmoji(textContent)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="im-message-text"
|
||||
:class="{
|
||||
left: float == 'left',
|
||||
right: float == 'right',
|
||||
maxwidth: maxWidth,
|
||||
'radius-reset': source != 'panel',
|
||||
}"
|
||||
>
|
||||
<pre v-html="textContent" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.im-message-text {
|
||||
min-width: 40rpx;
|
||||
min-height: 40rpx;
|
||||
padding: 22rpx 30rpx;
|
||||
color: #1a1a1a;
|
||||
background: #ffffff;
|
||||
border-radius: 0 16rpx 16rpx 16rpx;
|
||||
|
||||
&.right {
|
||||
background-color: #46299d;
|
||||
color: #ffffff;
|
||||
border-radius: 16rpx 0 16rpx 16rpx;
|
||||
}
|
||||
|
||||
&.maxwidth {
|
||||
max-width: 486rpx;
|
||||
}
|
||||
|
||||
&.radius-reset {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
pre {
|
||||
white-space: pre-wrap;
|
||||
overflow: hidden;
|
||||
word-break: break-word;
|
||||
word-wrap: break-word;
|
||||
font-size: 32rpx;
|
||||
font-family: 'PingFang SC', 'Microsoft YaHei', 'Alibaba PuHuiTi 2.0 45';
|
||||
line-height: 44rpx;
|
||||
|
||||
:deep(.emoji) {
|
||||
vertical-align: text-bottom;
|
||||
margin: 0 10rpx;
|
||||
width: 44rpx;
|
||||
height: 44rpx;
|
||||
}
|
||||
|
||||
:deep(a) {
|
||||
color: #2196f3;
|
||||
text-decoration: revert;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@ -4,34 +4,97 @@ 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: ''
|
||||
default: '',
|
||||
},
|
||||
msg_id: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
revokeInfo: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
},
|
||||
},
|
||||
extra:{
|
||||
type: String,
|
||||
default: '',
|
||||
}
|
||||
})
|
||||
</script>
|
||||
<template>
|
||||
<div class="im-message-revoke">
|
||||
<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 }}" 撤回了一条消息 |
|
||||
<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">
|
||||
你撤回了一条消息 |
|
||||
{{ 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>
|
||||
|
@ -2,6 +2,7 @@
|
||||
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
|
||||
@ -12,15 +13,14 @@ const props = defineProps<{
|
||||
|
||||
const float = props.data.float
|
||||
|
||||
let textContent = props.extra?.content || ''
|
||||
|
||||
textContent = textReplaceLink(textContent)
|
||||
|
||||
if (props.data.talk_type == 2) {
|
||||
textContent = textReplaceMention(textContent, '#1890ff')
|
||||
}
|
||||
|
||||
textContent = textReplaceEmoji(textContent)
|
||||
const textContent = computed(() => {
|
||||
let text = props.extra?.content || ''
|
||||
// text = textReplaceLink(text)
|
||||
if (props.data.talk_type == 2) {
|
||||
text = textReplaceMention(text, '#1890ff')
|
||||
}
|
||||
return textReplaceEmoji(text)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -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,57 +67,98 @@ 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"
|
||||
layerColor="#E3E3E3"
|
||||
color="#FFFFFF"
|
||||
color="#46299d"
|
||||
layer-color="#E3E3E3"
|
||||
:strokeWidth="6"
|
||||
:size="40"
|
||||
></wd-circle>
|
||||
@ -131,18 +172,32 @@ onMounted(() => {
|
||||
:width="70"
|
||||
:percent="props.data.uploadCurrent">
|
||||
</tm-progress> -->
|
||||
</div>
|
||||
<div v-show="open">
|
||||
<video :src="props.extra.url" controls @fullscreenchange="fullscreenchange" :id="props.extra.url">
|
||||
</video>
|
||||
<div class="upload-failed" v-if="data.uploadStatus === 3">
|
||||
<tm-icon :font-size="20" name="tmicon-times" color="#fff"></tm-icon>
|
||||
</div>
|
||||
</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;
|
||||
@ -200,10 +255,43 @@ 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>
|
||||
|
25
src/components/talk/message/system/SysGroupAdminMessage.vue
Normal file
@ -0,0 +1,25 @@
|
||||
<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>
|
19
src/components/talk/message/system/SysGroupDismissed.vue
Normal file
@ -0,0 +1,19 @@
|
||||
<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>
|
@ -0,0 +1,25 @@
|
||||
<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>
|
@ -24,7 +24,7 @@ const { showUserInfoModal } = useInject()
|
||||
<em v-show="index < extra.members.length - 1">、</em>
|
||||
</template>
|
||||
|
||||
<span>踢出群聊</span>
|
||||
<span>移出群聊</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -0,0 +1,23 @@
|
||||
<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>
|
@ -1,79 +1,114 @@
|
||||
<script setup>
|
||||
import {ref,nextTick} from 'vue'
|
||||
import WdPopup from "@/uni_modules/wot-design-uni/components/wd-popup/wd-popup.vue";
|
||||
import TmButton from "@/uni_modules/tmui/components/tm-button/tm-button.vue";
|
||||
import {language} from "@/uni_modules/tmui/tool/lib/language"
|
||||
const confirmState=ref(false)
|
||||
const cancel=ref(true)
|
||||
let onConfirm=null
|
||||
let onCancel=null
|
||||
const confirm=ref(true)
|
||||
const contentText=ref('')
|
||||
const imageRef=ref('')
|
||||
const confirmLabel=ref('确定')
|
||||
const cancelLabel=ref('取消')
|
||||
const confirmC=ref('#46299D')
|
||||
const cancelC=ref('#1A1A1A')
|
||||
const sendCancel=()=>{
|
||||
confirmState.value=false
|
||||
if (typeof onCancel==='function'){
|
||||
import { ref, nextTick } from 'vue'
|
||||
import WdPopup from '@/uni_modules/wot-design-uni/components/wd-popup/wd-popup.vue'
|
||||
import TmButton from '@/uni_modules/tmui/components/tm-button/tm-button.vue'
|
||||
import { language } from '@/uni_modules/tmui/tool/lib/language'
|
||||
const confirmState = ref(false)
|
||||
const cancel = ref(true)
|
||||
let onConfirm = null
|
||||
let onCancel = null
|
||||
const confirm = ref(true)
|
||||
const contentText = ref('')
|
||||
const subContentText = ref('')
|
||||
const subContentC = ref('#000')
|
||||
const imageRef = ref('')
|
||||
const confirmLabel = ref('确定')
|
||||
const cancelLabel = ref('取消')
|
||||
const confirmC = ref('#46299D')
|
||||
const cancelC = ref('#1A1A1A')
|
||||
const sendCancel = () => {
|
||||
confirmState.value = false
|
||||
if (typeof onCancel === 'function') {
|
||||
onCancel()
|
||||
}
|
||||
}
|
||||
const sendConfirm=()=>{
|
||||
confirmState.value=false
|
||||
if (typeof onConfirm==='function'){
|
||||
const sendConfirm = () => {
|
||||
confirmState.value = false
|
||||
if (typeof onConfirm === 'function') {
|
||||
onConfirm()
|
||||
}
|
||||
}
|
||||
const showConfirm=({content,image,onConfirm:confirm,onCancel:cancel,confirmText,cancelText,confirmColor,cancelColor})=>{
|
||||
confirmState.value=true
|
||||
contentText.value=content
|
||||
imageRef.value = image?image:''
|
||||
onConfirm=confirm
|
||||
onCancel=cancel
|
||||
const showConfirm = ({
|
||||
content,
|
||||
image,
|
||||
onConfirm: confirm,
|
||||
onCancel: cancel,
|
||||
confirmText,
|
||||
cancelText,
|
||||
confirmColor,
|
||||
cancelColor,
|
||||
subContent,
|
||||
subContentColor,
|
||||
}) => {
|
||||
confirmState.value = true
|
||||
contentText.value = content
|
||||
imageRef.value = image ? image : ''
|
||||
onConfirm = confirm
|
||||
onCancel = cancel
|
||||
confirmLabel.value = confirmText || confirmLabel.value
|
||||
cancelLabel.value = cancelText || cancelLabel.value
|
||||
confirmC.value = confirmColor || confirmC.value
|
||||
cancelC.value = cancelColor || cancelC.value
|
||||
subContentText.value = subContent || subContentText.value
|
||||
subContentC.value = subContentColor || subContentC.value
|
||||
}
|
||||
defineExpose({
|
||||
showConfirm
|
||||
showConfirm,
|
||||
})
|
||||
</script>
|
||||
<template>
|
||||
<wd-popup custom-style="border-radius: 16rpx;" modal-style="background-color: #000000;opacity: 0.6;" v-model="confirmState">
|
||||
<wd-popup
|
||||
custom-style="border-radius: 16rpx;"
|
||||
modal-style="background-color: #000000;opacity: 0.6;"
|
||||
v-model="confirmState"
|
||||
>
|
||||
<div class="flex flex-col w-[640rpx]">
|
||||
<div v-if="imageRef===''" class="flex justify-center items-center h-[288rpx] text-[32rpx] font-bold text-[#1A1A1A]">
|
||||
{{contentText}}
|
||||
<div
|
||||
v-if="imageRef === ''"
|
||||
class="flex flex-col justify-center items-center h-[288rpx] text-[#1A1A1A] popup-content"
|
||||
>
|
||||
<span class="text-[32rpx] font-bold">{{ contentText }}</span>
|
||||
<span
|
||||
class="text-[28rpx] font-regular"
|
||||
v-if="subContentText"
|
||||
:style="{ color: subContentC }"
|
||||
>
|
||||
{{ subContentText }}
|
||||
</span>
|
||||
</div>
|
||||
<div v-else class="flex flex-col items-center h-[456rpx] text-[32rpx] font-bold text-[#1A1A1A]" >
|
||||
<div class="wrap1 mt-[32rpx] mb-[44rpx]" >
|
||||
<img :src="imageRef" alt="">
|
||||
<div
|
||||
v-else
|
||||
class="flex flex-col items-center h-[456rpx] text-[32rpx] font-bold text-[#1A1A1A]"
|
||||
>
|
||||
<div class="wrap1 mt-[32rpx] mb-[44rpx]">
|
||||
<img :src="imageRef" alt="" />
|
||||
</div>
|
||||
<div class="mb-[56rpx]" > {{contentText}} </div>
|
||||
<div class="mb-[56rpx]">{{ contentText }}</div>
|
||||
</div>
|
||||
<div class="flex flex-grow border-t-solid border-[#E7E7E7] border-1rpx text-[32rpx]">
|
||||
<div
|
||||
class="flex flex-grow border-t-solid border-[#E7E7E7] border-1rpx text-[32rpx]"
|
||||
>
|
||||
<div class="flex justify-center items-center text-[#1A1A1A]">
|
||||
<tm-button
|
||||
@click="sendCancel"
|
||||
:width="319"
|
||||
@touchstart="cancel=false"
|
||||
@touchend="cancel=true"
|
||||
@touchstart="cancel = false"
|
||||
@touchend="cancel = true"
|
||||
:fontSize="32"
|
||||
:height="112"
|
||||
:margin="[0]"
|
||||
:font-color="cancelC"
|
||||
:transprent="cancel"
|
||||
text
|
||||
:label="cancelLabel"></tm-button>
|
||||
:label="cancelLabel"
|
||||
></tm-button>
|
||||
</div>
|
||||
<div class="h-[112rpx] w-[1rpx] bg-[#E7E7E7]"></div>
|
||||
<div class="flex justify-center items-center text-[#CF3050]">
|
||||
<tm-button
|
||||
@click="sendConfirm"
|
||||
@touchstart="confirm=false"
|
||||
@touchend="confirm=true"
|
||||
@touchstart="confirm = false"
|
||||
@touchend="confirm = true"
|
||||
:width="319"
|
||||
:fontSize="32"
|
||||
:transprent="confirm"
|
||||
@ -81,17 +116,23 @@ defineExpose({
|
||||
:margin="[0]"
|
||||
:font-color="confirmC"
|
||||
text
|
||||
:label="confirmLabel"></tm-button>
|
||||
:label="confirmLabel"
|
||||
></tm-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</wd-popup>
|
||||
</template>
|
||||
<style scoped lang="scss">
|
||||
.popup-content {
|
||||
span {
|
||||
line-height: 44rpx;
|
||||
}
|
||||
}
|
||||
.wrap1 {
|
||||
img {
|
||||
width: 381.59rpx;
|
||||
height: 280.14rpx;
|
||||
}
|
||||
}
|
||||
img {
|
||||
width: 381.59rpx;
|
||||
height: 280.14rpx;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -1,11 +0,0 @@
|
||||
<script setup>
|
||||
import tmNavbar from '@/uni_modules/tmui/components/tm-navbar/tm-navbar.vue';
|
||||
import {useStatus} from "@/store/status"
|
||||
const {currentNavbar} = useStatus()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<tm-navbar :hideBack="false" hideHome :title="currentNavbar.title"/>
|
||||
</template>
|
||||
<style scoped lang="scss">
|
||||
</style>
|
@ -1,20 +1,30 @@
|
||||
<script setup>
|
||||
import {ref} from 'vue'
|
||||
import ZPaging from '@/uni_modules/z-paging/components/z-paging/z-paging.vue'
|
||||
const pagingRef=ref(null)
|
||||
import { ref } from "vue";
|
||||
import ZPaging from "@/uni_modules/z-paging/components/z-paging/z-paging.vue";
|
||||
const pagingRef = ref(null);
|
||||
defineExpose({
|
||||
pagingRef
|
||||
})
|
||||
pagingRef,
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<z-paging ref="pagingRef" :show-scrollbar="false" :refresher-end-bounce-enabled="false" :refresher-complete-duration="500" :refresher-complete-delay="500" :refresher-fps="60" show-refresher-update-time use-virtual-list :fixed="false" v-bind="{ ...$attrs, ...$props}">
|
||||
<template v-for="(slot, name) in $slots" :key="name" #[name]>
|
||||
<slot :name="name"></slot>
|
||||
</template>
|
||||
</z-paging>
|
||||
<z-paging
|
||||
ref="pagingRef"
|
||||
:show-scrollbar="false"
|
||||
:refresher-end-bounce-enabled="false"
|
||||
:refresher-complete-duration="500"
|
||||
:refresher-complete-delay="500"
|
||||
:refresher-fps="60"
|
||||
show-refresher-update-time
|
||||
use-virtual-list
|
||||
:fixed="false"
|
||||
v-bind="{ ...$attrs, ...$props }"
|
||||
>
|
||||
<template v-for="(slot, name) in $slots" :key="name" #[name]>
|
||||
<slot :name="name"></slot>
|
||||
</template>
|
||||
</z-paging>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
</style>
|
||||
|
@ -30,6 +30,7 @@ class Connect {
|
||||
},
|
||||
// Websocket 连接成功回调方法
|
||||
onOpen: () => {
|
||||
console.log("socket已连接")
|
||||
// 更新 WebSocket 连接状态
|
||||
useUserStore().updateSocketStatus(true)
|
||||
// online.value = true;
|
||||
@ -37,6 +38,7 @@ class Connect {
|
||||
},
|
||||
// Websocket 断开连接回调方法
|
||||
onClose: () => {
|
||||
console.log("socket已断开")
|
||||
// 更新 WebSocket 连接状态
|
||||
useUserStore().updateSocketStatus(false)
|
||||
// online.value = false
|
||||
|
@ -11,12 +11,13 @@ export const ChatMsgTypeLogin = 10 // 登录消息
|
||||
export const ChatMsgTypeVote = 11 // 投票消息
|
||||
export const ChatMsgTypeMixed = 12 // 混合消息
|
||||
export const ChatMsgTypeGroupNotice = 13 // 群公告消息
|
||||
export const ChatMsgTypeLink = 14 // 链接消息
|
||||
|
||||
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 // 群禁言
|
||||
@ -25,6 +26,9 @@ 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 ChatMsgTypeMapping = {
|
||||
[ChatMsgTypeText]: '[文本消息]',
|
||||
@ -40,18 +44,23 @@ export const ChatMsgTypeMapping = {
|
||||
[ChatMsgTypeCode]: '[代码消息]',
|
||||
[ChatMsgTypeMixed]: '[图文消息]',
|
||||
[ChatMsgTypeGroupNotice]: '[群公告]',
|
||||
[ChatMsgTypeLink]: '[链接]',
|
||||
[ChatMsgSysText]: '[系统消息]',
|
||||
[ChatMsgSysGroupCreate]: '[创建群消息]',
|
||||
[ChatMsgSysGroupMemberJoin]: '[加入群消息]',
|
||||
[ChatMsgSysGroupMemberQuit]: '[退出群消息]',
|
||||
[ChatMsgSysGroupMemberKicked]: '[踢出群消息]',
|
||||
[ChatMsgSysGroupMemberKicked]: '[移出群消息]',
|
||||
[ChatMsgSysGroupMessageRevoke]: '[撤回消息]',
|
||||
[ChatMsgSysGroupDismissed]: '[群解散消息]',
|
||||
[ChatMsgSysGroupMuted]: '[群禁言消息]',
|
||||
[ChatMsgSysGroupCancelMuted]: '[群解除禁言消息]',
|
||||
[ChatMsgSysGroupMemberMuted]: '[群成员禁言消息]',
|
||||
[ChatMsgSysGroupMemberCancelMuted]: '[群成员解除禁言消息]',
|
||||
[ChatMsgSysGroupNotice]: '[群公告]'
|
||||
[ChatMsgSysGroupNotice]: '[群公告]',
|
||||
[ChatMsgSysGroupTransfer]: '[转让群主]',
|
||||
[ChatMsgSysGroupAdmin]: '[设置管理员]',
|
||||
[ChatMsgSysGroupMemberRemoved]: '[移出群成员消息]',
|
||||
[ChatMsgSysGroupInfoChange]: '[群信息更新]'
|
||||
}
|
||||
|
||||
// 消息类型 - 消息组件 映射关系
|
||||
@ -69,18 +78,22 @@ export const MessageComponents = {
|
||||
[ChatMsgTypeCode]: 'code-message',
|
||||
[ChatMsgTypeMixed]: 'mixed-message',
|
||||
[ChatMsgTypeGroupNotice]: 'group-notice-message',
|
||||
[ChatMsgTypeLink]: 'link-message',
|
||||
[ChatMsgSysText]: 'sys-text-message',
|
||||
[ChatMsgSysGroupCreate]: 'sys-group-create-message',
|
||||
[ChatMsgSysGroupMemberJoin]: 'sys-group-join-message',
|
||||
[ChatMsgSysGroupMemberQuit]: 'sys-group-member-quit-message',
|
||||
[ChatMsgSysGroupMemberKicked]: 'sys-group-member-kicked-message',
|
||||
// [ChatMsgSysGroupMessageRevoke]: '[撤回消息]',
|
||||
// [ChatMsgSysGroupDismissed]: '[群解散消息]',
|
||||
[ChatMsgSysGroupDismissed]: 'sys-group-dismissed',
|
||||
[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'
|
||||
[ChatMsgSysGroupTransfer]: 'sys-group-transfer-message',
|
||||
[ChatMsgSysGroupAdmin]:'sys-group-admin-message',
|
||||
[ChatMsgSysGroupMemberRemoved]:'sys-group-member-removed-message',
|
||||
[ChatMsgSysGroupInfoChange]:'sys-group-info-change-message'
|
||||
}
|
||||
|
||||
// 可转发的消息类型
|
||||
@ -92,5 +105,6 @@ export const ForwardableMessageType = [
|
||||
ChatMsgTypeVideo,
|
||||
ChatMsgTypeFile,
|
||||
ChatMsgTypeLocation,
|
||||
ChatMsgTypeCard
|
||||
ChatMsgTypeCard,
|
||||
ChatMsgTypeLink
|
||||
]
|
||||
|
@ -68,20 +68,38 @@ class Revoke extends Base {
|
||||
}
|
||||
|
||||
handle() {
|
||||
const {updateDialogueRecord} = useDialogueListStore()
|
||||
const { updateDialogueRecord } = useDialogueListStore()
|
||||
useTalkStore().updateItem({
|
||||
index_name: this.getIndexName(),
|
||||
msg_text: this.resource.text,
|
||||
updated_at: parseTime(new Date())
|
||||
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()),
|
||||
})
|
||||
|
||||
useDialogueStore().updateDialogueRecord({
|
||||
msg_id: this.msg_id,
|
||||
is_revoke: 1
|
||||
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,
|
||||
})
|
||||
updateDialogueRecord({
|
||||
msg_id: this.msg_id,
|
||||
is_revoke: 1
|
||||
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,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -5,8 +5,16 @@ 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 } from '@/store'
|
||||
import {
|
||||
ServeClearTalkUnreadNum,
|
||||
ServeCreateTalkList,
|
||||
} from '@/api/chat/index.js'
|
||||
import {
|
||||
useTalkStore,
|
||||
useDialogueStore,
|
||||
useDialogueListStore,
|
||||
useGroupStore,
|
||||
} from '@/store'
|
||||
|
||||
/**
|
||||
* 好友状态事件
|
||||
@ -32,6 +40,11 @@ class Talk extends Base {
|
||||
*/
|
||||
talk_type = 0
|
||||
|
||||
/**
|
||||
* 文件上传唯一随机值
|
||||
*/
|
||||
fileNum = ''
|
||||
|
||||
/**
|
||||
* 初始化构造方法
|
||||
*
|
||||
@ -43,6 +56,10 @@ 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()
|
||||
@ -89,11 +106,10 @@ class Talk extends Base {
|
||||
play() {
|
||||
// 客户端有消息提示
|
||||
// if (isElectronMode()) return
|
||||
|
||||
// useSettingsStore().isPromptTone && palyMusic()
|
||||
}
|
||||
|
||||
handle() {
|
||||
async handle() {
|
||||
// 不是自己发送的消息则需要播放提示音
|
||||
if (!this.isCurrSender()) {
|
||||
this.play()
|
||||
@ -101,7 +117,21 @@ class Talk extends Base {
|
||||
|
||||
// 判断会话列表是否存在,不存在则创建
|
||||
if (useTalkStore().findTalkIndex(this.getIndexName()) == -1) {
|
||||
return this.addTalkItem()
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
// 判断当前是否正在和好友对话
|
||||
@ -109,6 +139,37 @@ 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() {
|
||||
if (typeof plus !== 'undefined') {
|
||||
let OAWebView = plus.webview.all()
|
||||
OAWebView.forEach((webview) => {
|
||||
if (webview.id === 'webviewId1') {
|
||||
webview.evalJS(`updateUnreadMsgNumAdd()`)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
document.addEventListener('plusready', () => {
|
||||
let OAWebView = plus.webview.all()
|
||||
OAWebView.forEach((webview) => {
|
||||
if (webview.id === 'webviewId1') {
|
||||
webview.evalJS(`updateUnreadMsgNumAdd()`)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -123,7 +184,6 @@ class Talk extends Base {
|
||||
// lang: 'zh-CN',
|
||||
// body: '您有新的消息请注意查收'
|
||||
// })
|
||||
|
||||
// notification.onclick = () => {
|
||||
// notification.close()
|
||||
// }
|
||||
@ -151,12 +211,16 @@ class Talk extends Base {
|
||||
|
||||
ServeCreateTalkList({
|
||||
talk_type,
|
||||
receiver_id
|
||||
}).then(({ code, data }) => {
|
||||
receiver_id,
|
||||
}).then(async ({ code, data }) => {
|
||||
if (code == 200) {
|
||||
let item = formatTalkItem(data)
|
||||
item.unread_num = 1
|
||||
if (!item?.is_disturb) {
|
||||
item.unread_num = 1
|
||||
this.updateUnreadMsgNumAdd()
|
||||
}
|
||||
useTalkStore().addItem(item)
|
||||
await useTalkStore().loadTalkList()
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -166,24 +230,76 @@ 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].includes(record.msg_type)) {
|
||||
if ([1102, 1103, 1104, 1115].includes(record.msg_type)) {
|
||||
useDialogueStore().updateGroupMembers()
|
||||
}
|
||||
addDialogueRecord([newRecord],'add')
|
||||
addChatRecord(this.getIndexName(),newRecord)
|
||||
|
||||
//群解散时,需要更新群成员权限
|
||||
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)
|
||||
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)
|
||||
}
|
||||
|
||||
// 获取聊天面板元素节点
|
||||
@ -191,7 +307,8 @@ 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(() => {
|
||||
@ -204,14 +321,15 @@ 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,
|
||||
// })
|
||||
}
|
||||
}
|
||||
|
||||
@ -222,8 +340,31 @@ 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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -11,8 +11,13 @@ import { reactive, nextTick, computed, h, inject } from 'vue'
|
||||
// EditTwo,
|
||||
// IdCard
|
||||
// } from '@icon-park/vue-next'
|
||||
import { ServeTopTalkList, ServeDeleteTalkList, ServeSetNotDisturb } from '@/api/chat'
|
||||
import { useDialogueStore, useTalkStore } from '@/store'
|
||||
import {
|
||||
ServeTopTalkList,
|
||||
ServeDeleteTalkList,
|
||||
ServeSetNotDisturb,
|
||||
ServeClearTalkUnreadNum,
|
||||
} from '@/api/chat'
|
||||
import { useDialogueStore, useTalkStore, useDialogueListStore } from '@/store'
|
||||
import { ServeSecedeGroup } from '@/api/group'
|
||||
// import { ServeDeleteContact, ServeEditContactRemark } from '@/api/contact'
|
||||
// import { NInput } from 'naive-ui'
|
||||
@ -23,11 +28,12 @@ export function useSessionMenu() {
|
||||
show: false,
|
||||
x: 0,
|
||||
y: 0,
|
||||
item: {}
|
||||
item: {},
|
||||
})
|
||||
|
||||
const dialogueStore = useDialogueStore()
|
||||
const talkStore = useTalkStore()
|
||||
const dialogueListStore = useDialogueListStore()
|
||||
|
||||
const user = inject('$user')
|
||||
|
||||
@ -105,6 +111,7 @@ export function useSessionMenu() {
|
||||
|
||||
const onDeleteTalk = (index_name = '') => {
|
||||
talkStore.delItem(index_name)
|
||||
dialogueListStore.delDialogueStorage(index_name)
|
||||
|
||||
index_name === indexName.value && dialogueStore.$reset()
|
||||
}
|
||||
@ -116,10 +123,22 @@ 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(() => {
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -129,13 +148,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)
|
||||
@ -151,12 +170,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)
|
||||
@ -199,7 +218,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('已退出群聊')
|
||||
@ -208,7 +227,7 @@ export function useSessionMenu() {
|
||||
message.error(message)
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@ -257,12 +276,18 @@ 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 }
|
||||
return {
|
||||
dropdown,
|
||||
onCloseContextMenu,
|
||||
onContextMenuTalkHandle,
|
||||
onToTopTalk,
|
||||
onRemoveTalk,
|
||||
}
|
||||
}
|
||||
|
@ -7,25 +7,29 @@ import lodash from 'lodash'
|
||||
|
||||
export const useTalkRecord = (uid) => {
|
||||
const dialogueStore = useDialogueStore()
|
||||
const { getDialogueList,addDialogueRecord,zpagingComplete } = useDialogueListStore()
|
||||
const {
|
||||
getDialogueList,
|
||||
addDialogueRecord,
|
||||
zpagingComplete,
|
||||
} = useDialogueListStore()
|
||||
const records = computed(() => {
|
||||
const dialogueList = getDialogueList(dialogueStore.index_name)
|
||||
if(dialogueList){
|
||||
if (dialogueList) {
|
||||
return lodash.cloneDeep(dialogueList.records).reverse()
|
||||
}
|
||||
return lodash.cloneDeep(dialogueStore.records)?.reverse()
|
||||
} )
|
||||
})
|
||||
|
||||
const location = reactive({
|
||||
msgid: '',
|
||||
num: 0
|
||||
num: 0,
|
||||
})
|
||||
|
||||
const loadConfig = reactive({
|
||||
receiver_id: 0,
|
||||
talk_type: 0,
|
||||
status: 0,
|
||||
cursor: 0
|
||||
cursor: 0,
|
||||
})
|
||||
|
||||
const onJumpMessage = (msgid) => {
|
||||
@ -49,7 +53,7 @@ export const useTalkRecord = (uid) => {
|
||||
|
||||
return el?.scrollTo({
|
||||
top: 0,
|
||||
behavior: 'smooth'
|
||||
behavior: 'smooth',
|
||||
})
|
||||
}
|
||||
|
||||
@ -57,7 +61,7 @@ export const useTalkRecord = (uid) => {
|
||||
location.num = 0
|
||||
|
||||
element?.scrollIntoView({
|
||||
behavior: 'smooth'
|
||||
behavior: 'smooth',
|
||||
})
|
||||
|
||||
addClass(element, 'border')
|
||||
@ -70,7 +74,7 @@ export const useTalkRecord = (uid) => {
|
||||
// 加载数据列表
|
||||
const load = async (params) => {
|
||||
const request = {
|
||||
limit:30,
|
||||
limit: 30,
|
||||
...params,
|
||||
talk_type: params.talk_type,
|
||||
receiver_id: params.receiver_id,
|
||||
@ -81,9 +85,9 @@ export const useTalkRecord = (uid) => {
|
||||
|
||||
let scrollHeight = 0
|
||||
const el = document.getElementById('imChatPanel')
|
||||
// if (el) {
|
||||
// scrollHeight = el.scrollHeight
|
||||
// }
|
||||
if (el) {
|
||||
scrollHeight = el.scrollHeight
|
||||
}
|
||||
const { data, code } = await ServeTalkRecords(request)
|
||||
if (code != 200) {
|
||||
return (loadConfig.status = 1)
|
||||
@ -104,10 +108,14 @@ export const useTalkRecord = (uid) => {
|
||||
|
||||
const reverseItems = lodash.cloneDeep(items).reverse()
|
||||
dialogueStore.unshiftDialogueRecord(reverseItems)
|
||||
addDialogueRecord(params.direction=='down'?reverseItems:items,params.direction=='down'?'add':'unshift')
|
||||
zpagingComplete(dialogueStore.index_name)
|
||||
addDialogueRecord(
|
||||
params.direction == 'down' ? reverseItems : items,
|
||||
params.direction == 'down' ? 'add' : 'unshift',
|
||||
)
|
||||
zpagingComplete(dialogueStore.index_name, reverseItems)
|
||||
const dialogueList = getDialogueList(dialogueStore.index_name)
|
||||
loadConfig.status = dialogueList.records?.[0]?.sequence > 1 ? 1 : 2
|
||||
loadConfig.status =
|
||||
dialogueList.records?.[0]?.sequence > 1 && data?.items?.length > 0 ? 1 : 2
|
||||
// loadConfig.cursor = data.cursor
|
||||
|
||||
nextTick(() => {
|
||||
@ -133,17 +141,19 @@ export const useTalkRecord = (uid) => {
|
||||
|
||||
const onRefreshLoad = () => {
|
||||
let dialogueList = getDialogueList(dialogueStore.index_name)
|
||||
if (dialogueList.records[0].sequence === 1 ) {
|
||||
if (dialogueList.records[0].sequence === 1) {
|
||||
return false
|
||||
}
|
||||
if (loadConfig.status == 1) {
|
||||
let filterList = dialogueList.records.filter(item=>item.sequence !== -1)
|
||||
let filterList = dialogueList.records.filter(
|
||||
(item) => item.sequence !== -1,
|
||||
)
|
||||
loadConfig.cursor = filterList[0].sequence
|
||||
load({
|
||||
receiver_id: loadConfig.receiver_id,
|
||||
talk_type: loadConfig.talk_type,
|
||||
direction:'up',
|
||||
no_limit:0,
|
||||
direction: 'up',
|
||||
no_limit: 0,
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -152,12 +162,14 @@ export const useTalkRecord = (uid) => {
|
||||
let dialogueList = getDialogueList(params.index_name)
|
||||
if (!dialogueList) {
|
||||
loadConfig.cursor = 0
|
||||
}else{
|
||||
let filterList = dialogueList.records.filter(item=>item.sequence !== -1)
|
||||
if(params.direction=='up'){
|
||||
} else {
|
||||
let filterList = dialogueList.records.filter(
|
||||
(item) => item.sequence !== -1,
|
||||
)
|
||||
if (params.direction == 'up') {
|
||||
loadConfig.cursor = filterList?.[0]?.sequence
|
||||
}else{
|
||||
loadConfig.cursor = filterList?.[filterList.length-1]?.sequence
|
||||
} else {
|
||||
loadConfig.cursor = filterList?.[filterList.length - 1]?.sequence
|
||||
}
|
||||
}
|
||||
loadConfig.receiver_id = params.receiver_id
|
||||
|
136
src/main.js
@ -1,52 +1,142 @@
|
||||
import customNavbar from '@/components/custom-navbar/index'
|
||||
|
||||
import { createSSRApp } from 'vue'
|
||||
import App from './App.vue'
|
||||
import dayjs from "dayjs";
|
||||
import dayjs from 'dayjs'
|
||||
import 'virtual:uno.css'
|
||||
import VConsole from "vconsole";
|
||||
import VConsole from 'vconsole'
|
||||
import '@/utils/uni.webview.js'
|
||||
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 { vLoading } from "@/components/x-loading/index.js"
|
||||
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'
|
||||
const {showMessage}=messagePopup()
|
||||
import { useDialogueStore, useTalkStore, useUserStore, useDialogueListStore } from '@/store'
|
||||
import {uniStorage} from "@/utils/uniStorage.js"
|
||||
|
||||
const { showMessage } = messagePopup()
|
||||
dayjs.locale('zh-cn')
|
||||
if (import.meta.env.VITE_SHOW_CONSOLE){
|
||||
if (import.meta.env.VITE_SHOW_CONSOLE === 'true') {
|
||||
new VConsole()
|
||||
}
|
||||
export function createApp() {
|
||||
const app = createSSRApp(App)
|
||||
plugins.setPinia(app)
|
||||
plugins.setComponents(app)
|
||||
app.use(tmui,{...config})
|
||||
app.directive("loading", vLoading)
|
||||
app.use(tmui, { ...config })
|
||||
app.directive('loading', vLoading)
|
||||
app.mixin(pageAnimation)
|
||||
app.component('x-loaderror',xLoaderror)
|
||||
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) => {
|
||||
const originalValue = e.target.value;
|
||||
const newValue = originalValue.replace(/\s/g, '');
|
||||
const originalValue = e.target.value
|
||||
const newValue = originalValue.replace(/\s/g, '')
|
||||
if (originalValue !== newValue) {
|
||||
e.target.value = newValue;
|
||||
e.target.dispatchEvent(new Event('input'));
|
||||
e.target.value = newValue
|
||||
e.target.dispatchEvent(new Event('input'))
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
//获取当前聊天页面所在页面,并通过当前的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('===准备创建本地通知栏消息')
|
||||
let OAWebView = plus.webview.all()
|
||||
OAWebView.forEach((webview, index) => {
|
||||
if (webview.id === 'webviewId1') {
|
||||
webview.evalJS(`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'
|
||||
});
|
||||
};
|
||||
|
||||
//处理OA、墨册强制刷新时,聊天同步强制刷新
|
||||
window.doLocationRefresh = () => {
|
||||
uniStorage.removeItem('dialogueList')
|
||||
uniStorage.removeItem('dialogue')
|
||||
useUserStore().loadSetting()
|
||||
useDialogueListStore().dialogueList.value = []
|
||||
// location.reload(true)
|
||||
}
|
||||
|
||||
//检查聊天页面是否可用
|
||||
window.checkChatWebviewAvailable = () => {
|
||||
let OAWebView = plus.webview.all()
|
||||
OAWebView.forEach((webview, index) => {
|
||||
if (webview.id === 'webviewId1') {
|
||||
webview.evalJS(`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)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
window.message = ['success', 'error', 'warning'].reduce((acc, type) => {
|
||||
acc[type] = (message) => {
|
||||
if (typeof message === 'string') {
|
||||
showMessage({ type, message });
|
||||
showMessage({ type, message })
|
||||
} else if (typeof message === 'object') {
|
||||
showMessage({ type, ...message });
|
||||
showMessage({ type, ...message })
|
||||
}
|
||||
};
|
||||
return acc;
|
||||
}, {});
|
||||
}
|
||||
return acc
|
||||
}, {})
|
||||
return {
|
||||
app,
|
||||
}
|
||||
|
@ -5,8 +5,7 @@
|
||||
"^tm-(.*)": "@/tmui/components/tm-$1/tm-$1.vue"
|
||||
}
|
||||
},
|
||||
"pages": [
|
||||
{
|
||||
"pages": [{
|
||||
"path": "pages/index/index",
|
||||
"type": "page",
|
||||
"style": {
|
||||
@ -38,22 +37,6 @@
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/chooseDeps/index",
|
||||
"type": "page",
|
||||
"style": {
|
||||
"navigationStyle": "custom",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/chooseMembers/index",
|
||||
"type": "page",
|
||||
"style": {
|
||||
"navigationStyle": "custom",
|
||||
"enablePullDownRefresh":false
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/chooseGroupAdmin/index",
|
||||
"type": "page",
|
||||
@ -186,6 +169,30 @@
|
||||
"navigationStyle": "custom",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/chooseByDeps/index",
|
||||
"type": "page",
|
||||
"style": {
|
||||
"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
|
||||
}
|
||||
}
|
||||
],
|
||||
"globalStyle": {
|
||||
|
@ -9,7 +9,7 @@
|
||||
? ''
|
||||
: '0',
|
||||
}"
|
||||
v-for="(memberItem, memberIndex) in props?.memberList"
|
||||
v-for="(memberItem, memberIndex) in sortedMemberList"
|
||||
@click="toUserDetailPage(memberItem)"
|
||||
>
|
||||
<div
|
||||
@ -19,12 +19,12 @@
|
||||
"
|
||||
>
|
||||
<div class="group-member-avatar">
|
||||
<img v-if="memberItem.avatar" :src="memberItem.avatar" />
|
||||
<span v-if="!memberItem.avatar" class="text-[24rpx] font-bold">
|
||||
<img v-if="memberItem?.avatar" :src="memberItem?.avatar" />
|
||||
<span v-if="!memberItem?.avatar" class="text-[24rpx] font-bold">
|
||||
{{
|
||||
memberItem.nickname.length >= 2
|
||||
? memberItem.nickname.slice(-2)
|
||||
: memberItem.nickname
|
||||
(memberItem?.nickname || memberItem?.nickName)?.length >= 2
|
||||
? (memberItem?.nickname || memberItem?.nickName).slice(-2)
|
||||
: memberItem?.nickname || memberItem?.nickName
|
||||
}}
|
||||
</span>
|
||||
</div>
|
||||
@ -38,25 +38,40 @@
|
||||
</div>
|
||||
<div class="group-member-name">
|
||||
<span class="text-[24rpx] font-regular">
|
||||
{{ memberItem.nickname }}
|
||||
{{ memberItem.nickname || memberItem.nickName }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="group-member-list-each">
|
||||
<div
|
||||
class="group-member-list-each"
|
||||
@click="groupAddMembers"
|
||||
v-if="
|
||||
(props?.groupType == 1 || props?.groupType == 3) &&
|
||||
!props?.hideAddRemoveBtns
|
||||
"
|
||||
>
|
||||
<div class="group-member-each">
|
||||
<div class="group-member-avatar" :style="{ background: 'unset' }">
|
||||
<img src="/src/static/image/chatSettings/add-member.png" />
|
||||
<img src="@/static/image/chatSettings/add-member.png" />
|
||||
</div>
|
||||
<div class="group-member-name">
|
||||
<span class="text-[24rpx] font-regular">添加</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="group-member-list-each" v-if="props?.is_manager">
|
||||
<div
|
||||
class="group-member-list-each"
|
||||
@click="groupRemoveMembers"
|
||||
v-if="
|
||||
props?.is_manager &&
|
||||
(props?.groupType == 1 || props?.groupType == 3) &&
|
||||
!props?.hideAddRemoveBtns
|
||||
"
|
||||
>
|
||||
<div class="group-member-each">
|
||||
<div class="group-member-avatar" :style="{ background: 'unset' }">
|
||||
<img src="/src/static/image/chatSettings/remove-member.png" />
|
||||
<img src="@/static/image/chatSettings/remove-member.png" />
|
||||
</div>
|
||||
<div class="group-member-name">
|
||||
<span class="text-[24rpx] font-regular">移除</span>
|
||||
@ -66,19 +81,69 @@
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import { defineProps } from 'vue'
|
||||
import { defineProps, computed } from 'vue'
|
||||
const props = defineProps({
|
||||
memberList: Array, //人员列表
|
||||
memberListsLimit: Number, //人员列表数量限制
|
||||
is_manager: Boolean, //是否管理员
|
||||
groupType: Number, //群类型
|
||||
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;
|
||||
|
||||
// 如果leader状态不同,优先按leader排序
|
||||
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.erp_user_id)
|
||||
console.log(userItem)
|
||||
uni.navigateTo({
|
||||
url:
|
||||
'/pages/dialog/dialogDetail/userDetail?erpUserId=' + userItem.erp_user_id,
|
||||
'/pages/dialog/dialogDetail/userDetail?erpUserId=' +
|
||||
(userItem.erp_user_id || userItem.ID),
|
||||
})
|
||||
}
|
||||
|
||||
//点击群聊拉人
|
||||
const groupAddMembers = () => {
|
||||
uni.navigateTo({
|
||||
url:
|
||||
'/pages/chooseByDeps/index?chooseMode=2&preSelectedMembers=' +
|
||||
encodeURIComponent(JSON.stringify(props?.memberList)),
|
||||
})
|
||||
}
|
||||
|
||||
//点击群聊移除人
|
||||
const groupRemoveMembers = () => {
|
||||
uni.navigateTo({
|
||||
url:
|
||||
'/pages/chatSettings/groupManage/selectMembers?manageType=removeMembers',
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
@ -7,6 +7,10 @@
|
||||
? 'select-member-item-card'
|
||||
: 'select-member-item-list'
|
||||
"
|
||||
:style="{
|
||||
padding:
|
||||
props?.manageType === 'removeMembers' ? '18rpx 62rpx 18rpx 34rpx' : '',
|
||||
}"
|
||||
>
|
||||
<div class="member-info">
|
||||
<slot name="left"></slot>
|
||||
@ -39,12 +43,21 @@
|
||||
{{ $t('silence.tag.hasDone') }}
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="is-mine-tag"
|
||||
v-if="
|
||||
props?.memberItem?.is_mine && props?.manageType === 'removeMembers'
|
||||
"
|
||||
>
|
||||
<img src="@/static/image/chatSettings/is-mine.png" />
|
||||
</div>
|
||||
<div
|
||||
class="is-admin-tag"
|
||||
v-if="
|
||||
(props?.memberItem?.leader === 1 ||
|
||||
props?.memberItem?.leader === 2) &&
|
||||
props?.manageType === 'admin'
|
||||
(props?.manageType === 'admin' ||
|
||||
props?.manageType === 'removeMembers')
|
||||
"
|
||||
>
|
||||
<span class="text-[28rpx] font-regular">
|
||||
@ -135,16 +148,31 @@ const clickItem = () => {
|
||||
}
|
||||
}
|
||||
.select-member-tags {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
.done-silence-tag {
|
||||
span {
|
||||
color: #b4b4b4;
|
||||
line-height: 40rpx;
|
||||
}
|
||||
}
|
||||
.is-mine-tag {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
img {
|
||||
width: 52rpx;
|
||||
height: 52rpx;
|
||||
}
|
||||
}
|
||||
.is-admin-tag {
|
||||
padding: 6rpx 12rpx;
|
||||
border: 2rpx solid #b4b4b4;
|
||||
border-radius: 8rpx;
|
||||
margin: 0 0 0 20rpx;
|
||||
span {
|
||||
color: #b4b4b4;
|
||||
line-height: 40rpx;
|
||||
|
803
src/pages/chatSettings/components/selectMemberByAlphabet.vue
Normal file
@ -0,0 +1,803 @@
|
||||
<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, //1禁言2解禁
|
||||
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, //1管理员,2不是管理员
|
||||
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>
|
@ -5,7 +5,10 @@
|
||||
<span class="text-[32rpx] font-regular">{{ props?.item?.label }}</span>
|
||||
</div>
|
||||
<div class="item-main-value" @click="toManagePage(props?.item)">
|
||||
<span class="text-[32rpx] font-regular" v-if="props?.item?.value">
|
||||
<span
|
||||
class="text-[32rpx] font-regular"
|
||||
v-if="props?.item?.value && props?.item?.value.length < 29"
|
||||
>
|
||||
{{ props?.item?.value }}
|
||||
</span>
|
||||
<img
|
||||
@ -13,9 +16,10 @@
|
||||
props?.item?.hasPointer &&
|
||||
(props?.isManager ||
|
||||
(!props?.isManager &&
|
||||
props?.item?.label !== $t('chat.settings.groupName')))
|
||||
(props?.item?.label !== $t('chat.settings.groupName') ||
|
||||
props?.item?.label !== $t('chat.settings.groupType'))))
|
||||
"
|
||||
src="/src/static/image/chatSettings/pointer.png"
|
||||
src="@/static/image/chatSettings/pointer.png"
|
||||
/>
|
||||
<tm-switch
|
||||
:width="88"
|
||||
@ -30,7 +34,13 @@
|
||||
></tm-switch>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item-sub" v-if="props?.item?.subValue">
|
||||
<div
|
||||
class="item-sub"
|
||||
v-if="props?.item?.value && props?.item?.value.length > 28"
|
||||
>
|
||||
<span class="text-[32rpx] font-regular">{{ props?.item?.value }}</span>
|
||||
</div>
|
||||
<div class="item-sub cab1" v-if="props?.item?.subValue">
|
||||
<span class="text-[32rpx] font-regular">{{ props?.item?.subValue }}</span>
|
||||
</div>
|
||||
</div>
|
||||
@ -61,7 +71,14 @@ const props = defineProps({
|
||||
})
|
||||
const emits = defineEmits(['toManagePage', 'changeSwitch'])
|
||||
const toManagePage = (item) => {
|
||||
emits('toManagePage', item.label)
|
||||
if (
|
||||
props?.item?.hasPointer &&
|
||||
(props?.isManager ||
|
||||
(!props?.isManager &&
|
||||
props?.item?.label !== t('chat.settings.groupName')))
|
||||
) {
|
||||
emits('toManagePage', item.label)
|
||||
}
|
||||
}
|
||||
const modelValue = computed(() => {
|
||||
let switchStatus = false
|
||||
@ -111,6 +128,7 @@ const changeSwitch = (e, item) => {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
padding: 0 0 0 20rpx;
|
||||
span {
|
||||
line-height: 44rpx;
|
||||
color: #747474;
|
||||
@ -125,14 +143,17 @@ const changeSwitch = (e, item) => {
|
||||
|
||||
.item-sub {
|
||||
margin: 28rpx 0 0;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
max-width: 100%;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
span {
|
||||
line-height: 44rpx;
|
||||
color: #747474;
|
||||
}
|
||||
}
|
||||
.cab1 {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
max-width: 100%;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -3,51 +3,126 @@
|
||||
<div class="root">
|
||||
<ZPaging ref="zPaging" :show-scrollbar="false">
|
||||
<template #top>
|
||||
<tm-navbar :hideBack="false" hideHome title="" :leftWidth="220">
|
||||
<div class="navBar-title flex flex-col items-center justify-center">
|
||||
<span class="text-[34rpx] font-medium">
|
||||
{{ $t('chat.settings.editAvatar') }}
|
||||
</span>
|
||||
</div>
|
||||
</tm-navbar>
|
||||
<customNavbar :title="$t('chat.settings.editAvatar')"></customNavbar>
|
||||
</template>
|
||||
<div class="edit-group-info">
|
||||
<div class="group-avatar">
|
||||
<img :src="state.groupAvatar" />
|
||||
<avatarModule
|
||||
:mode="2"
|
||||
:avatar="groupParams?.groupInfo?.avatar"
|
||||
:groupType="groupParams?.groupInfo?.group_type"
|
||||
:customStyle="{
|
||||
width: '460rpx',
|
||||
height: '460rpx',
|
||||
borderRadius: '0',
|
||||
}"
|
||||
></avatarModule>
|
||||
</div>
|
||||
</div>
|
||||
<customBtn :btnText="$t('button.text.edit')"></customBtn>
|
||||
<customBtn
|
||||
:btnText="$t('button.text.edit')"
|
||||
@click="editAvatar"
|
||||
:isLoading="state.isLoading"
|
||||
></customBtn>
|
||||
</ZPaging>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import avatarModule from '@/components/avatar-module/index.vue'
|
||||
import customBtn from '@/components/custom-btn/custom-btn.vue'
|
||||
import { ServeEditGroup } from '@/api/group/index.js'
|
||||
import ZPaging from '@/uni_modules/z-paging/components/z-paging/z-paging.vue'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import { reactive } from 'vue'
|
||||
import { reactive, computed, watch } from 'vue'
|
||||
import { useGroupStore, useDialogueStore, useDialogueListStore } from '@/store'
|
||||
|
||||
import { uploadImg } from '@/api/chat'
|
||||
import { uniqueId } from '@/utils'
|
||||
|
||||
const groupStore = useGroupStore()
|
||||
const groupParams = reactive({
|
||||
groupInfo: computed(() => groupStore.groupInfo),
|
||||
})
|
||||
|
||||
const dialogueStore = useDialogueStore()
|
||||
const dialogueParams = reactive({
|
||||
receiver_id: computed(() => dialogueStore.talk.receiver_id),
|
||||
})
|
||||
|
||||
const state = reactive({
|
||||
pageTitle: '', //页面标题
|
||||
groupAvatar: '', //群头像
|
||||
groupName: '', //群名称
|
||||
isLoading: false, //是否正在加载中
|
||||
})
|
||||
|
||||
const onProgressFn = (progress, id) => {
|
||||
console.log((progress.loaded / progress.total) * 100, 'progress')
|
||||
|
||||
useDialogueListStore().updateUploadProgress(
|
||||
id,
|
||||
(progress.loaded / progress.total) * 100,
|
||||
)
|
||||
}
|
||||
|
||||
onLoad((options) => {
|
||||
console.log(options)
|
||||
if (options.groupAvatar) {
|
||||
state.groupAvatar = options.groupAvatar
|
||||
}
|
||||
})
|
||||
|
||||
//点击清空群名称输入
|
||||
const clearGroupNameInput = () => {
|
||||
state.groupName = ''
|
||||
}
|
||||
|
||||
//点击确认修改
|
||||
const confirmEdit = () => {
|
||||
console.log(state.groupName)
|
||||
//点击修改
|
||||
const editAvatar = () => {
|
||||
uni.chooseImage({
|
||||
sourceType: ['album'],
|
||||
count: 1,
|
||||
success: async (res) => {
|
||||
console.log(res, 'res')
|
||||
state.isLoading = true
|
||||
res.tempFiles.forEach(async (file) => {
|
||||
console.log(file)
|
||||
let image = new Image()
|
||||
image.src = URL.createObjectURL(file)
|
||||
image.onload = () => {
|
||||
const form = new FormData()
|
||||
form.append('file', file)
|
||||
form.append('source', 'fonchain-chat')
|
||||
form.append('urlParam', `width=${image.width}&height=${image.height}`)
|
||||
let randomId = uniqueId()
|
||||
uploadImg(form, (e) => onProgressFn(e, randomId)).then(
|
||||
({ status, data, msg }) => {
|
||||
console.log(status, data, msg)
|
||||
if (status == 0) {
|
||||
let avatar = data.ori_url
|
||||
let params = {
|
||||
group_id: dialogueParams.receiver_id,
|
||||
group_name: groupParams.groupInfo.group_name,
|
||||
avatar: avatar,
|
||||
}
|
||||
console.log(params)
|
||||
const resp = ServeEditGroup(params)
|
||||
resp.then(({ code }) => {
|
||||
if (code == 200) {
|
||||
state.isLoading = false
|
||||
groupStore.updateGroupInfo({
|
||||
avatar: data.ori_url,
|
||||
})
|
||||
// uni.navigateBack({
|
||||
// delta: 1,
|
||||
// })
|
||||
} else {
|
||||
state.isLoading = false
|
||||
}
|
||||
})
|
||||
resp.catch(() => {
|
||||
state.isLoading = false})
|
||||
} else {
|
||||
state.isLoading = false
|
||||
}
|
||||
},
|
||||
).catch(() => {
|
||||
state.isLoading = false
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
})
|
||||
}
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
@ -56,7 +131,7 @@ const confirmEdit = () => {
|
||||
background-image: url('@/static/image/mine/background.png');
|
||||
background-size: cover;
|
||||
background-repeat: no-repeat;
|
||||
background-position:center bottom;
|
||||
background-position: center bottom;
|
||||
}
|
||||
|
||||
.edit-group-info {
|
||||
@ -70,10 +145,6 @@ const confirmEdit = () => {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
img {
|
||||
width: 460rpx;
|
||||
height: 460rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -3,17 +3,21 @@
|
||||
<div class="root">
|
||||
<ZPaging ref="zPaging" :show-scrollbar="false">
|
||||
<template #top>
|
||||
<tm-navbar :hideBack="false" hideHome title="" :leftWidth="220">
|
||||
<div class="navBar-title flex flex-col items-center justify-center">
|
||||
<span class="text-[34rpx] font-medium">
|
||||
{{ $t('chat.settings.editGroupName') }}
|
||||
</span>
|
||||
</div>
|
||||
</tm-navbar>
|
||||
<customNavbar
|
||||
:title="$t('chat.settings.editGroupName')"
|
||||
></customNavbar>
|
||||
</template>
|
||||
<div class="edit-group-info">
|
||||
<div class="group-avatar">
|
||||
<img :src="state.groupAvatar" />
|
||||
<avatarModule
|
||||
:mode="2"
|
||||
:avatar="groupParams?.groupInfo?.avatar"
|
||||
:groupType="groupParams?.groupInfo?.group_type"
|
||||
:customStyle="{
|
||||
width: '192rpx',
|
||||
height: '192rpx',
|
||||
}"
|
||||
></avatarModule>
|
||||
</div>
|
||||
<div class="group-name">
|
||||
<span class="text-[28rpx] font-medium">
|
||||
@ -25,10 +29,13 @@
|
||||
: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="/src/static/image/chatSettings/clear-btn.png"
|
||||
src="@/static/image/chatSettings/clear-btn.png"
|
||||
@click="clearGroupNameInput"
|
||||
/>
|
||||
</div>
|
||||
@ -46,6 +53,7 @@
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import avatarModule from '@/components/avatar-module/index.vue'
|
||||
import customBtn from '@/components/custom-btn/custom-btn.vue'
|
||||
import { ServeEditGroup } from '@/api/group/index.js'
|
||||
import { useGroupStore, useDialogueStore } from '@/store'
|
||||
@ -65,15 +73,11 @@ const dialogueParams = reactive({
|
||||
|
||||
const state = reactive({
|
||||
pageTitle: '', //页面标题
|
||||
groupAvatar: '', //群头像
|
||||
groupName: '', //群名称
|
||||
})
|
||||
|
||||
onLoad((options) => {
|
||||
console.log(options)
|
||||
if (options.groupAvatar) {
|
||||
state.groupAvatar = options.groupAvatar
|
||||
}
|
||||
state.groupName = groupParams.groupInfo.group_name
|
||||
})
|
||||
|
||||
@ -81,7 +85,12 @@ 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)
|
||||
@ -91,6 +100,7 @@ const confirmEdit = () => {
|
||||
let params = {
|
||||
group_id: dialogueParams.receiver_id,
|
||||
group_name: state.groupName,
|
||||
avatar: groupParams.groupInfo.avatar,
|
||||
}
|
||||
console.log(params)
|
||||
const resp = ServeEditGroup(params)
|
||||
@ -125,15 +135,10 @@ const confirmEdit = () => {
|
||||
.edit-group-info {
|
||||
.group-avatar {
|
||||
padding: 250rpx 0 88rpx;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
img {
|
||||
width: 192rpx;
|
||||
height: 192rpx;
|
||||
}
|
||||
}
|
||||
.group-name {
|
||||
padding: 0 32rpx;
|
||||
@ -149,7 +154,7 @@ const confirmEdit = () => {
|
||||
height: 110rpx;
|
||||
box-shadow: 0 6px 12px 2px rgba(188, 188, 188, 0.08);
|
||||
padding: 0 74rpx 0 32rpx;
|
||||
color: #B747474;
|
||||
color: #747474;
|
||||
font-size: 28rpx;
|
||||
font-weight: 500;
|
||||
line-height: 40rpx;
|
||||
|
@ -3,13 +3,7 @@
|
||||
<div class="root">
|
||||
<ZPaging ref="zPaging" :show-scrollbar="false">
|
||||
<template #top>
|
||||
<tm-navbar :hideBack="false" hideHome title="" :leftWidth="220">
|
||||
<div class="navBar-title flex flex-col items-center justify-center">
|
||||
<span class="text-[34rpx] font-medium">
|
||||
{{ $t('chat.settings.groupAdmin') }}
|
||||
</span>
|
||||
</div>
|
||||
</tm-navbar>
|
||||
<customNavbar :title="$t('chat.settings.groupAdmin')"></customNavbar>
|
||||
</template>
|
||||
<div class="manage-group-admin">
|
||||
<span class="manage-group-admin-title text-[28rpx] font-regular">
|
||||
@ -23,14 +17,36 @@
|
||||
>
|
||||
<div
|
||||
class="group-admin-list-each"
|
||||
:style="{
|
||||
padding:
|
||||
groupParams?.groupInfo?.group_type == 1 ||
|
||||
groupParams?.groupInfo?.group_type == 3
|
||||
? '18rpx 26rpx 18rpx 14rpx'
|
||||
: '',
|
||||
}"
|
||||
v-for="(item, index) in state?.groupAdminList"
|
||||
:key="index"
|
||||
>
|
||||
<span>{{ item.deptPos }}</span>
|
||||
<selectMemberItem
|
||||
v-if="
|
||||
groupParams?.groupInfo?.group_type == 1 ||
|
||||
groupParams?.groupInfo?.group_type == 3
|
||||
"
|
||||
:groupType="groupParams.groupInfo.group_type"
|
||||
:memberItem="item"
|
||||
></selectMemberItem>
|
||||
<span
|
||||
v-if="
|
||||
groupParams?.groupInfo?.group_type == 2 ||
|
||||
groupParams?.groupInfo?.group_type == 4
|
||||
"
|
||||
>
|
||||
{{ item.deptPos }}
|
||||
</span>
|
||||
<div class="group-admin-list-each-btns">
|
||||
<img
|
||||
v-if="item.is_mine"
|
||||
src="/src/static/image/chatSettings/is-mine.png"
|
||||
src="@/static/image/chatSettings/is-mine.png"
|
||||
/>
|
||||
<div
|
||||
class="group-admin-list-each-btns-each"
|
||||
@ -51,7 +67,7 @@
|
||||
margin: state?.groupAdminList.length == 0 ? '20rpx 0 0' : '',
|
||||
}"
|
||||
>
|
||||
<img src="/src/static/image/chatSettings/add-btn.png" />
|
||||
<img src="@/static/image/chatSettings/add-btn.png" />
|
||||
<span class="text-[28rpx] font-medium">
|
||||
{{ $t('chat.manage.addAdmin') }}
|
||||
</span>
|
||||
@ -62,11 +78,15 @@
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import selectMemberItem from '../components/select-member-item.vue'
|
||||
import ZPaging from '@/uni_modules/z-paging/components/z-paging/z-paging.vue'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import { computed, onMounted, reactive, watch } from 'vue'
|
||||
|
||||
import { ServeEditGroupAdmin } from '@/api/group/index.js'
|
||||
import {
|
||||
ServeEditGroupAdmin,
|
||||
ServeGroupAssignAdmin,
|
||||
} from '@/api/group/index.js'
|
||||
import { useGroupStore, useDialogueStore } from '@/store'
|
||||
|
||||
import { useAuth } from '@/store/auth'
|
||||
@ -90,7 +110,7 @@ const state = reactive({
|
||||
})
|
||||
|
||||
watch(
|
||||
() => groupParams?.groupInfo,
|
||||
[() => groupParams?.groupInfo, , () => dialogueParams?.adminList],
|
||||
(newGroupInfo) => {
|
||||
getGroupAdminList()
|
||||
},
|
||||
@ -107,38 +127,59 @@ onMounted(async () => {
|
||||
|
||||
//加载群聊管理员列表
|
||||
const getGroupAdminList = () => {
|
||||
let myPositionsList = []
|
||||
if (groupParams?.groupInfo?.groupAdminList?.length > 0) {
|
||||
groupParams?.groupInfo?.groupAdminList.forEach((groupAdminItem) => {
|
||||
if (userInfo?.value?.PositionUsers?.length > 0) {
|
||||
userInfo?.value?.PositionUsers.forEach((item) => {
|
||||
// console.log(item.DepartmentId + '-' + item.PositionID)
|
||||
if (item.PositionID === groupAdminItem.position_id) {
|
||||
myPositionsList.push(groupAdminItem)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
if (
|
||||
groupParams?.groupInfo?.groupAdminList?.length > 0 &&
|
||||
myPositionsList?.length > 0
|
||||
groupParams?.groupInfo?.group_type == 1 ||
|
||||
groupParams?.groupInfo?.group_type == 3
|
||||
) {
|
||||
groupParams.groupInfo.groupAdminList.forEach((ele) => {
|
||||
myPositionsList.forEach((item) => {
|
||||
if (ele.deptPos === item.deptPos) {
|
||||
if (dialogueParams?.adminList?.length > 0) {
|
||||
dialogueParams?.adminList.forEach((item) => {
|
||||
if (item?.erp_user_id == userInfo?.value?.ID) {
|
||||
item.is_mine = true
|
||||
if (myPositionsList?.length == 1) {
|
||||
item.cannotRemove = true
|
||||
}
|
||||
item.cannotRemove = true
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
state.groupAdminList = dialogueParams?.adminList
|
||||
} else if (
|
||||
groupParams?.groupInfo?.group_type == 2 ||
|
||||
groupParams?.groupInfo?.group_type == 4
|
||||
) {
|
||||
let myPositionsList = []
|
||||
if (groupParams?.groupInfo?.groupAdminList?.length > 0) {
|
||||
groupParams?.groupInfo?.groupAdminList.forEach((groupAdminItem) => {
|
||||
if (userInfo?.value?.PositionUsers?.length > 0) {
|
||||
userInfo?.value?.PositionUsers.forEach((item) => {
|
||||
// console.log(item.DepartmentId + '-' + item.PositionID)
|
||||
if (
|
||||
item.PositionID === groupAdminItem.position_id &&
|
||||
item.DepartmentId === groupAdminItem.dept_id
|
||||
) {
|
||||
myPositionsList.push(groupAdminItem)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
if (
|
||||
groupParams?.groupInfo?.groupAdminList?.length > 0 &&
|
||||
myPositionsList?.length > 0
|
||||
) {
|
||||
groupParams.groupInfo.groupAdminList.forEach((ele) => {
|
||||
myPositionsList.forEach((item) => {
|
||||
if (ele.deptPos === item.deptPos) {
|
||||
item.is_mine = true
|
||||
if (myPositionsList?.length == 1) {
|
||||
item.cannotRemove = true
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
state.groupAdminList = groupParams?.groupInfo?.groupAdminList
|
||||
}
|
||||
state.groupAdminList = groupParams?.groupInfo?.groupAdminList
|
||||
}
|
||||
|
||||
//点击跳转到添加禁言成员页面
|
||||
//点击跳转到添加管理员页面
|
||||
const toSelectMembersPage = () => {
|
||||
uni.navigateTo({
|
||||
url: '/pages/chatSettings/groupManage/selectMembers?manageType=admin',
|
||||
@ -147,35 +188,54 @@ const toSelectMembersPage = () => {
|
||||
|
||||
//点击移除群管理员
|
||||
const removeGroupAdmin = (adminItem) => {
|
||||
let positionInfos = []
|
||||
if (state?.groupAdminList?.length > 0) {
|
||||
state?.groupAdminList.forEach((item) => {
|
||||
if (
|
||||
item.dept_id != adminItem.dept_id ||
|
||||
item.position_id != adminItem.position_id
|
||||
) {
|
||||
positionInfos.push({
|
||||
position_id: item.position_id,
|
||||
position_name: item.deptPos,
|
||||
})
|
||||
}
|
||||
})
|
||||
if (
|
||||
groupParams?.groupInfo?.group_type == 1 ||
|
||||
groupParams?.groupInfo?.group_type == 3
|
||||
) {
|
||||
let params = {
|
||||
source: 'app',
|
||||
id: dialogueParams.receiverId,
|
||||
deptInfos: groupParams.groupInfo.deptInfos,
|
||||
positionInfos: positionInfos,
|
||||
mode: 2, //1管理员,2不是管理员
|
||||
group_id: dialogueParams.receiverId, //群id
|
||||
user_ids: String(adminItem.id),
|
||||
}
|
||||
console.log(params)
|
||||
const resp = ServeEditGroupAdmin(params)
|
||||
const resp = ServeGroupAssignAdmin(params)
|
||||
resp.then(({ code, data }) => {
|
||||
console.log(data)
|
||||
if (code == 200) {
|
||||
groupStore.ServeGroupDetail()
|
||||
useDialogueStore().updateGroupMembers()
|
||||
} else {
|
||||
}
|
||||
})
|
||||
resp.catch(() => {})
|
||||
} else if (
|
||||
groupParams?.groupInfo?.group_type == 2 ||
|
||||
groupParams?.groupInfo?.group_type == 4
|
||||
) {
|
||||
let positionInfos = []
|
||||
if (state?.groupAdminList?.length > 0) {
|
||||
positionInfos = state?.groupAdminList.filter((item) => {
|
||||
return (
|
||||
item.dept_id != adminItem.dept_id ||
|
||||
item.position_id != adminItem.position_id
|
||||
)
|
||||
})
|
||||
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(() => {})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@ -196,18 +256,23 @@ const removeGroupAdmin = (adminItem) => {
|
||||
|
||||
.group-admin-list {
|
||||
.group-admin-list-each {
|
||||
padding: 36rpx 0;
|
||||
margin: 0 32rpx;
|
||||
padding: 36rpx 26rpx 36rpx 14rpx;
|
||||
margin: 0 18rpx;
|
||||
border-bottom: 1px solid $theme-border-color;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
::v-deep .select-member-item {
|
||||
padding: 0;
|
||||
border-bottom: 0;
|
||||
}
|
||||
.group-admin-list-each-btns {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
flex-shrink: 0;
|
||||
img {
|
||||
width: 52rpx;
|
||||
height: 52rpx;
|
||||
|
76
src/pages/chatSettings/groupManage/manageGroupDeps.vue
Normal file
@ -0,0 +1,76 @@
|
||||
<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>
|
@ -11,16 +11,12 @@
|
||||
:loading-more-enabled="false"
|
||||
>
|
||||
<template #top>
|
||||
<tm-navbar :hideBack="false" hideHome title="" :leftWidth="220">
|
||||
<div class="navBar-title flex flex-col items-center justify-center">
|
||||
<span class="text-[34rpx] font-medium">
|
||||
{{ $t('chat.settings.groupMember') }}
|
||||
</span>
|
||||
</div>
|
||||
</tm-navbar>
|
||||
<customNavbar :title="$t('chat.settings.groupMember')"></customNavbar>
|
||||
</template>
|
||||
<div class="group-members-list">
|
||||
<groupMemberList
|
||||
:groupType="groupParams?.groupInfo?.group_type"
|
||||
:is_manager="groupParams?.groupInfo?.is_manager"
|
||||
:memberList="talkParams?.memberList"
|
||||
></groupMemberList>
|
||||
</div>
|
||||
@ -33,13 +29,18 @@ import groupMemberList from '../components/groupMembersList.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 { ref, computed, reactive } from 'vue'
|
||||
import { useDialogueStore } from '@/store'
|
||||
import { useDialogueStore, useGroupStore } from '@/store'
|
||||
|
||||
const dialogueStore = useDialogueStore()
|
||||
const talkParams = reactive({
|
||||
memberList: computed(() => dialogueStore.members),
|
||||
})
|
||||
|
||||
const groupStore = useGroupStore()
|
||||
const groupParams = reactive({
|
||||
groupInfo: computed(() => groupStore.groupInfo),
|
||||
})
|
||||
|
||||
const zPaging = ref()
|
||||
useZPaging(zPaging)
|
||||
</script>
|
||||
|
@ -3,13 +3,7 @@
|
||||
<div class="root">
|
||||
<ZPaging ref="zPaging" :show-scrollbar="false">
|
||||
<template #top>
|
||||
<tm-navbar :hideBack="false" hideHome title="" :leftWidth="220">
|
||||
<div class="navBar-title flex flex-col items-center justify-center">
|
||||
<span class="text-[34rpx] font-medium">
|
||||
{{ $t('chat.settings.groupGag') }}
|
||||
</span>
|
||||
</div>
|
||||
</tm-navbar>
|
||||
<customNavbar :title="$t('chat.settings.groupGag')"></customNavbar>
|
||||
</template>
|
||||
<div class="manage-group-silence">
|
||||
<span class="manage-group-silence-title text-[28rpx] font-regular">
|
||||
@ -48,7 +42,7 @@
|
||||
class="add-silence-member-btn chat-settings-card"
|
||||
@click="toSelectMembersPage"
|
||||
>
|
||||
<img src="/src/static/image/chatSettings/add-btn.png" />
|
||||
<img src="@/static/image/chatSettings/add-btn.png" />
|
||||
<span class="text-[28rpx] font-medium">
|
||||
{{ $t('chat.manage.addSilenceMember') }}
|
||||
</span>
|
||||
|
@ -3,11 +3,9 @@
|
||||
<div class="root">
|
||||
<ZPaging ref="zPaging" :show-scrollbar="false">
|
||||
<template #top>
|
||||
<tm-navbar
|
||||
<customNavbar
|
||||
:hideBack="state.editMode === 3 ? false : true"
|
||||
hideHome
|
||||
title=""
|
||||
:leftWidth="220"
|
||||
:title="$t('chat.settings.groupNotice')"
|
||||
>
|
||||
<template #left v-if="state.editMode !== 3">
|
||||
<div class="nav-bar-cancel-btn">
|
||||
@ -16,12 +14,7 @@
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
<div class="navBar-title flex flex-col items-center justify-center">
|
||||
<span class="text-[34rpx] font-medium">
|
||||
{{ $t('chat.settings.groupNotice') }}
|
||||
</span>
|
||||
</div>
|
||||
<template #right>
|
||||
<template #right v-if="state.isManager === 'true'">
|
||||
<div
|
||||
v-if="state.editMode !== 3"
|
||||
class="nav-bar-done-btn"
|
||||
@ -42,11 +35,11 @@
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
</tm-navbar>
|
||||
</customNavbar>
|
||||
</template>
|
||||
<div class="notice-text-area">
|
||||
<div class="notice-view-area">
|
||||
<div class="notice-view-info" v-if="state.editMode !== 1">
|
||||
<div class="notice-view-info" v-if="state.editMode !== 1 && state?.groupNoticeObj?.content">
|
||||
<div class="notice-creater-avatar">
|
||||
<img :src="state?.groupNoticeObj?.avatar" />
|
||||
</div>
|
||||
@ -60,9 +53,12 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="notice-view-content" v-if="state.editMode === 3">
|
||||
<span class="text-[32rpx] font-regular">
|
||||
<span class="text-[32rpx] font-regular" v-if="state?.groupNoticeObj?.content">
|
||||
{{ state?.groupNoticeObj?.content }}
|
||||
</span>
|
||||
<span class="text-[32rpx] font-regular color-[#898989]" v-else>
|
||||
{{ $t('groupNotice.notice.empty') }}
|
||||
</span>
|
||||
</div>
|
||||
<wd-textarea
|
||||
size="large"
|
||||
@ -84,6 +80,7 @@ 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,
|
||||
@ -105,7 +102,8 @@ const state = reactive({
|
||||
groupNoticeObj: null, //群公告信息
|
||||
groupNotice: '', //群公告
|
||||
canDoComplete: false, //是否可以点击完成按钮
|
||||
editMode: 1, // 1:新增;2:修改;3:查看
|
||||
editMode: 3, // 1:新增;2:修改;3:查看
|
||||
isManager: 'false', //是否是群管理员
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
@ -114,6 +112,14 @@ 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
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -11,488 +11,137 @@
|
||||
@scroll="onScroll"
|
||||
>
|
||||
<template #top>
|
||||
<tm-navbar
|
||||
:hideBack="false"
|
||||
hideHome
|
||||
title=""
|
||||
:leftWidth="220"
|
||||
id="topArea"
|
||||
>
|
||||
<div class="navBar-title flex flex-col items-center justify-center">
|
||||
<span
|
||||
class="text-[34rpx] font-medium"
|
||||
v-if="state.manageType === 'silence'"
|
||||
>
|
||||
{{ $t('chat.manage.addSilenceMember') }}
|
||||
</span>
|
||||
<span
|
||||
class="text-[34rpx] font-medium"
|
||||
v-if="state.manageType === 'admin'"
|
||||
>
|
||||
{{ $t('chat.manage.addAdmin') }}
|
||||
</span>
|
||||
<span
|
||||
class="text-[34rpx] font-medium"
|
||||
v-if="state.manageType === 'searchRecord'"
|
||||
>
|
||||
{{ $t('search.condition.member') }}
|
||||
</span>
|
||||
</div>
|
||||
</tm-navbar>
|
||||
<div id="topArea">
|
||||
<customNavbar :title="pageTitle"></customNavbar>
|
||||
</div>
|
||||
</template>
|
||||
<div class="select-members">
|
||||
<div class="search-member">
|
||||
<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>
|
||||
</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' ? '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' ? 'list' : 'card'
|
||||
"
|
||||
>
|
||||
<template
|
||||
#left
|
||||
v-if="state.manageType !== 'searchRecord'"
|
||||
>
|
||||
<tm-checkbox
|
||||
: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))
|
||||
"
|
||||
></tm-checkbox>
|
||||
</template>
|
||||
</selectMemberItem>
|
||||
</tm-checkbox-group>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<selectMemberByAlphabet
|
||||
:manageType="state.manageType"
|
||||
:isCreateDepGroup="state.isCreateDepGroup"
|
||||
ref="selectMemberByAlphabetRef"
|
||||
:selectAreaHeight="state.selectAreaHeight"
|
||||
@updateSelectedMembersNum="updateSelectedMembersNum"
|
||||
></selectMemberByAlphabet>
|
||||
<template #bottom v-if="state.manageType !== 'searchRecord'">
|
||||
<customBtn
|
||||
:isBottom="true"
|
||||
:btnText="$t('ok')"
|
||||
@clickBtn="confirmSilenceMember"
|
||||
></customBtn>
|
||||
<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')
|
||||
}}
|
||||
</span>
|
||||
</div>
|
||||
<customBtn
|
||||
:btnText="$t('ok')"
|
||||
@clickBtn="confirmSelectMembers"
|
||||
:disabled="state.selectedMembersNum == 0 ? true : false"
|
||||
></customBtn>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</ZPaging>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import customInput from '@/components/custom-input/custom-input.vue'
|
||||
import selectMemberItem from '../components/select-member-item.vue'
|
||||
import selectMemberByAlphabet from '../components/selectMemberByAlphabet.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, onMounted, reactive, ref, watch, nextTick } from 'vue'
|
||||
import { computed, reactive, ref, onMounted, nextTick } from 'vue'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import { ServeGroupNoSpeak, ServeEditGroupAdmin } 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 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, //是否是创建部门群
|
||||
selectAreaHeight: 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)
|
||||
}
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
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)
|
||||
})
|
||||
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
|
||||
}
|
||||
},
|
||||
{ immediate: true, deep: true },
|
||||
)
|
||||
if (alphabetElementRefs.value.length > 0) {
|
||||
alphabetElementRefs.value.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'
|
||||
})
|
||||
})
|
||||
|
||||
// 观察者函数
|
||||
const handleIntersection = (entries) => {
|
||||
if (state.isAssign) {
|
||||
state.isAssign = false
|
||||
return
|
||||
//页面标题
|
||||
const pageTitle = computed(() => {
|
||||
let page_title = ''
|
||||
if (state.manageType === 'silence') {
|
||||
page_title = t('chat.manage.addSilenceMember')
|
||||
} else if (state.manageType === 'admin') {
|
||||
page_title = t('chat.manage.addAdmin')
|
||||
} else if (state.manageType === 'searchRecord') {
|
||||
page_title = t('search.condition.member')
|
||||
} else if (state.manageType === 'removeMembers') {
|
||||
page_title = t('select.member.remove')
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
return page_title
|
||||
})
|
||||
|
||||
//观察元素
|
||||
const observeElement = (el, index) => {
|
||||
if (el && observer) {
|
||||
observer.observe(el)
|
||||
//确认
|
||||
const confirmSelectMembers = () => {
|
||||
if (selectMemberByAlphabetRef.value) {
|
||||
selectMemberByAlphabetRef.value.confirmSelectMembers()
|
||||
}
|
||||
}
|
||||
|
||||
//输入搜索文本
|
||||
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))
|
||||
) {
|
||||
return
|
||||
}
|
||||
let itemList = dialogueParams.memberList
|
||||
if (
|
||||
state.manageType === 'admin' &&
|
||||
(groupParams.groupInfo.group_type == 2 ||
|
||||
groupParams.groupInfo.group_type == 4)
|
||||
) {
|
||||
itemList = state.resultMemberList[0].memberList
|
||||
}
|
||||
itemList.forEach((ele) => {
|
||||
if (ele.id == item.id) {
|
||||
ele.checkArr = ele.checkArr?.length > 0 ? [] : [item.id]
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
//点击确认要禁言的成员
|
||||
const confirmSilenceMember = () => {
|
||||
let selectedUserIds = ''
|
||||
let itemList = dialogueParams.memberList
|
||||
let positionInfos = []
|
||||
if (
|
||||
state.manageType === 'admin' &&
|
||||
(groupParams.groupInfo.group_type == 2 ||
|
||||
groupParams.groupInfo.group_type == 4)
|
||||
) {
|
||||
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))
|
||||
) {
|
||||
positionInfos.push(ele.positionInfo)
|
||||
}
|
||||
})
|
||||
console.log(selectedUserIds)
|
||||
if (selectedUserIds) {
|
||||
if (state.manageType === 'silence') {
|
||||
let params = {
|
||||
mode: 1, //1禁言2解禁
|
||||
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 (
|
||||
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(() => {})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//组装A-Z排序的人员列表
|
||||
const assembleAlphabetMemberList = async (newMemberList) => {
|
||||
if (state.manageType === 'searchRecord') {
|
||||
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)
|
||||
})
|
||||
}
|
||||
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: {
|
||||
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
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
// console.log(departmentAllPositions)
|
||||
state.resultMemberList = [
|
||||
{
|
||||
key: '',
|
||||
memberList: departmentAllPositions,
|
||||
},
|
||||
]
|
||||
} else {
|
||||
state.resultMemberList = [
|
||||
{
|
||||
key: '',
|
||||
memberList: newMemberList,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//滚动到指定的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 updateSelectedMembersNum = (numChange) => {
|
||||
state.selectedMembersNum = state.selectedMembersNum + numChange
|
||||
}
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
@ -503,45 +152,27 @@ const onScroll = (e) => {
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
.select-members {
|
||||
padding: 20rpx 32rpx;
|
||||
.search-member {
|
||||
padding: 22rpx 16rpx;
|
||||
background-color: #fff;
|
||||
.confirm-btn-area {
|
||||
background-color: #fff;
|
||||
padding: 14rpx 32rpx 72rpx;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
.confirm-btn-area-statistic-text {
|
||||
margin: 18rpx 0 0;
|
||||
span {
|
||||
line-height: 40rpx;
|
||||
color: $theme-text;
|
||||
}
|
||||
}
|
||||
.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
::v-deep .custom-btn-class {
|
||||
padding: 18rpx 104rpx !important;
|
||||
}
|
||||
::v-deep .is-disabled {
|
||||
background-color: #e6e6e6 !important;
|
||||
color: #bebebe !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
1177
src/pages/chooseByDeps/index.vue
Normal file
@ -1,36 +1,76 @@
|
||||
<template>
|
||||
<div>
|
||||
<div @click="cellClick" :class="['chatItem', props.data.is_top === 1 ? 'isTop' : '']">
|
||||
<div
|
||||
@click="cellClick"
|
||||
:class="['chatItem', props.data.is_top === 1 ? 'isTop' : '']"
|
||||
>
|
||||
<div>
|
||||
<tm-checkbox v-if="props.isMultiSelect" :round="10" :modelValue="props.data.isCheck"
|
||||
@update:modelValue="props.data.isCheck = !props.data.isCheck" :size="42" :color="props.data.isCheck ? '#46299D' : '#BABABA'"></tm-checkbox>
|
||||
<tm-checkbox
|
||||
v-if="props.isMultiSelect"
|
||||
:round="10"
|
||||
:modelValue="props.data.isCheck"
|
||||
@update:modelValue="props.data.isCheck = !props.data.isCheck"
|
||||
:size="32"
|
||||
:color="props.data.isCheck ? '#46299D' : '#BABABA'"
|
||||
class="pr-[4rpx]"
|
||||
></tm-checkbox>
|
||||
</div>
|
||||
<div class="avatarImg">
|
||||
<tm-image :width="96" :height="96" :round="12" :src="props.data.avatar"></tm-image>
|
||||
<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>
|
||||
</div>
|
||||
<div class="chatInfo">
|
||||
<div class="chatInfo_1">
|
||||
<div class="flex items-center">
|
||||
<div class="text-[#000000] text-[32rpx] font-bold opacity-90">{{ props.data.name }}</div>
|
||||
<div
|
||||
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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="props.index !== talkStore.talkItems.length - 1" class="divider"></div>
|
||||
<div
|
||||
v-if="props.index !== talkStore.talkItems.length - 1"
|
||||
class="divider"
|
||||
></div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import { ref, reactive, defineProps, defineEmits } from "vue"
|
||||
import dayjs from "dayjs";
|
||||
import avatarModule from '@/components/avatar-module/index.vue'
|
||||
import { ref, reactive, defineProps, defineEmits } 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
|
||||
} = useSessionMenu()
|
||||
const { onToTopTalk } = useSessionMenu()
|
||||
const dialogueStore = useDialogueStore()
|
||||
const emit = defineEmits(['itemClick'])
|
||||
const props = defineProps({
|
||||
@ -48,25 +88,22 @@ const props = defineProps({
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
})
|
||||
|
||||
const cellClick = () => {
|
||||
emit('itemClick', props.data)
|
||||
}
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.chatItem {
|
||||
width: 100%;
|
||||
height: 154rpx;
|
||||
padding: 30rpx 16rpx;
|
||||
padding: 24rpx 18rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&.isTop {
|
||||
background-color: #F3F3F3;
|
||||
background-color: #f3f3f3;
|
||||
}
|
||||
}
|
||||
|
||||
@ -77,7 +114,7 @@ const cellClick = () => {
|
||||
|
||||
.chatInfo {
|
||||
flex: 1;
|
||||
margin-left: 20rpx;
|
||||
margin-left: 16rpx;
|
||||
}
|
||||
|
||||
.chatInfo_1 {
|
||||
@ -97,19 +134,33 @@ const cellClick = () => {
|
||||
font-size: 28rpx;
|
||||
color: #000000;
|
||||
opacity: 40%;
|
||||
|
||||
}
|
||||
|
||||
.companyTag {
|
||||
width: 76rpx;
|
||||
height: 38rpx;
|
||||
border: 1px solid #7A58DE;
|
||||
font-size: 24rpx;
|
||||
.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;
|
||||
color: #7A58DE;
|
||||
font-weight: bold;
|
||||
margin-left: 12rpx;
|
||||
}
|
||||
.companyTag {
|
||||
border: 1px solid #7a58de;
|
||||
color: #7a58de;
|
||||
}
|
||||
.depTag {
|
||||
border: 1px solid #377ec6;
|
||||
color: #377ec6;
|
||||
}
|
||||
.projectTag {
|
||||
border: 1px solid #c1681c;
|
||||
color: #c1681c;
|
||||
}
|
||||
|
||||
.textEllipsis {
|
||||
|
@ -1,74 +1,134 @@
|
||||
<template>
|
||||
<div class="outer-layer">
|
||||
<div>
|
||||
<tm-navbar :hideBack="true" hideHome :leftWidth="320">
|
||||
<template v-slot:left>
|
||||
<div @click="handleClose" class="ml-[48rpx] text-[34rpx] leading-[40rpx]">
|
||||
关闭
|
||||
<div class="choose-chat-page">
|
||||
<ZPaging ref="zPaging" :show-scrollbar="false">
|
||||
<template #top>
|
||||
<customNavbar
|
||||
:title="$t('pageTitle.select.shareChat')"
|
||||
:hideBack="true"
|
||||
>
|
||||
<template #left>
|
||||
<div
|
||||
@click="handleClose"
|
||||
class="ml-[42rpx] text-[34rpx] leading-[48rpx] font-regular"
|
||||
>
|
||||
{{ $t('button.text.close') }}
|
||||
</div>
|
||||
</template>
|
||||
<template #right>
|
||||
<div
|
||||
v-if="!isMultiSelect"
|
||||
@click="isMultiSelect = true"
|
||||
class="mr-[36rpx] text-[34rpx] leading-[48rpx] font-regular"
|
||||
>
|
||||
{{ $t('button.multiple.choice') }}
|
||||
</div>
|
||||
<div v-else class="mr-[36rpx] btnBox">
|
||||
<customBtn
|
||||
:btnText="
|
||||
selectItems.length
|
||||
? $t('button.text.done') + `(${selectItems.length})`
|
||||
: $t('button.text.done')
|
||||
"
|
||||
:disabled="!selectItems.length"
|
||||
@clickBtn="handleFinish"
|
||||
></customBtn>
|
||||
</div>
|
||||
</template>
|
||||
</customNavbar>
|
||||
</template>
|
||||
<div class="choose-chat">
|
||||
<div class="searchRoot">
|
||||
<customInput></customInput>
|
||||
</div>
|
||||
<div class="contentRoot">
|
||||
<chatItem
|
||||
v-for="(item, index) in items"
|
||||
:key="item.index_name"
|
||||
:index="index"
|
||||
:data="item"
|
||||
:isMultiSelect="isMultiSelect"
|
||||
@itemClick="itemClick"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<tm-modal
|
||||
v-model:show="showWin"
|
||||
:height="forwardModalHeight"
|
||||
:round="4"
|
||||
okColor="#FFFFFF"
|
||||
@ok="handleOk"
|
||||
@cancel="handleCancel"
|
||||
:okText="'发送'"
|
||||
>
|
||||
<template v-slot:title>
|
||||
<div
|
||||
class="w-full pl-[30rpx] leading-[48rpx] mt-[30rpx] font-bold text-[32rpx]"
|
||||
>
|
||||
发送给:
|
||||
</div>
|
||||
</template>
|
||||
<template v-slot:default>
|
||||
<div class="text-[34rpx] font-bold ml-[-80rpx] leading-[40rpx]">
|
||||
选择一个聊天
|
||||
<div
|
||||
class="pl-[22rpx] pr-[32rpx] pt-[10rpx] flex items-center justify-start flex-wrap"
|
||||
>
|
||||
<div
|
||||
v-if="selectItemsModal.length > 1"
|
||||
v-for="item in selectItemsModal"
|
||||
class="w-1/4 mt-[20rpx] flex flex-col items-center justify-center"
|
||||
>
|
||||
<div class="w-[96rpx] h-[96rpx] rounded-[60rpx] flex-center">
|
||||
<tm-image
|
||||
:width="96"
|
||||
:height="96"
|
||||
:round="12"
|
||||
:src="item.avatar"
|
||||
></tm-image>
|
||||
</div>
|
||||
<div
|
||||
class="mt-[8rpx] text-[#666666] text-[24rpx] w-[94rpx] truncate"
|
||||
>
|
||||
<span>{{ item.name }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="w-full flex items-center justify-start">
|
||||
<avatarModule
|
||||
:mode="2"
|
||||
:avatar="selectItemsModal[0].avatar"
|
||||
:groupType="selectItemsModal[0].group_type"
|
||||
:customStyle="{ width: '96rpx', height: '96rpx' }"
|
||||
></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>
|
||||
</template>
|
||||
<template v-slot:right>
|
||||
<div v-if="!isMultiSelect" @click="isMultiSelect = true" class="mr-[36rpx] text-[34rpx] leading-[40rpx]">
|
||||
多选
|
||||
</div>
|
||||
<div v-else class="mr-[36rpx] btnBox">
|
||||
<tm-button @click="handleFinish" color="#5B3FB1" :fontSize="34" :disabled="!selectItems.length"
|
||||
:margin="[10]" :shadow="0" size="small" :width="selectItems.length ? 162 : 116"
|
||||
:label="selectItems.length ? `完成(${selectItems.length})` : '完成'">
|
||||
</tm-button>
|
||||
</div>
|
||||
</template>
|
||||
</tm-navbar>
|
||||
</div>
|
||||
<div class="root">
|
||||
<div class="searchRoot">
|
||||
<tm-input placeholder="请输入…" color="#F9F9FD" :round="1" prefix="tmicon-search" prefixColor="#46299D"></tm-input>
|
||||
</div>
|
||||
<div class="contentRoot">
|
||||
<chatItem v-for="(item, index) in items" :key="item.index_name" :index="index" :data="item"
|
||||
:isMultiSelect="isMultiSelect" @itemClick="itemClick" />
|
||||
</div>
|
||||
</div>
|
||||
<tm-modal v-model:show="showWin" :height="forwardModalHeight" :round="4" okColor="#FFFFFF" @ok="handleOk"
|
||||
@cancel="handleCancel">
|
||||
<template v-slot:title>
|
||||
<div class="w-full pl-[30rpx] leading-[48rpx] mt-[30rpx] font-bold text-[32rpx]">
|
||||
发送给:
|
||||
</div>
|
||||
</template>
|
||||
<template v-slot:default>
|
||||
<div class="pl-[22rpx] pr-[32rpx] pt-[10rpx] flex items-center justify-start flex-wrap">
|
||||
<div v-if="selectItemsModal.length > 1" v-for="item in selectItemsModal"
|
||||
class="w-1/4 mt-[20rpx] flex flex-col items-center justify-center">
|
||||
<div class="w-[96rpx] h-[96rpx] rounded-[60rpx] flex-center">
|
||||
<tm-image :width="96" :height="96" :round="12" :src="item.avatar"></tm-image>
|
||||
</div>
|
||||
<div class="mt-[8rpx] text-[#666666] text-[24rpx] w-[94rpx] truncate">{{ item.name }}</div>
|
||||
</div>
|
||||
<div v-else class="w-full flex items-center justify-start">
|
||||
<div class="w-[96rpx] h-[96rpx] rounded-[60rpx] flex-center">
|
||||
<tm-image :width="96" :height="96" :round="12" :src="selectItemsModal[0].avatar"></tm-image>
|
||||
</div>
|
||||
<div class="ml-[16rpx] text-[#161616] text-[32rpx] font-bold ">{{ selectItemsModal[0].name }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</tm-modal>
|
||||
</tm-modal>
|
||||
</ZPaging>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import { ref, watch, computed, onMounted, onUnmounted } from 'vue';
|
||||
import { onShow, onLoad } from "@dcloudio/uni-app";
|
||||
import { useChatList } from "@/store/chatList/index.js";
|
||||
import { useAuth } from "@/store/auth";
|
||||
import customInput from '@/components/custom-input/custom-input.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 { ref, watch, computed, onMounted, onUnmounted } from 'vue'
|
||||
import { onShow, onLoad } from '@dcloudio/uni-app'
|
||||
import { useChatList } from '@/store/chatList/index.js'
|
||||
import { useAuth } from '@/store/auth'
|
||||
import { useTalkStore, useUserStore, useDialogueStore } from '@/store'
|
||||
import chatItem from './components/chatItem.vue';
|
||||
import addCircle from "@/static/image/chatList/addCircle.png";
|
||||
import chatItem from './components/chatItem.vue'
|
||||
import addCircle from '@/static/image/chatList/addCircle.png'
|
||||
import lodash from 'lodash'
|
||||
|
||||
const talkStore = useTalkStore()
|
||||
@ -87,13 +147,13 @@ const items = ref([])
|
||||
// })
|
||||
|
||||
const selectItems = computed(() => {
|
||||
return items.value.filter(item => item.isCheck)
|
||||
return items.value.filter((item) => item.isCheck)
|
||||
})
|
||||
|
||||
const selectItemsModal = ref([])
|
||||
|
||||
const itemClick = (item) => {
|
||||
console.log(item);
|
||||
console.log(item)
|
||||
if (isMultiSelect.value) {
|
||||
item.isCheck = !item.isCheck
|
||||
return false
|
||||
@ -104,7 +164,7 @@ const itemClick = (item) => {
|
||||
}
|
||||
|
||||
const handleFinish = () => {
|
||||
console.log(selectItems.value);
|
||||
console.log(selectItems.value)
|
||||
selectItemsModal.value = selectItems.value
|
||||
if (selectItemsModal.value.length > 4) {
|
||||
forwardModalHeight.value = 560
|
||||
@ -123,7 +183,6 @@ const handleClose = () => {
|
||||
const handleOk = () => {
|
||||
let msg_ids = dialogueStore.forwardMessages.map((item) => item.msg_id)
|
||||
|
||||
|
||||
let user_ids = []
|
||||
let group_ids = []
|
||||
|
||||
@ -139,24 +198,28 @@ const handleOk = () => {
|
||||
mode: dialogueStore.forwardType,
|
||||
message_ids: msg_ids,
|
||||
uids: user_ids,
|
||||
gids: group_ids
|
||||
gids: group_ids,
|
||||
})
|
||||
|
||||
uni.navigateBack()
|
||||
}
|
||||
|
||||
const handleCancel = () => {
|
||||
console.log('cancel');
|
||||
console.log('cancel')
|
||||
}
|
||||
|
||||
watch(() => talkStore, (newValue, oldValue) => {
|
||||
console.log(talkStore);
|
||||
|
||||
}, { deep: true, immediate: true })
|
||||
watch(
|
||||
() => talkStore,
|
||||
(newValue, oldValue) => {
|
||||
console.log(talkStore)
|
||||
},
|
||||
{ deep: true, immediate: true },
|
||||
)
|
||||
|
||||
onMounted(() => {
|
||||
talkStore.loadTalkList()
|
||||
items.value = lodash.cloneDeep(talkStore.talkItems)
|
||||
console.log(talkStore.talkItems)
|
||||
items.value = lodash.cloneDeep(talkStore.talkItems).filter(item=>item.is_dismiss === 0 && item.is_quit === 0)
|
||||
})
|
||||
onUnmounted(() => {
|
||||
dialogueStore.setForwardType('')
|
||||
@ -164,24 +227,20 @@ onUnmounted(() => {
|
||||
})
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
uni-page-body,
|
||||
page {
|
||||
::v-deep .zp-paging-container-content {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.outer-layer {
|
||||
overflow-y: auto;
|
||||
.choose-chat {
|
||||
flex: 1;
|
||||
background-image: url("@/static/image/clockIn/z3280@3x.png");
|
||||
background-size: cover;
|
||||
padding: 0 32rpx 20rpx 32rpx;
|
||||
padding: 20rpx 32rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.root {
|
||||
flex: 1;
|
||||
padding: 20rpx 0;
|
||||
background-image: url('@/static/image/clockIn/z3280@3x.png');
|
||||
background-size: cover;
|
||||
background-position: bottom center;
|
||||
background-attachment: fixed;
|
||||
}
|
||||
|
||||
.searchRoot {
|
||||
@ -193,35 +252,65 @@ page {
|
||||
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 {
|
||||
:deep(.button) {
|
||||
&[disabled="true"] {
|
||||
background-color: #F3F3F3 !important;
|
||||
color: #BABABA !important;
|
||||
}
|
||||
::v-deep .custom-btn-class {
|
||||
padding: 6rpx 24rpx !important;
|
||||
width: unset !important;
|
||||
min-width: unset !important;
|
||||
height: unset !important;
|
||||
}
|
||||
::v-deep .wd-button__text {
|
||||
font-size: 34rpx !important;
|
||||
font-weight: 400 !important;
|
||||
line-height: 48rpx !important;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.noNvueBorder.round-bl-4) {
|
||||
border-top: 1rpx solid #E7E7E7 !important;
|
||||
background-color: #FFFFFF !important;
|
||||
border-top: 1rpx solid #e7e7e7 !important;
|
||||
background-color: #ffffff !important;
|
||||
|
||||
uni-text {
|
||||
color: #1A1A1A !important;
|
||||
color: #1a1a1a !important;
|
||||
font-size: 32rpx !important;
|
||||
line-height: 48rpx !important;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.noNvueBorder.round-br-4) {
|
||||
border-top: 1rpx solid #E7E7E7 !important;
|
||||
border-left: 1rpx solid #E7E7E7 !important;
|
||||
background-color: #FFFFFF !important;
|
||||
border-top: 1rpx solid #e7e7e7 !important;
|
||||
border-left: 1rpx solid #e7e7e7 !important;
|
||||
background-color: #ffffff !important;
|
||||
|
||||
uni-text {
|
||||
font-weight: bold;
|
||||
color: #46299D !important;
|
||||
color: #46299d !important;
|
||||
font-size: 32rpx !important;
|
||||
line-height: 48rpx !important;
|
||||
}
|
||||
|
@ -1,193 +1,222 @@
|
||||
<template>
|
||||
<div class="outer-layer">
|
||||
<div>
|
||||
<tm-navbar :hideBack="false" hideHome title="选择部门"> </tm-navbar>
|
||||
</div>
|
||||
<div class="root">
|
||||
<div
|
||||
class="w-full h-[1134rpx] mb-[20rpx] pl-[32rpx] pr-[32rpx] pb-[20rpx] overflow-y-auto"
|
||||
>
|
||||
<div class="pl-[16rpx] pr-[16rpx] pt-[22rpx] pb-[24rpx] bg-[#FFFFFF]">
|
||||
<tm-input
|
||||
placeholder="请输入…"
|
||||
color="#F9F9FD"
|
||||
:round="1"
|
||||
prefix="tmicon-search"
|
||||
prefixColor="#46299D"
|
||||
searchBgColor="#F9F9FD"
|
||||
focusColor="#F9F9FD"
|
||||
v-model="searchVal"
|
||||
></tm-input>
|
||||
<div
|
||||
v-if="crumbs.length"
|
||||
class="w-full overflow-x-auto mt-[22rpx] leading-[48rpx] text-[#2F2F2F] flex items-center no-scrollbar"
|
||||
ref="crumbsContainer"
|
||||
>
|
||||
<div class="choose-deps-page">
|
||||
<zPaging ref="zPaging" :show-scrollbar="false">
|
||||
<template #top>
|
||||
<customNavbar :title="$t('pageTitle.select.department')"></customNavbar>
|
||||
</template>
|
||||
<div class="choose-deps">
|
||||
<div class="w-full pl-[32rpx] pr-[32rpx] overflow-y-auto">
|
||||
<div class="pl-[16rpx] pr-[16rpx] pt-[22rpx] pb-[24rpx] bg-[#FFFFFF]">
|
||||
<tm-input
|
||||
placeholder="请输入…"
|
||||
color="#F9F9FD"
|
||||
:round="1"
|
||||
prefix="tmicon-search"
|
||||
prefixColor="#46299D"
|
||||
searchBgColor="#F9F9FD"
|
||||
focusColor="#F9F9FD"
|
||||
v-model="searchVal"
|
||||
></tm-input>
|
||||
<div
|
||||
v-for="(item, index) in crumbs"
|
||||
class="flex items-center text-[28rpx] leading-[48rpx] whitespace-nowrap"
|
||||
:class="[
|
||||
index === crumbsIndex ? 'text-[#747474]' : 'text-[#46299D]',
|
||||
index === 0 ? '' : 'ml-[12rpx]',
|
||||
]"
|
||||
@click="handleCrumbsClick(index)"
|
||||
v-if="crumbs.length"
|
||||
class="w-full overflow-x-auto mt-[22rpx] leading-[48rpx] text-[#2F2F2F] flex items-center no-scrollbar"
|
||||
ref="crumbsContainer"
|
||||
>
|
||||
<div>{{ item.name }}</div>
|
||||
<div
|
||||
v-if="index !== crumbs.length - 1"
|
||||
class="ml-[20rpx] flex items-center mb-[2rpx]"
|
||||
v-for="(item, index) in crumbs"
|
||||
class="flex items-center text-[28rpx] leading-[48rpx] whitespace-nowrap"
|
||||
:class="[
|
||||
index === crumbsIndex ? 'text-[#747474]' : 'text-[#46299D]',
|
||||
index === 0 ? '' : 'ml-[12rpx]',
|
||||
]"
|
||||
@click="handleCrumbsClick(index)"
|
||||
>
|
||||
<tm-icon
|
||||
name="tmicon-angle-right"
|
||||
:font-size="20"
|
||||
:color="index !== crumbs.length - 1 ? '#7A58DE' : '#C1B4EA'"
|
||||
></tm-icon>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="pl-[32rpx] bg-[#FFFFFF] mt-[20rpx] h-[110rpx] flex items-center"
|
||||
@click="()=>allCheck(allCheckStatus)"
|
||||
>
|
||||
<div>
|
||||
<checkBox
|
||||
:disabled="currentCrumbs?.sons?.length === 0"
|
||||
:modelValue="allCheckStatus"
|
||||
></checkBox>
|
||||
</div>
|
||||
<div class="font-bold text-[28rpx] leading-[54rpx] ml-[20rpx]">
|
||||
全选
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="currentCrumbs?.sons?.length"
|
||||
v-for="item in currentCrumbs?.sons"
|
||||
class="pl-[32rpx] bg-[#FFFFFF] mt-[20rpx] h-[110rpx] flex items-center"
|
||||
>
|
||||
<div class="w-full flex items-center justify-between">
|
||||
<div
|
||||
class="flex items-center"
|
||||
>
|
||||
<div>
|
||||
<checkBox
|
||||
:disabled="!item?.sons?.length"
|
||||
v-model="item.checkStatus"
|
||||
@change="(val) => checkItemChange(item, val)"
|
||||
></checkBox>
|
||||
</div>
|
||||
<div class="font-bold text-[28rpx] leading-[54rpx] ml-[20rpx]">
|
||||
{{ item.name }}
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="item.sons?.length" class="flex items-center mr-[32rpx]">
|
||||
<div class="vDivider mr-[32rpx]"></div>
|
||||
<div @click="() => toNextLevel(item)" class="flex items-center">
|
||||
<div class="mr-[12rpx]">
|
||||
<tm-image
|
||||
:width="26"
|
||||
:height="26"
|
||||
:src="item.checkStatus !== 'checked' ? downDep : downDepDis"
|
||||
></tm-image>
|
||||
</div>
|
||||
<div>{{ item.name }}</div>
|
||||
<div
|
||||
class="text-[28rpx] leading-[54rpx] font-bold"
|
||||
:class="
|
||||
item.checkStatus !== 'checked'
|
||||
? 'text-[#46299D]'
|
||||
: 'text-[#C1B4EA]'
|
||||
"
|
||||
v-if="index !== crumbs.length - 1"
|
||||
class="ml-[20rpx] flex items-center mb-[2rpx]"
|
||||
>
|
||||
下级
|
||||
<tm-icon
|
||||
name="tmicon-angle-right"
|
||||
:font-size="20"
|
||||
:color="index !== crumbs.length - 1 ? '#7A58DE' : '#C1B4EA'"
|
||||
></tm-icon>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="pl-[32rpx] bg-[#FFFFFF] mt-[20rpx] h-[110rpx] flex items-center"
|
||||
@click="() => allCheck(allCheckStatus)"
|
||||
>
|
||||
<div>
|
||||
<checkBox
|
||||
:disabled="currentCrumbs?.sons?.length === 0"
|
||||
:modelValue="allCheckStatus"
|
||||
></checkBox>
|
||||
</div>
|
||||
<div class="font-bold text-[28rpx] leading-[54rpx] ml-[20rpx]">
|
||||
全选
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="currentCrumbs?.sons?.length"
|
||||
v-for="item in currentCrumbs?.sons"
|
||||
class="pl-[32rpx] bg-[#FFFFFF] mt-[20rpx] h-[110rpx] flex items-center"
|
||||
>
|
||||
<div class="w-full flex items-center justify-between">
|
||||
<div class="flex items-center">
|
||||
<div>
|
||||
<checkBox
|
||||
:disabled="!item?.sons?.length"
|
||||
v-model="item.checkStatus"
|
||||
@change="(val) => checkItemChange(item, val)"
|
||||
></checkBox>
|
||||
</div>
|
||||
<div class="font-bold text-[28rpx] leading-[54rpx] ml-[20rpx]">
|
||||
{{ item.name }}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="item.sons?.length"
|
||||
class="flex items-center mr-[32rpx]"
|
||||
>
|
||||
<div class="vDivider mr-[32rpx]"></div>
|
||||
<div @click="() => toNextLevel(item)" class="flex items-center">
|
||||
<div class="mr-[12rpx]">
|
||||
<tm-image
|
||||
:width="26"
|
||||
:height="26"
|
||||
:src="
|
||||
item.checkStatus !== 'checked' ? downDep : downDepDis
|
||||
"
|
||||
></tm-image>
|
||||
</div>
|
||||
<div
|
||||
class="text-[28rpx] leading-[54rpx] font-bold"
|
||||
:class="
|
||||
item.checkStatus !== 'checked'
|
||||
? 'text-[#46299D]'
|
||||
: 'text-[#C1B4EA]'
|
||||
"
|
||||
>
|
||||
下级
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="h-[162rpx] pl-[32rpx] pr-[32rpx] bg-[#FFFFFF]">
|
||||
<div class="mt-[14rpx] flex justify-between">
|
||||
<div class="flex flex-col">
|
||||
<div @click="openDrawer" class="flex items-center text-[28rpx] leading-[60rpx] text-[#000000] font-bold">
|
||||
<div>已选择部门数:</div>
|
||||
<div>{{ allCheckedList.length }}</div>
|
||||
<div class="ml-[28rpx]">
|
||||
<tm-icon :fontSize="24" color="#46299D" name="tmicon-angle-up"></tm-icon>
|
||||
<template #bottom>
|
||||
<div class="h-[162rpx] pl-[32rpx] pr-[32rpx] bg-[#FFFFFF]">
|
||||
<div class="mt-[14rpx] flex justify-between">
|
||||
<div class="flex flex-col">
|
||||
<div
|
||||
@click="openDrawer"
|
||||
class="flex items-center text-[28rpx] leading-[60rpx] text-[#000000] font-bold"
|
||||
>
|
||||
<div>已选择部门数:</div>
|
||||
<div>{{ allCheckedList.length }}</div>
|
||||
<div class="ml-[28rpx]">
|
||||
<tm-icon
|
||||
:fontSize="24"
|
||||
color="#46299D"
|
||||
name="tmicon-angle-up"
|
||||
></tm-icon>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="text-[24rpx] leading-[24rpx] text-[#7A58DE] w-[280rpx] truncate"
|
||||
>
|
||||
{{ allCheckedList.map((v) => v.name).toString() }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-[24rpx] leading-[24rpx] text-[#7A58DE] w-[280rpx] truncate">
|
||||
{{ allCheckedList.map(v=>v.name).toString() }}
|
||||
<div class="btnBox">
|
||||
<tm-button
|
||||
@click="handleConfirm"
|
||||
color="#46299D"
|
||||
:disabled="!allCheckedList.length"
|
||||
disabledColor="#E6E6E6"
|
||||
:margin="[0]"
|
||||
:shadow="0"
|
||||
:width="264"
|
||||
:height="76"
|
||||
size="large"
|
||||
label="确定"
|
||||
></tm-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="btnBox">
|
||||
<tm-button
|
||||
@click="handleConfirm"
|
||||
color="#46299D"
|
||||
:disabled="!allCheckedList.length"
|
||||
disabledColor="#E6E6E6"
|
||||
:margin="[0]"
|
||||
:shadow="0"
|
||||
:width="264"
|
||||
:height="76"
|
||||
size="large"
|
||||
label="确定"
|
||||
>
|
||||
</tm-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<tm-drawer
|
||||
placement="bottom"
|
||||
v-model:show="showWin"
|
||||
:height="800"
|
||||
:hideHeader="true"
|
||||
:round="5"
|
||||
>
|
||||
<div class="flex flex-col w-full h-full pt-[36rpx] pl-[32rpx] pr-[32rpx] leading-[60rpx]" >
|
||||
<div class="text-[32rpx] font-bold flex items-center justify-between" >
|
||||
<div class="flex items-center ml-[10rpx]">
|
||||
<div>已选择部门数:</div>
|
||||
<div>{{ allCheckedList.length }}</div>
|
||||
</div>
|
||||
<div
|
||||
class="text-[#7A58DE] mr-[10rpx]"
|
||||
@click="()=>showWin = false"
|
||||
>
|
||||
确定
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-1 pb-[20rpx] overflow-y-auto pt-[30rpx]" >
|
||||
<tm-drawer
|
||||
placement="bottom"
|
||||
v-model:show="showWin"
|
||||
:height="800"
|
||||
:hideHeader="true"
|
||||
:round="5"
|
||||
>
|
||||
<div
|
||||
v-for="(item,index) in allCheckedList"
|
||||
class="flex flex-col"
|
||||
class="flex flex-col w-full h-full pt-[36rpx] pl-[32rpx] pr-[32rpx] leading-[60rpx]"
|
||||
>
|
||||
<div v-if="index ===0" class="divider" ></div>
|
||||
<div
|
||||
class="flex items-center justify-between mt-[36rpx] font-bold text-[#000000] leading-[54rpx] mb-[34rpx]"
|
||||
<div
|
||||
class="text-[32rpx] font-bold flex items-center justify-between"
|
||||
>
|
||||
<div class="text-[28rpx] ml-[10rpx]">{{ item.name }}</div>
|
||||
<div class="diyBtn">
|
||||
<tm-button @click="()=>deleteNode(item)" :disabled="userDepIds.includes(item.ID)" :margin="[10]" :shadow="0" text size="small" :width="106" :height="50" outlined label="移除"></tm-button>
|
||||
<div class="flex items-center ml-[10rpx]">
|
||||
<div>已选择部门数:</div>
|
||||
<div>{{ allCheckedList.length }}</div>
|
||||
</div>
|
||||
<div
|
||||
class="text-[#7A58DE] mr-[10rpx]"
|
||||
@click="() => (showWin = false)"
|
||||
>
|
||||
确定
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-1 pb-[20rpx] overflow-y-auto pt-[30rpx]">
|
||||
<div
|
||||
v-for="(item, index) in allCheckedList"
|
||||
class="flex flex-col"
|
||||
>
|
||||
<div v-if="index === 0" class="divider"></div>
|
||||
<div
|
||||
class="flex items-center justify-between mt-[36rpx] font-bold text-[#000000] leading-[54rpx] mb-[34rpx]"
|
||||
>
|
||||
<div class="text-[28rpx] ml-[10rpx]">{{ item.name }}</div>
|
||||
<div class="diyBtn">
|
||||
<tm-button
|
||||
@click="() => deleteNode(item)"
|
||||
:disabled="userDepIds.includes(item.ID)"
|
||||
:margin="[10]"
|
||||
:shadow="0"
|
||||
text
|
||||
size="small"
|
||||
:width="106"
|
||||
:height="50"
|
||||
outlined
|
||||
label="移除"
|
||||
></tm-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="divider"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="divider" ></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</tm-drawer>
|
||||
</tm-drawer>
|
||||
</template>
|
||||
</zPaging>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import { ref, watch, computed, onMounted,nextTick } from "vue";
|
||||
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 downDep from "@/static/image/chatList/downDep.png";
|
||||
import downDepDis from "@/static/image/chatList/downDepDis.png";
|
||||
import checkBox from "@/components/checkBox/index.vue";
|
||||
import zPaging from '@/uni_modules/z-paging/components/z-paging/z-paging.vue'
|
||||
import { ref, watch, computed, onMounted, nextTick } from 'vue'
|
||||
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 downDep from '@/static/image/chatList/downDep.png'
|
||||
import downDepDis from '@/static/image/chatList/downDepDis.png'
|
||||
import checkBox from '@/components/checkBox/index.vue'
|
||||
import lodash from 'lodash'
|
||||
|
||||
const {
|
||||
@ -197,205 +226,208 @@ const {
|
||||
crumbs,
|
||||
crumbsIndex,
|
||||
depCheckedKeys,
|
||||
} = useGroupTypeStore();
|
||||
} = useGroupTypeStore()
|
||||
const userStore = useUserStore()
|
||||
|
||||
const searchVal = ref("");
|
||||
const crumbsContainer = ref(null);
|
||||
const showWin = ref(false);
|
||||
const searchVal = ref('')
|
||||
const crumbsContainer = ref(null)
|
||||
const showWin = ref(false)
|
||||
|
||||
const getAllCheckedNodes = (node, checkedNodes = []) => {
|
||||
if (node.checkStatus === 'checked') {
|
||||
checkedNodes.push(node);
|
||||
checkedNodes.push(node)
|
||||
}
|
||||
if (node.sons && Array.isArray(node.sons)) {
|
||||
node.sons.forEach(son => getAllCheckedNodes(son, checkedNodes));
|
||||
node.sons.forEach((son) => getAllCheckedNodes(son, checkedNodes))
|
||||
}
|
||||
return checkedNodes;
|
||||
};
|
||||
return checkedNodes
|
||||
}
|
||||
|
||||
const userDepIds = computed(() => {
|
||||
return userStore.deps.map(v=>v.dept_id);
|
||||
});
|
||||
return userStore.deps.map((v) => v.dept_id)
|
||||
})
|
||||
|
||||
const allCheckedList = computed(() => {
|
||||
const checkedNodes = [];
|
||||
depTreeMyList.value.forEach(node => getAllCheckedNodes(node, checkedNodes));
|
||||
console.log("checkedNodes", checkedNodes);
|
||||
|
||||
return checkedNodes;
|
||||
const checkedNodes = []
|
||||
depTreeMyList.value.forEach((node) => getAllCheckedNodes(node, checkedNodes))
|
||||
console.log('checkedNodes', checkedNodes)
|
||||
|
||||
return checkedNodes
|
||||
})
|
||||
|
||||
const currentCrumbs = computed(() => {
|
||||
if (crumbs.value[crumbsIndex.value] ) {
|
||||
if (searchVal.value && searchVal.value !== "") {
|
||||
if (crumbs.value[crumbsIndex.value]) {
|
||||
if (searchVal.value && searchVal.value !== '') {
|
||||
let filterSons = crumbs.value[crumbsIndex.value].sons.filter((item) =>
|
||||
item.name.includes(searchVal.value)
|
||||
);
|
||||
return {
|
||||
...crumbs.value[crumbsIndex.value],
|
||||
sons: filterSons,
|
||||
};
|
||||
item.name.includes(searchVal.value),
|
||||
)
|
||||
return {
|
||||
...crumbs.value[crumbsIndex.value],
|
||||
sons: filterSons,
|
||||
}
|
||||
}
|
||||
console.log("allCheckedList", crumbs.value[crumbsIndex.value]);
|
||||
return crumbs.value[crumbsIndex.value];
|
||||
console.log('allCheckedList', crumbs.value[crumbsIndex.value])
|
||||
return crumbs.value[crumbsIndex.value]
|
||||
}
|
||||
return {}
|
||||
});
|
||||
})
|
||||
|
||||
const allCheckStatus = computed(() => {
|
||||
if (!currentCrumbs.value.sons) {
|
||||
return "noChecked";
|
||||
return 'noChecked'
|
||||
}
|
||||
const allChecked = currentCrumbs.value.sons.every((son) => son.checkStatus === "checked");
|
||||
const someChecked =currentCrumbs.value.sons.some(
|
||||
(son) =>
|
||||
son.checkStatus === "checked" || son.checkStatus === "halfChecked"
|
||||
);
|
||||
if (allChecked) {
|
||||
return "checked";
|
||||
const allChecked = currentCrumbs.value.sons.every(
|
||||
(son) => son.checkStatus === 'checked',
|
||||
)
|
||||
const someChecked = currentCrumbs.value.sons.some(
|
||||
(son) => son.checkStatus === 'checked' || son.checkStatus === 'halfChecked',
|
||||
)
|
||||
if (allChecked) {
|
||||
return 'checked'
|
||||
} else if (someChecked) {
|
||||
return "halfChecked";
|
||||
return 'halfChecked'
|
||||
} else {
|
||||
return "noChecked";
|
||||
return 'noChecked'
|
||||
}
|
||||
})
|
||||
|
||||
const findNodeById = (node, targetId) => {
|
||||
if (node.ID === targetId) {
|
||||
return node;
|
||||
return node
|
||||
}
|
||||
if (node.sons && Array.isArray(node.sons)) {
|
||||
for (const son of node.sons) {
|
||||
const found = findNodeById(son, targetId);
|
||||
const found = findNodeById(son, targetId)
|
||||
if (found) {
|
||||
return found;
|
||||
return found
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
return null
|
||||
}
|
||||
|
||||
const findParentNode = (node, targetId) => {
|
||||
if (!node.sons || !Array.isArray(node.sons)) return null;
|
||||
if (!node.sons || !Array.isArray(node.sons)) return null
|
||||
for (const son of node.sons) {
|
||||
if (son.ID === targetId) {
|
||||
return node;
|
||||
return node
|
||||
}
|
||||
const parent = findParentNode(son, targetId);
|
||||
const parent = findParentNode(son, targetId)
|
||||
if (parent) {
|
||||
return parent;
|
||||
return parent
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
return null
|
||||
}
|
||||
|
||||
// 检查并更新父节点的状态
|
||||
const updateParentStatus = (node) => {
|
||||
const parent = findParentNode(depTreeMyList.value[0], node.ID);
|
||||
if (!parent) return;
|
||||
if (parent.checkStatus !== "checked"){
|
||||
const parent = findParentNode(depTreeMyList.value[0], node.ID)
|
||||
if (!parent) return
|
||||
if (parent.checkStatus !== 'checked') {
|
||||
const allChecked =
|
||||
parent.sons &&
|
||||
Array.isArray(parent.sons) &&
|
||||
parent.sons.every((son) => son.checkStatus === "checked");
|
||||
parent.sons.every((son) => son.checkStatus === 'checked')
|
||||
const someChecked =
|
||||
parent.sons &&
|
||||
Array.isArray(parent.sons) &&
|
||||
parent.sons.some(
|
||||
(son) =>
|
||||
son.checkStatus === "checked" || son.checkStatus === "halfChecked"
|
||||
);
|
||||
|
||||
son.checkStatus === 'checked' || son.checkStatus === 'halfChecked',
|
||||
)
|
||||
|
||||
if (allChecked) {
|
||||
parent.checkStatus = "halfChecked";
|
||||
parent.checkStatus = 'halfChecked'
|
||||
} else if (someChecked) {
|
||||
parent.checkStatus = "halfChecked";
|
||||
parent.checkStatus = 'halfChecked'
|
||||
} else {
|
||||
parent.checkStatus = "noChecked";
|
||||
parent.checkStatus = 'noChecked'
|
||||
}
|
||||
}
|
||||
|
||||
updateParentStatus(parent);
|
||||
};
|
||||
updateParentStatus(parent)
|
||||
}
|
||||
//根据子节点状态更新当前节点状态
|
||||
const updateNodeStatus = (node) => {
|
||||
if (!node.sons || !Array.isArray(node.sons)) return;
|
||||
const allChecked = node.sons.every(son => son.checkStatus === 'checked');
|
||||
const someChecked = node.sons.some(son => son.checkStatus === 'checked' || son.checkStatus === 'halfChecked');
|
||||
if (!node.sons || !Array.isArray(node.sons)) return
|
||||
const allChecked = node.sons.every((son) => son.checkStatus === 'checked')
|
||||
const someChecked = node.sons.some(
|
||||
(son) => son.checkStatus === 'checked' || son.checkStatus === 'halfChecked',
|
||||
)
|
||||
|
||||
if (allChecked) {
|
||||
node.checkStatus = 'halfChecked';
|
||||
node.checkStatus = 'halfChecked'
|
||||
} else if (someChecked) {
|
||||
node.checkStatus = 'halfChecked';
|
||||
node.checkStatus = 'halfChecked'
|
||||
} else {
|
||||
node.checkStatus = 'noChecked';
|
||||
node.checkStatus = 'noChecked'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// 更新当前节点及其所有子节点的状态
|
||||
const updateCheckStatus = (node, status) => {
|
||||
node.checkStatus = status;
|
||||
node.checkStatus = status
|
||||
if (node.sons && Array.isArray(node.sons) && node.sons.length > 0) {
|
||||
node.sons.forEach((son) => updateCheckStatus(son, status));
|
||||
node.sons.forEach((son) => updateCheckStatus(son, status))
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const checkItemChange = (item, val) => {
|
||||
// 更新当前节点及其子节点的状态
|
||||
updateCheckStatus(item, val);
|
||||
updateCheckStatus(item, val)
|
||||
|
||||
// 更新父节点的状态
|
||||
updateParentStatus(item);
|
||||
};
|
||||
updateParentStatus(item)
|
||||
}
|
||||
|
||||
const deleteNode = (item) => {
|
||||
const treeNode = findNodeById(depTreeMyList.value[0], item.ID);
|
||||
if (!treeNode) return;
|
||||
treeNode.checkStatus = "noChecked";
|
||||
updateNodeStatus(treeNode);
|
||||
updateParentStatus(treeNode);
|
||||
};
|
||||
const treeNode = findNodeById(depTreeMyList.value[0], item.ID)
|
||||
if (!treeNode) return
|
||||
treeNode.checkStatus = 'noChecked'
|
||||
updateNodeStatus(treeNode)
|
||||
updateParentStatus(treeNode)
|
||||
}
|
||||
|
||||
const toNextLevel = async (item) => {
|
||||
if (item.checkStatus !== "checked") {
|
||||
crumbs.value.push(item);
|
||||
crumbsIndex.value++;
|
||||
await nextTick();
|
||||
if (item.checkStatus !== 'checked') {
|
||||
crumbs.value.push(item)
|
||||
crumbsIndex.value++
|
||||
await nextTick()
|
||||
if (crumbsContainer.value) {
|
||||
crumbsContainer.value.scrollLeft = crumbsContainer.value.scrollWidth;
|
||||
crumbsContainer.value.scrollLeft = crumbsContainer.value.scrollWidth
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const handleCrumbsClick = (index) => {
|
||||
crumbsIndex.value = index;
|
||||
crumbs.value = crumbs.value.slice(0, index + 1);
|
||||
};
|
||||
crumbsIndex.value = index
|
||||
crumbs.value = crumbs.value.slice(0, index + 1)
|
||||
}
|
||||
|
||||
const allCheck = (status) => {
|
||||
let statusT = 'noChecked';
|
||||
if (status === "checked") {
|
||||
statusT = "noChecked";
|
||||
let statusT = 'noChecked'
|
||||
if (status === 'checked') {
|
||||
statusT = 'noChecked'
|
||||
} else {
|
||||
statusT = "checked";
|
||||
statusT = 'checked'
|
||||
}
|
||||
currentCrumbs.value.sons.forEach((item) => {
|
||||
const itemT = findNodeById(depTreeMyList.value[0], item.ID)
|
||||
if (!itemT) return;
|
||||
checkItemChange(itemT, statusT);
|
||||
});
|
||||
};
|
||||
if (!itemT) return
|
||||
checkItemChange(itemT, statusT)
|
||||
})
|
||||
}
|
||||
|
||||
const openDrawer = () => {
|
||||
showWin.value = true;
|
||||
if (allCheckedList.length>0) {
|
||||
showWin.value = true
|
||||
if (allCheckedList.length > 0) {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// watch(() => depTreeMyList.value, (newValue, oldValue) => {
|
||||
// console.log("depTreeMyList", newValue);
|
||||
|
||||
|
||||
// }, { deep: true });
|
||||
|
||||
// watch(() => searchVal.value, (newValue, oldValue) => {
|
||||
@ -403,55 +435,49 @@ const openDrawer = () => {
|
||||
// });
|
||||
|
||||
const handleConfirm = () => {
|
||||
depCheckedKeys.value = lodash.cloneDeep(allCheckedList.value)
|
||||
console.log("depCheckedKeys", depCheckedKeys.value);
|
||||
|
||||
uni.navigateBack();
|
||||
depCheckedKeys.value = lodash.cloneDeep(allCheckedList.value)
|
||||
console.log('depCheckedKeys', depCheckedKeys.value)
|
||||
|
||||
uni.navigateBack()
|
||||
}
|
||||
|
||||
const initCheckedKeys = () => {
|
||||
depCheckedKeys.value.forEach((item) => {
|
||||
const node = findNodeById(depTreeMyList.value[0], item.ID);
|
||||
const node = findNodeById(depTreeMyList.value[0], item.ID)
|
||||
if (node) {
|
||||
node.checkStatus = "checked";
|
||||
updateParentStatus(node);
|
||||
node.checkStatus = 'checked'
|
||||
updateParentStatus(node)
|
||||
}
|
||||
});
|
||||
};
|
||||
})
|
||||
}
|
||||
|
||||
const init = async () => {
|
||||
crumbsIndex.value = 0;
|
||||
await getDepsTreeMy();
|
||||
crumbs.value = depTreeMyList.value.length ? [depTreeMyList.value[0]] : [];
|
||||
initCheckedKeys();
|
||||
};
|
||||
crumbsIndex.value = 0
|
||||
await getDepsTreeMy()
|
||||
crumbs.value = depTreeMyList.value.length ? [depTreeMyList.value[0]] : []
|
||||
initCheckedKeys()
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
init();
|
||||
});
|
||||
init()
|
||||
})
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
uni-page-body,
|
||||
page {
|
||||
::v-deep .zp-paging-container-content {
|
||||
height: 100%;
|
||||
}
|
||||
.outer-layer {
|
||||
overflow-y: auto;
|
||||
flex: 1;
|
||||
background-image: url("@/static/image/clockIn/z3280@3x.png");
|
||||
background-size: cover;
|
||||
padding-bottom: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.root {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
padding-top: 18rpx;
|
||||
overflow: hidden;
|
||||
.choose-deps-page {
|
||||
.choose-deps {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-image: url('@/static/image/clockIn/z3280@3x.png');
|
||||
background-size: cover;
|
||||
padding: 20rpx 0;
|
||||
}
|
||||
}
|
||||
|
||||
.divider {
|
||||
height: 1rpx;
|
||||
background-color: #707070;
|
||||
@ -478,42 +504,42 @@ page {
|
||||
background-repeat: no-repeat;
|
||||
border-radius: 12rpx;
|
||||
&.firstPanel {
|
||||
background-image: url("@/static/image/chatList/zu6033@2x.png");
|
||||
background-image: url('@/static/image/chatList/zu6033@2x.png');
|
||||
}
|
||||
&.secondPanel {
|
||||
background-image: url("@/static/image/chatList/zu6031@2x.png");
|
||||
background-image: url('@/static/image/chatList/zu6031@2x.png');
|
||||
margin-top: 28rpx;
|
||||
margin-bottom: 28rpx;
|
||||
}
|
||||
&.thirdPanel {
|
||||
background-image: url("@/static/image/chatList/zu6032@2x.png");
|
||||
background-image: url('@/static/image/chatList/zu6032@2x.png');
|
||||
}
|
||||
&.activePanel {
|
||||
box-shadow: 0 0 0 3rpx #46299d;
|
||||
}
|
||||
}
|
||||
.btnBox {
|
||||
:deep(uni-button[disabled="true"]) {
|
||||
:deep(uni-button[disabled='true']) {
|
||||
color: #bebebe !important;
|
||||
}
|
||||
}
|
||||
.no-scrollbar {
|
||||
-ms-overflow-style: none; /* IE and Edge */
|
||||
scrollbar-width: none; /* Firefox */
|
||||
-ms-overflow-style: none; /* IE and Edge */
|
||||
scrollbar-width: none; /* Firefox */
|
||||
}
|
||||
|
||||
.no-scrollbar::-webkit-scrollbar {
|
||||
display: none; /* Chrome, Safari, and Opera */
|
||||
display: none; /* Chrome, Safari, and Opera */
|
||||
}
|
||||
.diyBtn {
|
||||
:deep(uni-button) {
|
||||
color: #191919 !important;
|
||||
border: 1rpx solid #D6D6D8 !important;
|
||||
background-color: #FFFFFF !important;
|
||||
&[disabled="true"] {
|
||||
color: #BEBEBE !important;
|
||||
border: 1rpx solid #E6E6E6 !important;
|
||||
background-color: #E6E6E6 !important;
|
||||
border: 1rpx solid #d6d6d8 !important;
|
||||
background-color: #ffffff !important;
|
||||
&[disabled='true'] {
|
||||
color: #bebebe !important;
|
||||
border: 1rpx solid #e6e6e6 !important;
|
||||
background-color: #e6e6e6 !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,159 +1,220 @@
|
||||
<template>
|
||||
<div class="outer-layer">
|
||||
<div>
|
||||
<tm-navbar :hideBack="false" hideHome title="选择群类型">
|
||||
</tm-navbar>
|
||||
</div>
|
||||
<div class="root">
|
||||
<div class="w-full h-[1134rpx] mb-[20rpx] pl-[32rpx] pr-[32rpx] overflow-y-auto" >
|
||||
<div class=" pl-[32rpx] pr-[32rpx] pt-[44rpx] pb-[42rpx] bg-[#FFFFFF]" >
|
||||
<div class="text-[40rpx] leading-[54rpx] text-[#2F2F2F] font-bold" >
|
||||
群类型保存后将不可修改
|
||||
</div>
|
||||
<div class="text-[28rpx] leading-[54rpx] text-[#46299D] mt-[16rpx]" >
|
||||
请创建过程中正确选择
|
||||
</div>
|
||||
<div class="mt-[54rpx] w-full h-[872rpx]" >
|
||||
<div @click="groupActiveIndex = 0" class="groupCard firstPanel" :class="groupActiveIndex === 0?'activePanel':''">
|
||||
<div class="w-full h-full pt-[64rpx] pl-[36rpx]">
|
||||
<div class="text-[36rpx] leading-[54rpx] text-[#2F2F2F] font-bold" >
|
||||
普通群
|
||||
</div>
|
||||
<div class="text-[24rpx] leading-[36rpx] text-[#939393] w-[216rpx]" >
|
||||
员工线上沟通专用群
|
||||
离职后自动退群
|
||||
<div class="choose-group-type-page">
|
||||
<ZPaging ref="zPaging" :show-scrollbar="false">
|
||||
<template #top>
|
||||
<customNavbar :title="$t('pageTitle.select.groupType')"></customNavbar>
|
||||
</template>
|
||||
<div class="choose-group-type">
|
||||
<div class="w-full h-[1134rpx] mb-[20rpx] pl-[32rpx] pr-[32rpx]">
|
||||
<div class="pl-[32rpx] pr-[32rpx] pt-[44rpx] pb-[42rpx] bg-[#FFFFFF]">
|
||||
<div class="text-[40rpx] leading-[54rpx] text-[#2F2F2F] font-bold">
|
||||
群类型保存后将不可修改
|
||||
</div>
|
||||
<div class="text-[28rpx] leading-[54rpx] text-[#46299D] mt-[16rpx]">
|
||||
请创建过程中正确选择
|
||||
</div>
|
||||
<div class="mt-[54rpx] w-full h-[872rpx]">
|
||||
<div
|
||||
@click="groupActiveIndex = 0;depCheckedKeys = []"
|
||||
class="groupCard firstPanel"
|
||||
:class="groupActiveIndex === 0 ? 'activePanel' : ''"
|
||||
>
|
||||
<div class="w-full h-full pt-[64rpx] pl-[36rpx]">
|
||||
<div
|
||||
class="text-[36rpx] leading-[54rpx] text-[#2F2F2F] font-bold"
|
||||
>
|
||||
普通群
|
||||
</div>
|
||||
<div
|
||||
class="text-[24rpx] leading-[36rpx] text-[#939393] w-[216rpx]"
|
||||
>
|
||||
员工线上沟通专用群 离职后自动退群
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div @click="groupActiveIndex = 1" class="groupCard secondPanel" :class="groupActiveIndex === 1?'activePanel':''">
|
||||
<div class="w-full h-full pr-[36rpx] flex flex-col items-end justify-center">
|
||||
<div class="text-[36rpx] leading-[54rpx] text-[#2F2F2F] font-bold" >
|
||||
部门群
|
||||
</div>
|
||||
<div class="text-[24rpx] leading-[36rpx] text-[#939393] w-[288rpx] text-end" >
|
||||
指定部门员工入职自动进群
|
||||
离职后自动退群
|
||||
</div>
|
||||
<div v-if="!depCheckedKeys.length" @click="chooseDep" class="text-[24rpx] leading-[36rpx] text-[#C1B4EA] flex items-center mt-[16rpx]" >
|
||||
<div :class="groupActiveIndex === 1?'text-[#7A58DE]':'text-[#C1B4EA]'" >选择部门</div>
|
||||
<div class="ml-[20rpx]">
|
||||
<tm-icon name="tmicon-angle-right" :font-size="18" :color="groupActiveIndex === 1?'#7A58DE':'#C1B4EA'"></tm-icon>
|
||||
<div
|
||||
v-if="isHasPermission"
|
||||
@click="groupActiveIndex = 1"
|
||||
class="groupCard secondPanel"
|
||||
:class="groupActiveIndex === 1 ? 'activePanel' : ''"
|
||||
>
|
||||
<div
|
||||
class="w-full h-full pr-[36rpx] flex flex-col items-end justify-center"
|
||||
>
|
||||
<div
|
||||
class="text-[36rpx] leading-[54rpx] text-[#2F2F2F] font-bold"
|
||||
>
|
||||
部门群
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="text-[24rpx] leading-[36rpx] flex flex-col justify-center items-end mt-[16rpx] mb-[22rpx]" >
|
||||
<div class="max-w-[336rpx] truncate" >
|
||||
{{ depCheckedKeys.map(v=>v.name).toString() }}
|
||||
<div
|
||||
class="text-[24rpx] leading-[36rpx] text-[#939393] w-[288rpx] text-end"
|
||||
>
|
||||
指定部门员工入职自动进群 离职后自动退群
|
||||
</div>
|
||||
<div @click="chooseDep" class="flex items-center" >
|
||||
<div class="text-[#7A58DE]" >查看全部</div>
|
||||
<div
|
||||
v-if="!depCheckedKeys.length"
|
||||
@click="chooseDep"
|
||||
class="text-[24rpx] leading-[36rpx] text-[#C1B4EA] flex items-center mt-[16rpx]"
|
||||
>
|
||||
<div
|
||||
:class="
|
||||
groupActiveIndex === 1
|
||||
? 'text-[#7A58DE]'
|
||||
: 'text-[#C1B4EA]'
|
||||
"
|
||||
>
|
||||
选择部门
|
||||
</div>
|
||||
<div class="ml-[20rpx]">
|
||||
<tm-icon name="tmicon-angle-right" :font-size="18" :color="groupActiveIndex === 1?'#7A58DE':'#C1B4EA'"></tm-icon>
|
||||
<tm-icon
|
||||
name="tmicon-angle-right"
|
||||
:font-size="18"
|
||||
:color="groupActiveIndex === 1 ? '#7A58DE' : '#C1B4EA'"
|
||||
></tm-icon>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
class="text-[24rpx] leading-[36rpx] flex flex-col justify-center items-end mt-[16rpx] mb-[22rpx]"
|
||||
>
|
||||
<div class="max-w-[336rpx] truncate">
|
||||
{{ depCheckedKeys.map((v) => v.name).toString() }}
|
||||
</div>
|
||||
<div @click="chooseDep" class="flex items-center">
|
||||
<div class="text-[#7A58DE]">查看全部</div>
|
||||
<div class="ml-[20rpx]">
|
||||
<tm-icon
|
||||
name="tmicon-angle-right"
|
||||
:font-size="18"
|
||||
:color="
|
||||
groupActiveIndex === 1 ? '#7A58DE' : '#C1B4EA'
|
||||
"
|
||||
></tm-icon>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div @click="groupActiveIndex = 2" class="groupCard thirdPanel" :class="groupActiveIndex === 2?'activePanel':''">
|
||||
<div class="w-full h-full pt-[64rpx] pl-[36rpx]">
|
||||
<div class="text-[36rpx] leading-[54rpx] text-[#2F2F2F] font-bold" >
|
||||
项目群
|
||||
</div>
|
||||
<div class="text-[24rpx] leading-[36rpx] text-[#939393] w-[216rpx]" >
|
||||
项目成员沟通专用群
|
||||
离职后自动退群
|
||||
<div
|
||||
v-if="isCreateProjecy"
|
||||
@click="groupActiveIndex = 2;depCheckedKeys = [];"
|
||||
class="groupCard thirdPanel"
|
||||
:class="groupActiveIndex === 2 ? 'activePanel' : ''"
|
||||
>
|
||||
<div class="w-full h-full pt-[64rpx] pl-[36rpx]">
|
||||
<div
|
||||
class="text-[36rpx] leading-[54rpx] text-[#2F2F2F] font-bold"
|
||||
>
|
||||
项目群
|
||||
</div>
|
||||
<div
|
||||
class="text-[24rpx] leading-[36rpx] text-[#939393] w-[216rpx]"
|
||||
>
|
||||
项目成员沟通专用群 离职后自动退群
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="h-[162rpx] flex justify-center bg-[#FFFFFF]">
|
||||
<div class="mt-[14rpx] btnBox" >
|
||||
<tm-button
|
||||
@click="handleConfirm"
|
||||
color="#46299D"
|
||||
:disabled="confirmBtnStatus"
|
||||
disabledColor="#E6E6E6"
|
||||
:margin="[0]"
|
||||
:shadow="0"
|
||||
:width="426"
|
||||
:height="76"
|
||||
size="large"
|
||||
label="确定"
|
||||
>
|
||||
</tm-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<template #bottom>
|
||||
<customBtn
|
||||
:isBottom="true"
|
||||
:btnText="$t('ok')"
|
||||
@click="handleConfirm"
|
||||
:disabled="confirmBtnStatus"
|
||||
></customBtn>
|
||||
</template>
|
||||
</ZPaging>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
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 } from "@/store";
|
||||
import { useGroupTypeStore } from "@/store/groupType";
|
||||
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 { 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 { groupActiveIndex,depCheckedKeys } = useGroupTypeStore();
|
||||
})
|
||||
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 confirmBtnStatus = computed(() => {
|
||||
let disabledT = false;
|
||||
let disabledT = false
|
||||
switch (groupActiveIndex.value) {
|
||||
case 0:
|
||||
break;
|
||||
break
|
||||
case 1:
|
||||
if (!depCheckedKeys.value.length) {
|
||||
disabledT = true;
|
||||
disabledT = true
|
||||
}
|
||||
break;
|
||||
break
|
||||
case 2:
|
||||
break;
|
||||
break
|
||||
default:
|
||||
break;
|
||||
break
|
||||
}
|
||||
return disabledT
|
||||
})
|
||||
|
||||
const chooseDep = () => {
|
||||
uni.navigateTo({
|
||||
url: '/pages/chooseDeps/index'
|
||||
});
|
||||
};
|
||||
url: '/pages/chooseByDeps/index?chooseMode=1',
|
||||
})
|
||||
}
|
||||
|
||||
const handleConfirm = () => {
|
||||
uni.navigateBack()
|
||||
// uni.navigateTo({
|
||||
// url: '/pages/creatGroupChat/index'
|
||||
// });
|
||||
};
|
||||
|
||||
}
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
uni-page-body,
|
||||
page {
|
||||
::v-deep .zp-paging-container-content {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
}
|
||||
.outer-layer {
|
||||
overflow-y: auto;
|
||||
|
||||
.choose-group-type {
|
||||
flex: 1;
|
||||
background-image: url("@/static/image/clockIn/z3280@3x.png");
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-image: url('@/static/image/clockIn/z3280@3x.png');
|
||||
background-size: cover;
|
||||
padding-bottom: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-position: bottom center;
|
||||
padding: 20rpx 0;
|
||||
background-attachment: fixed;
|
||||
}
|
||||
.root {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
padding-top: 18rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
.divider{
|
||||
|
||||
.divider {
|
||||
height: 1rpx;
|
||||
background-color: #7C7C7C;
|
||||
background-color: #7c7c7c;
|
||||
opacity: 0.6;
|
||||
}
|
||||
.avatar-placeholder {
|
||||
@ -171,24 +232,23 @@ page {
|
||||
background-repeat: no-repeat;
|
||||
border-radius: 12rpx;
|
||||
&.firstPanel {
|
||||
background-image: url("@/static/image/chatList/zu6033@2x.png");
|
||||
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;
|
||||
background-image: url('@/static/image/chatList/zu6031@2x.png');
|
||||
margin-bottom: 28rpx;
|
||||
}
|
||||
&.thirdPanel {
|
||||
background-image: url("@/static/image/chatList/zu6032@2x.png");
|
||||
background-image: url('@/static/image/chatList/zu6032@2x.png');
|
||||
}
|
||||
&.activePanel {
|
||||
box-shadow: 0 0 0 3rpx #46299D;
|
||||
box-shadow: 0 0 0 3rpx #46299d;
|
||||
}
|
||||
}
|
||||
.btnBox {
|
||||
:deep(uni-button[disabled="true"]){
|
||||
color: #BEBEBE !important;
|
||||
:deep(uni-button[disabled='true']) {
|
||||
color: #bebebe !important;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="outer-layer">
|
||||
<div>
|
||||
<tm-navbar :hideBack="false" hideHome title="选择部门"> </tm-navbar>
|
||||
<tm-navbar :hideBack="false" hideHome title="选择部门"></tm-navbar>
|
||||
</div>
|
||||
<div class="root">
|
||||
<div
|
||||
@ -22,7 +22,7 @@
|
||||
v-if="crumbs.length"
|
||||
class="w-full overflow-x-auto mt-[22rpx] leading-[48rpx] text-[#2F2F2F] flex items-center no-scrollbar"
|
||||
ref="crumbsContainer"
|
||||
>
|
||||
>
|
||||
<div
|
||||
v-for="(item, index) in crumbs"
|
||||
class="flex items-center text-[28rpx] leading-[48rpx] whitespace-nowrap"
|
||||
@ -48,7 +48,7 @@
|
||||
</div>
|
||||
<div
|
||||
class="pl-[32rpx] bg-[#FFFFFF] mt-[20rpx] h-[110rpx] flex items-center"
|
||||
@click="()=>allCheck(allCheckStatus)"
|
||||
@click="() => allCheck(allCheckStatus)"
|
||||
>
|
||||
<div>
|
||||
<checkBox
|
||||
@ -63,12 +63,20 @@
|
||||
<div
|
||||
v-if="currentCrumbs?.sons?.length"
|
||||
v-for="item in currentCrumbs?.sons"
|
||||
@click="
|
||||
checkItemChange(
|
||||
item,
|
||||
item.checkStatus
|
||||
? item.checkStatus === 'checked'
|
||||
? 'noChecked'
|
||||
: 'checked'
|
||||
: 'checked',
|
||||
)
|
||||
"
|
||||
class="pl-[32rpx] bg-[#FFFFFF] mt-[20rpx] h-[110rpx] flex items-center"
|
||||
>
|
||||
<div class="w-full flex items-center justify-between">
|
||||
<div
|
||||
class="flex items-center"
|
||||
>
|
||||
<div class="flex items-center">
|
||||
<div>
|
||||
<checkBox
|
||||
:disabled="!item?.sons?.length"
|
||||
@ -82,7 +90,10 @@
|
||||
</div>
|
||||
<div v-if="item.sons?.length" class="flex items-center mr-[32rpx]">
|
||||
<div class="vDivider mr-[32rpx]"></div>
|
||||
<div @click="() => toNextLevel(item)" class="flex items-center">
|
||||
<div
|
||||
@click.stop="() => toNextLevel(item)"
|
||||
class="flex items-center"
|
||||
>
|
||||
<div class="mr-[12rpx]">
|
||||
<tm-image
|
||||
:width="26"
|
||||
@ -108,15 +119,26 @@
|
||||
<div
|
||||
v-if="currentMembers.length"
|
||||
v-for="item in currentMembers"
|
||||
@click="
|
||||
checkMember(
|
||||
item,
|
||||
membersCheckedKeys.filter((v) => v.ID === item.ID).length > 0
|
||||
? 'noChecked'
|
||||
: 'checked',
|
||||
)
|
||||
"
|
||||
class="pl-[32rpx] pr-[32rpx] bg-[#FFFFFF] mt-[20rpx] h-[110rpx] flex items-center"
|
||||
>
|
||||
<div class="w-full flex items-center justify-between">
|
||||
<div
|
||||
class="flex items-center"
|
||||
>
|
||||
<div class="flex items-center">
|
||||
<div>
|
||||
<checkBox
|
||||
:modelValue="membersCheckedKeys.filter(v=>v.ID === item.ID).length>0?'checked':'noChecked'"
|
||||
:modelValue="
|
||||
membersCheckedKeys.filter((v) => v.ID === item.ID).length >
|
||||
0
|
||||
? 'checked'
|
||||
: 'noChecked'
|
||||
"
|
||||
@change="(val) => checkMember(item, val)"
|
||||
></checkBox>
|
||||
</div>
|
||||
@ -124,26 +146,32 @@
|
||||
<div class="userAvatar flex items-center justify-center">
|
||||
{{ item.nickName.slice(-2) }}
|
||||
</div>
|
||||
<div class="ml-[20rpx] flex flex-col items-center">
|
||||
<div class="text-[28rpx] font-bold" >{{ item.nickName }}</div>
|
||||
<div class="text-[20rpx] text-[#747474]" >{{ item.jobNum }}</div>
|
||||
<div class="ml-[20rpx] flex flex-col justify-center">
|
||||
<div class="text-[28rpx] font-bold">{{ item.nickName }}</div>
|
||||
<div class="text-[20rpx] text-[#747474]">
|
||||
{{ item.jobNum }}
|
||||
</div>
|
||||
</div>
|
||||
<tm-popover position="tc">
|
||||
<div class="ml-[20rpx] flex h-[68rpx] flex-wrap line-clamp-2 max-w-[342rpx]">
|
||||
<div
|
||||
class="ml-[6rpx] flex h-[68rpx] flex-wrap line-clamp-2 max-w-[342rpx]"
|
||||
>
|
||||
<div
|
||||
v-for="post in item.positions"
|
||||
class="postTag truncate mb-[4rpx] mr-[14rpx] max-w-[164rpx]"
|
||||
class="postTag truncate mb-[4rpx] ml-[14rpx] max-w-[164rpx]"
|
||||
>
|
||||
{{ post.name }}
|
||||
</div>
|
||||
</div>
|
||||
<template v-slot:label>
|
||||
<div class="max-h-[250rpx] overflow-y-auto pt-[10rpx] pl-[18rpx] pr-[18rpx] pb-[12rpx]">
|
||||
<div
|
||||
class="max-h-[250rpx] overflow-y-auto pt-[10rpx] pl-[18rpx] pr-[18rpx] pb-[12rpx]"
|
||||
>
|
||||
<div
|
||||
v-for="post in item.positions"
|
||||
class="postTag truncate mb-[10rpx] "
|
||||
v-for="post in item.positions"
|
||||
class="postTag truncate mb-[10rpx]"
|
||||
>
|
||||
{{ post.name }}
|
||||
{{ post.name }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@ -152,25 +180,33 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="h-[162rpx] pl-[32rpx] pr-[32rpx] bg-[#FFFFFF]">
|
||||
<div class="mt-[14rpx] flex justify-between">
|
||||
<div class="flex flex-col">
|
||||
<div @click="openDrawer" class="flex items-center text-[28rpx] leading-[60rpx] text-[#000000] font-bold">
|
||||
<div
|
||||
@click="openDrawer"
|
||||
class="flex items-center text-[28rpx] leading-[60rpx] text-[#000000] font-bold"
|
||||
>
|
||||
<div>已选择:</div>
|
||||
<div>{{ totalMembers }}人</div>
|
||||
<div class="ml-[28rpx]">
|
||||
<tm-icon :fontSize="24" color="#46299D" name="tmicon-angle-up"></tm-icon>
|
||||
<tm-icon
|
||||
:fontSize="24"
|
||||
color="#46299D"
|
||||
name="tmicon-angle-up"
|
||||
></tm-icon>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-[24rpx] leading-[24rpx] text-[#7A58DE] w-[280rpx] truncate ">
|
||||
<span v-for="item in allCheckedList" >
|
||||
<div
|
||||
class="text-[24rpx] leading-[24rpx] text-[#7A58DE] w-[280rpx] truncate"
|
||||
>
|
||||
<span v-for="item in allCheckedList">
|
||||
{{ item.name }}({{ item.staffNum }})
|
||||
</span>
|
||||
<span v-for="(item,index) in membersCheckedKeys" >
|
||||
<span v-for="(item, index) in membersCheckedKeys">
|
||||
{{ item.nickName }}
|
||||
{{ index!== membersCheckedKeys.length-1?',':"" }}
|
||||
{{ index !== membersCheckedKeys.length - 1 ? ',' : '' }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@ -186,74 +222,98 @@
|
||||
:height="76"
|
||||
size="large"
|
||||
label="确定"
|
||||
>
|
||||
</tm-button>
|
||||
></tm-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<tm-drawer
|
||||
placement="bottom"
|
||||
v-model:show="showWin"
|
||||
:height="800"
|
||||
<tm-drawer
|
||||
placement="bottom"
|
||||
v-model:show="showWin"
|
||||
:height="800"
|
||||
:hideHeader="true"
|
||||
:round="5"
|
||||
>
|
||||
<div class="flex flex-col w-full h-full pt-[36rpx] pl-[32rpx] pr-[32rpx] leading-[60rpx]" >
|
||||
<div class="text-[32rpx] font-bold flex items-center justify-between" >
|
||||
<div
|
||||
class="flex flex-col w-full h-full pt-[36rpx] pl-[32rpx] pr-[32rpx] leading-[60rpx]"
|
||||
>
|
||||
<div class="text-[32rpx] font-bold flex items-center justify-between">
|
||||
<div class="flex items-center ml-[10rpx]">
|
||||
<div>已选择部门数:</div>
|
||||
<div>{{ allCheckedList.length }}</div>
|
||||
</div>
|
||||
<div
|
||||
<div
|
||||
class="text-[#7A58DE] mr-[10rpx]"
|
||||
@click="()=>showWin = false"
|
||||
@click="() => (showWin = false)"
|
||||
>
|
||||
确定
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-1 pb-[20rpx] overflow-y-auto pt-[30rpx]" >
|
||||
<div
|
||||
v-for="(item,index) in allCheckedList"
|
||||
class="flex flex-col"
|
||||
>
|
||||
<div v-if="index ===0" class="divider" ></div>
|
||||
<div
|
||||
class="flex items-center justify-between mt-[36rpx] font-bold text-[#000000] leading-[54rpx] mb-[34rpx]"
|
||||
<div class="flex-1 pb-[20rpx] overflow-y-auto pt-[30rpx]">
|
||||
<div v-for="(item, index) in allCheckedList" class="flex flex-col">
|
||||
<div v-if="index === 0" class="divider"></div>
|
||||
<div
|
||||
class="flex items-center justify-between mt-[36rpx] font-bold text-[#000000] leading-[54rpx] mb-[34rpx]"
|
||||
>
|
||||
<div class="text-[28rpx] ml-[10rpx]">{{ item.name }}({{ item.staffNum }})</div>
|
||||
<div class="text-[28rpx] ml-[10rpx]">
|
||||
{{ item.name }}({{ item.staffNum }})
|
||||
</div>
|
||||
<div class="diyBtn">
|
||||
<tm-button @click="()=>deleteNode(item)" :disabled="userDepIds.includes(item.ID)" :margin="[10]" :shadow="0" text size="small" :width="106" :height="50" outlined label="移除"></tm-button>
|
||||
<tm-button
|
||||
@click="() => deleteNode(item)"
|
||||
:disabled="userDepIds.includes(item.ID)"
|
||||
:margin="[10]"
|
||||
:shadow="0"
|
||||
text
|
||||
size="small"
|
||||
:width="106"
|
||||
:height="50"
|
||||
outlined
|
||||
label="移除"
|
||||
></tm-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="divider" ></div>
|
||||
<div class="divider"></div>
|
||||
</div>
|
||||
<div
|
||||
v-for="(item,index) in membersCheckedKeys"
|
||||
v-for="(item, index) in membersCheckedKeys"
|
||||
class="flex flex-col"
|
||||
>
|
||||
<div v-if="index ===0 && allCheckedList.length==0" class="divider" ></div>
|
||||
<div
|
||||
class="flex items-center justify-between mt-[36rpx] font-bold text-[#000000] leading-[54rpx] mb-[34rpx]"
|
||||
<div
|
||||
v-if="index === 0 && allCheckedList.length == 0"
|
||||
class="divider"
|
||||
></div>
|
||||
<div
|
||||
class="flex items-center justify-between mt-[36rpx] font-bold text-[#000000] leading-[54rpx] mb-[34rpx]"
|
||||
>
|
||||
<div class="text-[28rpx] ml-[10rpx] flex">
|
||||
<div>
|
||||
{{ item.nickName }}({{ item.jobNum }})
|
||||
</div>
|
||||
<div class="ml-[20rpx] flex h-[68rpx] flex-wrap line-clamp-2 max-w-[342rpx]">
|
||||
<div
|
||||
v-for="post in item.positions"
|
||||
class="postTag truncate mb-[4rpx] mr-[14rpx] max-w-[164rpx]"
|
||||
>
|
||||
{{ post.name }}
|
||||
</div>
|
||||
<div>{{ item.nickName }}({{ item.jobNum }})</div>
|
||||
<div
|
||||
class="ml-[20rpx] flex h-[68rpx] flex-wrap line-clamp-2 max-w-[342rpx]"
|
||||
>
|
||||
<div
|
||||
v-for="post in item.positions"
|
||||
class="postTag truncate mb-[4rpx] mr-[14rpx] max-w-[164rpx]"
|
||||
>
|
||||
{{ post.name }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="diyBtn">
|
||||
<tm-button @click="()=>deleteMember(item)" :margin="[10]" :shadow="0" text size="small" :width="106" :height="50" outlined label="移除"></tm-button>
|
||||
<tm-button
|
||||
@click="() => deleteMember(item)"
|
||||
:margin="[10]"
|
||||
:shadow="0"
|
||||
text
|
||||
size="small"
|
||||
:width="106"
|
||||
:height="50"
|
||||
outlined
|
||||
label="移除"
|
||||
></tm-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="divider" ></div>
|
||||
<div class="divider"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -261,15 +321,15 @@
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import { ref, watch, computed, onMounted,nextTick } from "vue";
|
||||
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 downDep from "@/static/image/chatList/downDep.png";
|
||||
import downDepDis from "@/static/image/chatList/downDepDis.png";
|
||||
import checkBox from "@/components/checkBox/index.vue";
|
||||
import { ref, watch, computed, onMounted, nextTick } from 'vue'
|
||||
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 downDep from '@/static/image/chatList/downDep.png'
|
||||
import downDepDis from '@/static/image/chatList/downDepDis.png'
|
||||
import checkBox from '@/components/checkBox/index.vue'
|
||||
import lodash from 'lodash'
|
||||
|
||||
const {
|
||||
@ -282,76 +342,78 @@ const {
|
||||
getDepMembers,
|
||||
membersCheckedKeys,
|
||||
allChooseMembers,
|
||||
} = useGroupTypeStore();
|
||||
} = useGroupTypeStore()
|
||||
const userStore = useUserStore()
|
||||
|
||||
const searchVal = ref("");
|
||||
const crumbsContainer = ref(null);
|
||||
const showWin = ref(false);
|
||||
const searchVal = ref('')
|
||||
const crumbsContainer = ref(null)
|
||||
const showWin = ref(false)
|
||||
const currentMembers = ref([])
|
||||
|
||||
const getAllCheckedNodes = (node, checkedNodes = []) => {
|
||||
if (node.checkStatus === 'checked') {
|
||||
checkedNodes.push(node);
|
||||
checkedNodes.push(node)
|
||||
}
|
||||
if (node.sons && Array.isArray(node.sons)) {
|
||||
node.sons.forEach(son => getAllCheckedNodes(son, checkedNodes));
|
||||
node.sons.forEach((son) => getAllCheckedNodes(son, checkedNodes))
|
||||
}
|
||||
return checkedNodes;
|
||||
};
|
||||
return checkedNodes
|
||||
}
|
||||
|
||||
const userDepIds = computed(() => {
|
||||
return userStore.deps.map(v=>v.dept_id);
|
||||
});
|
||||
return userStore.deps.map((v) => v.dept_id)
|
||||
})
|
||||
|
||||
const calculateTotalStaffNum = (node) => {
|
||||
let total = node.staffNum || 0;
|
||||
let total = node.staffNum || 0
|
||||
if (node.sons && Array.isArray(node.sons)) {
|
||||
node.sons.forEach(son => {
|
||||
total += calculateTotalStaffNum(son);
|
||||
});
|
||||
node.sons.forEach((son) => {
|
||||
total += calculateTotalStaffNum(son)
|
||||
})
|
||||
}
|
||||
return total;
|
||||
};
|
||||
return total
|
||||
}
|
||||
|
||||
const allCheckedList = computed(() => {
|
||||
const checkedNodes = [];
|
||||
depTreeMyList.value.forEach(node => getAllCheckedNodes(node, checkedNodes));
|
||||
console.log("checkedNodes", checkedNodes);
|
||||
|
||||
return checkedNodes;
|
||||
const checkedNodes = []
|
||||
depTreeMyList.value.forEach((node) => getAllCheckedNodes(node, checkedNodes))
|
||||
console.log('checkedNodes', checkedNodes)
|
||||
|
||||
return checkedNodes
|
||||
})
|
||||
|
||||
const currentCrumbs = computed(() => {
|
||||
if (crumbs.value[crumbsIndex.value] ) {
|
||||
if (searchVal.value && searchVal.value !== "") {
|
||||
if (crumbs.value[crumbsIndex.value]) {
|
||||
if (searchVal.value && searchVal.value !== '') {
|
||||
let filterSons = crumbs.value[crumbsIndex.value].sons.filter((item) =>
|
||||
item.name.includes(searchVal.value)
|
||||
);
|
||||
return {
|
||||
...crumbs.value[crumbsIndex.value],
|
||||
sons: filterSons,
|
||||
};
|
||||
item.name.includes(searchVal.value),
|
||||
)
|
||||
return {
|
||||
...crumbs.value[crumbsIndex.value],
|
||||
sons: filterSons,
|
||||
}
|
||||
}
|
||||
console.log("allCheckedList", crumbs.value[crumbsIndex.value]);
|
||||
return crumbs.value[crumbsIndex.value];
|
||||
console.log('allCheckedList', crumbs.value[crumbsIndex.value])
|
||||
return crumbs.value[crumbsIndex.value]
|
||||
}
|
||||
return {}
|
||||
});
|
||||
})
|
||||
|
||||
const getCurrentMembers = async (depItem) => {
|
||||
const res = await getDepMembers({
|
||||
departmentId:depItem.ID,
|
||||
status:'notactive'
|
||||
departmentId: depItem.ID,
|
||||
status: 'notactive',
|
||||
})
|
||||
if (res.status === 0) {
|
||||
currentMembers.value = res.data.data.length?res.data.data.map(v=>{
|
||||
return {
|
||||
...v,
|
||||
isMember:true,
|
||||
}
|
||||
}):[]
|
||||
}else{
|
||||
if (res.code === 200) {
|
||||
currentMembers.value = res.data.data.length
|
||||
? res.data.data.map((v) => {
|
||||
return {
|
||||
...v,
|
||||
isMember: true,
|
||||
}
|
||||
})
|
||||
: []
|
||||
} else {
|
||||
currentMembers.value = []
|
||||
}
|
||||
}
|
||||
@ -359,231 +421,244 @@ const getCurrentMembers = async (depItem) => {
|
||||
const checkMember = (item, val) => {
|
||||
if (val === 'checked') {
|
||||
membersCheckedKeys.value.push(item)
|
||||
}else {
|
||||
membersCheckedKeys.value = membersCheckedKeys.value.filter(v=>v.ID !==item.ID)
|
||||
} else {
|
||||
membersCheckedKeys.value = membersCheckedKeys.value.filter(
|
||||
(v) => v.ID !== item.ID,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
watch(() => currentCrumbs.value, (newValue, oldValue) => {
|
||||
getCurrentMembers(newValue)
|
||||
})
|
||||
watch(
|
||||
() => currentCrumbs.value,
|
||||
(newValue, oldValue) => {
|
||||
getCurrentMembers(newValue)
|
||||
},
|
||||
)
|
||||
|
||||
const allCheckStatus = computed(() => {
|
||||
if (!currentCrumbs.value.sons) {
|
||||
return "noChecked";
|
||||
return 'noChecked'
|
||||
}
|
||||
const allChecked = currentCrumbs.value.sons.every((son) => son.checkStatus === "checked");
|
||||
const someChecked =currentCrumbs.value.sons.some(
|
||||
(son) =>
|
||||
son.checkStatus === "checked" || son.checkStatus === "halfChecked"
|
||||
);
|
||||
if (allChecked) {
|
||||
return "checked";
|
||||
const allChecked = currentCrumbs.value.sons.every(
|
||||
(son) => son.checkStatus === 'checked',
|
||||
)
|
||||
const someChecked = currentCrumbs.value.sons.some(
|
||||
(son) => son.checkStatus === 'checked' || son.checkStatus === 'halfChecked',
|
||||
)
|
||||
if (allChecked) {
|
||||
return 'checked'
|
||||
} else if (someChecked) {
|
||||
return "halfChecked";
|
||||
return 'halfChecked'
|
||||
} else {
|
||||
return "noChecked";
|
||||
return 'noChecked'
|
||||
}
|
||||
})
|
||||
|
||||
const findNodeById = (node, targetId) => {
|
||||
if (node.ID === targetId) {
|
||||
return node;
|
||||
return node
|
||||
}
|
||||
if (node.sons && Array.isArray(node.sons)) {
|
||||
for (const son of node.sons) {
|
||||
const found = findNodeById(son, targetId);
|
||||
const found = findNodeById(son, targetId)
|
||||
if (found) {
|
||||
return found;
|
||||
return found
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
return null
|
||||
}
|
||||
|
||||
const findParentNode = (node, targetId) => {
|
||||
if (!node.sons || !Array.isArray(node.sons)) return null;
|
||||
if (!node.sons || !Array.isArray(node.sons)) return null
|
||||
for (const son of node.sons) {
|
||||
if (son.ID === targetId) {
|
||||
return node;
|
||||
return node
|
||||
}
|
||||
const parent = findParentNode(son, targetId);
|
||||
const parent = findParentNode(son, targetId)
|
||||
if (parent) {
|
||||
return parent;
|
||||
return parent
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
return null
|
||||
}
|
||||
|
||||
// 检查并更新父节点的状态
|
||||
const updateParentStatus = (node) => {
|
||||
const parent = findParentNode(depTreeMyList.value[0], node.ID);
|
||||
if (!parent) return;
|
||||
if (parent.checkStatus !== "checked"){
|
||||
const parent = findParentNode(depTreeMyList.value[0], node.ID)
|
||||
if (!parent) return
|
||||
if (parent.checkStatus !== 'checked') {
|
||||
const allChecked =
|
||||
parent.sons &&
|
||||
Array.isArray(parent.sons) &&
|
||||
parent.sons.every((son) => son.checkStatus === "checked");
|
||||
parent.sons.every((son) => son.checkStatus === 'checked')
|
||||
const someChecked =
|
||||
parent.sons &&
|
||||
Array.isArray(parent.sons) &&
|
||||
parent.sons.some(
|
||||
(son) =>
|
||||
son.checkStatus === "checked" || son.checkStatus === "halfChecked"
|
||||
);
|
||||
|
||||
son.checkStatus === 'checked' || son.checkStatus === 'halfChecked',
|
||||
)
|
||||
|
||||
if (allChecked) {
|
||||
parent.checkStatus = "halfChecked";
|
||||
parent.checkStatus = 'halfChecked'
|
||||
} else if (someChecked) {
|
||||
parent.checkStatus = "halfChecked";
|
||||
parent.checkStatus = 'halfChecked'
|
||||
} else {
|
||||
parent.checkStatus = "noChecked";
|
||||
parent.checkStatus = 'noChecked'
|
||||
}
|
||||
}
|
||||
|
||||
updateParentStatus(parent);
|
||||
};
|
||||
updateParentStatus(parent)
|
||||
}
|
||||
//根据子节点状态更新当前节点状态
|
||||
const updateNodeStatus = (node) => {
|
||||
if (!node.sons || !Array.isArray(node.sons)) return;
|
||||
const allChecked = node.sons.every(son => son.checkStatus === 'checked');
|
||||
const someChecked = node.sons.some(son => son.checkStatus === 'checked' || son.checkStatus === 'halfChecked');
|
||||
if (!node.sons || !Array.isArray(node.sons)) return
|
||||
const allChecked = node.sons.every((son) => son.checkStatus === 'checked')
|
||||
const someChecked = node.sons.some(
|
||||
(son) => son.checkStatus === 'checked' || son.checkStatus === 'halfChecked',
|
||||
)
|
||||
|
||||
if (allChecked) {
|
||||
node.checkStatus = 'halfChecked';
|
||||
node.checkStatus = 'halfChecked'
|
||||
} else if (someChecked) {
|
||||
node.checkStatus = 'halfChecked';
|
||||
node.checkStatus = 'halfChecked'
|
||||
} else {
|
||||
node.checkStatus = 'noChecked';
|
||||
node.checkStatus = 'noChecked'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// 更新当前节点及其所有子节点的状态
|
||||
const updateCheckStatus = (node, status) => {
|
||||
node.checkStatus = status;
|
||||
node.checkStatus = status
|
||||
if (node.sons && Array.isArray(node.sons) && node.sons.length > 0) {
|
||||
node.sons.forEach((son) => updateCheckStatus(son, status));
|
||||
node.sons.forEach((son) => updateCheckStatus(son, status))
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const checkItemChange = (item, val) => {
|
||||
// 更新当前节点及其子节点的状态
|
||||
updateCheckStatus(item, val);
|
||||
updateCheckStatus(item, val)
|
||||
|
||||
// 更新父节点的状态
|
||||
updateParentStatus(item);
|
||||
};
|
||||
updateParentStatus(item)
|
||||
}
|
||||
|
||||
const deleteNode = (item) => {
|
||||
const treeNode = findNodeById(depTreeMyList.value[0], item.ID);
|
||||
if (!treeNode) return;
|
||||
treeNode.checkStatus = "noChecked";
|
||||
updateNodeStatus(treeNode);
|
||||
updateParentStatus(treeNode);
|
||||
};
|
||||
const treeNode = findNodeById(depTreeMyList.value[0], item.ID)
|
||||
if (!treeNode) return
|
||||
treeNode.checkStatus = 'noChecked'
|
||||
updateNodeStatus(treeNode)
|
||||
updateParentStatus(treeNode)
|
||||
}
|
||||
|
||||
const deleteMember = (item) => {
|
||||
membersCheckedKeys.value = membersCheckedKeys.value.filter(v=>v.ID !== item.ID)
|
||||
membersCheckedKeys.value = membersCheckedKeys.value.filter(
|
||||
(v) => v.ID !== item.ID,
|
||||
)
|
||||
}
|
||||
|
||||
const toNextLevel = async (item) => {
|
||||
if (item.checkStatus !== "checked") {
|
||||
crumbs.value.push(item);
|
||||
crumbsIndex.value++;
|
||||
await nextTick();
|
||||
if (item.checkStatus !== 'checked') {
|
||||
crumbs.value.push(item)
|
||||
crumbsIndex.value++
|
||||
await nextTick()
|
||||
if (crumbsContainer.value) {
|
||||
crumbsContainer.value.scrollLeft = crumbsContainer.value.scrollWidth;
|
||||
crumbsContainer.value.scrollLeft = crumbsContainer.value.scrollWidth
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const handleCrumbsClick = (index) => {
|
||||
crumbsIndex.value = index;
|
||||
crumbs.value = crumbs.value.slice(0, index + 1);
|
||||
};
|
||||
crumbsIndex.value = index
|
||||
crumbs.value = crumbs.value.slice(0, index + 1)
|
||||
}
|
||||
|
||||
const allCheck = (status) => {
|
||||
let statusT = 'noChecked';
|
||||
if (status === "checked") {
|
||||
statusT = "noChecked";
|
||||
let statusT = 'noChecked'
|
||||
if (status === 'checked') {
|
||||
statusT = 'noChecked'
|
||||
} else {
|
||||
statusT = "checked";
|
||||
statusT = 'checked'
|
||||
}
|
||||
currentCrumbs.value.sons.forEach((item) => {
|
||||
const itemT = findNodeById(depTreeMyList.value[0], item.ID)
|
||||
if (!itemT) return;
|
||||
checkItemChange(itemT, statusT);
|
||||
});
|
||||
};
|
||||
if (!itemT) return
|
||||
checkItemChange(itemT, statusT)
|
||||
})
|
||||
}
|
||||
|
||||
const openDrawer = () => {
|
||||
showWin.value = true;
|
||||
if (allCheckedList.length>0) {
|
||||
showWin.value = true
|
||||
if (allCheckedList.length > 0) {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// watch(() => depTreeMyList.value, (newValue, oldValue) => {
|
||||
// console.log("depTreeMyList", newValue);
|
||||
|
||||
|
||||
// }, { deep: true });
|
||||
|
||||
// watch(() => searchVal.value, (newValue, oldValue) => {
|
||||
// console.log("searchVal", newValue);
|
||||
// });
|
||||
|
||||
|
||||
const getDepTotalMembers = (item) => {
|
||||
const rootNode = depTreeMyList.value[0];
|
||||
const targetNode = findNodeById(rootNode, item.ID);
|
||||
const rootNode = depTreeMyList.value[0]
|
||||
const targetNode = findNodeById(rootNode, item.ID)
|
||||
if (targetNode) {
|
||||
return calculateTotalStaffNum(targetNode);
|
||||
return calculateTotalStaffNum(targetNode)
|
||||
}
|
||||
return 0;
|
||||
return 0
|
||||
}
|
||||
|
||||
const totalMembers = computed(() => {
|
||||
const depMembers = allCheckedList.value.reduce((sum, item) => sum + item.staffNum, 0);
|
||||
const memberCount = membersCheckedKeys.value.length;
|
||||
return depMembers + memberCount;
|
||||
const depMembers = allCheckedList.value.reduce(
|
||||
(sum, item) => sum + item.staffNum,
|
||||
0,
|
||||
)
|
||||
const memberCount = membersCheckedKeys.value.length
|
||||
return depMembers + memberCount
|
||||
})
|
||||
|
||||
const handleConfirm = async () => {
|
||||
uni.showLoading()
|
||||
const listT = membersCheckedKeys.value.map(v=>v)
|
||||
const listT = membersCheckedKeys.value.map((v) => v)
|
||||
const res = await getDepMembers({
|
||||
departmentIds:allCheckedList.value.map(v=>v.ID),
|
||||
status:'notactive',
|
||||
departmentIds: allCheckedList.value.map((v) => v.ID),
|
||||
status: 'notactive',
|
||||
})
|
||||
if (res.status == 0 && res.data?.data?.length) {
|
||||
res.data?.data.forEach(v=>{
|
||||
if (res.code == 200 && res.data?.data?.length) {
|
||||
res.data?.data.forEach((v) => {
|
||||
listT.push(v)
|
||||
})
|
||||
}
|
||||
allChooseMembers.value = listT
|
||||
depCheckedKeys.value = lodash.cloneDeep(allCheckedList.value)
|
||||
uni.hideLoading()
|
||||
uni.navigateBack();
|
||||
uni.navigateBack()
|
||||
}
|
||||
|
||||
const initCheckedKeys = () => {
|
||||
depCheckedKeys.value.forEach((item) => {
|
||||
const node = findNodeById(depTreeMyList.value[0], item.ID);
|
||||
const node = findNodeById(depTreeMyList.value[0], item.ID)
|
||||
if (node) {
|
||||
node.checkStatus = "checked";
|
||||
updateParentStatus(node);
|
||||
node.checkStatus = 'checked'
|
||||
updateParentStatus(node)
|
||||
}
|
||||
});
|
||||
};
|
||||
})
|
||||
}
|
||||
|
||||
const init = async () => {
|
||||
crumbsIndex.value = 0;
|
||||
await getDepsTreeMy();
|
||||
crumbs.value = depTreeMyList.value.length ? [depTreeMyList.value[0]] : [];
|
||||
initCheckedKeys();
|
||||
};
|
||||
crumbsIndex.value = 0
|
||||
await getDepsTreeMy()
|
||||
crumbs.value = depTreeMyList.value.length ? [depTreeMyList.value[0]] : []
|
||||
initCheckedKeys()
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
init();
|
||||
});
|
||||
init()
|
||||
})
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
uni-page-body,
|
||||
@ -593,7 +668,7 @@ page {
|
||||
.outer-layer {
|
||||
overflow-y: auto;
|
||||
flex: 1;
|
||||
background-image: url("@/static/image/clockIn/z3280@3x.png");
|
||||
background-image: url('@/static/image/clockIn/z3280@3x.png');
|
||||
background-size: cover;
|
||||
padding-bottom: 0;
|
||||
display: flex;
|
||||
@ -633,47 +708,47 @@ page {
|
||||
background-repeat: no-repeat;
|
||||
border-radius: 12rpx;
|
||||
&.firstPanel {
|
||||
background-image: url("@/static/image/chatList/zu6033@2x.png");
|
||||
background-image: url('@/static/image/chatList/zu6033@2x.png');
|
||||
}
|
||||
&.secondPanel {
|
||||
background-image: url("@/static/image/chatList/zu6031@2x.png");
|
||||
background-image: url('@/static/image/chatList/zu6031@2x.png');
|
||||
margin-top: 28rpx;
|
||||
margin-bottom: 28rpx;
|
||||
}
|
||||
&.thirdPanel {
|
||||
background-image: url("@/static/image/chatList/zu6032@2x.png");
|
||||
background-image: url('@/static/image/chatList/zu6032@2x.png');
|
||||
}
|
||||
&.activePanel {
|
||||
box-shadow: 0 0 0 3rpx #46299d;
|
||||
}
|
||||
}
|
||||
.btnBox {
|
||||
:deep(uni-button[disabled="true"]) {
|
||||
:deep(uni-button[disabled='true']) {
|
||||
color: #bebebe !important;
|
||||
}
|
||||
}
|
||||
.no-scrollbar {
|
||||
-ms-overflow-style: none; /* IE and Edge */
|
||||
scrollbar-width: none; /* Firefox */
|
||||
-ms-overflow-style: none; /* IE and Edge */
|
||||
scrollbar-width: none; /* Firefox */
|
||||
}
|
||||
|
||||
.no-scrollbar::-webkit-scrollbar {
|
||||
display: none; /* Chrome, Safari, and Opera */
|
||||
display: none; /* Chrome, Safari, and Opera */
|
||||
}
|
||||
.diyBtn {
|
||||
:deep(uni-button) {
|
||||
color: #191919 !important;
|
||||
border: 1rpx solid #D6D6D8 !important;
|
||||
background-color: #FFFFFF !important;
|
||||
&[disabled="true"] {
|
||||
color: #BEBEBE !important;
|
||||
border: 1rpx solid #E6E6E6 !important;
|
||||
background-color: #E6E6E6 !important;
|
||||
border: 1rpx solid #d6d6d8 !important;
|
||||
background-color: #ffffff !important;
|
||||
&[disabled='true'] {
|
||||
color: #bebebe !important;
|
||||
border: 1rpx solid #e6e6e6 !important;
|
||||
background-color: #e6e6e6 !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
.userAvatar{
|
||||
background: linear-gradient(#674BBC, #46299D);
|
||||
.userAvatar {
|
||||
background: linear-gradient(#674bbc, #46299d);
|
||||
width: 72rpx;
|
||||
height: 72rpx;
|
||||
border-radius: 36rpx;
|
||||
@ -681,12 +756,12 @@ page {
|
||||
font-size: 24rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
.postTag{
|
||||
background-color: #EEE9F8;
|
||||
.postTag {
|
||||
background-color: #eee9f8;
|
||||
height: 32rpx;
|
||||
line-height: 32rpx;
|
||||
font-size: 20rpx;
|
||||
padding: 0 12rpx 0 12rpx;
|
||||
color: #46299D;
|
||||
color: #46299d;
|
||||
}
|
||||
</style>
|
||||
|
366
src/pages/complaintReport/index.vue
Normal file
@ -0,0 +1,366 @@
|
||||
<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>
|
@ -1,328 +1,344 @@
|
||||
<template>
|
||||
<div class="outer-layer">
|
||||
<div>
|
||||
<tm-navbar :hideBack="false" hideHome title="发起群聊" :leftWidth="320">
|
||||
</tm-navbar>
|
||||
</div>
|
||||
<div class="root">
|
||||
<div class="w-full pl-[32rpx] pr-[32rpx] mb-[20rpx]" >
|
||||
<div class="w-full mt-[60rpx] flex justify-center" >
|
||||
<div v-if="groupActiveIndex === -1" class="avatar-placeholder">
|
||||
|
||||
</div>
|
||||
<div class="mb-[40rpx]" v-else>
|
||||
<tm-image
|
||||
:width="192"
|
||||
:height="192"
|
||||
:round="12"
|
||||
:src="avatarImg"
|
||||
></tm-image>
|
||||
<div class="create-group-chat-page">
|
||||
<zPaging ref="zPaging" :show-scrollbar="false">
|
||||
<template #top>
|
||||
<customNavbar :title="$t('pageTitle.create.group')"></customNavbar>
|
||||
</template>
|
||||
<div class="create-group-chat flex flex-col">
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
<div class="input-group flex items-center justify-between" >
|
||||
<div class="input-group flex items-center justify-between">
|
||||
<div class="input-item">
|
||||
群名称
|
||||
</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>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
<div class="input-group w-full flex flex-col mt-[20rpx] leading-[40rpx]" >
|
||||
<div class="flex items-center justify-between" >
|
||||
<div 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="chooseGroupType" class="left-box" >
|
||||
<div class="text-[#B4B4B4] text-[28rpx] font-bold" >
|
||||
<span v-if="groupActiveIndex ===-1">请选择群类型</span>
|
||||
<span v-else-if="groupActiveIndex ===0">普通群</span>
|
||||
<span v-else-if="groupActiveIndex ===1">部门群</span>
|
||||
<span v-else-if="groupActiveIndex ===2">项目群</span>
|
||||
<div @click="chooseGroupType" class="left-box">
|
||||
<div class="text-[#B4B4B4] text-[28rpx] font-bold">
|
||||
<span v-if="groupActiveIndex === -1">请选择群类型</span>
|
||||
<span v-else-if="groupActiveIndex === 0">普通群</span>
|
||||
<span v-else-if="groupActiveIndex === 1">部门群</span>
|
||||
<span v-else-if="groupActiveIndex === 2">项目群</span>
|
||||
</div>
|
||||
<div class="ml-[32rpx]" >
|
||||
<div class="ml-[32rpx]">
|
||||
<tm-icon :font-size="22" color="#747474" name="tmicon-angle-right"></tm-icon>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="depCheckedKeys.length" 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&&index>4 ? 'hidden':''
|
||||
]"
|
||||
>
|
||||
index !== 0 ? 'mt-[10rpx]' : '',
|
||||
depsNoExpanded_1 && 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 = !depsNoExpanded" class="w-[100rpx]">
|
||||
{{ depsNoExpanded? '展开':'收起' }}
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="groupActiveIndex===0" class="input-group w-full flex flex-col mt-[20rpx] leading-[40rpx]" >
|
||||
<div class="flex items-center justify-between" >
|
||||
|
||||
<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 v-if="!groupAdmins.length" class="text-[#B4B4B4] text-[28rpx] font-bold mr-[32rpx]">全部({{ 0 }})</div>
|
||||
<div @click="chooseMembers" class="left-box">
|
||||
<div class="ml-[32rpx] flex items-center">
|
||||
<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>
|
||||
</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"
|
||||
:class="[
|
||||
index !==0 ? 'mt-[10rpx]':'',
|
||||
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 = !depsNoExpanded" class="w-[100rpx]">
|
||||
{{ depsNoExpanded? '展开':'收起' }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<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 class="flex items-center justify-between" >
|
||||
<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>
|
||||
<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>
|
||||
<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-if="groupAdmins.length" class="mt-[32rpx]">
|
||||
<div v-for="(item, index) in groupAdmins" class="text-[#747474] text-[28rpx] leading-[40rpx] font-bold"
|
||||
:class="[
|
||||
index !==0 ? 'mt-[10rpx]':'',
|
||||
depsNoExpanded&&index>4 ? 'hidden':''
|
||||
]"
|
||||
>
|
||||
index !== 0 ? 'mt-[10rpx]' : '',
|
||||
depsNoExpanded_2 && 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 = !depsNoExpanded" class="w-[100rpx]">
|
||||
{{ depsNoExpanded? '展开':'收起' }}
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="h-[162rpx] flex justify-center bg-[#FFFFFF]">
|
||||
<div class="mt-[14rpx] btnBox" >
|
||||
<tm-button
|
||||
@click="handleConfirm"
|
||||
color="#46299D"
|
||||
:disabled="confirmBtnStatus"
|
||||
disabledColor="#E6E6E6"
|
||||
:margin="[0]"
|
||||
:shadow="0"
|
||||
:width="426"
|
||||
:height="76"
|
||||
size="large"
|
||||
label="发起群聊"
|
||||
>
|
||||
</tm-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<template #bottom>
|
||||
<customBtn :isBottom="true" :btnText="$t('pageTitle.create.group')" @click="handleConfirm"
|
||||
:isLoading="isLoading" :disabled="confirmBtnStatus || isLoading"></customBtn>
|
||||
</template>
|
||||
</zPaging>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
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 } from "@/store";
|
||||
import addCircle from "@/static/image/chatList/addCircle.png";
|
||||
import cahtPopover from "@/static/image/chatList/cahtPopover.png";
|
||||
import zu4992 from "@/static/image/chatList/zu4992@2x.png";
|
||||
import zu4991 from "@/static/image/chatList/zu4991@2x.png";
|
||||
import zu4989 from "@/static/image/chatList/zu4989@2x.png";
|
||||
import { useGroupTypeStore } from "@/store/groupType";
|
||||
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'
|
||||
|
||||
const { groupName,groupActiveIndex,depCheckedKeys,groupAdmins,createDepGroup,resetGroupInfo } = useGroupTypeStore();
|
||||
const talkStore = useTalkStore();
|
||||
const userStore = useUserStore();
|
||||
const { userInfo } = useAuth();
|
||||
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'
|
||||
|
||||
const groupChatType = ref('');
|
||||
const depsNoExpanded = ref(true);
|
||||
const {
|
||||
groupName,
|
||||
groupActiveIndex,
|
||||
depCheckedKeys,
|
||||
groupAdmins,
|
||||
createDepGroup,
|
||||
resetGroupInfo,
|
||||
allChooseMembers,
|
||||
} = useGroupTypeStore()
|
||||
const talkStore = useTalkStore()
|
||||
const userStore = useUserStore()
|
||||
const groupStore = useGroupStore()
|
||||
const {
|
||||
userInfo
|
||||
} = useAuth()
|
||||
|
||||
const avatarImg = computed(() => {
|
||||
let srcT = "";
|
||||
switch (groupActiveIndex.value) {
|
||||
case 0:
|
||||
srcT = zu4992;
|
||||
break;
|
||||
case 1:
|
||||
srcT = zu4989;
|
||||
break;
|
||||
case 2:
|
||||
srcT = zu4991;
|
||||
break;
|
||||
default:
|
||||
srcT = zu4992;
|
||||
}
|
||||
return srcT;
|
||||
})
|
||||
const groupChatType = ref('')
|
||||
const depsNoExpanded_1 = ref(true)
|
||||
const depsNoExpanded_2 = ref(true)
|
||||
|
||||
const chooseGroupType = () => {
|
||||
uni.navigateTo({
|
||||
url: '/pages/chooseGroupType/index'
|
||||
|
||||
onLoad(() => {
|
||||
groupStore.$reset()
|
||||
})
|
||||
}
|
||||
|
||||
const chooseGroupAdmin = () => {
|
||||
uni.navigateTo({
|
||||
url: '/pages/chooseGroupAdmin/index'
|
||||
onUnload(() => {
|
||||
resetGroupInfo();
|
||||
})
|
||||
}
|
||||
|
||||
const chooseMembers = () => {
|
||||
uni.navigateTo({
|
||||
url: '/pages/chooseMembers/index'
|
||||
onMounted(() => {
|
||||
handleSetWebviewStyle()
|
||||
})
|
||||
}
|
||||
|
||||
const handleConfirm = async () => {
|
||||
if (groupActiveIndex.value === 0) {
|
||||
|
||||
}else if(groupActiveIndex.value === 1){
|
||||
const res = await createDepGroup()
|
||||
if (res.code === 200) {
|
||||
resetGroupInfo()
|
||||
uni.navigateBack()
|
||||
//群类型
|
||||
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
|
||||
})
|
||||
|
||||
}else {
|
||||
|
||||
//点击跳转到选择群类型页面
|
||||
const chooseGroupType = () => {
|
||||
uni.navigateTo({
|
||||
url: '/pages/chooseGroupType/index',
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const confirmBtnStatus = computed(() => {
|
||||
let disabledT = false;
|
||||
if (groupName.value === "" || !groupActiveIndex.value) {
|
||||
return true;
|
||||
const chooseGroupAdmin = () => {
|
||||
uni.navigateTo({
|
||||
url: '/pages/chatSettings/groupManage/selectMembers?manageType=admin&isCreateDepGroup=1',
|
||||
})
|
||||
}
|
||||
switch (groupActiveIndex.value) {
|
||||
case 0:
|
||||
break;
|
||||
case 1:
|
||||
if (!depCheckedKeys.value.length) {
|
||||
disabledT = true;
|
||||
|
||||
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
|
||||
}
|
||||
})
|
||||
}
|
||||
if (!groupAdmins.value.length) {
|
||||
disabledT = true;
|
||||
|
||||
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)
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
||||
if (res?.code === 200) {
|
||||
resetGroupInfo()
|
||||
uni.navigateBack()
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
return disabledT
|
||||
})
|
||||
|
||||
onShow(() => {
|
||||
depsNoExpanded.value = true;
|
||||
})
|
||||
|
||||
//发起群聊按钮可点击状态
|
||||
const confirmBtnStatus = computed(() => {
|
||||
return groupActiveIndex.value === -1;
|
||||
});
|
||||
|
||||
onShow(() => {
|
||||
depsNoExpanded_1.value = true;
|
||||
depsNoExpanded_2.value = true;
|
||||
})
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
uni-page-body,
|
||||
page {
|
||||
height: 100%;
|
||||
}
|
||||
.outer-layer {
|
||||
overflow-y: auto;
|
||||
flex: 1;
|
||||
background-image: url("@/static/image/clockIn/z3280@3x.png");
|
||||
background-size: cover;
|
||||
padding-bottom: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.root {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.divider{
|
||||
height: 1rpx;
|
||||
background-color: #7C7C7C;
|
||||
opacity: 0.6;
|
||||
}
|
||||
.avatar-placeholder {
|
||||
width: 192rpx;
|
||||
height: 192rpx;
|
||||
background-color: #e0e0e0;
|
||||
border-radius: 50%;
|
||||
margin-bottom: 40rpx;
|
||||
}
|
||||
.input-group {
|
||||
background-color: #fff;
|
||||
padding-top: 38rpx;
|
||||
padding-bottom: 32rpx;
|
||||
padding-left: 32rpx;
|
||||
padding-right: 40rpx;
|
||||
// display: flex;
|
||||
// align-items: center;
|
||||
// justify-content: space-between;
|
||||
}
|
||||
.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;
|
||||
}
|
||||
.btnBox {
|
||||
:deep(uni-button[disabled="true"]) {
|
||||
color: #bebebe !important;
|
||||
::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%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -1,25 +1,34 @@
|
||||
<template>
|
||||
<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 :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>
|
||||
</template>
|
||||
<script setup>
|
||||
import { ref, reactive, defineProps,defineEmits } from "vue"
|
||||
import dayjs from "dayjs";
|
||||
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 { 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 = '') => {
|
||||
@ -33,21 +42,15 @@ 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 {
|
||||
|
@ -1,25 +1,39 @@
|
||||
<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>
|
||||
@ -27,10 +41,15 @@
|
||||
</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'
|
||||
@ -43,82 +62,167 @@ const props = defineProps({
|
||||
sendUserInfo: {
|
||||
type: Object,
|
||||
default: {},
|
||||
required: true
|
||||
required: true,
|
||||
},
|
||||
talkParams: {
|
||||
type: Object,
|
||||
default: {},
|
||||
required: true
|
||||
}
|
||||
});
|
||||
required: true,
|
||||
},
|
||||
})
|
||||
|
||||
const state = reactive({
|
||||
base64Url: '',
|
||||
})
|
||||
|
||||
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) {
|
||||
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{
|
||||
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 {
|
||||
// let OAWebView = plus.webview.all()
|
||||
// OAWebView.forEach((webview, index) => {
|
||||
// if (webview.id === 'webviewId1') {
|
||||
// webview.evalJS(`getPlusVideoPicker()`)
|
||||
// }
|
||||
// })
|
||||
// return
|
||||
uni.chooseVideo({
|
||||
sourceType: ['album'],
|
||||
compressed: true,
|
||||
maxDuration: 60,
|
||||
success: async (res) => {
|
||||
console.log(res,'res');
|
||||
let data = await onUploadImageVideo(res.tempFile, 'video',res.tempFilePath)
|
||||
emit('selectImg', data)
|
||||
}
|
||||
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)
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const onUploadImageVideo = async (file, type = 'image',fileUrl) => {
|
||||
console.log(file, 'file');
|
||||
const onUploadImageVideo = async (file, type = 'image', fileUrl) => {
|
||||
console.log('开始上传文件:', file.name)
|
||||
uploadsStore.updateUploadStatus(true)
|
||||
return new Promise(async (resolve) => {
|
||||
if (type === 'image') {
|
||||
let image = new Image()
|
||||
image.src = URL.createObjectURL(file)
|
||||
image.onload = () => {
|
||||
image.onload = async () => {
|
||||
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,
|
||||
@ -126,210 +230,372 @@ 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)
|
||||
uploadImg(form, (e) => onProgressFn(e, randomId)).then(({ status, data, msg }) => {
|
||||
if (status == 0) {
|
||||
|
||||
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: 'image',
|
||||
url: data.ori_url,
|
||||
url: result.data.ori_url,
|
||||
size: file.size,
|
||||
width: image.width,
|
||||
height: image.height
|
||||
height: image.height,
|
||||
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('')
|
||||
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:(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,
|
||||
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: {
|
||||
duration: parseInt(resp.duration),
|
||||
size: file.size
|
||||
})
|
||||
} else {
|
||||
// resolve('')
|
||||
// message.error(msg)
|
||||
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,
|
||||
}
|
||||
})
|
||||
}
|
||||
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('您的系统暂不支持发送原图哦')
|
||||
}
|
||||
// base64转file
|
||||
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)
|
||||
}
|
||||
})
|
||||
},
|
||||
(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);
|
||||
},
|
||||
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?.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 chooseFile = () => {
|
||||
uni.chooseFile({
|
||||
count: 1,
|
||||
extension:[''],
|
||||
success: (res) => {
|
||||
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,
|
||||
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.initUploadFile(res.tempFiles[0], props.talkParams,randomId)
|
||||
}
|
||||
|
||||
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: [''],
|
||||
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 上传失败
|
||||
}
|
||||
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)
|
||||
}
|
||||
},
|
||||
)
|
||||
},
|
||||
})
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.emojiRoot {
|
||||
|
@ -3,13 +3,7 @@
|
||||
<div class="root">
|
||||
<ZPaging ref="zPaging" :show-scrollbar="false">
|
||||
<template #top>
|
||||
<tm-navbar
|
||||
class="tmNavBar"
|
||||
:hideBack="false"
|
||||
hideHome
|
||||
title=""
|
||||
:leftWidth="220"
|
||||
></tm-navbar>
|
||||
<customNavbar class="tmNavBar"></customNavbar>
|
||||
</template>
|
||||
<div class="user-detail-info">
|
||||
<div class="user-info-head user-info-card">
|
||||
@ -33,14 +27,17 @@
|
||||
<span class="text-[40rpx] font-medium user-info-name">
|
||||
{{ state?.userInfo?.nickname }}
|
||||
</span>
|
||||
<span class="text-[28rpx] font-medium user-info-job-num">
|
||||
<span
|
||||
class="text-[28rpx] font-medium user-info-job-num"
|
||||
v-if="state?.userInfo?.job_num"
|
||||
>
|
||||
{{ $t('user.info.jobNum') + ':' + state?.userInfo?.job_num }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="user-info-main user-info-card">
|
||||
<div class="user-info-main-title">
|
||||
<img src="/src/static/image/mine/ming001@3x.png" />
|
||||
<img src="@/static/image/mine/ming001@3x.png" />
|
||||
<span class="text-[28rpx] font-medium">
|
||||
{{ $t('index.mine.basic') }}
|
||||
</span>
|
||||
@ -65,11 +62,43 @@
|
||||
<customBtn
|
||||
:isBottom="true"
|
||||
:btnText="$t('user.detail.sendMsg')"
|
||||
:subBtnText="$t('user.detail.ringBell')"
|
||||
:subBtnText="
|
||||
state.userInfo.sys_id === state.uid
|
||||
? ''
|
||||
: $t('user.detail.ringBell')
|
||||
"
|
||||
@clickBtn="toTalkUser"
|
||||
@clickSubBtn="handleCall"
|
||||
></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>
|
||||
@ -78,6 +107,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'
|
||||
const talkStore = useTalkStore()
|
||||
const userStore = useUserStore()
|
||||
|
||||
import { getUserInfoByClickAvatar } from '@/api/user/index'
|
||||
|
||||
import { useI18n } from 'vue-i18n'
|
||||
@ -87,12 +120,15 @@ const state = reactive({
|
||||
erpUserId: '', //erp用户id
|
||||
userInfo: null, //用户详情
|
||||
userBasicInfos: [], //用户基本信息
|
||||
isShowPhoneCall: false, //是否显示电话拨号弹框
|
||||
phoneNumber: '', //手机号
|
||||
uid: computed(() => userStore.uid), //当前用户id
|
||||
})
|
||||
|
||||
onLoad((options) => {
|
||||
console.log(options)
|
||||
if (options.erpUserId) {
|
||||
state.erpUserId = options.erpUserId
|
||||
state.erpUserId = Number(options.erpUserId)
|
||||
getUserInfo()
|
||||
}
|
||||
})
|
||||
@ -100,7 +136,7 @@ onLoad((options) => {
|
||||
//获取用户信息
|
||||
const getUserInfo = () => {
|
||||
let params = {
|
||||
erp_user_id: Number(state.erpUserId),
|
||||
erp_user_id: state.erpUserId,
|
||||
}
|
||||
console.log(params)
|
||||
const resp = getUserInfoByClickAvatar(params)
|
||||
@ -166,12 +202,41 @@ const getUserInfo = () => {
|
||||
value: data.enter_date,
|
||||
},
|
||||
]
|
||||
state.phoneNumber = data.tel_num
|
||||
} else {
|
||||
}
|
||||
})
|
||||
|
||||
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)
|
||||
},
|
||||
})
|
||||
}
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
.outer-layer {
|
||||
@ -284,4 +349,58 @@ const getUserInfo = () => {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.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>
|
||||
|
@ -1,4 +1,5 @@
|
||||
<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'
|
||||
@ -24,8 +25,13 @@ const onLoadData = () => {
|
||||
}).then((res) => {
|
||||
if (res.code == 200) {
|
||||
items.value = res.data.items || []
|
||||
|
||||
title.value = [...new Set(items.value.map((v) => v.nickname))].join('、')
|
||||
// 修复标题逻辑
|
||||
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('和') + '等';
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -42,12 +48,12 @@ onMounted(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="outer-layer">
|
||||
<div>
|
||||
<tm-navbar :hideBack="false" hideHome :title="`${title}的会话记录`" >
|
||||
</tm-navbar>
|
||||
</div>
|
||||
<div class="main-box">
|
||||
<div class="forward-record-page">
|
||||
<zPaging ref="zPaging" :show-scrollbar="false">
|
||||
<template #top>
|
||||
<customNavbar :title="`${title}的会话记录`"></customNavbar>
|
||||
</template>
|
||||
<div class="main-box">
|
||||
<div v-if="items.length === 0" class="flex justify-center items-center w-full mt-[200rpx]">
|
||||
<wd-loading />
|
||||
</div>
|
||||
@ -71,16 +77,15 @@ onMounted(() => {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</zPaging>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.outer-layer {
|
||||
overflow-y: auto;
|
||||
.forward-record-page {
|
||||
flex: 1;
|
||||
background-image: url("@/static/image/clockIn/z3280@3x.png");
|
||||
background-size: cover;
|
||||
padding: 0 66rpx 20rpx 50rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
@ -91,7 +96,7 @@ onMounted(() => {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: auto;
|
||||
padding-top: 28rpx;
|
||||
padding: 28rpx 66rpx 20rpx 50rpx;
|
||||
}
|
||||
|
||||
.message-item {
|
||||
|
@ -1,48 +1,73 @@
|
||||
<template>
|
||||
<div>
|
||||
<wd-swipe-action>
|
||||
<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"
|
||||
: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"
|
||||
>
|
||||
<tm-image
|
||||
:width="96"
|
||||
:height="96"
|
||||
:round="12"
|
||||
:src="avatarCpt"
|
||||
></tm-image>
|
||||
<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="flex items-center">
|
||||
<div class="text-[#000000] text-[32rpx] font-bold opacity-90">
|
||||
{{ props.data.name }}
|
||||
</div>
|
||||
<div>
|
||||
<div v-if="props.data.group_type === 2" class="depTag">
|
||||
<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">
|
||||
部门
|
||||
</div>
|
||||
<div v-if="props.data.group_type === 3" class="projectTag">
|
||||
</span>
|
||||
<span v-if="props.data.group_type === 3" class="projectTag tag">
|
||||
项目
|
||||
</div>
|
||||
<div v-if="props.data.group_type === 4" class="companyTag">
|
||||
</span>
|
||||
<span v-if="props.data.group_type === 4" class="companyTag tag">
|
||||
公司
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-[#000000] text-[28rpx] font-medium opacity-26">
|
||||
<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>
|
||||
@ -50,13 +75,16 @@
|
||||
</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 ? "取消置顶" : "置顶" }}
|
||||
{{ props.data.is_top === 1 ? '取消置顶' : '置顶' }}
|
||||
</div>
|
||||
<div
|
||||
@click="handleDelete"
|
||||
class="w-[156rpx] h-[154rpx] text-[#ffffff] bg-[#CF3050] flex items-center justify-center"
|
||||
>
|
||||
删除
|
||||
@ -71,20 +99,20 @@
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
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 zu4989 from "@/static/image/chatList/zu4989@2x.png";
|
||||
import zu4991 from "@/static/image/chatList/zu4991@2x.png";
|
||||
import zu4992 from "@/static/image/chatList/zu4992@2x.png";
|
||||
import zu5296 from "@/static/image/chatList/zu5296@2x.png";
|
||||
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 } = useSessionMenu();
|
||||
const dialogueStore = useDialogueStore();
|
||||
const talkStore = useTalkStore()
|
||||
const { onToTopTalk, onRemoveTalk } = useSessionMenu()
|
||||
const dialogueStore = useDialogueStore()
|
||||
const dialogueParams = reactive({
|
||||
unReadNum: computed(() => dialogueStore.unreadNum),
|
||||
})
|
||||
const props = defineProps({
|
||||
data: {
|
||||
type: Object,
|
||||
@ -96,59 +124,59 @@ const props = defineProps({
|
||||
default: -1,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
})
|
||||
|
||||
const avatarCpt = computed(() => {
|
||||
let avatar = null;
|
||||
if (props.data.avatar !== "") {
|
||||
avatar = props.data.avatar;
|
||||
} else {
|
||||
switch (props.data.group_type) {
|
||||
case 1:
|
||||
avatar = zu4992;
|
||||
break;
|
||||
case 2:
|
||||
avatar = zu4989;
|
||||
break;
|
||||
case 3:
|
||||
avatar = zu4991;
|
||||
break;
|
||||
case 4:
|
||||
avatar = zu5296;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return avatar;
|
||||
});
|
||||
// 添加格式化方法
|
||||
const formatNameText = (text, maxLength = 16) => {
|
||||
return text.length > maxLength ? `${text.slice(0, maxLength - 1)}...` : text
|
||||
}
|
||||
|
||||
const cellClick = () => {
|
||||
console.log(props.data);
|
||||
console.log(props.data)
|
||||
// 更新编辑信息
|
||||
dialogueStore.setDialogue(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,
|
||||
}).then(() => {
|
||||
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,
|
||||
});
|
||||
};
|
||||
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)
|
||||
}
|
||||
</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;
|
||||
}
|
||||
.chatItem {
|
||||
width: 100%;
|
||||
height: 154rpx;
|
||||
@ -161,11 +189,6 @@ const handleTop = () => {
|
||||
}
|
||||
}
|
||||
|
||||
.avatarImg {
|
||||
height: 96rpx;
|
||||
width: 96rpx;
|
||||
}
|
||||
|
||||
.chatInfo {
|
||||
flex: 1;
|
||||
margin-left: 20rpx;
|
||||
@ -186,47 +209,54 @@ const handleTop = () => {
|
||||
|
||||
.chatInfo_2_1 {
|
||||
font-size: 28rpx;
|
||||
color: #000000;
|
||||
opacity: 40%;
|
||||
color: rgba($color: #000000, $alpha: 0.4);
|
||||
}
|
||||
.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;
|
||||
line-height: 38rpx;
|
||||
border: 1px solid #7a58de;
|
||||
font-size: 24rpx;
|
||||
text-align: center;
|
||||
border-radius: 6rpx;
|
||||
color: #7a58de;
|
||||
font-weight: bold;
|
||||
margin-left: 12rpx;
|
||||
}
|
||||
|
||||
.depTag {
|
||||
width: 76rpx;
|
||||
height: 38rpx;
|
||||
line-height: 38rpx;
|
||||
border: 1px solid #377ec6;
|
||||
font-size: 24rpx;
|
||||
text-align: center;
|
||||
border-radius: 6rpx;
|
||||
color: #377ec6;
|
||||
font-weight: bold;
|
||||
margin-left: 12rpx;
|
||||
}
|
||||
|
||||
.projectTag {
|
||||
width: 76rpx;
|
||||
height: 38rpx;
|
||||
line-height: 38rpx;
|
||||
border: 1px solid #c1681c;
|
||||
font-size: 24rpx;
|
||||
text-align: center;
|
||||
border-radius: 6rpx;
|
||||
color: #c1681c;
|
||||
font-weight: bold;
|
||||
margin-left: 12rpx;
|
||||
}
|
||||
.name_center {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
.name_text {
|
||||
display: inline-block;
|
||||
max-height: 88rpx; // 两行文字的高度
|
||||
line-height: 44rpx;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
word-break: break-all;
|
||||
}
|
||||
.time_right {
|
||||
/* white-space: nowrap;
|
||||
max-width: 146rpx; */
|
||||
flex: 0 0 auto; /* 不伸缩,保持内容宽度 */
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.textEllipsis {
|
||||
@ -235,6 +265,7 @@ const handleTop = () => {
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 1;
|
||||
-webkit-box-orient: vertical;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.divider {
|
||||
|
@ -1,101 +1,167 @@
|
||||
<template>
|
||||
<div class="outer-layer">
|
||||
<div>
|
||||
<tm-navbar :hideBack="false" hideHome title="" :leftWidth="420">
|
||||
<template v-slot:left>
|
||||
<div class="flex items-center ml-[48rpx]">
|
||||
<tm-image
|
||||
:width="72"
|
||||
:height="72"
|
||||
:round="12"
|
||||
:src="userStore.avatar"
|
||||
></tm-image>
|
||||
<div class="ml-[24rpx] text-[36rpx] font-bold">
|
||||
{{ userStore.nickname }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template v-slot:right>
|
||||
<div class="mr-[48rpx] popoverBox">
|
||||
<tm-popover position="br" color="#333333" :width="260">
|
||||
<tm-image
|
||||
:width="41"
|
||||
:height="41"
|
||||
:round="12"
|
||||
:src="addCircle"
|
||||
></tm-image>
|
||||
<template v-slot:label>
|
||||
<div class="w-full h-[208rpx] pt-[22rpx] pb-[22rpx] pl-[34rpx] pr-[32rpx]" >
|
||||
<div @click="creatGroupChat" class="flex items-center mb-[30rpx]" >
|
||||
<div class="mr-[26rpx] flex items-center">
|
||||
<tm-image
|
||||
:width="40"
|
||||
:height="40"
|
||||
:src="cahtPopover"
|
||||
></tm-image>
|
||||
</div>
|
||||
<div class="leading-[54rpx] text-[32rpx] text-[#FFFFFF] font-bold" >发起群聊</div>
|
||||
</div>
|
||||
<div class="divider" ></div>
|
||||
<div class="flex items-center mt-[28rpx]" >
|
||||
<div class="mr-[26rpx] flex items-center">
|
||||
<tm-image
|
||||
:width="40"
|
||||
:height="40"
|
||||
:src="zu3289"
|
||||
></tm-image>
|
||||
</div>
|
||||
<div class="leading-[54rpx] text-[32rpx] text-[#FFFFFF] font-bold" >
|
||||
通讯录
|
||||
</div>
|
||||
</div>
|
||||
<ZPaging
|
||||
class="paging_container"
|
||||
ref="paging"
|
||||
refresher-enabled
|
||||
:refresher-threshold="80"
|
||||
:refresher-max-angle="0"
|
||||
:refresher-pull-rate="0.5"
|
||||
:refresher-fixed-bac-height="80"
|
||||
refresher-fixed-background="#F9F9FD"
|
||||
refresher-background="#F9F9FD"
|
||||
v-model="items"
|
||||
@query="queryList"
|
||||
:loading-more-enabled="false"
|
||||
:refresher-end-bounce-enabled="false"
|
||||
:auto="true"
|
||||
:empty-view-show="isEmptyViewShow"
|
||||
@refresherRefresh="onRefresh"
|
||||
:show-scrollbar="false"
|
||||
>
|
||||
<template #top>
|
||||
<div>
|
||||
<tm-navbar
|
||||
class="index_top_navbar"
|
||||
:hideBack="false"
|
||||
hideHome
|
||||
title=""
|
||||
:leftWidth="420"
|
||||
:showStatusBar="false"
|
||||
>
|
||||
<template v-slot:left>
|
||||
<div class="flex items-center ml-[48rpx]">
|
||||
<!-- <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>
|
||||
</template>
|
||||
</tm-popover>
|
||||
</div>
|
||||
</template>
|
||||
<template v-slot:right>
|
||||
<div class="mr-[48rpx] popoverBox">
|
||||
<tm-popover position="br" color="#333333" :width="260">
|
||||
<image
|
||||
class="w-[48rpx] h-[48rpx]"
|
||||
style="border-radius: 50%;"
|
||||
:src="addCircle"
|
||||
mode="scaleToFill"
|
||||
/>
|
||||
<template v-slot:label>
|
||||
<div
|
||||
class="w-full h-[208rpx] pt-[22rpx] pb-[32rpx] pl-[14rpx] pr-[12rpx]"
|
||||
>
|
||||
<div
|
||||
@click="creatGroupChat"
|
||||
class="flex items-center pl-[22rpx] mb-[32rpx]"
|
||||
>
|
||||
<div class="mr-[26rpx] flex items-center">
|
||||
<tm-image
|
||||
:width="40"
|
||||
:height="39"
|
||||
:src="cahtPopover"
|
||||
></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] mt-[32rpx]"
|
||||
>
|
||||
<div class="mr-[26rpx] flex items-center">
|
||||
<tm-image
|
||||
:width="40"
|
||||
:height="43"
|
||||
:src="zu3289"
|
||||
></tm-image>
|
||||
</div>
|
||||
<div
|
||||
class="leading-[54rpx] text-[32rpx] text-[#FFFFFF] font-bold"
|
||||
>
|
||||
通讯录
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</tm-popover>
|
||||
</div>
|
||||
</template>
|
||||
</tm-navbar>
|
||||
</div>
|
||||
</template>
|
||||
<template #refresher="{ refresherStatus }">
|
||||
<custom-refresher :status="refresherStatus" />
|
||||
</template>
|
||||
<div class="content">
|
||||
<div class="root">
|
||||
<div class="searchRoot" @click="toSearchPage">
|
||||
<customInput :disabled="true"></customInput>
|
||||
</div>
|
||||
</template>
|
||||
</tm-navbar>
|
||||
</div>
|
||||
<div class="root">
|
||||
<div class="searchRoot" @click="toSearchPage">
|
||||
<tm-input
|
||||
placeholder="请输入…"
|
||||
color="#F9F9FD"
|
||||
:round="1"
|
||||
prefix="tmicon-search"
|
||||
prefixColor="#46299D"
|
||||
></tm-input>
|
||||
<div class="contentRoot">
|
||||
<chatItem
|
||||
v-for="(item, index) in items"
|
||||
:key="item.index_name"
|
||||
:index="index"
|
||||
:data="item"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="contentRoot">
|
||||
<chatItem
|
||||
v-for="(item, index) in items"
|
||||
:key="item.index_name"
|
||||
:index="index"
|
||||
:data="item"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</ZPaging>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
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 } 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 customInput from '@/components/custom-input/custom-input.vue'
|
||||
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 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 talkStore = useTalkStore();
|
||||
const userStore = useUserStore();
|
||||
const { userInfo } = useAuth();
|
||||
|
||||
const topItems = computed(() => talkStore.topItems);
|
||||
const topItems = computed(() => talkStore.topItems)
|
||||
const items = computed(() => {
|
||||
// if (searchKeyword.value.length === 0) {
|
||||
return talkStore.talkItems;
|
||||
console.log(talkStore.talkItems)
|
||||
|
||||
return talkStore.talkItems
|
||||
// }
|
||||
|
||||
// return talkStore.talkItems.filter((item) => {
|
||||
@ -103,66 +169,179 @@ const items = computed(() => {
|
||||
|
||||
// return keyword.toLowerCase().indexOf(searchKeyword.value.toLowerCase()) != -1
|
||||
// })
|
||||
});
|
||||
})
|
||||
|
||||
const queryList = (pageNo, pageSize) => {
|
||||
// paging.value.complete(res.data.list);
|
||||
console.log(talkStore)
|
||||
talkStore
|
||||
.loadTalkList()
|
||||
.then(() => {
|
||||
isEmptyViewShow.value = talkStore.talkItems.length === 0
|
||||
// 数据加载成功
|
||||
paging.value.complete(talkStore.talkItems)
|
||||
})
|
||||
.catch((error) => {
|
||||
// 数据加载失败
|
||||
console.error('加载失败', error)
|
||||
paging.value.complete(false)
|
||||
})
|
||||
}
|
||||
|
||||
// 添加下拉刷新处理函数
|
||||
const onRefresh = () => {
|
||||
console.log('触发下拉刷新')
|
||||
talkStore
|
||||
.loadTalkList()
|
||||
.then(() => {
|
||||
// 数据加载成功
|
||||
paging.value.complete(talkStore.talkItems)
|
||||
})
|
||||
.catch((error) => {
|
||||
// 数据加载失败
|
||||
console.error('加载失败', error)
|
||||
paging.value.complete(false)
|
||||
})
|
||||
}
|
||||
|
||||
const creatGroupChat = () => {
|
||||
uni.navigateTo({
|
||||
url: "/pages/creatGroupChat/index",
|
||||
});
|
||||
};
|
||||
url: '/pages/creatGroupChat/index',
|
||||
})
|
||||
}
|
||||
|
||||
const toSearchPage = () => {
|
||||
uni.navigateTo({
|
||||
url: "/pages/search/index",
|
||||
});
|
||||
};
|
||||
url: '/pages/search/index',
|
||||
})
|
||||
}
|
||||
|
||||
watch(
|
||||
() => talkStore,
|
||||
(newValue, oldValue) => {
|
||||
console.log(talkStore);
|
||||
},
|
||||
{ deep: true, immediate: true }
|
||||
);
|
||||
//点击跳转到通讯录页面
|
||||
const toAddressBookPage = () => {
|
||||
uni.navigateTo({
|
||||
url: '/pages/chooseByDeps/index?chooseMode=3',
|
||||
})
|
||||
}
|
||||
|
||||
/* watch(
|
||||
() => talkStore,
|
||||
(newValue, oldValue) => {
|
||||
// console.log(talkStore)
|
||||
},
|
||||
{ deep: true, immediate: true }
|
||||
); */
|
||||
|
||||
onShow(() => {
|
||||
talkStore.loadTalkList();
|
||||
});
|
||||
handleSetWebviewStyle(true)
|
||||
// 页面显示时重新加载数据
|
||||
talkStore
|
||||
.loadTalkList()
|
||||
.then(() => {
|
||||
// 如果paging已经初始化,则刷新列表
|
||||
if (paging.value) {
|
||||
paging.value.reload()
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('页面显示时数据加载失败', error)
|
||||
})
|
||||
})
|
||||
|
||||
onLoad((options) => {
|
||||
console.log(options)
|
||||
|
||||
if (options?.openSessionIndexName) {
|
||||
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()
|
||||
})
|
||||
}
|
||||
uni.navigateTo({
|
||||
url: `/pages/dialog/index?sessionId=${openSession.id}`,
|
||||
})
|
||||
}, 500)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
uni-page-body,
|
||||
page {
|
||||
height: 100%;
|
||||
height: 100vh;
|
||||
background-image: url('@/static/image/clockIn/z3280@3x.png');
|
||||
background-size: cover;
|
||||
flex-direction: column;
|
||||
}
|
||||
.outer-layer {
|
||||
overflow-y: auto;
|
||||
flex: 1;
|
||||
background-image: url("@/static/image/clockIn/z3280@3x.png");
|
||||
background-size: cover;
|
||||
padding: 0 32rpx 20rpx 32rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
.paging_container {
|
||||
// border: 1px solid red;
|
||||
background: #fff;
|
||||
margin: 0 32rpx 20rpx 32rpx;
|
||||
}
|
||||
|
||||
::v-deep .index_top_navbar > .statusHeight:first-child {
|
||||
height: 70px !important;
|
||||
}
|
||||
|
||||
::v-deep .index_top_navbar .statusHeightTop {
|
||||
height: 70px !important;
|
||||
}
|
||||
|
||||
::v-deep .index_top_navbar .statusHeightTop > .noNvueBorder:first-child {
|
||||
height: 70px !important;
|
||||
}
|
||||
|
||||
.content {
|
||||
// overflow: auto;
|
||||
}
|
||||
.root {
|
||||
flex: 1;
|
||||
padding: 20rpx 0;
|
||||
}
|
||||
|
||||
.searchRoot {
|
||||
background-color: #fff;
|
||||
padding: 22rpx 18rpx;
|
||||
border-bottom: 16rpx rgba(249, 249, 253, 1) solid;
|
||||
::v-deep .noNvueBorder > .noNvueBorder > .noNvueBorder {
|
||||
background: #f9f9fd !important;
|
||||
}
|
||||
}
|
||||
|
||||
.contentRoot {
|
||||
margin-top: 20rpx;
|
||||
background-color: #fff;
|
||||
// min-height: calc(100vh - 180px);
|
||||
}
|
||||
.divider{
|
||||
|
||||
.divider {
|
||||
height: 1rpx;
|
||||
background-color: #7C7C7C;
|
||||
background-color: #7c7c7c;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.popoverBox {
|
||||
:deep(.popover-bcc){
|
||||
transform:translateX(20rpx) translateY(40rpx);
|
||||
:deep(.popover-bcc) {
|
||||
transform: translateX(16rpx) translateY(48rpx);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -9,15 +9,23 @@
|
||||
: ''
|
||||
"
|
||||
>
|
||||
<div
|
||||
class="avatar-img"
|
||||
:class="props?.conditionType ? 'avatar-img-condition' : ''"
|
||||
>
|
||||
<img v-if="avatarImg !== 'textImg'" :src="avatarImg" />
|
||||
<span v-if="avatarImg === 'textImg'" class="text-[32rpx] font-bold">
|
||||
{{ imgText }}
|
||||
</span>
|
||||
</div>
|
||||
<avatarModule
|
||||
:mode="props.searchItem?.group_type === 0 ? 1 : 2"
|
||||
:avatar="avatarImg"
|
||||
:userName="resultName"
|
||||
:groupType="props.searchItem?.group_type"
|
||||
:customStyle="{
|
||||
width: props?.conditionType ? '64rpx' : '96rpx',
|
||||
height: props?.conditionType ? '64rpx' : '96rpx',
|
||||
margin: props?.conditionType ? '0 18rpx 0 0' : '0 20rpx 0 0',
|
||||
}"
|
||||
:customTextStyle="{
|
||||
fontSize: props?.conditionType ? '20rpx' : '32rpx',
|
||||
fontWeight: 'bold',
|
||||
color: '#fff',
|
||||
lineHeight: '44rpx',
|
||||
}"
|
||||
></avatarModule>
|
||||
<div class="result-info">
|
||||
<div
|
||||
class="info-name"
|
||||
@ -75,10 +83,7 @@
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import zu4992 from '@/static/image/chatList/zu4992@2x.png'
|
||||
import zu4991 from '@/static/image/chatList/zu4991@2x.png'
|
||||
import zu4989 from '@/static/image/chatList/zu4989@2x.png'
|
||||
import zu5296 from '@/static/image/chatList/zu5296@2x.png'
|
||||
import avatarModule from '@/components/avatar-module/index.vue'
|
||||
import {
|
||||
ref,
|
||||
watch,
|
||||
@ -91,14 +96,30 @@ 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: String,
|
||||
searchText: String, //搜索内容
|
||||
searchRecordDetail: Boolean, //是否是搜索聊天记录详情
|
||||
pointerIconSrc: String, //箭头图标
|
||||
conditionType: Number, //搜索类型
|
||||
searchResultKey: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
searchText: {
|
||||
type: String,
|
||||
default: '',
|
||||
}, //搜索内容
|
||||
searchRecordDetail: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
}, //是否是搜索聊天记录详情
|
||||
pointerIconSrc: {
|
||||
type: String,
|
||||
default: '',
|
||||
}, //箭头图标
|
||||
conditionType: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
}, //搜索类型
|
||||
})
|
||||
// 映射表-查找对应结构下的属性名
|
||||
const keyMapping = {
|
||||
@ -155,6 +176,13 @@ 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) => {
|
||||
@ -167,14 +195,8 @@ const getKeyValue = (keys) => {
|
||||
//头像
|
||||
const avatarImg = computed(() => {
|
||||
let avatar = getKeyValue(keyMapping[props.searchResultKey]?.avatar)
|
||||
if (!avatar) {
|
||||
avatar = groupTypeMapping[props.searchItem?.group_type]?.defaultImg
|
||||
}
|
||||
if (props?.conditionType) {
|
||||
avatar = props.searchItem.avatar
|
||||
if (!avatar) {
|
||||
avatar = groupTypeMapping[0]?.defaultImg
|
||||
}
|
||||
}
|
||||
return avatar
|
||||
})
|
||||
@ -194,26 +216,19 @@ const imgText = computed(() => {
|
||||
})
|
||||
// 映射表-根据groupType设置对应值
|
||||
const groupTypeMapping = {
|
||||
0: {
|
||||
defaultImg: 'textImg',
|
||||
},
|
||||
1: {
|
||||
defaultImg: zu4992,
|
||||
},
|
||||
0: {},
|
||||
1: {},
|
||||
2: {
|
||||
result_type: t('index.mine.department'),
|
||||
result_type_color: '#377EC6',
|
||||
defaultImg: zu4989,
|
||||
},
|
||||
3: {
|
||||
result_type: t('index.mine.project'),
|
||||
result_type_color: '#C1681C',
|
||||
defaultImg: zu4991,
|
||||
},
|
||||
4: {
|
||||
result_type: t('index.type.company'),
|
||||
result_type_color: '#7A58DE',
|
||||
defaultImg: zu5296,
|
||||
},
|
||||
}
|
||||
//群人数
|
||||
@ -247,6 +262,12 @@ 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 = ''
|
||||
}
|
||||
@ -262,35 +283,6 @@ const resultDetail = computed(() => {
|
||||
padding: 22rpx 0 24rpx;
|
||||
border-bottom: 1px solid $theme-border-color;
|
||||
|
||||
.avatar-img {
|
||||
width: 96rpx;
|
||||
height: 96rpx;
|
||||
margin: 0 20rpx 0 0;
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: linear-gradient(to right, #674bbc, #46299d);
|
||||
flex-shrink: 0;
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
span {
|
||||
color: #fff;
|
||||
line-height: 44rpx;
|
||||
}
|
||||
}
|
||||
.avatar-img-condition {
|
||||
width: 64rpx;
|
||||
height: 64rpx;
|
||||
margin: 0 18rpx 0 0;
|
||||
span {
|
||||
font-size: 20rpx;
|
||||
}
|
||||
}
|
||||
.result-info {
|
||||
width: 100%;
|
||||
.info-name {
|
||||
@ -310,6 +302,7 @@ const resultDetail = computed(() => {
|
||||
padding: 2rpx 14rpx;
|
||||
border: 2rpx solid #000;
|
||||
border-radius: 6rpx;
|
||||
flex-shrink: 0;
|
||||
span {
|
||||
line-height: 34rpx;
|
||||
}
|
||||
@ -334,6 +327,7 @@ const resultDetail = computed(() => {
|
||||
.info-detail-searchRecordDetail {
|
||||
span {
|
||||
color: $theme-text;
|
||||
word-break: break-all;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -9,7 +9,7 @@
|
||||
:default-page-size="props.searchResultPageSize"
|
||||
:loading-more-default-as-loading="true"
|
||||
:inside-more="true"
|
||||
:empty-view-img="'/src/static//image/search/search-no-data.png'"
|
||||
:empty-view-img="searchNoData"
|
||||
:empty-view-text="$t('search.hint')"
|
||||
:empty-view-img-style="{ width: '476rpx', height: '261rpx' }"
|
||||
:empty-view-title-style="{
|
||||
@ -19,6 +19,7 @@
|
||||
'font-size': '28rpx',
|
||||
'font-weight': 400,
|
||||
}"
|
||||
:refresher-enabled="false"
|
||||
>
|
||||
<template #top>
|
||||
<div class="searchRoot">
|
||||
@ -35,7 +36,10 @@
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
<div class="search-record-detail" v-if="props.searchRecordDetail">
|
||||
<div
|
||||
class="search-record-detail"
|
||||
v-if="props.searchRecordDetail && !props?.hideFirstRecord"
|
||||
>
|
||||
<searchItem
|
||||
@click="
|
||||
clickSearchItem(
|
||||
@ -109,6 +113,7 @@
|
||||
</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'
|
||||
@ -117,6 +122,10 @@ 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)
|
||||
|
||||
@ -131,17 +140,45 @@ const state = reactive({
|
||||
searchResultList: [], //搜素结果列表
|
||||
searchResult: null, //搜索结果
|
||||
pageNum: 1, //当前请求数据页数
|
||||
uid: computed(() => userStore.uid), //当前用户id
|
||||
})
|
||||
|
||||
const props = defineProps({
|
||||
searchResultPageSize: Number, //搜索结果每页数据量
|
||||
listLimit: Boolean, //是否限制列表内数据数量
|
||||
apiParams: String, //请求参数
|
||||
searchResultPageSize: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
}, //搜索结果每页数据量
|
||||
listLimit: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
}, //是否限制列表内数据数量
|
||||
apiParams: {
|
||||
type: String,
|
||||
default: '',
|
||||
}, //请求参数
|
||||
apiRequest: Function, //请求
|
||||
searchText: String, //搜索内容
|
||||
isPagination: Boolean, //是否分页
|
||||
searchRecordDetail: Boolean, //是否是搜索聊天记录的详情
|
||||
first_talk_record_infos: Object, //接受者信息
|
||||
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,
|
||||
}, //是否隐藏前缀及搜索群/用户主体信息
|
||||
})
|
||||
|
||||
const { t } = useI18n()
|
||||
@ -154,13 +191,14 @@ 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()
|
||||
@ -197,15 +235,35 @@ 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,
|
||||
data.talk_record_infos[0],
|
||||
receiverInfo,
|
||||
)
|
||||
;(data.talk_record_infos || []).forEach((item) => {
|
||||
item.group_type = 0
|
||||
})
|
||||
}
|
||||
|
||||
let tempGeneral_infos = Array.isArray(data.general_infos)
|
||||
? [...data.general_infos]
|
||||
: data.general_infos
|
||||
@ -214,6 +272,8 @@ const queryAllSearch = (pageNum, searchResultPageSize) => {
|
||||
data.group_member_infos || [],
|
||||
)
|
||||
data.general_infos = tempGeneral_infos
|
||||
|
||||
// 检查数据是否为空
|
||||
let isEmpty = true
|
||||
let dataKeys = Object.keys(data)
|
||||
let paginationKey = ''
|
||||
@ -223,58 +283,94 @@ const queryAllSearch = (pageNum, searchResultPageSize) => {
|
||||
isEmpty = false
|
||||
}
|
||||
})
|
||||
|
||||
if (isEmpty) {
|
||||
if (pageNum == 1) {
|
||||
if (pageNum === 1) {
|
||||
// 第一页请求且为空,清空结果
|
||||
state.searchResult = null
|
||||
zPaging.value?.complete([])
|
||||
} else {
|
||||
data = state.searchResult
|
||||
zPaging.value?.complete([data])
|
||||
// 加载更多且为空,保持原列表不变
|
||||
zPaging.value?.complete(state.searchResult ? [state.searchResult] : [])
|
||||
}
|
||||
} else {
|
||||
if (props.isPagination) {
|
||||
if (
|
||||
paginationKey &&
|
||||
Array.isArray(
|
||||
(state?.searchResult && state?.searchResult[paginationKey]) || [],
|
||||
) &&
|
||||
((state?.searchResult && state?.searchResult[paginationKey]) || [])
|
||||
.length > 0
|
||||
) {
|
||||
data[paginationKey] = state.searchResult[paginationKey].concat(
|
||||
data[paginationKey],
|
||||
)
|
||||
if (pageNum === 1) {
|
||||
// 第一页请求,直接设置新数据
|
||||
state.searchResult = data
|
||||
} else {
|
||||
// 加载更多,合并数据
|
||||
if (
|
||||
paginationKey &&
|
||||
Array.isArray(
|
||||
(state?.searchResult && state?.searchResult[paginationKey]) || [],
|
||||
)
|
||||
) {
|
||||
data[paginationKey] = state.searchResult[paginationKey].concat(
|
||||
data[paginationKey],
|
||||
)
|
||||
}
|
||||
state.searchResult = data
|
||||
}
|
||||
|
||||
emits(
|
||||
'lastIdChange',
|
||||
data.last_id,
|
||||
data.last_group_id,
|
||||
data.last_member_id,
|
||||
data.last_receiver_user_name,
|
||||
data.last_receiver_group_name,
|
||||
)
|
||||
let total = data.count
|
||||
if (props.searchRecordDetail) {
|
||||
total = data.group_record_count
|
||||
if (state?.first_talk_record_infos?.talk_type === 1) {
|
||||
total = data.user_record_count
|
||||
} else if (state?.first_talk_record_infos?.talk_type === 2) {
|
||||
total = data.group_record_count
|
||||
}
|
||||
}
|
||||
zPaging.value?.completeByTotal([data], total)
|
||||
} else {
|
||||
state.searchResult = data
|
||||
zPaging.value?.complete([data])
|
||||
}
|
||||
}
|
||||
state.searchResult = data
|
||||
} else {
|
||||
zPaging.value?.complete([])
|
||||
if (pageNum === 1) {
|
||||
// 第一页请求失败,清空结果
|
||||
state.searchResult = null
|
||||
zPaging.value?.complete([])
|
||||
} else {
|
||||
// 加载更多失败,保持原列表不变
|
||||
zPaging.value?.complete(state.searchResult ? [state.searchResult] : [])
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
resp.catch(() => {
|
||||
zPaging.value?.complete([])
|
||||
if (pageNum === 1) {
|
||||
// 第一页请求异常,清空结果
|
||||
state.searchResult = null
|
||||
zPaging.value?.complete([])
|
||||
} else {
|
||||
// 加载更多异常,保持原列表不变
|
||||
zPaging.value?.complete(state.searchResult ? [state.searchResult] : [])
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
//点击取消搜索
|
||||
const cancelSearch = () => {
|
||||
uni.navigateBack({
|
||||
delta: 1,
|
||||
})
|
||||
const pages = getCurrentPages()
|
||||
if (pages.length > 1) {
|
||||
uni.navigateBack({
|
||||
delta: 1,
|
||||
})
|
||||
} else {
|
||||
uni.reLaunch({
|
||||
url: '/pages/index/index'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
//获取key对应值
|
||||
@ -342,6 +438,12 @@ 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:
|
||||
}
|
||||
@ -355,12 +457,41 @@ 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,
|
||||
searchItem.talk_type,
|
||||
searchItem.receiver_id,
|
||||
talk_type,
|
||||
receiver_id,
|
||||
encodeURIComponent(JSON.stringify(searchItem)),
|
||||
)
|
||||
}
|
||||
</script>
|
||||
|
@ -13,7 +13,19 @@
|
||||
</template>
|
||||
<script setup>
|
||||
import searchList from './components/searchList.vue'
|
||||
import { ServeSeachQueryAll } from '@/api/search/index'
|
||||
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()
|
||||
})
|
||||
|
||||
//点击跳转到更多结果页面
|
||||
const toMoreResultPage = (searchResultKey, searchText) => {
|
||||
@ -27,14 +39,49 @@ const toMoreResultPage = (searchResultKey, searchText) => {
|
||||
}
|
||||
|
||||
//点击了搜索结果项
|
||||
const clickSearchItem = (
|
||||
const clickSearchItem = async (
|
||||
searchText,
|
||||
searchResultKey,
|
||||
talk_type,
|
||||
receiver_id,
|
||||
res,
|
||||
) => {
|
||||
console.log(searchResultKey)
|
||||
if (searchResultKey === 'general_infos') {
|
||||
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') {
|
||||
uni.navigateTo({
|
||||
url:
|
||||
'/pages/search/moreResult/moreResultDetail?searchText=' +
|
||||
@ -46,5 +93,25 @@ const clickSearchItem = (
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
//获取会话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>
|
||||
|
@ -21,14 +21,21 @@ 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: String,
|
||||
searchText: String,
|
||||
searchResultKey: String,
|
||||
apiParams: '',
|
||||
searchText: '',
|
||||
searchResultKey: '',
|
||||
})
|
||||
|
||||
onLoad((options) => {
|
||||
@ -57,6 +64,8 @@ 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
|
||||
@ -68,11 +77,19 @@ onLoad((options) => {
|
||||
})
|
||||
|
||||
//分页查询时,最后一条id变化
|
||||
const lastIdChange = (last_id, last_group_id, last_member_id) => {
|
||||
const lastIdChange = (
|
||||
last_id,
|
||||
last_group_id,
|
||||
last_member_id,
|
||||
last_receiver_user_name,
|
||||
last_receiver_group_name,
|
||||
) => {
|
||||
let idChanges = {
|
||||
last_id,
|
||||
last_group_id,
|
||||
last_member_id,
|
||||
last_receiver_user_name,
|
||||
last_receiver_group_name,
|
||||
}
|
||||
state.apiParams = encodeURIComponent(
|
||||
JSON.stringify(
|
||||
@ -86,12 +103,79 @@ const lastIdChange = (last_id, last_group_id, last_member_id) => {
|
||||
}
|
||||
|
||||
//点击了搜索结果项
|
||||
const clickSearchItem = (searchText) => {
|
||||
if (state.searchResultKey === 'general_infos') {
|
||||
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,
|
||||
})
|
||||
uni.navigateTo({
|
||||
url: '/pages/search/moreResult/moreResultDetail?searchText=' + searchText,
|
||||
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,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
//获取会话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>
|
||||
|
@ -7,9 +7,11 @@
|
||||
:apiRequest="ServeTalkRecord"
|
||||
:apiParams="state.apiParams"
|
||||
:searchText="state.searchText"
|
||||
:hideFirstRecord="state.hideFirstRecord"
|
||||
:isPagination="true"
|
||||
:searchRecordDetail="true"
|
||||
@lastIdChange="lastIdChange"
|
||||
@clickSearchItem="clickSearchItem"
|
||||
></searchList>
|
||||
</div>
|
||||
</div>
|
||||
@ -19,10 +21,17 @@ 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: String,
|
||||
searchText: String,
|
||||
apiParams: '',
|
||||
searchText: '',
|
||||
uid: computed(() => userStore.uid), //当前用户id
|
||||
hideFirstRecord: false, //是否隐藏前缀及搜索群/用户主体信息
|
||||
})
|
||||
|
||||
onLoad((options) => {
|
||||
@ -48,6 +57,10 @@ onLoad((options) => {
|
||||
}
|
||||
|
||||
console.log(JSON.parse(decodeURIComponent(state.apiParams)))
|
||||
|
||||
if (options.hideFirstRecord) {
|
||||
state.hideFirstRecord = options.hideFirstRecord === '1' ? true : false
|
||||
}
|
||||
})
|
||||
|
||||
//分页查询时,最后一条id变化
|
||||
@ -67,5 +80,51 @@ 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>
|
||||
|
@ -9,15 +9,10 @@
|
||||
:auto="false"
|
||||
:loading-more-default-as-loading="true"
|
||||
:inside-more="true"
|
||||
v-model="state.flatList"
|
||||
>
|
||||
<template #top v-if="state.showPageTitle">
|
||||
<tm-navbar :hideBack="false" hideHome title="" :leftWidth="220">
|
||||
<div class="navBar-title flex flex-col items-center justify-center">
|
||||
<span class="text-[34rpx] font-medium">
|
||||
{{ state.pageTitle }}
|
||||
</span>
|
||||
</div>
|
||||
</tm-navbar>
|
||||
<customNavbar :title="state.pageTitle"></customNavbar>
|
||||
</template>
|
||||
<div v-if="state.condition === 'date'" class="search-by-date">
|
||||
<tm-time-picker
|
||||
@ -47,7 +42,7 @@
|
||||
<span class="text-[28rpx] font-regular">
|
||||
{{ state.selectedMonth }}
|
||||
</span>
|
||||
<img src="/src/static/image/search/down-pointer.png" />
|
||||
<img src="@/static/image/search/down-pointer.png" />
|
||||
</div>
|
||||
</tm-time-picker>
|
||||
<tm-calendar-view
|
||||
@ -70,7 +65,8 @@
|
||||
v-if="
|
||||
state.condition === 'imgAndVideo' ||
|
||||
state.condition === 'file' ||
|
||||
state.condition === 'link'
|
||||
state.condition === 'link' ||
|
||||
state.condition === 'member'
|
||||
"
|
||||
:style="{
|
||||
padding: state.condition === 'imgAndVideo' ? '0 27rpx' : '',
|
||||
@ -118,11 +114,27 @@
|
||||
v-for="(item, index) in conditionItem.monthResultList"
|
||||
:key="index"
|
||||
:style="{
|
||||
border: state.condition === 'imgAndVideo' ? '0' : '',
|
||||
border:
|
||||
state.condition === 'imgAndVideo' ||
|
||||
state.condition === 'member'
|
||||
? '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'"
|
||||
@ -148,17 +160,27 @@
|
||||
class="condition-result-imgAndVideo-area"
|
||||
v-if="item?.extra?.url"
|
||||
>
|
||||
<tm-image
|
||||
preview
|
||||
:src="
|
||||
item?.msg_type === 3
|
||||
? item?.extra?.url
|
||||
: item?.msg_type === 5
|
||||
? item?.extra?.cover
|
||||
: ''
|
||||
"
|
||||
model="aspectFill"
|
||||
/>
|
||||
<template v-if="item?.msg_type === 3">
|
||||
<tm-image
|
||||
preview
|
||||
:src="item?.extra?.url"
|
||||
model="aspectFill"
|
||||
/>
|
||||
</template>
|
||||
<template v-else-if="item?.msg_type === 5">
|
||||
<div
|
||||
class="video-preview"
|
||||
@click="onPlay(item?.extra?.url)"
|
||||
>
|
||||
<tm-image
|
||||
:src="item?.extra?.cover"
|
||||
model="aspectFill"
|
||||
/>
|
||||
<div class="play-icon">
|
||||
<img :src="playCircle" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
@ -179,6 +201,7 @@
|
||||
</div>
|
||||
<div
|
||||
class="condition-each-result-attachments"
|
||||
@click="previewPDF(item)"
|
||||
v-if="
|
||||
state.condition === 'file' || state.condition === 'link'
|
||||
"
|
||||
@ -189,7 +212,7 @@
|
||||
v-if="state.condition === 'file'"
|
||||
/>
|
||||
<img
|
||||
src="/src/static/image/search/result-link-icon.png"
|
||||
src="@/static/image/search/result-link-icon.png"
|
||||
v-if="state.condition === 'link'"
|
||||
/>
|
||||
</div>
|
||||
@ -244,6 +267,20 @@
|
||||
</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>
|
||||
@ -253,15 +290,16 @@ 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 } from 'vue'
|
||||
import { onMounted, reactive, computed, ref, nextTick } from 'vue'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import { ServeTalkDate } from '@/api/search/index'
|
||||
import { ServeTalkDate, ServeGetSessionId } from '@/api/search/index'
|
||||
import { ServeFindTalkRecords } from '@/api/chat/index'
|
||||
import { useDialogueStore } from '@/store'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
@ -279,7 +317,6 @@ const dialogueParams = reactive({
|
||||
let nowDay = new Date().setHours(0, 0, 0, 0)
|
||||
|
||||
const state = reactive({
|
||||
receiver_id: '', //目标人id
|
||||
pageTitle: '', //页面标题
|
||||
dateStyle: [], //日期样式
|
||||
nowDate: new Date(nowDay), //当前时间
|
||||
@ -295,16 +332,53 @@ 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 === 'date') {
|
||||
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') {
|
||||
state.showPageTitle = true
|
||||
state.pageTitle = t('search.condition.date')
|
||||
ServeQueryTalkDate(parseTime(state.nowDate, '{y}{m}'))
|
||||
@ -357,8 +431,8 @@ onMounted(() => {
|
||||
const ServeQueryTalkDate = (month) => {
|
||||
let params = {
|
||||
month: month,
|
||||
talk_type: 2, //1私聊2群聊
|
||||
receiver_id: state.receiver_id, //目标人id
|
||||
talk_type: dialogueParams.talk_type, //1私聊2群聊
|
||||
receiver_id: dialogueParams.receiver_id, //目标人id
|
||||
}
|
||||
const resp = ServeTalkDate(params)
|
||||
console.log(resp)
|
||||
@ -393,7 +467,7 @@ const ServeQueryTalkDate = (month) => {
|
||||
}
|
||||
|
||||
//点击选择日期
|
||||
const selectDate = (e) => {
|
||||
const selectDate = async (e) => {
|
||||
if (e == parseTime(state.nowDate, '{y}/{m}/{d}')) {
|
||||
console.log('==今日')
|
||||
state.dateStyle = [
|
||||
@ -419,6 +493,38 @@ const selectDate = (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(() => {})
|
||||
})
|
||||
}
|
||||
|
||||
//点击确认选择月份
|
||||
@ -449,10 +555,23 @@ const getDArray = (dArray) => {
|
||||
//输入搜索内容
|
||||
const inputSearchText = (e) => {
|
||||
state.searchText = e
|
||||
state.cursor = 0
|
||||
queryAllSearch()
|
||||
}
|
||||
|
||||
//点击取消搜索
|
||||
const cancelSearch = () => {}
|
||||
const cancelSearch = () => {
|
||||
const pages = getCurrentPages()
|
||||
if (pages.length > 1) {
|
||||
uni.navigateBack({
|
||||
delta: 1,
|
||||
})
|
||||
} else {
|
||||
uni.reLaunch({
|
||||
url: '/pages/index/index',
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
//查询数据
|
||||
const queryAllSearch = () => {
|
||||
@ -466,7 +585,8 @@ const queryAllSearch = () => {
|
||||
direction: 'up', //down向下查最新,up向上查老数据
|
||||
start_time: '',
|
||||
end_time: '',
|
||||
group_member_user_id: 0, //群成员id,当查询群历史消息的时候,需要指定群成员的时候送
|
||||
group_member_user_id: state.group_member_id, //群成员id,当查询群历史消息的时候,需要指定群成员的时候送
|
||||
file_name: state.msg_type === 6 ? state.searchText : '',
|
||||
}
|
||||
console.log(params)
|
||||
const resp = ServeFindTalkRecords(params)
|
||||
@ -474,17 +594,20 @@ const queryAllSearch = () => {
|
||||
resp.then(({ code, data }) => {
|
||||
console.log(data)
|
||||
if (code == 200) {
|
||||
let dateList = state.searchResultList
|
||||
// 当cursor为0时,清空searchResultList
|
||||
let dateList = state.cursor === 0 ? [] : state.searchResultList
|
||||
let noMore = false
|
||||
if (data?.items?.length > 0) {
|
||||
data.items.forEach((item) => {
|
||||
item.dateTime = parseTime(item?.created_at, '{m}/{d}')
|
||||
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)
|
||||
if (item?.extra) {
|
||||
item.extra.fileSize = fileFormatSize(item?.extra?.size)
|
||||
item.extra.typeText = item?.extra?.name
|
||||
? fileSuffix(item?.extra?.name)
|
||||
: ''
|
||||
item.extra.file_avatar = fileTypeAvatar(item?.extra?.typeText)
|
||||
console.log(item.extra.type)
|
||||
}
|
||||
let year = new Date(item.created_at).getFullYear()
|
||||
let month = new Date(item.created_at).getMonth() + 1
|
||||
let dateMonth =
|
||||
@ -517,15 +640,35 @@ const queryAllSearch = () => {
|
||||
} else {
|
||||
noMore = true
|
||||
}
|
||||
console.log(dateList)
|
||||
|
||||
// 保存分组数据用于显示
|
||||
state.searchResultList = dateList
|
||||
|
||||
// 将分组数据扁平化,用于z-paging分页
|
||||
state.flatList = dateList.reduce((acc, group) => {
|
||||
return acc.concat(group.monthResultList)
|
||||
}, [])
|
||||
|
||||
if (state.cursor === 0) {
|
||||
zPaging.value?.complete(state.flatList)
|
||||
} else {
|
||||
zPaging.value?.completeByNoMore(state.flatList, noMore)
|
||||
}
|
||||
state.cursor = data?.cursor
|
||||
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([])
|
||||
})
|
||||
}
|
||||
@ -548,6 +691,59 @@ 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 {
|
||||
@ -654,6 +850,7 @@ body::v-deep .round-3 {
|
||||
span {
|
||||
line-height: 40rpx;
|
||||
color: $theme-text;
|
||||
word-break: break-all;
|
||||
}
|
||||
}
|
||||
.attachment-sub-info {
|
||||
@ -700,6 +897,26 @@ 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -708,4 +925,23 @@ 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>
|
||||
|
@ -23,7 +23,7 @@ class WsSocket {
|
||||
lockReconnect: false,
|
||||
setTimeout: null, // 计时器对象
|
||||
time: 3000, // 重连间隔时间
|
||||
number: 10000000 // 重连次数
|
||||
number: 20 // 重连次数
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,21 @@
|
||||
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 = [];
|
||||
@ -24,19 +40,25 @@ const request = new Request({
|
||||
},
|
||||
responseInterceptors: async (res) => {
|
||||
if(res.data.status===1){
|
||||
message.warning(res.data.msg)
|
||||
// message.warning(res.data.msg)
|
||||
messageInstance.warning(res.data.msg)
|
||||
}
|
||||
if (res.data.status === 401) {
|
||||
return getRefreshToken(res);
|
||||
return
|
||||
// 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.');*/
|
||||
return Promise.reject(new 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.'));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -59,13 +81,14 @@ async function getRefreshToken(response) {
|
||||
})
|
||||
return request.request(response.config);
|
||||
} else {
|
||||
message.error(res.message || res.msg);
|
||||
|
||||
messageInstance.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;
|
||||
@ -73,9 +96,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 {
|
||||
|
BIN
src/static/image/chatList/groupAllMember.png
Normal file
After Width: | Height: | Size: 3.0 KiB |
BIN
src/static/image/chatList/groupCompany.png
Normal file
After Width: | Height: | Size: 5.5 KiB |
BIN
src/static/image/chatList/groupDepartment.png
Normal file
After Width: | Height: | Size: 6.6 KiB |
BIN
src/static/image/chatList/groupNormal.png
Normal file
After Width: | Height: | Size: 6.3 KiB |
BIN
src/static/image/chatList/groupProject.png
Normal file
After Width: | Height: | Size: 5.5 KiB |
BIN
src/static/image/chatList/mention_select_hide_bg.png
Normal file
After Width: | Height: | Size: 752 B |
BIN
src/static/image/chatList/mention_select_hide_icon.png
Normal file
After Width: | Height: | Size: 208 B |
Before Width: | Height: | Size: 6.2 KiB |
Before Width: | Height: | Size: 5.6 KiB |
Before Width: | Height: | Size: 6.1 KiB |
Before Width: | Height: | Size: 6.0 KiB |
BIN
src/static/image/checkBox/disabled.png
Normal file
After Width: | Height: | Size: 413 B |
BIN
src/static/image/clockIn/addressBook_head.png
Normal file
After Width: | Height: | Size: 2.5 KiB |
BIN
src/static/image/clockIn/loading@2x.png
Normal file
After Width: | Height: | Size: 2.7 KiB |
BIN
src/static/image/mine/page_top.png
Normal file
After Width: | Height: | Size: 10 KiB |
BIN
src/static/image/record/chat-voice-animation-bg-blue.png
Normal file
After Width: | Height: | Size: 2.2 KiB |