UI优化,红包领取后缺少头像,名称字段
This commit is contained in:
@@ -103,4 +103,9 @@
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped></style>
|
||||
<style lang="scss" scoped>
|
||||
.tui-navigation {
|
||||
border-bottom: 2rpx solid #0000000a;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
}
|
||||
|
||||
.message-input-toolbar-uni {
|
||||
background-color: #ebf0f6;
|
||||
background-color: #ffffff;
|
||||
flex-direction: column;
|
||||
z-index: 100;
|
||||
|
||||
|
||||
@@ -1,5 +1,15 @@
|
||||
@import "../../../../../assets/styles/common";
|
||||
@import "./color";
|
||||
@import "./web";
|
||||
@import "./h5";
|
||||
@import "./uni";
|
||||
@import '../../../../../assets/styles/common';
|
||||
@import './color';
|
||||
@import './web';
|
||||
@import './h5';
|
||||
@import './uni';
|
||||
|
||||
.toolbar-item-container-icon {
|
||||
background: #f4f4f4;
|
||||
}
|
||||
|
||||
.toolbar-item-container-uni-title {
|
||||
font-weight: 500;
|
||||
font-size: 24rpx;
|
||||
color: #666666;
|
||||
}
|
||||
|
||||
@@ -212,7 +212,7 @@ defineExpose({
|
||||
flex-direction: column;
|
||||
border: none;
|
||||
overflow: hidden;
|
||||
background: #ebf0f6;
|
||||
background: #ffffff;
|
||||
|
||||
&-h5 {
|
||||
padding: 10px 10px 15px;
|
||||
|
||||
@@ -2,22 +2,21 @@
|
||||
<div
|
||||
:class="{
|
||||
'message-input-container': true,
|
||||
'message-input-container-h5': !isPC,
|
||||
'message-input-container-h5': !isPC
|
||||
}"
|
||||
>
|
||||
<div
|
||||
v-if="props.isMuted"
|
||||
class="message-input-mute"
|
||||
>
|
||||
<div v-if="props.isMuted" class="message-input-mute">
|
||||
{{ props.muteText }}
|
||||
</div>
|
||||
<!-- #ifdef APP-PLUS -->
|
||||
<div
|
||||
v-if="inputToolbarDisplayType === 'emojiPicker' || inputToolbarDisplayType === 'tools'"
|
||||
v-if="
|
||||
inputToolbarDisplayType === 'emojiPicker' ||
|
||||
inputToolbarDisplayType === 'tools'
|
||||
"
|
||||
class="input-click-mask"
|
||||
@tap.stop.prevent="handleMaskClick"
|
||||
>
|
||||
</div>
|
||||
></div>
|
||||
<!-- #endif -->
|
||||
<input
|
||||
id="editor"
|
||||
@@ -27,7 +26,7 @@
|
||||
cursor-spacing="20"
|
||||
confirm-type="send"
|
||||
:confirm-hold="true"
|
||||
:focus="programmaticFocus"
|
||||
:focus="programmaticFocus"
|
||||
maxlength="140"
|
||||
type="text"
|
||||
placeholder-class="input-placeholder"
|
||||
@@ -38,284 +37,314 @@
|
||||
@input="onInput"
|
||||
@blur="onBlur"
|
||||
@focus="onFocus"
|
||||
>
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { ref, watch, onMounted, onUnmounted } from '../../../adapter-vue';
|
||||
import { TUIStore, StoreName, IConversationModel, IMessageModel } from '@tencentcloud/chat-uikit-engine-lite';
|
||||
import { TUIGlobal } from '@tencentcloud/universal-api';
|
||||
import DraftManager from '../utils/conversationDraft';
|
||||
import { transformTextWithEmojiNamesToKeys } from '../emoji-config';
|
||||
import { isPC } from '../../../utils/env';
|
||||
import { sendMessages } from '../utils/sendMessage';
|
||||
import { ISendMessagePayload, ToolbarDisplayType } from '../../../interface';
|
||||
import {
|
||||
ref,
|
||||
watch,
|
||||
onMounted,
|
||||
onUnmounted
|
||||
} from '../../../adapter-vue'
|
||||
import {
|
||||
TUIStore,
|
||||
StoreName,
|
||||
IConversationModel,
|
||||
IMessageModel
|
||||
} from '@tencentcloud/chat-uikit-engine-lite'
|
||||
import { TUIGlobal } from '@tencentcloud/universal-api'
|
||||
import DraftManager from '../utils/conversationDraft'
|
||||
import { transformTextWithEmojiNamesToKeys } from '../emoji-config'
|
||||
import { isPC } from '../../../utils/env'
|
||||
import { sendMessages } from '../utils/sendMessage'
|
||||
import {
|
||||
ISendMessagePayload,
|
||||
ToolbarDisplayType
|
||||
} from '../../../interface'
|
||||
|
||||
const props = defineProps({
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: 'this is placeholder',
|
||||
},
|
||||
replayOrReferenceMessage: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
required: false,
|
||||
},
|
||||
isMuted: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
muteText: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
enableInput: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
enableAt: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
enableTyping: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
isGroup: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
inputToolbarDisplayType: {
|
||||
type: String,
|
||||
defult: '',
|
||||
}
|
||||
});
|
||||
|
||||
const emits = defineEmits(['onTyping', 'onFocus', 'onAt']);
|
||||
const inputText = ref('');
|
||||
const inputRef = ref();
|
||||
const inputBlur = ref(true);
|
||||
const programmaticFocus = ref(false);
|
||||
const inputContentEmpty = ref(true);
|
||||
const allInsertedAtInfo = new Map();
|
||||
const currentConversation = ref<IConversationModel>();
|
||||
const currentConversationID = ref<string>('');
|
||||
const currentQuoteMessage = ref<{ message: IMessageModel; type: string }>();
|
||||
|
||||
onMounted(() => {
|
||||
TUIStore.watch(StoreName.CONV, {
|
||||
currentConversation: onCurrentConversationUpdated,
|
||||
});
|
||||
|
||||
TUIStore.watch(StoreName.CHAT, {
|
||||
quoteMessage: onQuoteMessageUpdated,
|
||||
});
|
||||
|
||||
uni.$on('insert-emoji', (data) => {
|
||||
inputText.value += data?.emoji?.name;
|
||||
});
|
||||
|
||||
uni.$on('send-message-in-emoji-picker', () => {
|
||||
handleSendMessage();
|
||||
});
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
if (currentConversationID.value) {
|
||||
DraftManager.setStore(currentConversationID.value, inputText.value, inputText.value, currentQuoteMessage.value);
|
||||
}
|
||||
|
||||
uni.$off('insertEmoji');
|
||||
uni.$off('send-message-in-emoji-picker');
|
||||
|
||||
TUIStore.unwatch(StoreName.CONV, {
|
||||
currentConversation: onCurrentConversationUpdated,
|
||||
});
|
||||
|
||||
TUIStore.unwatch(StoreName.CHAT, {
|
||||
quoteMessage: onQuoteMessageUpdated,
|
||||
});
|
||||
|
||||
reset();
|
||||
});
|
||||
|
||||
const handleSendMessage = () => {
|
||||
const messageList = getEditorContent();
|
||||
resetEditor();
|
||||
sendMessages(messageList as any, currentConversation.value!);
|
||||
};
|
||||
|
||||
const insertAt = (atInfo: any) => {
|
||||
if (!allInsertedAtInfo?.has(atInfo?.id)) {
|
||||
allInsertedAtInfo?.set(atInfo?.id, atInfo?.label);
|
||||
}
|
||||
inputText.value += atInfo?.label;
|
||||
};
|
||||
|
||||
const getEditorContent = () => {
|
||||
let text = inputText.value;
|
||||
text = transformTextWithEmojiNamesToKeys(text);
|
||||
const atUserList: string[] = [];
|
||||
allInsertedAtInfo?.forEach((value: string, key: string) => {
|
||||
if (text?.includes('@' + value)) {
|
||||
atUserList.push(key);
|
||||
}
|
||||
});
|
||||
const payload: ISendMessagePayload = {
|
||||
text,
|
||||
};
|
||||
if (atUserList?.length) {
|
||||
payload.atUserList = atUserList;
|
||||
}
|
||||
return [
|
||||
{
|
||||
type: 'text',
|
||||
payload,
|
||||
const props = defineProps({
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: 'this is placeholder'
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
const resetEditor = () => {
|
||||
inputText.value = '';
|
||||
inputContentEmpty.value = true;
|
||||
allInsertedAtInfo?.clear();
|
||||
};
|
||||
|
||||
const setEditorContent = (content: any) => {
|
||||
inputText.value = content;
|
||||
};
|
||||
|
||||
const onBlur = () => {
|
||||
inputBlur.value = true;
|
||||
programmaticFocus.value = false;
|
||||
};
|
||||
|
||||
const onFocus = (e: any) => {
|
||||
inputBlur.value = false;
|
||||
emits('onFocus', e?.detail?.height);
|
||||
uni.$emit('scroll-to-bottom');
|
||||
};
|
||||
|
||||
const isEditorContentEmpty = () => {
|
||||
inputContentEmpty.value = inputText?.value?.length ? false : true;
|
||||
};
|
||||
|
||||
const onInput = (e: any) => {
|
||||
// uni-app recognizes mention messages
|
||||
const text = e?.detail?.value;
|
||||
isEditorContentEmpty();
|
||||
if (props.isGroup && (text.endsWith('@') || text.endsWith('@\n'))) {
|
||||
TUIGlobal?.hideKeyboard();
|
||||
emits('onAt', true);
|
||||
}
|
||||
};
|
||||
|
||||
watch(
|
||||
() => [inputContentEmpty.value, inputBlur.value],
|
||||
(newVal: any, oldVal: any) => {
|
||||
if (newVal !== oldVal) {
|
||||
emits('onTyping', inputContentEmpty.value, inputBlur.value);
|
||||
replayOrReferenceMessage: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
required: false
|
||||
},
|
||||
isMuted: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
muteText: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
enableInput: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
enableAt: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
enableTyping: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
isGroup: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
inputToolbarDisplayType: {
|
||||
type: String,
|
||||
defult: ''
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
deep: true,
|
||||
},
|
||||
);
|
||||
})
|
||||
|
||||
function onCurrentConversationUpdated(conversation: IConversationModel) {
|
||||
const prevConversationID = currentConversationID.value;
|
||||
currentConversation.value = conversation;
|
||||
currentConversationID.value = conversation?.conversationID;
|
||||
if (prevConversationID !== currentConversationID.value) {
|
||||
if (prevConversationID) {
|
||||
DraftManager.setStore(
|
||||
prevConversationID,
|
||||
inputText.value,
|
||||
inputText.value,
|
||||
currentQuoteMessage.value,
|
||||
);
|
||||
}
|
||||
resetEditor();
|
||||
const emits = defineEmits(['onTyping', 'onFocus', 'onAt'])
|
||||
const inputText = ref('')
|
||||
const inputRef = ref()
|
||||
const inputBlur = ref(true)
|
||||
const programmaticFocus = ref(false)
|
||||
const inputContentEmpty = ref(true)
|
||||
const allInsertedAtInfo = new Map()
|
||||
const currentConversation = ref<IConversationModel>()
|
||||
const currentConversationID = ref<string>('')
|
||||
const currentQuoteMessage = ref<{
|
||||
message: IMessageModel
|
||||
type: string
|
||||
}>()
|
||||
|
||||
onMounted(() => {
|
||||
TUIStore.watch(StoreName.CONV, {
|
||||
currentConversation: onCurrentConversationUpdated
|
||||
})
|
||||
|
||||
TUIStore.watch(StoreName.CHAT, {
|
||||
quoteMessage: onQuoteMessageUpdated
|
||||
})
|
||||
|
||||
uni.$on('insert-emoji', data => {
|
||||
inputText.value += data?.emoji?.name
|
||||
})
|
||||
|
||||
uni.$on('send-message-in-emoji-picker', () => {
|
||||
handleSendMessage()
|
||||
})
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
if (currentConversationID.value) {
|
||||
DraftManager.getStore(currentConversationID.value, setEditorContent);
|
||||
DraftManager.setStore(
|
||||
currentConversationID.value,
|
||||
inputText.value,
|
||||
inputText.value,
|
||||
currentQuoteMessage.value
|
||||
)
|
||||
}
|
||||
|
||||
uni.$off('insertEmoji')
|
||||
uni.$off('send-message-in-emoji-picker')
|
||||
|
||||
TUIStore.unwatch(StoreName.CONV, {
|
||||
currentConversation: onCurrentConversationUpdated
|
||||
})
|
||||
|
||||
TUIStore.unwatch(StoreName.CHAT, {
|
||||
quoteMessage: onQuoteMessageUpdated
|
||||
})
|
||||
|
||||
reset()
|
||||
})
|
||||
|
||||
const handleSendMessage = () => {
|
||||
const messageList = getEditorContent()
|
||||
resetEditor()
|
||||
sendMessages(messageList as any, currentConversation.value!)
|
||||
}
|
||||
|
||||
const insertAt = (atInfo: any) => {
|
||||
if (!allInsertedAtInfo?.has(atInfo?.id)) {
|
||||
allInsertedAtInfo?.set(atInfo?.id, atInfo?.label)
|
||||
}
|
||||
inputText.value += atInfo?.label
|
||||
}
|
||||
|
||||
const getEditorContent = () => {
|
||||
let text = inputText.value
|
||||
text = transformTextWithEmojiNamesToKeys(text)
|
||||
const atUserList: string[] = []
|
||||
allInsertedAtInfo?.forEach((value: string, key: string) => {
|
||||
if (text?.includes('@' + value)) {
|
||||
atUserList.push(key)
|
||||
}
|
||||
})
|
||||
const payload: ISendMessagePayload = {
|
||||
text
|
||||
}
|
||||
if (atUserList?.length) {
|
||||
payload.atUserList = atUserList
|
||||
}
|
||||
return [
|
||||
{
|
||||
type: 'text',
|
||||
payload
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
const resetEditor = () => {
|
||||
inputText.value = ''
|
||||
inputContentEmpty.value = true
|
||||
allInsertedAtInfo?.clear()
|
||||
}
|
||||
|
||||
const setEditorContent = (content: any) => {
|
||||
inputText.value = content
|
||||
}
|
||||
|
||||
const onBlur = () => {
|
||||
inputBlur.value = true
|
||||
programmaticFocus.value = false
|
||||
}
|
||||
|
||||
const onFocus = (e: any) => {
|
||||
inputBlur.value = false
|
||||
emits('onFocus', e?.detail?.height)
|
||||
uni.$emit('scroll-to-bottom')
|
||||
}
|
||||
|
||||
const isEditorContentEmpty = () => {
|
||||
inputContentEmpty.value = inputText?.value?.length ? false : true
|
||||
}
|
||||
|
||||
const onInput = (e: any) => {
|
||||
// uni-app recognizes mention messages
|
||||
const text = e?.detail?.value
|
||||
isEditorContentEmpty()
|
||||
if (props.isGroup && (text.endsWith('@') || text.endsWith('@\n'))) {
|
||||
TUIGlobal?.hideKeyboard()
|
||||
emits('onAt', true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function onQuoteMessageUpdated(options?: { message: IMessageModel; type: string }) {
|
||||
currentQuoteMessage.value = options;
|
||||
}
|
||||
watch(
|
||||
() => [inputContentEmpty.value, inputBlur.value],
|
||||
(newVal: any, oldVal: any) => {
|
||||
if (newVal !== oldVal) {
|
||||
emits('onTyping', inputContentEmpty.value, inputBlur.value)
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
deep: true
|
||||
}
|
||||
)
|
||||
|
||||
function reset() {
|
||||
inputBlur.value = true;
|
||||
currentConversation.value = null;
|
||||
currentConversationID.value = '';
|
||||
currentQuoteMessage.value = null;
|
||||
resetEditor();
|
||||
}
|
||||
function onCurrentConversationUpdated(
|
||||
conversation: IConversationModel
|
||||
) {
|
||||
const prevConversationID = currentConversationID.value
|
||||
currentConversation.value = conversation
|
||||
currentConversationID.value = conversation?.conversationID
|
||||
if (prevConversationID !== currentConversationID.value) {
|
||||
if (prevConversationID) {
|
||||
DraftManager.setStore(
|
||||
prevConversationID,
|
||||
inputText.value,
|
||||
inputText.value,
|
||||
currentQuoteMessage.value
|
||||
)
|
||||
}
|
||||
resetEditor()
|
||||
if (currentConversationID.value) {
|
||||
DraftManager.getStore(
|
||||
currentConversationID.value,
|
||||
setEditorContent
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function onQuoteMessageUpdated(options?: {
|
||||
message: IMessageModel
|
||||
type: string
|
||||
}) {
|
||||
currentQuoteMessage.value = options
|
||||
}
|
||||
|
||||
function handleMaskClick(e: Event) {
|
||||
// #ifdef APP-PLUS
|
||||
e.stopPropagation();
|
||||
emits('onFocus');
|
||||
uni.$emit('scroll-to-bottom');
|
||||
setTimeout(() => {
|
||||
programmaticFocus.value = true;
|
||||
// IOS set 500ms timeout
|
||||
}, 100);
|
||||
// #endif
|
||||
}
|
||||
defineExpose({
|
||||
insertAt,
|
||||
resetEditor,
|
||||
setEditorContent,
|
||||
getEditorContent,
|
||||
});
|
||||
function reset() {
|
||||
inputBlur.value = true
|
||||
currentConversation.value = null
|
||||
currentConversationID.value = ''
|
||||
currentQuoteMessage.value = null
|
||||
resetEditor()
|
||||
}
|
||||
|
||||
function handleMaskClick(e: Event) {
|
||||
// #ifdef APP-PLUS
|
||||
e.stopPropagation()
|
||||
emits('onFocus')
|
||||
uni.$emit('scroll-to-bottom')
|
||||
setTimeout(() => {
|
||||
programmaticFocus.value = true
|
||||
// IOS set 500ms timeout
|
||||
}, 100)
|
||||
// #endif
|
||||
}
|
||||
defineExpose({
|
||||
insertAt,
|
||||
resetEditor,
|
||||
setEditorContent,
|
||||
getEditorContent
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../../assets/styles/common";
|
||||
@import '../../../assets/styles/common';
|
||||
|
||||
.message-input-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
padding: 3px 10px 10px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
|
||||
&-h5 {
|
||||
flex: 1;
|
||||
height: auto;
|
||||
background: #fff;
|
||||
border-radius: 10px;
|
||||
padding: 7px 0 7px 10px;
|
||||
font-size: 16px !important;
|
||||
max-height: 86px;
|
||||
}
|
||||
|
||||
.message-input-mute {
|
||||
flex: 1;
|
||||
.message-input-container {
|
||||
display: flex;
|
||||
color: #999;
|
||||
font-size: 14px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.message-input-area {
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
overflow-y: scroll;
|
||||
min-height: 25px;
|
||||
}
|
||||
}
|
||||
padding: 3px 10px 10px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
border-radius: 64rpx !important;
|
||||
background: #f4f4f4 !important;
|
||||
|
||||
.input-click-mask {
|
||||
background-color: transparent;
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: 1;
|
||||
}
|
||||
&-h5 {
|
||||
flex: 1;
|
||||
height: auto;
|
||||
background: #fff;
|
||||
border-radius: 10px;
|
||||
padding: 7px 0 7px 10px;
|
||||
font-size: 16px !important;
|
||||
max-height: 86px;
|
||||
}
|
||||
|
||||
.message-input-mute {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
color: #999;
|
||||
font-size: 14px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.message-input-area {
|
||||
flex: 1;
|
||||
overflow-y: scroll;
|
||||
min-height: 25px;
|
||||
}
|
||||
}
|
||||
|
||||
.input-click-mask {
|
||||
background-color: transparent;
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: 1;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -616,16 +616,35 @@
|
||||
const onClaim = (item: IMessageModel, index: number) => {
|
||||
const { conversationType, cloudCustomData, flow, payload } = item
|
||||
const data = JSON.parse(payload.data)
|
||||
// 群聊
|
||||
if (conversationType === TYPES.value.CONV_GROUP) {
|
||||
console.log(item)
|
||||
console.log(data)
|
||||
receiveRedEnvelope({
|
||||
redPacketId: data.id
|
||||
})
|
||||
} else {
|
||||
// 个人红包
|
||||
getRedEnvelopeDetail(data.id).then(async (res: any) => {
|
||||
|
||||
getRedEnvelopeDetail(data.id).then(async (res: any) => {
|
||||
// 群聊
|
||||
if (conversationType === TYPES.value.CONV_GROUP) {
|
||||
if (res.data.hasReceived) {
|
||||
// 直接去详情页
|
||||
navigateTo('/pages/red-packet/details', {
|
||||
id: data.id,
|
||||
type: conversationType
|
||||
})
|
||||
} else {
|
||||
const show = await showDialog('提示', '是否领取该红包?')
|
||||
if (show) {
|
||||
// newMessage.in = true
|
||||
// customRefMessage.value[index].updateClaimStatus(newMessage)
|
||||
// item.modifyMessage({
|
||||
// cloudCustomData: JSON.stringify(newMessage)
|
||||
// })
|
||||
await receiveRedEnvelope({
|
||||
redPacketId: data.id
|
||||
})
|
||||
navigateTo('/pages/red-packet/details', {
|
||||
id: data.id,
|
||||
type: conversationType
|
||||
})
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 个人红包
|
||||
console.log(res)
|
||||
let newMessage = {
|
||||
// ...data,
|
||||
@@ -639,23 +658,37 @@
|
||||
if (flow === 'in') {
|
||||
if (res.data.hasReceived) {
|
||||
// 直接去详情页
|
||||
navigateTo('/pages/red-packet/details', {
|
||||
id: data.id,
|
||||
type: conversationType
|
||||
})
|
||||
} else {
|
||||
const show = await showDialog('提示', '是否领取该红包?')
|
||||
if (show) {
|
||||
newMessage.in = true
|
||||
customRefMessage.value[index].updateClaimStatus(newMessage)
|
||||
item.modifyMessage({
|
||||
cloudCustomData: JSON.stringify(newMessage)
|
||||
// newMessage.in = true
|
||||
// customRefMessage.value[index].updateClaimStatus(newMessage)
|
||||
// item.modifyMessage({
|
||||
// cloudCustomData: JSON.stringify(newMessage)
|
||||
// })
|
||||
await receiveRedEnvelope({
|
||||
redPacketId: data.id
|
||||
})
|
||||
navigateTo('/pages/red-packet/details', {
|
||||
id: data.id,
|
||||
type: conversationType
|
||||
})
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
newMessage.out = true
|
||||
customRefMessage.value[index].updateClaimStatus(newMessage)
|
||||
item.modifyMessage({
|
||||
cloudCustomData: JSON.stringify(newMessage)
|
||||
navigateTo('/pages/red-packet/details', {
|
||||
id: data.id,
|
||||
type: conversationType
|
||||
})
|
||||
// newMessage.out = true
|
||||
// customRefMessage.value[index].updateClaimStatus(newMessage)
|
||||
// item.modifyMessage({
|
||||
// cloudCustomData: JSON.stringify(newMessage)
|
||||
// })
|
||||
// .then(() => {
|
||||
// navigateTo('/pages/red-packet/details', {
|
||||
// id: data.id,
|
||||
@@ -663,23 +696,8 @@
|
||||
// })
|
||||
// })
|
||||
}
|
||||
})
|
||||
|
||||
return
|
||||
if (flow === 'in') {
|
||||
// 修改后的消息
|
||||
const newMessage = {
|
||||
...data,
|
||||
isOpen: true
|
||||
}
|
||||
item.modifyMessage({
|
||||
cloudCustomData: JSON.stringify(newMessage)
|
||||
})
|
||||
receiveRedEnvelope({
|
||||
redPacketId: data.id
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const resendMessageConfirm = () => {
|
||||
|
||||
@@ -21,19 +21,24 @@
|
||||
<Avatar
|
||||
useSkeletonAnimation
|
||||
:url="message.avatar || ''"
|
||||
:style="{flex: '0 0 auto'}"
|
||||
:style="{ flex: '0 0 auto' }"
|
||||
/>
|
||||
<main
|
||||
class="message-body"
|
||||
@click.stop
|
||||
>
|
||||
<main class="message-body" @click.stop>
|
||||
<div
|
||||
v-if="message.flow === 'in' && message.conversationType === 'GROUP'"
|
||||
v-if="
|
||||
message.flow === 'in' &&
|
||||
message.conversationType === 'GROUP'
|
||||
"
|
||||
class="message-body-nick-name"
|
||||
>
|
||||
{{ props.content.showName }}
|
||||
</div>
|
||||
<div :class="['message-body-main', message.flow === 'out' && 'message-body-main-reverse']">
|
||||
<div
|
||||
:class="[
|
||||
'message-body-main',
|
||||
message.flow === 'out' && 'message-body-main-reverse'
|
||||
]"
|
||||
>
|
||||
<div
|
||||
:class="[
|
||||
'blink',
|
||||
@@ -42,18 +47,22 @@
|
||||
message.hasRiskContent && 'content-has-risk',
|
||||
isNoPadding ? 'content-no-padding' : '',
|
||||
isNoPadding && isBlink ? 'blink-shadow' : '',
|
||||
!isNoPadding && isBlink ? 'blink-content' : '',
|
||||
!isNoPadding && isBlink ? 'blink-content' : ''
|
||||
]"
|
||||
>
|
||||
<div class="content-main">
|
||||
<img
|
||||
v-if="
|
||||
(message.type === TYPES.MSG_IMAGE || message.type === TYPES.MSG_VIDEO) &&
|
||||
message.hasRiskContent
|
||||
(message.type === TYPES.MSG_IMAGE ||
|
||||
message.type === TYPES.MSG_VIDEO) &&
|
||||
message.hasRiskContent
|
||||
"
|
||||
:class="['message-risk-replace', !isPC && 'message-risk-replace-h5']"
|
||||
:class="[
|
||||
'message-risk-replace',
|
||||
!isPC && 'message-risk-replace-h5'
|
||||
]"
|
||||
:src="riskImageReplaceUrl"
|
||||
>
|
||||
/>
|
||||
<template v-else>
|
||||
<slot />
|
||||
</template>
|
||||
@@ -67,10 +76,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<!-- audio unplay mark -->
|
||||
<div
|
||||
v-if="isDisplayUnplayMark"
|
||||
class="audio-unplay-mark"
|
||||
/>
|
||||
<div v-if="isDisplayUnplayMark" class="audio-unplay-mark" />
|
||||
<!-- Fail Icon -->
|
||||
<div
|
||||
v-if="message.status === 'fail' || message.hasRiskContent"
|
||||
@@ -81,7 +87,10 @@
|
||||
</div>
|
||||
<!-- Loading Icon -->
|
||||
<Icon
|
||||
v-if="message.status === 'unSend' && needLoadingIconMessageType.includes(message.type)"
|
||||
v-if="
|
||||
message.status === 'unSend' &&
|
||||
needLoadingIconMessageType.includes(message.type)
|
||||
"
|
||||
class="message-label loading-circle"
|
||||
:file="loadingIcon"
|
||||
:width="'15px'"
|
||||
@@ -122,353 +131,157 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, toRefs } from '../../../../adapter-vue';
|
||||
import TUIChatEngine, { TUITranslateService, IMessageModel } from '@tencentcloud/chat-uikit-engine-lite';
|
||||
import Icon from '../../../common/Icon.vue';
|
||||
import ReadStatus from './read-status/index.vue';
|
||||
import MessageQuote from './message-quote/index.vue';
|
||||
import Avatar from '../../../common/Avatar/index.vue';
|
||||
import MessageTranslate from './message-translate/index.vue';
|
||||
import MessageConvert from './message-convert/index.vue';
|
||||
import RadioSelect from '../../../common/RadioSelect/index.vue';
|
||||
import loadingIcon from '../../../../assets/icon/loading.png';
|
||||
import { shallowCopyMessage } from '../../utils/utils';
|
||||
import { isPC } from '../../../../utils/env';
|
||||
import { computed, toRefs } from '../../../../adapter-vue'
|
||||
import TUIChatEngine, {
|
||||
TUITranslateService,
|
||||
IMessageModel
|
||||
} from '@tencentcloud/chat-uikit-engine-lite'
|
||||
import Icon from '../../../common/Icon.vue'
|
||||
import ReadStatus from './read-status/index.vue'
|
||||
import MessageQuote from './message-quote/index.vue'
|
||||
import Avatar from '../../../common/Avatar/index.vue'
|
||||
import MessageTranslate from './message-translate/index.vue'
|
||||
import MessageConvert from './message-convert/index.vue'
|
||||
import RadioSelect from '../../../common/RadioSelect/index.vue'
|
||||
import loadingIcon from '../../../../assets/icon/loading.png'
|
||||
import { shallowCopyMessage } from '../../utils/utils'
|
||||
import { isPC } from '../../../../utils/env'
|
||||
|
||||
interface IProps {
|
||||
messageItem: IMessageModel;
|
||||
content?: any;
|
||||
classNameList?: string[];
|
||||
blinkMessageIDList?: string[];
|
||||
isMultipleSelectMode?: boolean;
|
||||
isAudioPlayed?: boolean | undefined;
|
||||
multipleSelectedMessageIDList?: string[];
|
||||
}
|
||||
|
||||
interface IEmits {
|
||||
(e: 'resendMessage'): void;
|
||||
(e: 'blinkMessage', messageID: string): void;
|
||||
(e: 'setReadReceiptPanelVisible', visible: boolean, message?: IMessageModel): void;
|
||||
(e: 'changeSelectMessageIDList', options: { type: 'add' | 'remove' | 'clearAll'; messageID: string }): void;
|
||||
// Only for uni-app
|
||||
(e: 'scrollTo', scrollHeight: number): void;
|
||||
}
|
||||
|
||||
const emits = defineEmits<IEmits>();
|
||||
|
||||
const props = withDefaults(defineProps<IProps>(), {
|
||||
isAudioPlayed: false,
|
||||
messageItem: () => ({} as IMessageModel),
|
||||
content: () => ({}),
|
||||
blinkMessageIDList: () => [],
|
||||
classNameList: () => [],
|
||||
isMultipleSelectMode: false,
|
||||
multipleSelectedMessageIDList: () => [],
|
||||
});
|
||||
|
||||
const TYPES = TUIChatEngine.TYPES;
|
||||
const riskImageReplaceUrl = 'https://web.sdk.qcloud.com/component/TUIKit/assets/has_risk_default.png';
|
||||
const needLoadingIconMessageType = [
|
||||
TYPES.MSG_LOCATION,
|
||||
TYPES.MSG_TEXT,
|
||||
TYPES.MSG_CUSTOM,
|
||||
TYPES.MSG_MERGER,
|
||||
TYPES.MSG_FACE,
|
||||
];
|
||||
|
||||
const { blinkMessageIDList, messageItem: message } = toRefs(props);
|
||||
|
||||
const isMultipleSelected = computed<boolean>(() => {
|
||||
return props.multipleSelectedMessageIDList.includes(message.value.ID);
|
||||
});
|
||||
|
||||
const isDisplayUnplayMark = computed<boolean>(() => {
|
||||
return message.value.flow === 'in'
|
||||
&& message.value.status === 'success'
|
||||
&& message.value.type === TYPES.MSG_AUDIO
|
||||
&& !props.isAudioPlayed;
|
||||
});
|
||||
|
||||
const containerClassNameList = computed(() => {
|
||||
return [
|
||||
'message-bubble',
|
||||
isMultipleSelected.value ? 'multiple-selected' : '',
|
||||
...props.classNameList,
|
||||
];
|
||||
});
|
||||
|
||||
const isNoPadding = computed(() => {
|
||||
return [TYPES.MSG_IMAGE, TYPES.MSG_VIDEO, TYPES.MSG_MERGER].includes(message.value.type);
|
||||
});
|
||||
|
||||
const riskContentText = computed<string>(() => {
|
||||
let content = TUITranslateService.t('TUIChat.涉及敏感内容') + ', ';
|
||||
if (message.value.flow === 'out') {
|
||||
content += TUITranslateService.t('TUIChat.发送失败');
|
||||
} else {
|
||||
content += TUITranslateService.t(
|
||||
message.value.type === TYPES.MSG_AUDIO ? 'TUIChat.无法收听' : 'TUIChat.无法查看',
|
||||
);
|
||||
interface IProps {
|
||||
messageItem: IMessageModel
|
||||
content?: any
|
||||
classNameList?: string[]
|
||||
blinkMessageIDList?: string[]
|
||||
isMultipleSelectMode?: boolean
|
||||
isAudioPlayed?: boolean | undefined
|
||||
multipleSelectedMessageIDList?: string[]
|
||||
}
|
||||
return content;
|
||||
});
|
||||
|
||||
const isBlink = computed(() => {
|
||||
if (message.value?.ID) {
|
||||
return blinkMessageIDList?.value?.includes(message.value.ID);
|
||||
interface IEmits {
|
||||
(e: 'resendMessage'): void
|
||||
(e: 'blinkMessage', messageID: string): void
|
||||
(
|
||||
e: 'setReadReceiptPanelVisible',
|
||||
visible: boolean,
|
||||
message?: IMessageModel
|
||||
): void
|
||||
(
|
||||
e: 'changeSelectMessageIDList',
|
||||
options: { type: 'add' | 'remove' | 'clearAll'; messageID: string }
|
||||
): void
|
||||
// Only for uni-app
|
||||
(e: 'scrollTo', scrollHeight: number): void
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
function toggleMultipleSelect(isSelected: boolean) {
|
||||
emits('changeSelectMessageIDList', {
|
||||
type: isSelected ? 'add' : 'remove',
|
||||
messageID: message.value.ID,
|
||||
});
|
||||
}
|
||||
const emits = defineEmits<IEmits>()
|
||||
|
||||
function resendMessage() {
|
||||
if (!message.value?.hasRiskContent) {
|
||||
emits('resendMessage');
|
||||
const props = withDefaults(defineProps<IProps>(), {
|
||||
isAudioPlayed: false,
|
||||
messageItem: () => ({} as IMessageModel),
|
||||
content: () => ({}),
|
||||
blinkMessageIDList: () => [],
|
||||
classNameList: () => [],
|
||||
isMultipleSelectMode: false,
|
||||
multipleSelectedMessageIDList: () => []
|
||||
})
|
||||
|
||||
const TYPES = TUIChatEngine.TYPES
|
||||
const riskImageReplaceUrl =
|
||||
'https://web.sdk.qcloud.com/component/TUIKit/assets/has_risk_default.png'
|
||||
const needLoadingIconMessageType = [
|
||||
TYPES.MSG_LOCATION,
|
||||
TYPES.MSG_TEXT,
|
||||
TYPES.MSG_CUSTOM,
|
||||
TYPES.MSG_MERGER,
|
||||
TYPES.MSG_FACE
|
||||
]
|
||||
|
||||
const { blinkMessageIDList, messageItem: message } = toRefs(props)
|
||||
|
||||
const isMultipleSelected = computed<boolean>(() => {
|
||||
return props.multipleSelectedMessageIDList.includes(message.value.ID)
|
||||
})
|
||||
|
||||
const isDisplayUnplayMark = computed<boolean>(() => {
|
||||
return (
|
||||
message.value.flow === 'in' &&
|
||||
message.value.status === 'success' &&
|
||||
message.value.type === TYPES.MSG_AUDIO &&
|
||||
!props.isAudioPlayed
|
||||
)
|
||||
})
|
||||
|
||||
const containerClassNameList = computed(() => {
|
||||
return [
|
||||
'message-bubble',
|
||||
isMultipleSelected.value ? 'multiple-selected' : '',
|
||||
...props.classNameList
|
||||
]
|
||||
})
|
||||
|
||||
const isNoPadding = computed(() => {
|
||||
return [TYPES.MSG_IMAGE, TYPES.MSG_VIDEO, TYPES.MSG_MERGER].includes(
|
||||
message.value.type
|
||||
)
|
||||
})
|
||||
|
||||
const riskContentText = computed<string>(() => {
|
||||
let content = TUITranslateService.t('TUIChat.涉及敏感内容') + ', '
|
||||
if (message.value.flow === 'out') {
|
||||
content += TUITranslateService.t('TUIChat.发送失败')
|
||||
} else {
|
||||
content += TUITranslateService.t(
|
||||
message.value.type === TYPES.MSG_AUDIO
|
||||
? 'TUIChat.无法收听'
|
||||
: 'TUIChat.无法查看'
|
||||
)
|
||||
}
|
||||
return content
|
||||
})
|
||||
|
||||
const isBlink = computed(() => {
|
||||
if (message.value?.ID) {
|
||||
return blinkMessageIDList?.value?.includes(message.value.ID)
|
||||
}
|
||||
return false
|
||||
})
|
||||
|
||||
function toggleMultipleSelect(isSelected: boolean) {
|
||||
emits('changeSelectMessageIDList', {
|
||||
type: isSelected ? 'add' : 'remove',
|
||||
messageID: message.value.ID
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function blinkMessage(messageID: string) {
|
||||
emits('blinkMessage', messageID);
|
||||
}
|
||||
function resendMessage() {
|
||||
if (!message.value?.hasRiskContent) {
|
||||
emits('resendMessage')
|
||||
}
|
||||
}
|
||||
|
||||
function scrollTo(scrollHeight: number) {
|
||||
emits('scrollTo', scrollHeight);
|
||||
}
|
||||
function blinkMessage(messageID: string) {
|
||||
emits('blinkMessage', messageID)
|
||||
}
|
||||
|
||||
function openReadUserPanel() {
|
||||
emits('setReadReceiptPanelVisible', true, message.value);
|
||||
}
|
||||
function scrollTo(scrollHeight: number) {
|
||||
emits('scrollTo', scrollHeight)
|
||||
}
|
||||
|
||||
function openReadUserPanel() {
|
||||
emits('setReadReceiptPanelVisible', true, message.value)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
:not(not) {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.flex-row {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.reverse {
|
||||
display: flex;
|
||||
flex-direction: row-reverse;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.message-bubble {
|
||||
padding: 10px 15px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
user-select: none;
|
||||
-webkit-touch-callout: none;
|
||||
-webkit-user-select: none;
|
||||
-khtml-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
|
||||
&.multiple-selected {
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
.multiple-select-radio {
|
||||
margin-right: 12px;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.control-reverse {
|
||||
flex: 1 1 auto;
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
|
||||
.message-bubble-main-content {
|
||||
:not(not) {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-direction: column;
|
||||
min-width: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.message-avatar {
|
||||
display: block;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 5px;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.message-body {
|
||||
display: flex;
|
||||
flex: 0 1 auto;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
margin: 0 8px;
|
||||
|
||||
.message-body-nick-name {
|
||||
display: block;
|
||||
margin-bottom: 4px;
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
max-width: 150px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.message-body-main {
|
||||
max-width: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
min-width: 0;
|
||||
box-sizing: border-box;
|
||||
|
||||
&-reverse {
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
|
||||
.audio-unplay-mark {
|
||||
flex: 0 0 auto;
|
||||
width: 5px;
|
||||
height: 5px;
|
||||
border-radius: 50%;
|
||||
background-color: #f00;
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
.message-body-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: 0;
|
||||
box-sizing: border-box;
|
||||
padding: 12px;
|
||||
font-size: 14px;
|
||||
color: #000;
|
||||
letter-spacing: 0;
|
||||
word-wrap: break-word;
|
||||
word-break: break-all;
|
||||
position: relative;
|
||||
|
||||
.content-main {
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-shrink: 0;
|
||||
align-content: flex-start;
|
||||
border: 0 solid black;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
min-width: 0;
|
||||
|
||||
.message-risk-replace {
|
||||
width: 130px;
|
||||
height: 130px;
|
||||
}
|
||||
}
|
||||
|
||||
.content-has-risk-tips {
|
||||
font-size: 12px;
|
||||
color: #fa5151;
|
||||
font-family: PingFangSC-Regular;
|
||||
margin-top: 5px;
|
||||
border-top: 1px solid #e5c7c7;
|
||||
padding-top: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.content-in {
|
||||
background: #fbfbfb;
|
||||
border-radius: 0 10px 10px;
|
||||
}
|
||||
|
||||
.content-out {
|
||||
background: #dceafd;
|
||||
border-radius: 10px 0 10px 10px;
|
||||
}
|
||||
|
||||
.content-no-padding {
|
||||
padding: 0;
|
||||
background: transparent;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.content-no-padding.content-has-risk {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.content-has-risk {
|
||||
background: rgba(250, 81, 81, 0.16);
|
||||
}
|
||||
|
||||
.blink-shadow {
|
||||
@keyframes shadow-blink {
|
||||
50% {
|
||||
box-shadow: rgba(255, 156, 25, 1) 0 0 10px 0;
|
||||
}
|
||||
}
|
||||
|
||||
box-shadow: rgba(255, 156, 25, 0) 0 0 10px 0;
|
||||
animation: shadow-blink 1s linear 3;
|
||||
}
|
||||
|
||||
.blink-content {
|
||||
@keyframes reference-blink {
|
||||
50% {
|
||||
background-color: #ff9c19;
|
||||
}
|
||||
}
|
||||
|
||||
animation: reference-blink 1s linear 3;
|
||||
}
|
||||
|
||||
.message-label {
|
||||
align-self: flex-end;
|
||||
font-family: PingFangSC-Regular;
|
||||
font-size: 12px;
|
||||
color: #b6b8ba;
|
||||
word-break: keep-all;
|
||||
flex: 0 0 auto;
|
||||
margin: 0 8px;
|
||||
|
||||
&.fail {
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
border-radius: 15px;
|
||||
background: red;
|
||||
color: #fff;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&.loading-circle {
|
||||
opacity: 0;
|
||||
animation: circle-loading 2s linear 1s infinite;
|
||||
}
|
||||
|
||||
@keyframes circle-loading {
|
||||
0% {
|
||||
transform: rotate(0);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.align-self-bottom {
|
||||
align-self: flex-end;
|
||||
}
|
||||
}
|
||||
}
|
||||
.flex-row {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.reverse {
|
||||
@@ -477,9 +290,222 @@ function openReadUserPanel() {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.message-bubble-extra-content {
|
||||
.message-bubble {
|
||||
padding: 10px 15px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-direction: row;
|
||||
user-select: none;
|
||||
-webkit-touch-callout: none;
|
||||
-webkit-user-select: none;
|
||||
-khtml-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
|
||||
&.multiple-selected {
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
.multiple-select-radio {
|
||||
margin-right: 12px;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.control-reverse {
|
||||
flex: 1 1 auto;
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
|
||||
.message-bubble-main-content {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
.message-avatar {
|
||||
display: block;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 5px;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.message-body {
|
||||
display: flex;
|
||||
flex: 0 1 auto;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
margin: 0 8px;
|
||||
|
||||
.message-body-nick-name {
|
||||
display: block;
|
||||
margin-bottom: 4px;
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
max-width: 150px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.message-body-main {
|
||||
max-width: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
min-width: 0;
|
||||
box-sizing: border-box;
|
||||
|
||||
&-reverse {
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
|
||||
.audio-unplay-mark {
|
||||
flex: 0 0 auto;
|
||||
width: 5px;
|
||||
height: 5px;
|
||||
border-radius: 50%;
|
||||
background-color: #f00;
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
.message-body-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: 0;
|
||||
box-sizing: border-box;
|
||||
padding: 16rpx 20rpx;
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
letter-spacing: 0;
|
||||
word-wrap: break-word;
|
||||
word-break: break-all;
|
||||
position: relative;
|
||||
|
||||
.content-main {
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-shrink: 0;
|
||||
align-content: flex-start;
|
||||
border: 0 solid black;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
min-width: 0;
|
||||
|
||||
.message-risk-replace {
|
||||
width: 130px;
|
||||
height: 130px;
|
||||
}
|
||||
}
|
||||
|
||||
.content-has-risk-tips {
|
||||
font-size: 12px;
|
||||
color: #fa5151;
|
||||
font-family: PingFangSC-Regular;
|
||||
margin-top: 5px;
|
||||
border-top: 1px solid #e5c7c7;
|
||||
padding-top: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.content-in {
|
||||
background: #f4f4f4;
|
||||
border-radius: 0 10px 10px;
|
||||
}
|
||||
|
||||
.content-out {
|
||||
background: #00D9C5;
|
||||
border-radius: 10px 0 10px 10px;
|
||||
}
|
||||
|
||||
.content-no-padding {
|
||||
padding: 0;
|
||||
background: transparent;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.content-no-padding.content-has-risk {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.content-has-risk {
|
||||
background: rgba(250, 81, 81, 0.16);
|
||||
}
|
||||
|
||||
.blink-shadow {
|
||||
@keyframes shadow-blink {
|
||||
50% {
|
||||
box-shadow: rgba(255, 156, 25, 1) 0 0 10px 0;
|
||||
}
|
||||
}
|
||||
|
||||
box-shadow: rgba(255, 156, 25, 0) 0 0 10px 0;
|
||||
animation: shadow-blink 1s linear 3;
|
||||
}
|
||||
|
||||
.blink-content {
|
||||
@keyframes reference-blink {
|
||||
50% {
|
||||
background-color: #ff9c19;
|
||||
}
|
||||
}
|
||||
|
||||
animation: reference-blink 1s linear 3;
|
||||
}
|
||||
|
||||
.message-label {
|
||||
align-self: flex-end;
|
||||
font-family: PingFangSC-Regular;
|
||||
font-size: 12px;
|
||||
color: #b6b8ba;
|
||||
word-break: keep-all;
|
||||
flex: 0 0 auto;
|
||||
margin: 0 8px;
|
||||
|
||||
&.fail {
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
border-radius: 15px;
|
||||
background: red;
|
||||
color: #fff;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&.loading-circle {
|
||||
opacity: 0;
|
||||
animation: circle-loading 2s linear 1s infinite;
|
||||
}
|
||||
|
||||
@keyframes circle-loading {
|
||||
0% {
|
||||
transform: rotate(0);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.align-self-bottom {
|
||||
align-self: flex-end;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.reverse {
|
||||
display: flex;
|
||||
flex-direction: row-reverse;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.message-bubble-extra-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -8,4 +8,12 @@
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.tui-message-list {
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.message-li {
|
||||
margin-top: 0 !important;
|
||||
}
|
||||
@@ -89,7 +89,12 @@ export const sendMessages = async (
|
||||
options.payload.atUserList = content.payload.atUserList;
|
||||
await TUIChatService.sendTextAtMessage(options, sendMessageOptions);
|
||||
} else {
|
||||
await TUIChatService.sendTextMessage(options, sendMessageOptions);
|
||||
try {
|
||||
await TUIChatService.sendTextMessage(options, sendMessageOptions);
|
||||
} catch (err) {
|
||||
console.log('发送失败,对方不是你的好友')
|
||||
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'image':
|
||||
|
||||
@@ -1,28 +1,34 @@
|
||||
<template>
|
||||
<div
|
||||
v-if="typeof contactInfoData === 'object' && Object.keys(contactInfoData).length"
|
||||
v-if="
|
||||
typeof contactInfoData === 'object' &&
|
||||
Object.keys(contactInfoData).length
|
||||
"
|
||||
:class="['tui-contact-info', !isPC && 'tui-contact-info-h5']"
|
||||
>
|
||||
<Navigation>
|
||||
<template #left>
|
||||
<div @click="resetContactSearchingUIData">
|
||||
<Icon
|
||||
:file="backSVG"
|
||||
/>
|
||||
<Icon :file="backSVG" />
|
||||
</div>
|
||||
</template>
|
||||
</Navigation>
|
||||
<div :class="['tui-contact-info-basic', !isPC && 'tui-contact-info-h5-basic']">
|
||||
<div
|
||||
:class="[
|
||||
'tui-contact-info-basic',
|
||||
!isPC && 'tui-contact-info-h5-basic'
|
||||
]"
|
||||
>
|
||||
<div
|
||||
:class="[
|
||||
'tui-contact-info-basic-text',
|
||||
!isPC && 'tui-contact-info-h5-basic-text',
|
||||
!isPC && 'tui-contact-info-h5-basic-text'
|
||||
]"
|
||||
>
|
||||
<div
|
||||
:class="[
|
||||
'tui-contact-info-basic-text-name',
|
||||
!isPC && 'tui-contact-info-h5-basic-text-name',
|
||||
!isPC && 'tui-contact-info-h5-basic-text-name'
|
||||
]"
|
||||
>
|
||||
{{ generateContactInfoName(contactInfoData) }}
|
||||
@@ -32,7 +38,7 @@
|
||||
:key="item.label"
|
||||
:class="[
|
||||
'tui-contact-info-basic-text-other',
|
||||
!isPC && 'tui-contact-info-h5-basic-text-other',
|
||||
!isPC && 'tui-contact-info-h5-basic-text-other'
|
||||
]"
|
||||
>
|
||||
{{
|
||||
@@ -44,14 +50,17 @@
|
||||
<img
|
||||
:class="[
|
||||
'tui-contact-info-basic-avatar',
|
||||
!isPC && 'tui-contact-info-h5-basic-avatar',
|
||||
!isPC && 'tui-contact-info-h5-basic-avatar'
|
||||
]"
|
||||
:src="generateAvatar(contactInfoData)"
|
||||
>
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-if="contactInfoMoreList[0]"
|
||||
:class="['tui-contact-info-more', !isPC && 'tui-contact-info-h5-more']"
|
||||
:class="[
|
||||
'tui-contact-info-more',
|
||||
!isPC && 'tui-contact-info-h5-more'
|
||||
]"
|
||||
>
|
||||
<div
|
||||
v-for="item in contactInfoMoreList"
|
||||
@@ -61,13 +70,13 @@
|
||||
!isPC && 'tui-contact-info-h5-more-item',
|
||||
item.labelPosition === CONTACT_INFO_LABEL_POSITION.TOP
|
||||
? 'tui-contact-info-more-item-top'
|
||||
: 'tui-contact-info-more-item-left',
|
||||
: 'tui-contact-info-more-item-left'
|
||||
]"
|
||||
>
|
||||
<div
|
||||
:class="[
|
||||
'tui-contact-info-more-item-label',
|
||||
!isPC && 'tui-contact-info-h5-more-item-label',
|
||||
!isPC && 'tui-contact-info-h5-more-item-label'
|
||||
]"
|
||||
>
|
||||
{{ `${TUITranslateService.t(`TUIContact.${item.label}`)}` }}
|
||||
@@ -75,20 +84,20 @@
|
||||
<div
|
||||
:class="[
|
||||
'tui-contact-info-more-item-content',
|
||||
!isPC && 'tui-contact-info-h5-more-item-content',
|
||||
!isPC && 'tui-contact-info-h5-more-item-content'
|
||||
]"
|
||||
>
|
||||
<div
|
||||
v-if="!item.editing"
|
||||
:class="[
|
||||
'tui-contact-info-more-item-content-text',
|
||||
!isPC && 'tui-contact-info-h5-more-item-content-text',
|
||||
!isPC && 'tui-contact-info-h5-more-item-content-text'
|
||||
]"
|
||||
>
|
||||
<div
|
||||
:class="[
|
||||
'tui-contact-info-more-item-content-text-data',
|
||||
!isPC && 'tui-contact-info-h5-more-item-content-text-data',
|
||||
!isPC && 'tui-contact-info-h5-more-item-content-text-data'
|
||||
]"
|
||||
>
|
||||
{{ item.data }}
|
||||
@@ -97,39 +106,41 @@
|
||||
v-if="item.editable"
|
||||
:class="[
|
||||
'tui-contact-info-more-item-content-text-icon',
|
||||
!isPC && 'tui-contact-info-h5-more-item-content-text-icon',
|
||||
!isPC && 'tui-contact-info-h5-more-item-content-text-icon'
|
||||
]"
|
||||
@click="setEditing(item)"
|
||||
>
|
||||
<Icon
|
||||
:file="editSVG"
|
||||
width="14px"
|
||||
height="14px"
|
||||
/>
|
||||
<Icon :file="editSVG" width="14px" height="14px" />
|
||||
</div>
|
||||
</div>
|
||||
<input
|
||||
v-else-if="item.editType === CONTACT_INFO_MORE_EDIT_TYPE.INPUT"
|
||||
v-else-if="
|
||||
item.editType === CONTACT_INFO_MORE_EDIT_TYPE.INPUT
|
||||
"
|
||||
v-model="item.data"
|
||||
:class="[
|
||||
'tui-contact-info-more-item-content-input',
|
||||
!isPC && 'tui-contact-info-h5-more-item-content-input',
|
||||
!isPC && 'tui-contact-info-h5-more-item-content-input'
|
||||
]"
|
||||
type="text"
|
||||
@confirm="onContactInfoEmitSubmit(item)"
|
||||
@keyup.enter="onContactInfoEmitSubmit(item)"
|
||||
>
|
||||
/>
|
||||
<textarea
|
||||
v-else-if="item.editType === CONTACT_INFO_MORE_EDIT_TYPE.TEXTAREA"
|
||||
v-else-if="
|
||||
item.editType === CONTACT_INFO_MORE_EDIT_TYPE.TEXTAREA
|
||||
"
|
||||
v-model="item.data"
|
||||
:class="[
|
||||
'tui-contact-info-more-item-content-textarea',
|
||||
!isPC && 'tui-contact-info-h5-more-item-content-textarea',
|
||||
!isPC && 'tui-contact-info-h5-more-item-content-textarea'
|
||||
]"
|
||||
confirm-type="done"
|
||||
/>
|
||||
<div
|
||||
v-else-if="item.editType === CONTACT_INFO_MORE_EDIT_TYPE.SWITCH"
|
||||
v-else-if="
|
||||
item.editType === CONTACT_INFO_MORE_EDIT_TYPE.SWITCH
|
||||
"
|
||||
@click="onContactInfoEmitSubmit(item)"
|
||||
>
|
||||
<SwitchBar :value="item.data" />
|
||||
@@ -140,7 +151,7 @@
|
||||
<div
|
||||
:class="[
|
||||
'tui-contact-info-button',
|
||||
!isPC && 'tui-contact-info-h5-button',
|
||||
!isPC && 'tui-contact-info-h5-button'
|
||||
]"
|
||||
>
|
||||
<button
|
||||
@@ -151,7 +162,7 @@
|
||||
!isPC && 'tui-contact-info-h5-button-item',
|
||||
item.type === CONTACT_INFO_BUTTON_TYPE.CANCEL
|
||||
? `tui-contact-info-button-item-cancel`
|
||||
: `tui-contact-info-button-item-submit`,
|
||||
: `tui-contact-info-button-item-submit`
|
||||
]"
|
||||
@click="onContactInfoButtonClicked(item)"
|
||||
>
|
||||
@@ -161,263 +172,339 @@
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import TUIChatEngine, {
|
||||
TUIStore,
|
||||
StoreName,
|
||||
TUITranslateService,
|
||||
IGroupModel,
|
||||
Friend,
|
||||
FriendApplication,
|
||||
} from '@tencentcloud/chat-uikit-engine-lite';
|
||||
import { TUIGlobal } from '@tencentcloud/universal-api';
|
||||
import { ref, computed, onMounted, onUnmounted } from '../../../adapter-vue';
|
||||
import { isPC } from '../../../utils/env';
|
||||
import TUIChatEngine, {
|
||||
TUIStore,
|
||||
StoreName,
|
||||
TUITranslateService,
|
||||
IGroupModel,
|
||||
Friend,
|
||||
FriendApplication,
|
||||
TUIUserService
|
||||
} from '@tencentcloud/chat-uikit-engine-lite'
|
||||
import { TUIGlobal } from '@tencentcloud/universal-api'
|
||||
import {
|
||||
ref,
|
||||
computed,
|
||||
onMounted,
|
||||
onUnmounted
|
||||
} from '../../../adapter-vue'
|
||||
import { isPC } from '../../../utils/env'
|
||||
|
||||
import {
|
||||
generateAvatar,
|
||||
generateContactInfoName,
|
||||
generateContactInfoBasic,
|
||||
isFriend,
|
||||
isApplicationType,
|
||||
} from '../utils/index';
|
||||
import {
|
||||
contactMoreInfoConfig,
|
||||
contactButtonConfig,
|
||||
} from './contact-info-config';
|
||||
import Navigation from '../../common/Navigation/index.vue';
|
||||
import Icon from '../../common/Icon.vue';
|
||||
import editSVG from '../../../assets/icon/edit.svg';
|
||||
import backSVG from '../../../assets/icon/back.svg';
|
||||
import SwitchBar from '../../common/SwitchBar/index.vue';
|
||||
import {
|
||||
IBlackListUserItem,
|
||||
IContactInfoMoreItem,
|
||||
IContactInfoButton,
|
||||
} from '../../../interface';
|
||||
import {
|
||||
CONTACT_INFO_LABEL_POSITION,
|
||||
CONTACT_INFO_MORE_EDIT_TYPE,
|
||||
CONTACT_INFO_BUTTON_TYPE,
|
||||
CONTACT_INFO_TITLE,
|
||||
} from '../../../constant';
|
||||
import { deepCopy } from '../../TUIChat/utils/utils';
|
||||
import {
|
||||
generateAvatar,
|
||||
generateContactInfoName,
|
||||
generateContactInfoBasic,
|
||||
isFriend,
|
||||
isApplicationType
|
||||
} from '../utils/index'
|
||||
import {
|
||||
contactMoreInfoConfig,
|
||||
contactButtonConfig
|
||||
} from './contact-info-config'
|
||||
import Navigation from '../../common/Navigation/index.vue'
|
||||
import Icon from '../../common/Icon.vue'
|
||||
import editSVG from '../../../assets/icon/edit.svg'
|
||||
import backSVG from '../../../assets/icon/back.svg'
|
||||
import SwitchBar from '../../common/SwitchBar/index.vue'
|
||||
import {
|
||||
IBlackListUserItem,
|
||||
IContactInfoMoreItem,
|
||||
IContactInfoButton
|
||||
} from '../../../interface'
|
||||
import {
|
||||
CONTACT_INFO_LABEL_POSITION,
|
||||
CONTACT_INFO_MORE_EDIT_TYPE,
|
||||
CONTACT_INFO_BUTTON_TYPE,
|
||||
CONTACT_INFO_TITLE
|
||||
} from '../../../constant'
|
||||
import { deepCopy } from '../../TUIChat/utils/utils'
|
||||
import { useUI } from '../../../../utils/use-ui'
|
||||
|
||||
type IContactInfoType = IGroupModel | Friend | FriendApplication | IBlackListUserItem;
|
||||
type IContactInfoType =
|
||||
| IGroupModel
|
||||
| Friend
|
||||
| FriendApplication
|
||||
| IBlackListUserItem
|
||||
|
||||
const emits = defineEmits(['switchConversation']);
|
||||
const { showLoading, hideLoading } = useUI()
|
||||
|
||||
const contactInfoData = ref<IContactInfoType>({} as IContactInfoType);
|
||||
const contactInfoBasicList = ref<Array<{ label: string; data: string }>>([]);
|
||||
const contactInfoMoreList = ref<IContactInfoMoreItem[]>([]);
|
||||
const contactInfoButtonList = ref<IContactInfoButton[]>([]);
|
||||
const contactInfoTitle = ref<string>('');
|
||||
const emits = defineEmits(['switchConversation'])
|
||||
|
||||
const setEditing = (item: any) => {
|
||||
item.editing = true;
|
||||
};
|
||||
const contactInfoData = ref<IContactInfoType>({} as IContactInfoType)
|
||||
const contactInfoBasicList = ref<
|
||||
Array<{ label: string; data: string }>
|
||||
>([])
|
||||
const contactInfoMoreList = ref<IContactInfoMoreItem[]>([])
|
||||
const contactInfoButtonList = ref<IContactInfoButton[]>([])
|
||||
const contactInfoTitle = ref<string>('')
|
||||
|
||||
const isGroup = computed((): boolean =>
|
||||
(contactInfoData.value as IGroupModel)?.groupID ? true : false,
|
||||
);
|
||||
|
||||
const isApplication = computed((): boolean => {
|
||||
return isApplicationType(contactInfoData?.value);
|
||||
});
|
||||
|
||||
// is both friend, if is group type always false
|
||||
const isBothFriend = ref<boolean>(false);
|
||||
|
||||
// is group member, including ordinary member, admin, group owner
|
||||
const isGroupMember = computed((): boolean => {
|
||||
return (contactInfoData.value as IGroupModel)?.selfInfo?.userID ? true : false;
|
||||
});
|
||||
|
||||
// is in black list, if is group type always false
|
||||
const isInBlackList = computed((): boolean => {
|
||||
return (
|
||||
!isGroup.value
|
||||
&& blackList.value?.findIndex(
|
||||
(item: IBlackListUserItem) =>
|
||||
item?.userID === (contactInfoData.value as IBlackListUserItem)?.userID,
|
||||
) >= 0
|
||||
);
|
||||
});
|
||||
|
||||
const blackList = ref<IBlackListUserItem[]>([]);
|
||||
|
||||
onMounted(() => {
|
||||
TUIStore.watch(StoreName.CUSTOM, {
|
||||
currentContactInfo: onCurrentContactInfoUpdated,
|
||||
currentContactListKey: onCurrentContactListKeyUpdated,
|
||||
});
|
||||
TUIStore.watch(StoreName.USER, {
|
||||
userBlacklist: onUserBlacklistUpdated,
|
||||
});
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
TUIStore.unwatch(StoreName.CUSTOM, {
|
||||
currentContactInfo: onCurrentContactInfoUpdated,
|
||||
currentContactListKey: onCurrentContactListKeyUpdated,
|
||||
});
|
||||
TUIStore.unwatch(StoreName.USER, {
|
||||
userBlacklist: onUserBlacklistUpdated,
|
||||
});
|
||||
});
|
||||
const onCurrentContactListKeyUpdated = (key: string) => {
|
||||
if (CONTACT_INFO_TITLE[key]) {
|
||||
contactInfoTitle.value = TUITranslateService.t(`TUIContact.${CONTACT_INFO_TITLE[key]}`);
|
||||
const setEditing = (item: any) => {
|
||||
item.editing = true
|
||||
}
|
||||
};
|
||||
|
||||
const resetContactInfoUIData = () => {
|
||||
contactInfoData.value = {} as IContactInfoType;
|
||||
contactInfoBasicList.value = [];
|
||||
contactInfoMoreList.value = [];
|
||||
contactInfoButtonList.value = [];
|
||||
};
|
||||
const isGroup = computed((): boolean =>
|
||||
(contactInfoData.value as IGroupModel)?.groupID ? true : false
|
||||
)
|
||||
|
||||
const resetContactSearchingUIData = () => {
|
||||
TUIStore.update(StoreName.CUSTOM, 'currentContactInfo', {});
|
||||
TUIStore.update(StoreName.CUSTOM, 'currentContactSearchingStatus', false);
|
||||
TUIGlobal?.closeSearching && TUIGlobal?.closeSearching();
|
||||
};
|
||||
const isApplication = computed((): boolean => {
|
||||
return isApplicationType(contactInfoData?.value)
|
||||
})
|
||||
|
||||
const onContactInfoEmitSubmit = (item: any) => {
|
||||
item.editSubmitHandler
|
||||
&& item.editSubmitHandler({
|
||||
item,
|
||||
contactInfoData: contactInfoData.value,
|
||||
isBothFriend: isBothFriend.value,
|
||||
isInBlackList: isInBlackList.value,
|
||||
});
|
||||
};
|
||||
// is both friend, if is group type always false
|
||||
const isBothFriend = ref<boolean>(false)
|
||||
|
||||
const onContactInfoButtonClicked = (item: any) => {
|
||||
item.onClick
|
||||
&& item.onClick({
|
||||
contactInfoData: contactInfoData.value,
|
||||
contactInfoMoreList: contactInfoMoreList.value,
|
||||
});
|
||||
if (
|
||||
item.key === 'enterGroupConversation'
|
||||
|| item.key === 'enterC2CConversation'
|
||||
) {
|
||||
emits('switchConversation', contactInfoData.value);
|
||||
resetContactSearchingUIData();
|
||||
// is group member, including ordinary member, admin, group owner
|
||||
const isGroupMember = computed((): boolean => {
|
||||
return (contactInfoData.value as IGroupModel)?.selfInfo?.userID
|
||||
? true
|
||||
: false
|
||||
})
|
||||
|
||||
// is in black list, if is group type always false
|
||||
const isInBlackList = computed((): boolean => {
|
||||
return (
|
||||
!isGroup.value &&
|
||||
blackList.value?.findIndex(
|
||||
(item: IBlackListUserItem) =>
|
||||
item?.userID ===
|
||||
(contactInfoData.value as IBlackListUserItem)?.userID
|
||||
) >= 0
|
||||
)
|
||||
})
|
||||
|
||||
const blackList = ref<IBlackListUserItem[]>([])
|
||||
|
||||
onMounted(() => {
|
||||
TUIStore.watch(StoreName.CUSTOM, {
|
||||
currentContactInfo: onCurrentContactInfoUpdated,
|
||||
currentContactListKey: onCurrentContactListKeyUpdated
|
||||
})
|
||||
TUIStore.watch(StoreName.USER, {
|
||||
userBlacklist: onUserBlacklistUpdated
|
||||
})
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
TUIStore.unwatch(StoreName.CUSTOM, {
|
||||
currentContactInfo: onCurrentContactInfoUpdated,
|
||||
currentContactListKey: onCurrentContactListKeyUpdated
|
||||
})
|
||||
TUIStore.unwatch(StoreName.USER, {
|
||||
userBlacklist: onUserBlacklistUpdated
|
||||
})
|
||||
})
|
||||
const onCurrentContactListKeyUpdated = (key: string) => {
|
||||
if (CONTACT_INFO_TITLE[key]) {
|
||||
contactInfoTitle.value = TUITranslateService.t(
|
||||
`TUIContact.${CONTACT_INFO_TITLE[key]}`
|
||||
)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const generateMoreInfo = async () => {
|
||||
if (!isApplication.value) {
|
||||
if (
|
||||
(!isGroup.value && !isBothFriend.value && !isInBlackList.value)
|
||||
|| (isGroup.value
|
||||
&& !isGroupMember.value
|
||||
&& (contactInfoData.value as IGroupModel)?.type !== TUIChatEngine?.TYPES?.GRP_AVCHATROOM)
|
||||
) {
|
||||
contactMoreInfoConfig.setWords.data = '';
|
||||
contactInfoMoreList.value.push(contactMoreInfoConfig.setWords);
|
||||
}
|
||||
if (!isGroup.value && !isInBlackList.value) {
|
||||
contactMoreInfoConfig.setRemark.data
|
||||
= (contactInfoData.value as Friend)?.remark || '';
|
||||
contactMoreInfoConfig.setRemark.editing = false;
|
||||
contactInfoMoreList.value.push(contactMoreInfoConfig.setRemark);
|
||||
}
|
||||
if (!isGroup.value && (isBothFriend.value || isInBlackList.value)) {
|
||||
contactMoreInfoConfig.blackList.data = isInBlackList.value || false;
|
||||
contactInfoMoreList.value.push(contactMoreInfoConfig.blackList);
|
||||
}
|
||||
} else {
|
||||
contactMoreInfoConfig.displayWords.data
|
||||
= (contactInfoData.value as FriendApplication)?.wording || '';
|
||||
contactInfoMoreList.value.push(contactMoreInfoConfig.displayWords);
|
||||
const resetContactInfoUIData = () => {
|
||||
contactInfoData.value = {} as IContactInfoType
|
||||
contactInfoBasicList.value = []
|
||||
contactInfoMoreList.value = []
|
||||
contactInfoButtonList.value = []
|
||||
}
|
||||
};
|
||||
|
||||
const generateButton = () => {
|
||||
if (isInBlackList.value) {
|
||||
return;
|
||||
const resetContactSearchingUIData = () => {
|
||||
TUIStore.update(StoreName.CUSTOM, 'currentContactInfo', {})
|
||||
TUIStore.update(
|
||||
StoreName.CUSTOM,
|
||||
'currentContactSearchingStatus',
|
||||
false
|
||||
)
|
||||
TUIGlobal?.closeSearching && TUIGlobal?.closeSearching()
|
||||
}
|
||||
if (isApplication.value) {
|
||||
if (
|
||||
(contactInfoData.value as FriendApplication)?.type
|
||||
=== TUIChatEngine?.TYPES?.SNS_APPLICATION_SENT_TO_ME
|
||||
) {
|
||||
contactInfoButtonList?.value?.push(
|
||||
contactButtonConfig.refuseFriendApplication,
|
||||
);
|
||||
contactInfoButtonList?.value?.push(
|
||||
contactButtonConfig.acceptFriendApplication,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if (isGroup.value && isGroupMember.value) {
|
||||
switch ((contactInfoData.value as IGroupModel)?.selfInfo?.role) {
|
||||
case 'Owner':
|
||||
contactInfoButtonList?.value?.push(contactButtonConfig.dismissGroup);
|
||||
break;
|
||||
default:
|
||||
contactInfoButtonList?.value?.push(contactButtonConfig.quitGroup);
|
||||
break;
|
||||
}
|
||||
contactInfoButtonList?.value?.push(
|
||||
contactButtonConfig.enterGroupConversation,
|
||||
);
|
||||
} else if (!isGroup.value && isBothFriend.value) {
|
||||
contactInfoButtonList?.value?.push(contactButtonConfig.deleteFriend);
|
||||
contactInfoButtonList?.value?.push(
|
||||
contactButtonConfig.enterC2CConversation,
|
||||
);
|
||||
} else {
|
||||
if (isGroup.value) {
|
||||
contactInfoButtonList?.value?.push(
|
||||
(contactInfoData.value as IGroupModel)?.type === TUIChatEngine?.TYPES?.GRP_AVCHATROOM
|
||||
? contactButtonConfig.joinAVChatGroup
|
||||
: contactButtonConfig.joinGroup,
|
||||
);
|
||||
|
||||
const onContactInfoEmitSubmit = (item: any) => {
|
||||
if (item.key === 'blackList') {
|
||||
// item.data = true
|
||||
// resetContactSearchingUIData()
|
||||
const userID = (contactInfoData.value as { userID: string }).userID
|
||||
|
||||
if (item.data) {
|
||||
showLoading()
|
||||
TUIUserService.removeFromBlacklist({ userIDList: [userID] })
|
||||
.then(() => {
|
||||
item.data = false
|
||||
})
|
||||
.finally(() => {
|
||||
hideLoading()
|
||||
})
|
||||
} else {
|
||||
contactInfoButtonList?.value?.push(contactButtonConfig.addFriend);
|
||||
showLoading()
|
||||
TUIUserService.addToBlacklist({ userIDList: [userID] })
|
||||
.then(() => {
|
||||
item.data = true
|
||||
})
|
||||
.finally(() => {
|
||||
hideLoading()
|
||||
})
|
||||
}
|
||||
} else {
|
||||
item.editSubmitHandler &&
|
||||
item.editSubmitHandler({
|
||||
item,
|
||||
contactInfoData: contactInfoData.value,
|
||||
isBothFriend: isBothFriend.value,
|
||||
isInBlackList: isInBlackList.value
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const onContactInfoButtonClicked = (item: any) => {
|
||||
item.onClick &&
|
||||
item.onClick({
|
||||
contactInfoData: contactInfoData.value,
|
||||
contactInfoMoreList: contactInfoMoreList.value
|
||||
})
|
||||
if (
|
||||
item.key === 'enterGroupConversation' ||
|
||||
item.key === 'enterC2CConversation'
|
||||
) {
|
||||
emits('switchConversation', contactInfoData.value)
|
||||
resetContactSearchingUIData()
|
||||
}
|
||||
}
|
||||
|
||||
const generateMoreInfo = async () => {
|
||||
if (!isApplication.value) {
|
||||
if (
|
||||
(!isGroup.value && !isBothFriend.value && !isInBlackList.value) ||
|
||||
(isGroup.value &&
|
||||
!isGroupMember.value &&
|
||||
(contactInfoData.value as IGroupModel)?.type !==
|
||||
TUIChatEngine?.TYPES?.GRP_AVCHATROOM)
|
||||
) {
|
||||
contactMoreInfoConfig.setWords.data = ''
|
||||
contactInfoMoreList.value.push(contactMoreInfoConfig.setWords)
|
||||
}
|
||||
if (!isGroup.value && !isInBlackList.value) {
|
||||
contactMoreInfoConfig.setRemark.data =
|
||||
(contactInfoData.value as Friend)?.remark || ''
|
||||
contactMoreInfoConfig.setRemark.editing = false
|
||||
contactInfoMoreList.value.push(contactMoreInfoConfig.setRemark)
|
||||
}
|
||||
if (!isGroup.value && (isBothFriend.value || isInBlackList.value)) {
|
||||
contactMoreInfoConfig.blackList.data =
|
||||
isInBlackList.value || false
|
||||
contactInfoMoreList.value.push(contactMoreInfoConfig.blackList)
|
||||
}
|
||||
} else {
|
||||
contactMoreInfoConfig.displayWords.data =
|
||||
(contactInfoData.value as FriendApplication)?.wording || ''
|
||||
contactInfoMoreList.value.push(contactMoreInfoConfig.displayWords)
|
||||
}
|
||||
}
|
||||
|
||||
const generateButton = () => {
|
||||
if (isInBlackList.value) {
|
||||
return
|
||||
}
|
||||
if (isApplication.value) {
|
||||
if (
|
||||
(contactInfoData.value as FriendApplication)?.type ===
|
||||
TUIChatEngine?.TYPES?.SNS_APPLICATION_SENT_TO_ME
|
||||
) {
|
||||
contactInfoButtonList?.value?.push(
|
||||
contactButtonConfig.refuseFriendApplication
|
||||
)
|
||||
contactInfoButtonList?.value?.push(
|
||||
contactButtonConfig.acceptFriendApplication
|
||||
)
|
||||
}
|
||||
} else {
|
||||
if (isGroup.value && isGroupMember.value) {
|
||||
switch ((contactInfoData.value as IGroupModel)?.selfInfo?.role) {
|
||||
case 'Owner':
|
||||
contactInfoButtonList?.value?.push(
|
||||
contactButtonConfig.dismissGroup
|
||||
)
|
||||
break
|
||||
default:
|
||||
contactInfoButtonList?.value?.push(
|
||||
contactButtonConfig.quitGroup
|
||||
)
|
||||
break
|
||||
}
|
||||
contactInfoButtonList?.value?.push(
|
||||
contactButtonConfig.enterGroupConversation
|
||||
)
|
||||
} else if (!isGroup.value && isBothFriend.value) {
|
||||
contactInfoButtonList?.value?.push(
|
||||
contactButtonConfig.deleteFriend
|
||||
)
|
||||
contactInfoButtonList?.value?.push(
|
||||
contactButtonConfig.enterC2CConversation
|
||||
)
|
||||
} else {
|
||||
if (isGroup.value) {
|
||||
contactInfoButtonList?.value?.push(
|
||||
(contactInfoData.value as IGroupModel)?.type ===
|
||||
TUIChatEngine?.TYPES?.GRP_AVCHATROOM
|
||||
? contactButtonConfig.joinAVChatGroup
|
||||
: contactButtonConfig.joinGroup
|
||||
)
|
||||
} else {
|
||||
contactInfoButtonList?.value?.push(
|
||||
contactButtonConfig.addFriend
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function onUserBlacklistUpdated(userBlacklist: IBlackListUserItem[]) {
|
||||
blackList.value = userBlacklist;
|
||||
}
|
||||
function onUserBlacklistUpdated(userBlacklist: IBlackListUserItem[]) {
|
||||
blackList.value = userBlacklist
|
||||
}
|
||||
|
||||
async function onCurrentContactInfoUpdated(contactInfo: IContactInfoType) {
|
||||
if (
|
||||
contactInfoData.value
|
||||
&& contactInfo
|
||||
&& JSON.stringify(contactInfoData.value) === JSON.stringify(contactInfo)
|
||||
async function onCurrentContactInfoUpdated(
|
||||
contactInfo: IContactInfoType
|
||||
) {
|
||||
return;
|
||||
if (
|
||||
contactInfoData.value &&
|
||||
contactInfo &&
|
||||
JSON.stringify(contactInfoData.value) ===
|
||||
JSON.stringify(contactInfo)
|
||||
) {
|
||||
return
|
||||
}
|
||||
resetContactInfoUIData()
|
||||
// deep clone
|
||||
contactInfoData.value = deepCopy(contactInfo) || {}
|
||||
if (
|
||||
!contactInfoData.value ||
|
||||
Object.keys(contactInfoData.value)?.length === 0
|
||||
) {
|
||||
return
|
||||
}
|
||||
contactInfoBasicList.value = generateContactInfoBasic(
|
||||
contactInfoData.value
|
||||
)
|
||||
isBothFriend.value = await isFriend(contactInfoData.value)
|
||||
generateMoreInfo()
|
||||
generateButton()
|
||||
if (contactInfo.infoKeyList) {
|
||||
contactInfoMoreList.value = contactInfo.infoKeyList.map(
|
||||
(key: string) => {
|
||||
return (contactMoreInfoConfig as any)[key]
|
||||
}
|
||||
)
|
||||
}
|
||||
if (contactInfo.btnKeyList) {
|
||||
contactInfoButtonList.value = contactInfo.btnKeyList.map(
|
||||
(key: string) => {
|
||||
return (contactButtonConfig as any)[key]
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
resetContactInfoUIData();
|
||||
// deep clone
|
||||
contactInfoData.value = deepCopy(contactInfo) || {};
|
||||
if (!contactInfoData.value || Object.keys(contactInfoData.value)?.length === 0) {
|
||||
return;
|
||||
}
|
||||
contactInfoBasicList.value = generateContactInfoBasic(
|
||||
contactInfoData.value,
|
||||
);
|
||||
isBothFriend.value = await isFriend(contactInfoData.value);
|
||||
generateMoreInfo();
|
||||
generateButton();
|
||||
if (contactInfo.infoKeyList) {
|
||||
contactInfoMoreList.value = contactInfo.infoKeyList.map((key: string) => {
|
||||
return (contactMoreInfoConfig as any)[key];
|
||||
});
|
||||
}
|
||||
if (contactInfo.btnKeyList) {
|
||||
contactInfoButtonList.value = contactInfo.btnKeyList.map((key: string) => {
|
||||
return (contactButtonConfig as any)[key];
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped src="./style/index.scss"></style>
|
||||
<style lang="scss" scoped>
|
||||
.tui-contact-info-basic-avatar {
|
||||
border-radius: 100rpx;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,28 +1,34 @@
|
||||
<template>
|
||||
<div
|
||||
v-if="Object.keys(friendListData.map).length > 0"
|
||||
class="friend-list"
|
||||
>
|
||||
<ul
|
||||
v-for="(groupData, groupKey) in friendListData.map"
|
||||
:key="groupKey"
|
||||
<view>
|
||||
<view
|
||||
v-if="Object.keys(friendListData.map).length > 0"
|
||||
class="friend-list"
|
||||
>
|
||||
<div class="friend-group-title">
|
||||
{{ groupKey }} ({{ groupData.length }})
|
||||
</div>
|
||||
<li
|
||||
v-for="contactListItem in groupData"
|
||||
:key="contactListItem.renderKey"
|
||||
class="friend-item"
|
||||
@click="enterConversation(contactListItem)"
|
||||
<ul
|
||||
v-for="(groupData, groupKey) in friendListData.map"
|
||||
:key="groupKey"
|
||||
>
|
||||
<ContactListItem
|
||||
<div class="friend-group-title">
|
||||
{{ groupKey }} ({{ groupData.length }})
|
||||
</div>
|
||||
<li
|
||||
v-for="contactListItem in groupData"
|
||||
:key="contactListItem.renderKey"
|
||||
:item="deepCopy(contactListItem)"
|
||||
/>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
class="friend-item"
|
||||
@click="enterConversation(contactListItem)"
|
||||
>
|
||||
<ContactListItem
|
||||
:key="contactListItem.renderKey"
|
||||
:item="deepCopy(contactListItem)"
|
||||
/>
|
||||
</li>
|
||||
</ul>
|
||||
</view>
|
||||
<cb-empty
|
||||
v-if="Object.keys(friendListData.map).length === 0"
|
||||
name="您还没有好友"
|
||||
></cb-empty>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
@@ -83,7 +89,7 @@
|
||||
|
||||
.friend-group-title {
|
||||
padding: 8px 16px;
|
||||
background-color: #f8f9fa;
|
||||
background-color: #ffffff;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #666;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<div class="tui-contact-list-card-left">
|
||||
<Avatar
|
||||
useSkeletonAnimation
|
||||
size="30px"
|
||||
size="62rpx"
|
||||
:url="generateAvatar(props.item)"
|
||||
/>
|
||||
<div
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
v-if="!contactSearchingStatus"
|
||||
:class="['tui-contact-list', !isPC && 'tui-contact-list-h5']"
|
||||
>
|
||||
<div v-if="!currentContactListKey">
|
||||
<ul>
|
||||
<li
|
||||
<view v-if="!currentContactListKey">
|
||||
<view class="top-list_box">
|
||||
<view
|
||||
v-for="(contactListObj, key) in contactListMap"
|
||||
:key="key"
|
||||
class="tui-contact-list-item"
|
||||
@@ -14,11 +14,11 @@
|
||||
class="tui-contact-list-item-header"
|
||||
@click="toggleCurrentContactList(key)"
|
||||
>
|
||||
<div class="tui-contact-list-item-header-left">
|
||||
<view class="tui-contact-list-item-header-left">
|
||||
<Icon
|
||||
v-if="contactListObj.icon"
|
||||
:file="contactListObj.icon"
|
||||
size="30px"
|
||||
size="96rpx"
|
||||
/>
|
||||
<span
|
||||
v-if="contactListObj.unreadCount"
|
||||
@@ -26,25 +26,21 @@
|
||||
>
|
||||
{{ contactListObj.unreadCount }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="tui-contact-list-item-header-right">
|
||||
<div>
|
||||
{{
|
||||
TUITranslateService.t(
|
||||
`TUIContact.${contactListObj.title}`
|
||||
)
|
||||
}}
|
||||
</div>
|
||||
<Icon
|
||||
</view>
|
||||
<view class="tui-contact-list-item-header-right">
|
||||
<text>
|
||||
{{ contactListObj.title }}
|
||||
</text>
|
||||
<!-- <Icon
|
||||
:file="currentContactListKey === key ? downSVG : rightSVG"
|
||||
size="20px"
|
||||
/>
|
||||
</div>
|
||||
/> -->
|
||||
</view>
|
||||
</header>
|
||||
</li>
|
||||
</ul>
|
||||
</view>
|
||||
</view>
|
||||
<FriendList @enterConversation="selectFriend" />
|
||||
</div>
|
||||
</view>
|
||||
<template v-else>
|
||||
<li
|
||||
v-for="contactListItem in contactListMap[currentContactListKey]
|
||||
@@ -119,9 +115,10 @@
|
||||
|
||||
import downSVG from '../../../assets/icon/down-icon.svg'
|
||||
import rightSVG from '../../../assets/icon/right-icon.svg'
|
||||
import newContactsSVG from '../../../assets/icon/new-contacts.svg'
|
||||
import groupSVG from '../../../assets/icon/groups.svg'
|
||||
import blackListSVG from '../../../assets/icon/black-list.svg'
|
||||
import newContactsSVG from '../../../assets/icon/friend-request-icon.svg'
|
||||
import groupSVG from '../../../assets/icon/my-group-chat.svg'
|
||||
import blackListSVG from '../../../assets/icon/blacklist-icon.svg'
|
||||
import addFrienIcon from '../../../assets/icon/add-frien-icon.svg'
|
||||
|
||||
import type {
|
||||
IContactList,
|
||||
@@ -143,10 +140,16 @@
|
||||
friendApplicationList: {
|
||||
icon: newContactsSVG,
|
||||
key: 'friendApplicationList',
|
||||
title: '新的联系人',
|
||||
title: '好友请求',
|
||||
list: [] as FriendApplication[],
|
||||
unreadCount: 0
|
||||
},
|
||||
currentContactSearchingStatus: {
|
||||
icon: addFrienIcon,
|
||||
key: 'currentContactSearchingStatus',
|
||||
title: '添加好友',
|
||||
list: [] as IGroupModel[]
|
||||
},
|
||||
groupList: {
|
||||
icon: groupSVG,
|
||||
key: 'groupList',
|
||||
@@ -233,6 +236,10 @@
|
||||
})
|
||||
|
||||
function toggleCurrentContactList(key: keyof IContactList) {
|
||||
if (key === 'currentContactSearchingStatus') {
|
||||
TUIStore.update(StoreName.CUSTOM, key, true)
|
||||
return
|
||||
}
|
||||
if (currentContactListKey.value === key) {
|
||||
currentContactListKey.value = ''
|
||||
currentContactInfo.value = {} as IContactInfoType
|
||||
@@ -465,4 +472,31 @@
|
||||
height: 100% !important;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.tui-contact-search-list-title {
|
||||
padding: 16rpx 0;
|
||||
}
|
||||
|
||||
.tui-contact-list-item-header {
|
||||
&::after {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
.top-list_box {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 32rpx 50rpx;
|
||||
.tui-contact-list-item {
|
||||
.tui-contact-list-item-header {
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
&:active {
|
||||
background: none !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -3,175 +3,179 @@
|
||||
<div
|
||||
:class="[
|
||||
'tui-contact-search-main',
|
||||
!isPC && 'tui-contact-search-h5-main',
|
||||
!isPC && 'tui-contact-search-h5-main'
|
||||
]"
|
||||
>
|
||||
<input
|
||||
v-model="searchValue"
|
||||
class="tui-contact-search-main-input"
|
||||
type="text"
|
||||
:placeholder="searchingPlaceholder"
|
||||
placeholder="请输入用户 / 群组搜索"
|
||||
enterkeyhint="search"
|
||||
@keyup.enter="search"
|
||||
@blur="search"
|
||||
@confirm="search"
|
||||
>
|
||||
<div
|
||||
class="tui-contact-search-main-cancel"
|
||||
@click="cancel"
|
||||
>
|
||||
{{ TUITranslateService.t("取消") }}
|
||||
/>
|
||||
<div class="tui-contact-search-main-cancel" @click="cancel">
|
||||
{{ TUITranslateService.t('取消') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref, watch } from '../../../adapter-vue';
|
||||
import {
|
||||
TUITranslateService,
|
||||
TUIStore,
|
||||
StoreName,
|
||||
} from '@tencentcloud/chat-uikit-engine-lite';
|
||||
import TUICore, { TUIConstants } from '@tencentcloud/tui-core-lite';
|
||||
import { TUIGlobal } from '@tencentcloud/universal-api';
|
||||
import { isPC } from '../../../utils/env';
|
||||
import { IContactSearchResult } from '../../../interface';
|
||||
import { onMounted, ref, watch } from '../../../adapter-vue'
|
||||
import {
|
||||
TUITranslateService,
|
||||
TUIStore,
|
||||
StoreName
|
||||
} from '@tencentcloud/chat-uikit-engine-lite'
|
||||
import TUICore, { TUIConstants } from '@tencentcloud/tui-core-lite'
|
||||
import { TUIGlobal } from '@tencentcloud/universal-api'
|
||||
import { isPC } from '../../../utils/env'
|
||||
import { IContactSearchResult } from '../../../interface'
|
||||
|
||||
const searchingPlaceholder = TUITranslateService.t('TUIContact.输入ID');
|
||||
const searchValue = ref<string>('');
|
||||
const searchResult = ref<IContactSearchResult>({
|
||||
user: {
|
||||
label: '联系人',
|
||||
list: [],
|
||||
},
|
||||
group: {
|
||||
label: '群聊',
|
||||
list: [],
|
||||
},
|
||||
});
|
||||
|
||||
const cancel = () => {
|
||||
TUIStore.update(
|
||||
StoreName.CUSTOM,
|
||||
'currentContactSearchingStatus',
|
||||
false,
|
||||
);
|
||||
};
|
||||
|
||||
const search = async () => {
|
||||
if (!searchValue.value) {
|
||||
return;
|
||||
}
|
||||
TUICore.callService({
|
||||
serviceName: TUIConstants.TUISearch.SERVICE.NAME,
|
||||
method: TUIConstants.TUISearch.SERVICE.METHOD.SEARCH_USER,
|
||||
params: {
|
||||
userID: searchValue.value,
|
||||
const searchValue = ref<string>('')
|
||||
const searchResult = ref<IContactSearchResult>({
|
||||
user: {
|
||||
label: '联系人',
|
||||
list: []
|
||||
},
|
||||
group: {
|
||||
label: '群聊',
|
||||
list: []
|
||||
}
|
||||
})
|
||||
.then((res: any) => {
|
||||
searchResult.value.user.list = res.data;
|
||||
})
|
||||
.catch((error: any) => {
|
||||
searchResult.value.user.list = [];
|
||||
console.warn('search user error', error);
|
||||
});
|
||||
TUICore.callService({
|
||||
serviceName: TUIConstants.TUISearch.SERVICE.NAME,
|
||||
method: TUIConstants.TUISearch.SERVICE.METHOD.SEARCH_GROUP,
|
||||
params: {
|
||||
groupID: searchValue.value,
|
||||
},
|
||||
})
|
||||
.then((res: any) => {
|
||||
searchResult.value.group.list = [res.data.group];
|
||||
})
|
||||
.catch((error: any) => {
|
||||
searchResult.value.group.list = [];
|
||||
console.warn('search group error', error);
|
||||
});
|
||||
};
|
||||
watch(
|
||||
() => searchResult.value,
|
||||
() => {
|
||||
|
||||
const cancel = () => {
|
||||
TUIStore.update(
|
||||
StoreName.CUSTOM,
|
||||
'currentContactSearchResult',
|
||||
searchResult.value,
|
||||
);
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
immediate: true,
|
||||
},
|
||||
);
|
||||
'currentContactSearchingStatus',
|
||||
false
|
||||
)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
searchValue.value = '';
|
||||
searchResult.value.user.list = [];
|
||||
searchResult.value.group.list = [];
|
||||
});
|
||||
const search = async () => {
|
||||
if (!searchValue.value) {
|
||||
return
|
||||
}
|
||||
TUICore.callService({
|
||||
serviceName: TUIConstants.TUISearch.SERVICE.NAME,
|
||||
method: TUIConstants.TUISearch.SERVICE.METHOD.SEARCH_USER,
|
||||
params: {
|
||||
userID: searchValue.value
|
||||
}
|
||||
})
|
||||
.then((res: any) => {
|
||||
searchResult.value.user.list = res.data
|
||||
})
|
||||
.catch((error: any) => {
|
||||
searchResult.value.user.list = []
|
||||
console.warn('search user error', error)
|
||||
})
|
||||
TUICore.callService({
|
||||
serviceName: TUIConstants.TUISearch.SERVICE.NAME,
|
||||
method: TUIConstants.TUISearch.SERVICE.METHOD.SEARCH_GROUP,
|
||||
params: {
|
||||
groupID: searchValue.value
|
||||
}
|
||||
})
|
||||
.then((res: any) => {
|
||||
searchResult.value.group.list = [res.data.group]
|
||||
})
|
||||
.catch((error: any) => {
|
||||
searchResult.value.group.list = []
|
||||
console.warn('search group error', error)
|
||||
})
|
||||
}
|
||||
watch(
|
||||
() => searchResult.value,
|
||||
() => {
|
||||
TUIStore.update(
|
||||
StoreName.CUSTOM,
|
||||
'currentContactSearchResult',
|
||||
searchResult.value
|
||||
)
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
immediate: true
|
||||
}
|
||||
)
|
||||
|
||||
TUIGlobal.updateContactSearch = search;
|
||||
TUIGlobal.closeSearching = () => {
|
||||
searchValue.value = '';
|
||||
searchResult.value.user.list = [];
|
||||
searchResult.value.group.list = [];
|
||||
};
|
||||
onMounted(() => {
|
||||
searchValue.value = ''
|
||||
searchResult.value.user.list = []
|
||||
searchResult.value.group.list = []
|
||||
})
|
||||
|
||||
TUIGlobal.updateContactSearch = search
|
||||
TUIGlobal.closeSearching = () => {
|
||||
searchValue.value = ''
|
||||
searchResult.value.user.list = []
|
||||
searchResult.value.group.list = []
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.tui-contact-search {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 1;
|
||||
padding: 12px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
background: #fff;
|
||||
border-bottom: 1px solid #f4f5f9;
|
||||
flex-direction: column;
|
||||
|
||||
&-main {
|
||||
width: 100%;
|
||||
height: 30px;
|
||||
.tui-contact-search {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 1;
|
||||
padding: 12px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
background: #fff;
|
||||
border-bottom: 1px solid #f4f5f9;
|
||||
flex-direction: column;
|
||||
|
||||
&-main {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex: 1;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
|
||||
&-input {
|
||||
flex: 1;
|
||||
font-size: 14px;
|
||||
border-radius: 5px;
|
||||
padding: 7px;
|
||||
border: 1px solid #ddd;
|
||||
}
|
||||
|
||||
&-input:focus {
|
||||
outline: none;
|
||||
border: 1px solid #006eff;
|
||||
}
|
||||
|
||||
&-cancel {
|
||||
padding-left: 10px;
|
||||
user-select: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
&-h5 {
|
||||
&-header {
|
||||
&-main {
|
||||
width: 100%;
|
||||
height: 30px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&-main {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex: 1;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
|
||||
&-input {
|
||||
flex: 1;
|
||||
font-size: 14px;
|
||||
border-radius: 5px;
|
||||
padding: 7px;
|
||||
border: 1px solid #ddd;
|
||||
}
|
||||
|
||||
&-input:focus {
|
||||
outline: none;
|
||||
border: 1px solid #006eff;
|
||||
}
|
||||
|
||||
&-cancel {
|
||||
padding-left: 10px;
|
||||
user-select: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
&-h5 {
|
||||
&-header {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tui-contact-search-main-input {
|
||||
border-radius: 64rpx;
|
||||
height: 64rpx;
|
||||
padding: 0 32rpx;
|
||||
|
||||
background: #f4f4f4;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -4,13 +4,7 @@
|
||||
v-else-if="isShowContactList"
|
||||
:class="['tui-contact', !isPC && 'tui-contact-h5']"
|
||||
>
|
||||
<Navigation
|
||||
:title="
|
||||
currentContactKey
|
||||
? contactInfoTitle
|
||||
: '通讯录'
|
||||
"
|
||||
>
|
||||
<Navigation :title="currentContactKey ? contactInfoTitle : '通讯录'">
|
||||
<template #left>
|
||||
<div v-show="currentContactKey" @click="resetContactType">
|
||||
<Icon :file="backSVG" />
|
||||
@@ -67,7 +61,7 @@
|
||||
import ContactList from './contact-list/index.vue'
|
||||
import ContactInfo from './contact-info/index.vue'
|
||||
|
||||
import addCircle from '../../assets/icon/add-circle.svg'
|
||||
import addCircle from '../../assets/icon/add-friend.svg'
|
||||
import backSVG from '../../assets/icon/back.svg'
|
||||
import { CONTACT_INFO_TITLE } from '../../constant'
|
||||
|
||||
|
||||
@@ -379,4 +379,12 @@
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
.tui-conversation-item {
|
||||
&::after {
|
||||
display: none !important;
|
||||
}
|
||||
&:active {
|
||||
background: #f4f4f4 !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -53,19 +53,19 @@
|
||||
<!-- <Icon :file="rightIcon" /> -->
|
||||
</span>
|
||||
</div>
|
||||
<article v-if="!isPC" class="group-h5-list-item-introduction">
|
||||
<!-- <article v-if="!isPC" class="group-h5-list-item-introduction">
|
||||
<label class="introduction-name">
|
||||
{{ groupTypeDetail.label }}:
|
||||
</label>
|
||||
<span class="introduction-detail">
|
||||
{{ groupTypeDetail.detail }}
|
||||
</span>
|
||||
<!-- <a :href="documentLink.product.url" target="view_window">
|
||||
<a :href="documentLink.product.url" target="view_window">
|
||||
{{
|
||||
TUITranslateService.t(`TUIGroup.${groupTypeDetail.src}`)
|
||||
}}
|
||||
</a> -->
|
||||
</article>
|
||||
</a>
|
||||
</article> -->
|
||||
</li>
|
||||
</ul>
|
||||
</ul>
|
||||
@@ -370,6 +370,9 @@
|
||||
</script>
|
||||
<style lang="scss" scoped src="./style/index.scss"></style>
|
||||
<style lang="scss" scoped>
|
||||
.group-list {
|
||||
margin: 0 !important;
|
||||
}
|
||||
.popup-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
'tui-search',
|
||||
!isPC && 'tui-search-h5',
|
||||
`tui-search-main-${currentSearchType}`,
|
||||
isFullScreen && 'tui-search-h5-full-screen',
|
||||
isFullScreen && 'tui-search-h5-full-screen'
|
||||
]"
|
||||
>
|
||||
<div
|
||||
@@ -16,7 +16,7 @@
|
||||
<div
|
||||
:class="[
|
||||
'tui-search-global-header',
|
||||
!isPC && 'tui-search-h5-global-header',
|
||||
!isPC && 'tui-search-h5-global-header'
|
||||
]"
|
||||
>
|
||||
<SearchInput
|
||||
@@ -40,12 +40,13 @@
|
||||
</div>
|
||||
<div
|
||||
v-else-if="
|
||||
(currentSearchType === 'conversation' && isShowInConversationSearch) ||
|
||||
isUniFrameWork
|
||||
(currentSearchType === 'conversation' &&
|
||||
isShowInConversationSearch) ||
|
||||
isUniFrameWork
|
||||
"
|
||||
:class="[
|
||||
'tui-search-conversation',
|
||||
!isPC && 'tui-search-h5-conversation',
|
||||
!isPC && 'tui-search-h5-conversation'
|
||||
]"
|
||||
>
|
||||
<SearchContainer
|
||||
@@ -55,9 +56,7 @@
|
||||
@closeInConversationSearch="closeInConversationSearch"
|
||||
>
|
||||
<template #input>
|
||||
<SearchInput
|
||||
:searchType="currentSearchType"
|
||||
/>
|
||||
<SearchInput :searchType="currentSearchType" />
|
||||
</template>
|
||||
<template #result>
|
||||
<SearchResult
|
||||
@@ -70,149 +69,176 @@
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import {
|
||||
ref,
|
||||
onMounted,
|
||||
computed,
|
||||
withDefaults,
|
||||
onUnmounted,
|
||||
watch,
|
||||
} from '../../adapter-vue';
|
||||
import { TUIStore, StoreName } from '@tencentcloud/chat-uikit-engine-lite';
|
||||
import { TUIGlobal, outsideClick } from '@tencentcloud/universal-api';
|
||||
import SearchInput from './search-input/index.vue';
|
||||
import SearchContainer from './search-container/index.vue';
|
||||
import SearchResult from './search-result/index.vue';
|
||||
import { searchMessageTypeDefault } from './search-type-list';
|
||||
import { searchMessageTimeDefault } from './search-time-list';
|
||||
import { isPC, isUniFrameWork } from '../../utils/env';
|
||||
import { ISearchingStatus, SEARCH_TYPE } from './type';
|
||||
import {
|
||||
ref,
|
||||
onMounted,
|
||||
computed,
|
||||
withDefaults,
|
||||
onUnmounted,
|
||||
watch
|
||||
} from '../../adapter-vue'
|
||||
import {
|
||||
TUIStore,
|
||||
StoreName
|
||||
} from '@tencentcloud/chat-uikit-engine-lite'
|
||||
import { TUIGlobal, outsideClick } from '@tencentcloud/universal-api'
|
||||
import SearchInput from './search-input/index.vue'
|
||||
import SearchContainer from './search-container/index.vue'
|
||||
import SearchResult from './search-result/index.vue'
|
||||
import { searchMessageTypeDefault } from './search-type-list'
|
||||
import { searchMessageTimeDefault } from './search-time-list'
|
||||
import { isPC, isUniFrameWork } from '../../utils/env'
|
||||
import { ISearchingStatus, SEARCH_TYPE } from './type'
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
searchType?: SEARCH_TYPE;
|
||||
}>(),
|
||||
{
|
||||
searchType: () => {
|
||||
return 'global';
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const globalSearchRef = ref<HTMLElement | null>();
|
||||
const currentConversationID = ref<string>('');
|
||||
const searchingStatus = ref<boolean>(false);
|
||||
const currentSearchType = ref<SEARCH_TYPE>('global');
|
||||
const isShowSearch = ref<boolean>(false);
|
||||
// Whether to display the search in the chat
|
||||
const isShowInConversationSearch = ref<boolean>(isUniFrameWork);
|
||||
// Whether to search in full screen - Search in full screen when the mobile terminal is searching
|
||||
const isFullScreen = computed(
|
||||
() =>
|
||||
!isPC
|
||||
&& ((currentSearchType.value === 'global' && searchingStatus.value)
|
||||
|| (currentSearchType.value === 'conversation' && isShowInConversationSearch.value)),
|
||||
);
|
||||
|
||||
watch(() => [currentConversationID.value, isShowInConversationSearch.value], (data) => {
|
||||
if (isUniFrameWork && data[0]) {
|
||||
currentSearchType.value = 'conversation';
|
||||
} else {
|
||||
currentSearchType.value = props.searchType;
|
||||
}
|
||||
isShowSearch.value = currentSearchType.value === 'global'
|
||||
|| ((currentSearchType.value === 'conversation' || (!currentSearchType.value && isUniFrameWork))
|
||||
&& !!data[1]);
|
||||
}, { immediate: true, deep: true });
|
||||
|
||||
const initSearchValue = (searchType: SEARCH_TYPE) => {
|
||||
TUIStore.update(StoreName.SEARCH, 'currentSearchInputValue', {
|
||||
value: '',
|
||||
searchType: searchType,
|
||||
});
|
||||
TUIStore.update(StoreName.SEARCH, 'currentSearchMessageType', {
|
||||
value: searchMessageTypeDefault[searchType],
|
||||
searchType: searchType,
|
||||
});
|
||||
TUIStore.update(StoreName.SEARCH, 'currentSearchMessageTime', {
|
||||
value: searchMessageTimeDefault,
|
||||
searchType: searchType,
|
||||
});
|
||||
TUIStore.update(StoreName.SEARCH, 'currentSearchingStatus', {
|
||||
isSearching: false,
|
||||
searchType: currentSearchType.value,
|
||||
});
|
||||
};
|
||||
|
||||
function onCurrentConversationIDUpdate(conversationID: string) {
|
||||
if (!isUniFrameWork && currentConversationID.value !== conversationID) {
|
||||
// PC side single page switch session, close search
|
||||
closeInConversationSearch();
|
||||
}
|
||||
if (!conversationID && isUniFrameWork) {
|
||||
initSearchValue('global');
|
||||
}
|
||||
currentConversationID.value = conversationID;
|
||||
}
|
||||
function onCurrentSearchingStatusChange(value: ISearchingStatus) {
|
||||
if (value?.searchType === currentSearchType.value) {
|
||||
searchingStatus.value = value?.isSearching;
|
||||
// global search ui bind on click outside close
|
||||
if (value?.searchType === 'global' && globalSearchRef.value) {
|
||||
if (isPC && value.isSearching) {
|
||||
outsideClick.listen({
|
||||
domRefs: globalSearchRef.value,
|
||||
handler: closeGlobalSearch,
|
||||
});
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
searchType?: SEARCH_TYPE
|
||||
}>(),
|
||||
{
|
||||
searchType: () => {
|
||||
return 'global'
|
||||
}
|
||||
}
|
||||
if (value?.searchType === 'global' && isUniFrameWork) {
|
||||
// hide tab bar in uni-app when global searching
|
||||
value.isSearching ? TUIGlobal?.hideTabBar()?.catch(() => { /* ignore */ }) : TUIGlobal?.showTabBar()?.catch(() => { /* ignore */ });
|
||||
)
|
||||
|
||||
const globalSearchRef = ref<HTMLElement | null>()
|
||||
const currentConversationID = ref<string>('')
|
||||
const searchingStatus = ref<boolean>(false)
|
||||
const currentSearchType = ref<SEARCH_TYPE>('global')
|
||||
const isShowSearch = ref<boolean>(false)
|
||||
// Whether to display the search in the chat
|
||||
const isShowInConversationSearch = ref<boolean>(isUniFrameWork)
|
||||
// Whether to search in full screen - Search in full screen when the mobile terminal is searching
|
||||
const isFullScreen = computed(
|
||||
() =>
|
||||
!isPC &&
|
||||
((currentSearchType.value === 'global' && searchingStatus.value) ||
|
||||
(currentSearchType.value === 'conversation' &&
|
||||
isShowInConversationSearch.value))
|
||||
)
|
||||
|
||||
watch(
|
||||
() => [currentConversationID.value, isShowInConversationSearch.value],
|
||||
data => {
|
||||
if (isUniFrameWork && data[0]) {
|
||||
currentSearchType.value = 'conversation'
|
||||
} else {
|
||||
currentSearchType.value = props.searchType
|
||||
}
|
||||
isShowSearch.value =
|
||||
currentSearchType.value === 'global' ||
|
||||
((currentSearchType.value === 'conversation' ||
|
||||
(!currentSearchType.value && isUniFrameWork)) &&
|
||||
!!data[1])
|
||||
},
|
||||
{ immediate: true, deep: true }
|
||||
)
|
||||
|
||||
const initSearchValue = (searchType: SEARCH_TYPE) => {
|
||||
TUIStore.update(StoreName.SEARCH, 'currentSearchInputValue', {
|
||||
value: '',
|
||||
searchType: searchType
|
||||
})
|
||||
TUIStore.update(StoreName.SEARCH, 'currentSearchMessageType', {
|
||||
value: searchMessageTypeDefault[searchType],
|
||||
searchType: searchType
|
||||
})
|
||||
TUIStore.update(StoreName.SEARCH, 'currentSearchMessageTime', {
|
||||
value: searchMessageTimeDefault,
|
||||
searchType: searchType
|
||||
})
|
||||
TUIStore.update(StoreName.SEARCH, 'currentSearchingStatus', {
|
||||
isSearching: false,
|
||||
searchType: currentSearchType.value
|
||||
})
|
||||
}
|
||||
|
||||
function onCurrentConversationIDUpdate(conversationID: string) {
|
||||
if (
|
||||
!isUniFrameWork &&
|
||||
currentConversationID.value !== conversationID
|
||||
) {
|
||||
// PC side single page switch session, close search
|
||||
closeInConversationSearch()
|
||||
}
|
||||
if (!conversationID && isUniFrameWork) {
|
||||
initSearchValue('global')
|
||||
}
|
||||
currentConversationID.value = conversationID
|
||||
}
|
||||
function onCurrentSearchingStatusChange(value: ISearchingStatus) {
|
||||
if (value?.searchType === currentSearchType.value) {
|
||||
searchingStatus.value = value?.isSearching
|
||||
// global search ui bind on click outside close
|
||||
if (value?.searchType === 'global' && globalSearchRef.value) {
|
||||
if (isPC && value.isSearching) {
|
||||
outsideClick.listen({
|
||||
domRefs: globalSearchRef.value,
|
||||
handler: closeGlobalSearch
|
||||
})
|
||||
}
|
||||
}
|
||||
if (value?.searchType === 'global' && isUniFrameWork) {
|
||||
// hide tab bar in uni-app when global searching
|
||||
value.isSearching
|
||||
? TUIGlobal?.hideTabBar()?.catch(() => {
|
||||
/* ignore */
|
||||
})
|
||||
: TUIGlobal?.showTabBar()?.catch(() => {
|
||||
/* ignore */
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function onIsShowInConversationSearchChange(value: boolean) {
|
||||
isShowInConversationSearch.value = value ? true : false;
|
||||
isShowInConversationSearch.value && initSearchValue(currentSearchType.value);
|
||||
}
|
||||
function onIsShowInConversationSearchChange(value: boolean) {
|
||||
isShowInConversationSearch.value = value ? true : false
|
||||
isShowInConversationSearch.value &&
|
||||
initSearchValue(currentSearchType.value)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// init with default value
|
||||
['global', 'conversation'].forEach((type: string) => {
|
||||
initSearchValue(type as SEARCH_TYPE);
|
||||
});
|
||||
// watch store change
|
||||
TUIStore.watch(StoreName.CONV, {
|
||||
currentConversationID: onCurrentConversationIDUpdate,
|
||||
});
|
||||
TUIStore.watch(StoreName.SEARCH, {
|
||||
currentSearchingStatus: onCurrentSearchingStatusChange,
|
||||
isShowInConversationSearch: onIsShowInConversationSearchChange,
|
||||
});
|
||||
});
|
||||
onMounted(() => {
|
||||
// init with default value
|
||||
;['global', 'conversation'].forEach((type: string) => {
|
||||
initSearchValue(type as SEARCH_TYPE)
|
||||
})
|
||||
// watch store change
|
||||
TUIStore.watch(StoreName.CONV, {
|
||||
currentConversationID: onCurrentConversationIDUpdate
|
||||
})
|
||||
TUIStore.watch(StoreName.SEARCH, {
|
||||
currentSearchingStatus: onCurrentSearchingStatusChange,
|
||||
isShowInConversationSearch: onIsShowInConversationSearchChange
|
||||
})
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
// unwatch store change
|
||||
TUIStore.unwatch(StoreName.CONV, {
|
||||
currentConversationID: onCurrentConversationIDUpdate,
|
||||
});
|
||||
TUIStore.unwatch(StoreName.SEARCH, {
|
||||
currentSearchingStatus: onCurrentSearchingStatusChange,
|
||||
isShowInConversationSearch: onIsShowInConversationSearchChange,
|
||||
});
|
||||
});
|
||||
onUnmounted(() => {
|
||||
// unwatch store change
|
||||
TUIStore.unwatch(StoreName.CONV, {
|
||||
currentConversationID: onCurrentConversationIDUpdate
|
||||
})
|
||||
TUIStore.unwatch(StoreName.SEARCH, {
|
||||
currentSearchingStatus: onCurrentSearchingStatusChange,
|
||||
isShowInConversationSearch: onIsShowInConversationSearchChange
|
||||
})
|
||||
})
|
||||
|
||||
function closeGlobalSearch() {
|
||||
TUIStore.update(StoreName.SEARCH, 'currentSearchingStatus', {
|
||||
isSearching: false,
|
||||
searchType: currentSearchType.value,
|
||||
});
|
||||
}
|
||||
function closeGlobalSearch() {
|
||||
TUIStore.update(StoreName.SEARCH, 'currentSearchingStatus', {
|
||||
isSearching: false,
|
||||
searchType: currentSearchType.value
|
||||
})
|
||||
}
|
||||
|
||||
function closeInConversationSearch() {
|
||||
TUIStore.update(StoreName.SEARCH, 'isShowInConversationSearch', false);
|
||||
}
|
||||
function closeInConversationSearch() {
|
||||
TUIStore.update(StoreName.SEARCH, 'isShowInConversationSearch', false)
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped src="./style/index.scss"></style>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.tui-search-global {
|
||||
// background: red;
|
||||
padding: 0 !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -12,10 +12,7 @@
|
||||
v-if="!searchingStatus && props.searchType === 'global'"
|
||||
:class="['tui-search-input', !isPC && 'tui-search-input-h5']"
|
||||
>
|
||||
<div
|
||||
class="tui-search-input-place"
|
||||
@click="onSearchInputClick"
|
||||
>
|
||||
<div class="tui-search-input-place" @click="onSearchInputClick">
|
||||
<Icon
|
||||
class="icon"
|
||||
:file="searchIcon"
|
||||
@@ -47,200 +44,197 @@
|
||||
@blur="onBlur"
|
||||
@keyup.enter="search"
|
||||
@confirm="search"
|
||||
>
|
||||
/>
|
||||
<div
|
||||
v-if="searchingStatus"
|
||||
class="tui-search-input-right"
|
||||
@click="endSearching"
|
||||
>
|
||||
<Icon
|
||||
class="icon"
|
||||
:file="closeIcon"
|
||||
width="14px"
|
||||
height="14px"
|
||||
/>
|
||||
<Icon class="icon" :file="closeIcon" width="14px" height="14px" />
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="!isPC && searchingStatus && props.searchType === 'global'"
|
||||
:class="[
|
||||
'tui-search-input-cancel',
|
||||
!isPC && 'tui-search-input-h5-cancel',
|
||||
!isPC && 'tui-search-input-h5-cancel'
|
||||
]"
|
||||
@click="endSearching"
|
||||
>
|
||||
{{ TUITranslateService.t("TUISearch.取消") }}
|
||||
{{ TUITranslateService.t('TUISearch.取消') }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { ref, onMounted, onUnmounted } from '../../../adapter-vue';
|
||||
import {
|
||||
TUIStore,
|
||||
StoreName,
|
||||
TUITranslateService,
|
||||
} from '@tencentcloud/chat-uikit-engine-lite';
|
||||
import { TUIGlobal } from '@tencentcloud/universal-api';
|
||||
import Icon from '../../common/Icon.vue';
|
||||
import searchIcon from '../../../assets/icon/search.svg';
|
||||
import closeIcon from '../../../assets/icon/input-close.svg';
|
||||
import { isPC } from '../../../utils/env';
|
||||
import { ISearchInputValue, ISearchingStatus } from '../type';
|
||||
const props = defineProps({
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: () => TUITranslateService.t('TUISearch.搜索'),
|
||||
},
|
||||
searchType: {
|
||||
type: String,
|
||||
default: 'global', // "global" / "conversation"
|
||||
validator(value: string) {
|
||||
return ['global', 'conversation'].includes(value);
|
||||
import { ref, onMounted, onUnmounted } from '../../../adapter-vue'
|
||||
import {
|
||||
TUIStore,
|
||||
StoreName,
|
||||
TUITranslateService
|
||||
} from '@tencentcloud/chat-uikit-engine-lite'
|
||||
import { TUIGlobal } from '@tencentcloud/universal-api'
|
||||
import Icon from '../../common/Icon.vue'
|
||||
import searchIcon from '../../../assets/icon/search.svg'
|
||||
import closeIcon from '../../../assets/icon/input-close.svg'
|
||||
import { isPC } from '../../../utils/env'
|
||||
import { ISearchInputValue, ISearchingStatus } from '../type'
|
||||
const props = defineProps({
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: () => TUITranslateService.t('TUISearch.搜索')
|
||||
},
|
||||
},
|
||||
});
|
||||
searchType: {
|
||||
type: String,
|
||||
default: 'global', // "global" / "conversation"
|
||||
validator(value: string) {
|
||||
return ['global', 'conversation'].includes(value)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const searchValueModel = ref<string>('');
|
||||
const currentSearchInputValue = ref<string>('');
|
||||
const searchingStatus = ref<boolean>(false);
|
||||
const searchValueModel = ref<string>('')
|
||||
const currentSearchInputValue = ref<string>('')
|
||||
const searchingStatus = ref<boolean>(false)
|
||||
|
||||
function onCurrentSearchInputValueChange(obj: ISearchInputValue) {
|
||||
if (obj?.searchType === props?.searchType) {
|
||||
currentSearchInputValue.value = obj?.value;
|
||||
searchValueModel.value = obj?.value;
|
||||
function onCurrentSearchInputValueChange(obj: ISearchInputValue) {
|
||||
if (obj?.searchType === props?.searchType) {
|
||||
currentSearchInputValue.value = obj?.value
|
||||
searchValueModel.value = obj?.value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function onCurrentSearchingStatusChange(obj: ISearchingStatus) {
|
||||
if (obj?.searchType === props?.searchType) {
|
||||
searchingStatus.value = obj?.isSearching;
|
||||
function onCurrentSearchingStatusChange(obj: ISearchingStatus) {
|
||||
if (obj?.searchType === props?.searchType) {
|
||||
searchingStatus.value = obj?.isSearching
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
TUIStore.watch(StoreName.SEARCH, {
|
||||
currentSearchInputValue: onCurrentSearchInputValueChange,
|
||||
currentSearchingStatus: onCurrentSearchingStatusChange,
|
||||
});
|
||||
});
|
||||
onMounted(() => {
|
||||
TUIStore.watch(StoreName.SEARCH, {
|
||||
currentSearchInputValue: onCurrentSearchInputValueChange,
|
||||
currentSearchingStatus: onCurrentSearchingStatusChange
|
||||
})
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
TUIStore.unwatch(StoreName.SEARCH, {
|
||||
currentSearchInputValue: onCurrentSearchInputValueChange,
|
||||
currentSearchingStatus: onCurrentSearchingStatusChange,
|
||||
});
|
||||
});
|
||||
onUnmounted(() => {
|
||||
TUIStore.unwatch(StoreName.SEARCH, {
|
||||
currentSearchInputValue: onCurrentSearchInputValueChange,
|
||||
currentSearchingStatus: onCurrentSearchingStatusChange
|
||||
})
|
||||
})
|
||||
|
||||
const search = () => {
|
||||
// Avoid duplicate searches
|
||||
if (searchValueModel.value === currentSearchInputValue.value) {
|
||||
return;
|
||||
const search = () => {
|
||||
// Avoid duplicate searches
|
||||
if (searchValueModel.value === currentSearchInputValue.value) {
|
||||
return
|
||||
}
|
||||
TUIStore.update(StoreName.SEARCH, 'currentSearchInputValue', {
|
||||
value: searchValueModel.value,
|
||||
searchType: props.searchType
|
||||
})
|
||||
}
|
||||
TUIStore.update(StoreName.SEARCH, 'currentSearchInputValue', {
|
||||
value: searchValueModel.value,
|
||||
searchType: props.searchType,
|
||||
});
|
||||
};
|
||||
|
||||
const endSearching = () => {
|
||||
searchingStatus.value = false;
|
||||
TUIStore.update(StoreName.SEARCH, 'currentSearchingStatus', {
|
||||
isSearching: false,
|
||||
searchType: props.searchType,
|
||||
});
|
||||
TUIStore.update(StoreName.SEARCH, 'currentSearchInputValue', {
|
||||
value: '',
|
||||
searchType: props.searchType,
|
||||
});
|
||||
};
|
||||
const endSearching = () => {
|
||||
searchingStatus.value = false
|
||||
TUIStore.update(StoreName.SEARCH, 'currentSearchingStatus', {
|
||||
isSearching: false,
|
||||
searchType: props.searchType
|
||||
})
|
||||
TUIStore.update(StoreName.SEARCH, 'currentSearchInputValue', {
|
||||
value: '',
|
||||
searchType: props.searchType
|
||||
})
|
||||
}
|
||||
|
||||
const onSearchInputClick = () => {
|
||||
TUIStore.update(StoreName.SEARCH, 'currentSearchingStatus', {
|
||||
isSearching: true,
|
||||
searchType: props.searchType,
|
||||
});
|
||||
};
|
||||
const onSearchInputClick = () => {
|
||||
TUIStore.update(StoreName.SEARCH, 'currentSearchingStatus', {
|
||||
isSearching: true,
|
||||
searchType: props.searchType
|
||||
})
|
||||
}
|
||||
|
||||
const onBlur = () => {
|
||||
TUIGlobal?.hideKeyboard?.();
|
||||
};
|
||||
const onBlur = () => {
|
||||
TUIGlobal?.hideKeyboard?.()
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.tui-search-input-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
box-sizing: border-box;
|
||||
border-radius: 8px;
|
||||
padding: 0 2px;
|
||||
|
||||
&-global {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.tui-search-input {
|
||||
flex: 1;
|
||||
.tui-search-input-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
margin: 10px;
|
||||
background: #FEFEFE;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 28px;
|
||||
box-sizing: border-box;
|
||||
border-radius: 8px;
|
||||
|
||||
&-main {
|
||||
padding: 0 2px;
|
||||
&-global {
|
||||
flex: 1;
|
||||
background: transparent;
|
||||
border: none;
|
||||
caret-color: #007aff;
|
||||
font-size: 14px;
|
||||
|
||||
&:focus {
|
||||
border: none;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
&::placeholder {
|
||||
color: #666;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
&-left,
|
||||
&-right {
|
||||
.tui-search-input {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
padding: 0 7px;
|
||||
flex-direction: row;
|
||||
margin: 0 26rpx;
|
||||
margin-top: 12rpx;
|
||||
background: #F4F4F4;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 64rpx;
|
||||
border-radius: 64rpx;
|
||||
margin-bottom: 12rpx;
|
||||
|
||||
&-main {
|
||||
flex: 1;
|
||||
background: transparent;
|
||||
border: none;
|
||||
caret-color: #007aff;
|
||||
font-size: 14px;
|
||||
|
||||
&:focus {
|
||||
border: none;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
&::placeholder {
|
||||
color: #666;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
&-left,
|
||||
&-right {
|
||||
display: flex;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
padding: 0 7px;
|
||||
}
|
||||
}
|
||||
|
||||
.tui-search-input-place {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
gap: 5px;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
font-family: PingFang SC;
|
||||
font-weight: 400;
|
||||
color: #bbbbbb;
|
||||
padding-left: 32rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.tui-search-input-place {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
gap: 5px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-family: PingFang SC;
|
||||
font-weight: 400;
|
||||
color: #BBBBBB;
|
||||
}
|
||||
}
|
||||
.tui-search-input-container-h5 {
|
||||
.tui-search-input-h5 {
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.tui-search-input-container-h5 {
|
||||
.tui-search-input-h5 {
|
||||
height: 40px;
|
||||
.tui-search-input-cancel {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: #007aff;
|
||||
font-size: 16px;
|
||||
padding: 7px 10px 7px 3px;
|
||||
font-family: 'PingFang SC', sans-serif;
|
||||
}
|
||||
}
|
||||
|
||||
.tui-search-input-cancel {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: #007aff;
|
||||
font-size: 16px;
|
||||
padding: 7px 10px 7px 3px;
|
||||
font-family: "PingFang SC", sans-serif;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
.tui-search {
|
||||
background: #EBF0F6;
|
||||
background: #fff;
|
||||
|
||||
&-main-global {
|
||||
width: 100%;
|
||||
@@ -14,7 +14,7 @@
|
||||
}
|
||||
|
||||
.tui-search-global {
|
||||
padding-bottom: 10px;
|
||||
padding-bottom: 32rpx;
|
||||
&-header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
:style="{
|
||||
width: avatarSize,
|
||||
height: avatarSize,
|
||||
borderRadius: avatarBorderRadius,
|
||||
borderRadius: '80rpx'
|
||||
}"
|
||||
>
|
||||
<template v-if="isUniFrameWork">
|
||||
@@ -29,7 +29,7 @@
|
||||
:src="avatarImageUrl || defaultAvatarUrl"
|
||||
@load="avatarLoadSuccess"
|
||||
@error="avatarLoadFailed"
|
||||
>
|
||||
/>
|
||||
<div
|
||||
v-if="useAvatarSkeletonAnimation && !isImgLoaded"
|
||||
:class="{
|
||||
@@ -42,106 +42,107 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, toRefs } from '../../../adapter-vue';
|
||||
import { isUniFrameWork } from '../../../utils/env';
|
||||
import { ref, toRefs } from '../../../adapter-vue'
|
||||
import { isUniFrameWork } from '../../../utils/env'
|
||||
|
||||
interface IProps {
|
||||
url: string;
|
||||
size?: string;
|
||||
borderRadius?: string;
|
||||
useSkeletonAnimation?: boolean;
|
||||
}
|
||||
|
||||
interface IEmits {
|
||||
(key: 'onLoad', e: Event): void;
|
||||
(key: 'onError', e: Event): void;
|
||||
}
|
||||
|
||||
const defaultAvatarUrl = ref('https://web.sdk.qcloud.com/component/TUIKit/assets/avatar_21.png');
|
||||
const emits = defineEmits<IEmits>();
|
||||
const props = withDefaults(defineProps<IProps>(), {
|
||||
// uniapp vue2 does not support constants in defineProps
|
||||
url: 'https://web.sdk.qcloud.com/component/TUIKit/assets/avatar_21.png',
|
||||
size: '36px',
|
||||
borderRadius: '5px',
|
||||
useSkeletonAnimation: false,
|
||||
});
|
||||
|
||||
const {
|
||||
size: avatarSize,
|
||||
url: avatarImageUrl,
|
||||
borderRadius: avatarBorderRadius,
|
||||
useSkeletonAnimation: useAvatarSkeletonAnimation,
|
||||
} = toRefs(props);
|
||||
|
||||
let reloadAvatarTime = 0;
|
||||
const isImgLoaded = ref<boolean>(false);
|
||||
const loadErrorInUniapp = ref<boolean>(false);
|
||||
|
||||
function avatarLoadSuccess(e: Event) {
|
||||
isImgLoaded.value = true;
|
||||
emits('onLoad', e);
|
||||
}
|
||||
|
||||
function avatarLoadFailed(e: Event) {
|
||||
reloadAvatarTime += 1;
|
||||
if (reloadAvatarTime > 3) {
|
||||
return;
|
||||
interface IProps {
|
||||
url: string
|
||||
size?: string
|
||||
borderRadius?: string
|
||||
useSkeletonAnimation?: boolean
|
||||
}
|
||||
if (isUniFrameWork) {
|
||||
loadErrorInUniapp.value = true;
|
||||
} else {
|
||||
(e.currentTarget as HTMLImageElement).src = defaultAvatarUrl.value;
|
||||
|
||||
interface IEmits {
|
||||
(key: 'onLoad', e: Event): void
|
||||
(key: 'onError', e: Event): void
|
||||
}
|
||||
|
||||
const defaultAvatarUrl = ref(
|
||||
'https://web.sdk.qcloud.com/component/TUIKit/assets/avatar_21.png'
|
||||
)
|
||||
const emits = defineEmits<IEmits>()
|
||||
const props = withDefaults(defineProps<IProps>(), {
|
||||
// uniapp vue2 does not support constants in defineProps
|
||||
url: 'https://web.sdk.qcloud.com/component/TUIKit/assets/avatar_21.png',
|
||||
size: '36px',
|
||||
borderRadius: '5px',
|
||||
useSkeletonAnimation: false
|
||||
})
|
||||
|
||||
const {
|
||||
size: avatarSize,
|
||||
url: avatarImageUrl,
|
||||
borderRadius: avatarBorderRadius,
|
||||
useSkeletonAnimation: useAvatarSkeletonAnimation
|
||||
} = toRefs(props)
|
||||
|
||||
let reloadAvatarTime = 0
|
||||
const isImgLoaded = ref<boolean>(false)
|
||||
const loadErrorInUniapp = ref<boolean>(false)
|
||||
|
||||
function avatarLoadSuccess(e: Event) {
|
||||
isImgLoaded.value = true
|
||||
emits('onLoad', e)
|
||||
}
|
||||
|
||||
function avatarLoadFailed(e: Event) {
|
||||
reloadAvatarTime += 1
|
||||
if (reloadAvatarTime > 3) {
|
||||
return
|
||||
}
|
||||
if (isUniFrameWork) {
|
||||
loadErrorInUniapp.value = true
|
||||
} else {
|
||||
;(e.currentTarget as HTMLImageElement).src = defaultAvatarUrl.value
|
||||
}
|
||||
emits('onError', e)
|
||||
}
|
||||
emits('onError', e);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
:not(not) {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
min-width: 0;
|
||||
}
|
||||
:not(not) {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.avatar-container {
|
||||
position: relative;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
flex: 0 0 auto;
|
||||
.avatar-container {
|
||||
position: relative;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
flex: 0 0 auto;
|
||||
|
||||
.placeholder {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: #ececec;
|
||||
transition:
|
||||
opacity 0.3s,
|
||||
background-color 0.1s ease-out;
|
||||
.placeholder {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: #ececec;
|
||||
transition: opacity 0.3s, background-color 0.1s ease-out;
|
||||
|
||||
&.skeleton-animation {
|
||||
animation: breath 2s linear 0.3s infinite;
|
||||
}
|
||||
&.skeleton-animation {
|
||||
animation: breath 2s linear 0.3s infinite;
|
||||
}
|
||||
|
||||
&.hidden {
|
||||
opacity: 0;
|
||||
&.hidden {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.avatar-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
width: 80rpx !important;
|
||||
height: 80rpx !important;
|
||||
border-radius: 80rpx !important;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes breath {
|
||||
50% {
|
||||
/* stylelint-disable-next-line scss/no-global-function-names */
|
||||
background-color: darken(#ececec, 10%);
|
||||
@keyframes breath {
|
||||
50% {
|
||||
/* stylelint-disable-next-line scss/no-global-function-names */
|
||||
background-color: darken(#ececec, 10%);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -9,10 +9,7 @@
|
||||
<slot name="left" />
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="title"
|
||||
class="tui-navigation-title"
|
||||
>
|
||||
<div v-if="title" class="tui-navigation-title">
|
||||
<h1 class="tui-navigation-title-text">
|
||||
{{ title }}
|
||||
</h1>
|
||||
@@ -25,82 +22,85 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, onMounted } from '../../../adapter-vue';
|
||||
import { isH5 } from '../../../utils/env';
|
||||
import { ref, onMounted } from '../../../adapter-vue'
|
||||
import { isH5 } from '../../../utils/env'
|
||||
|
||||
interface Props {
|
||||
title?: string;
|
||||
customStyle?: string;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
title: '',
|
||||
customStyle: '',
|
||||
});
|
||||
|
||||
const statusBarHeight = ref<number>(0);
|
||||
|
||||
onMounted(() => {
|
||||
if (isH5) {
|
||||
statusBarHeight.value = 0;
|
||||
} else {
|
||||
const sysInfo = uni.getSystemInfoSync();
|
||||
statusBarHeight.value = sysInfo.statusBarHeight;
|
||||
interface Props {
|
||||
title?: string
|
||||
customStyle?: string
|
||||
}
|
||||
});
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
title: '',
|
||||
customStyle: ''
|
||||
})
|
||||
|
||||
const statusBarHeight = ref<number>(0)
|
||||
|
||||
onMounted(() => {
|
||||
if (isH5) {
|
||||
statusBarHeight.value = 0
|
||||
} else {
|
||||
const sysInfo = uni.getSystemInfoSync()
|
||||
statusBarHeight.value = sysInfo.statusBarHeight
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.tui-navigation {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
background: #EBF0F6;
|
||||
min-height: 44px;
|
||||
padding: 0 12px;
|
||||
|
||||
&-left {
|
||||
.tui-navigation {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
gap: 8px;
|
||||
flex: 1;
|
||||
}
|
||||
// background: #ebf0f6;
|
||||
background: #fff;
|
||||
min-height: 44px;
|
||||
padding: 0 12px;
|
||||
border-bottom: 2rpx solid #0000000a;
|
||||
box-sizing: border-box;
|
||||
|
||||
&-title {
|
||||
flex: 10;
|
||||
text-align: center;
|
||||
min-width: 0;
|
||||
&-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
gap: 8px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
&-text {
|
||||
overflow: hidden;
|
||||
word-break: keep-all;
|
||||
text-overflow: ellipsis;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin: 0;
|
||||
&-title {
|
||||
flex: 10;
|
||||
text-align: center;
|
||||
min-width: 0;
|
||||
|
||||
&-text {
|
||||
overflow: hidden;
|
||||
word-break: keep-all;
|
||||
text-overflow: ellipsis;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 8px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
&-back-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
&-back-text {
|
||||
font-size: 16px;
|
||||
color: #007aff;
|
||||
}
|
||||
}
|
||||
|
||||
&-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 8px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
&-back-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
&-back-text {
|
||||
font-size: 16px;
|
||||
color: #007AFF;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
<template>
|
||||
<div
|
||||
class="transfer"
|
||||
:class="[!isPC ? 'transfer-h5' : '', isWeChat ? 'transfer-h5-wechat' : '']"
|
||||
:class="[
|
||||
!isPC ? 'transfer-h5' : '',
|
||||
isWeChat ? 'transfer-h5-wechat' : ''
|
||||
]"
|
||||
>
|
||||
<header
|
||||
v-if="!isPC && transferTitle"
|
||||
class="transfer-header transfer-h5-header"
|
||||
>
|
||||
<div
|
||||
v-if="!props.isHiddenBackIcon"
|
||||
@click="cancel"
|
||||
>
|
||||
<div v-if="!props.isHiddenBackIcon" @click="cancel">
|
||||
<Icon
|
||||
class="icon"
|
||||
:file="backIcon"
|
||||
@@ -29,22 +29,22 @@
|
||||
v-if="isPC && isTransferSearch"
|
||||
type="text"
|
||||
:value="searchValue"
|
||||
:placeholder="TUITranslateService.t('component.请输入userID')"
|
||||
placeholder="搜素"
|
||||
enterkeyhint="search"
|
||||
:class="[isUniFrameWork ? 'left-uniapp-input' : '']"
|
||||
@keyup.enter="handleInput"
|
||||
>
|
||||
/>
|
||||
<!-- not PC triggers blur -->
|
||||
<input
|
||||
v-if="!isPC && isTransferSearch"
|
||||
type="text"
|
||||
:placeholder="TUITranslateService.t('component.请输入userID')"
|
||||
placeholder="搜素"
|
||||
enterkeyhint="search"
|
||||
:value="searchValue"
|
||||
:class="[isUniFrameWork ? 'left-uniapp-input' : '']"
|
||||
@blur="handleInput"
|
||||
@confirm="handleInput"
|
||||
>
|
||||
/>
|
||||
</header>
|
||||
<main class="transfer-left-main">
|
||||
<ul class="transfer-list">
|
||||
@@ -59,13 +59,10 @@
|
||||
:width="'18px'"
|
||||
:height="'18px'"
|
||||
/>
|
||||
<i
|
||||
v-else
|
||||
class="icon-unselected"
|
||||
/>
|
||||
<span class="select-all">{{
|
||||
TUITranslateService.t("component.全选")
|
||||
}}</span>
|
||||
<i v-else class="icon-unselected" />
|
||||
<span class="select-all">
|
||||
{{ TUITranslateService.t('component.全选') }}
|
||||
</span>
|
||||
</li>
|
||||
<li
|
||||
v-for="item in transferList"
|
||||
@@ -82,25 +79,27 @@
|
||||
/>
|
||||
<i
|
||||
v-else
|
||||
:class="[item.isDisabled && 'disabled', 'icon-unselected']"
|
||||
:class="[
|
||||
item.isDisabled && 'disabled',
|
||||
'icon-unselected'
|
||||
]"
|
||||
/>
|
||||
<template v-if="!isTransferCustomItem">
|
||||
<img
|
||||
class="avatar"
|
||||
:src="
|
||||
item.avatar ||
|
||||
'https://web.sdk.qcloud.com/component/TUIKit/assets/avatar_21.png'
|
||||
'https://web.sdk.qcloud.com/component/TUIKit/assets/avatar_21.png'
|
||||
"
|
||||
onerror="this.onerror=null;this.src='https://web.sdk.qcloud.com/component/TUIKit/assets/avatar_21.png'"
|
||||
>
|
||||
/>
|
||||
<span class="name">{{ item.nick || item.userID }}</span>
|
||||
<span v-if="item.isDisabled">({{ TUITranslateService.t("component.已在群中") }})</span>
|
||||
<span v-if="item.isDisabled">
|
||||
({{ TUITranslateService.t('component.已在群中') }})
|
||||
</span>
|
||||
</template>
|
||||
<template v-else>
|
||||
<slot
|
||||
name="left"
|
||||
:data="item"
|
||||
/>
|
||||
<slot name="left" :data="item" />
|
||||
</template>
|
||||
</li>
|
||||
<li
|
||||
@@ -108,29 +107,23 @@
|
||||
class="transfer-list-item more"
|
||||
@click="getMore"
|
||||
>
|
||||
{{ TUITranslateService.t("component.查看更多") }}
|
||||
{{ TUITranslateService.t('component.查看更多') }}
|
||||
</li>
|
||||
</ul>
|
||||
</main>
|
||||
</div>
|
||||
<div class="right">
|
||||
<header
|
||||
v-if="isPC"
|
||||
class="transfer-header"
|
||||
>
|
||||
<header v-if="isPC" class="transfer-header">
|
||||
{{ transferTitle }}
|
||||
</header>
|
||||
<ul
|
||||
v-if="resultShow"
|
||||
class="transfer-list"
|
||||
>
|
||||
<ul v-if="resultShow" class="transfer-list">
|
||||
<p
|
||||
v-if="transferSelectedList.length > 0 && isPC"
|
||||
class="transfer-text"
|
||||
>
|
||||
{{ TUITranslateService.t("component.已选中")
|
||||
{{ TUITranslateService.t('component.已选中')
|
||||
}}{{ transferSelectedList.length
|
||||
}}{{ TUITranslateService.t("component.人") }}
|
||||
}}{{ TUITranslateService.t('component.人') }}
|
||||
</p>
|
||||
<li
|
||||
v-for="(item, index) in transferSelectedList"
|
||||
@@ -143,54 +136,36 @@
|
||||
class="avatar"
|
||||
:src="
|
||||
item.avatar ||
|
||||
'https://web.sdk.qcloud.com/component/TUIKit/assets/avatar_21.png'
|
||||
'https://web.sdk.qcloud.com/component/TUIKit/assets/avatar_21.png'
|
||||
"
|
||||
onerror="this.onerror=null;this.src='https://web.sdk.qcloud.com/component/TUIKit/assets/avatar_21.png'"
|
||||
>
|
||||
<span
|
||||
v-if="isPC"
|
||||
class="name"
|
||||
>{{ item.nick || item.userID }}</span>
|
||||
/>
|
||||
<span v-if="isPC" class="name">
|
||||
{{ item.nick || item.userID }}
|
||||
</span>
|
||||
</template>
|
||||
<template v-else>
|
||||
<slot
|
||||
name="right"
|
||||
:data="item"
|
||||
/>
|
||||
<slot name="right" :data="item" />
|
||||
</template>
|
||||
</aside>
|
||||
<span
|
||||
v-if="isPC"
|
||||
@click="selected(item)"
|
||||
>
|
||||
<Icon
|
||||
:file="cancelIcon"
|
||||
:width="'18px'"
|
||||
:height="'18px'"
|
||||
/>
|
||||
<span v-if="isPC" @click="selected(item)">
|
||||
<Icon :file="cancelIcon" :width="'18px'" :height="'18px'" />
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
<footer class="transfer-right-footer">
|
||||
<button
|
||||
class="btn btn-cancel"
|
||||
@click="cancel"
|
||||
>
|
||||
{{ TUITranslateService.t("component.取消") }}
|
||||
<button class="btn btn-cancel" @click="cancel">
|
||||
{{ TUITranslateService.t('component.取消') }}
|
||||
</button>
|
||||
<button
|
||||
v-if="transferSelectedList.length > 0"
|
||||
class="btn"
|
||||
class="btn btn-confirm"
|
||||
@click="submit"
|
||||
>
|
||||
{{ TUITranslateService.t("component.完成") }}
|
||||
{{ TUITranslateService.t('component.完成') }}
|
||||
</button>
|
||||
<button
|
||||
v-else
|
||||
class="btn btn-no"
|
||||
@click="submit"
|
||||
>
|
||||
{{ TUITranslateService.t("component.完成") }}
|
||||
<button v-else class="btn btn-no" @click="submit">
|
||||
{{ TUITranslateService.t('component.完成') }}
|
||||
</button>
|
||||
</footer>
|
||||
</div>
|
||||
@@ -199,134 +174,175 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, watchEffect, computed } from '../../../adapter-vue';
|
||||
import { TUITranslateService } from '@tencentcloud/chat-uikit-engine-lite';
|
||||
import { ITransferListItem } from '../../../interface';
|
||||
import Icon from '../Icon.vue';
|
||||
import selectedIcon from '../../../assets/icon/selected.svg';
|
||||
import backIcon from '../../../assets/icon/back.svg';
|
||||
import cancelIcon from '../../../assets/icon/cancel.svg';
|
||||
import { isPC, isUniFrameWork, isWeChat } from '../../../utils/env';
|
||||
import { ref, watchEffect, computed } from '../../../adapter-vue'
|
||||
import { TUITranslateService } from '@tencentcloud/chat-uikit-engine-lite'
|
||||
import { ITransferListItem } from '../../../interface'
|
||||
import Icon from '../Icon.vue'
|
||||
import selectedIcon from '../../../assets/icon/selected.svg'
|
||||
import backIcon from '../../../assets/icon/back.svg'
|
||||
import cancelIcon from '../../../assets/icon/cancel.svg'
|
||||
import searchIcon from '../../../assets/icon/search.svg'
|
||||
import { isPC, isUniFrameWork, isWeChat } from '../../../utils/env'
|
||||
|
||||
const props = defineProps({
|
||||
list: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
selectedList: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
isSearch: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
isRadio: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
isCustomItem: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
resultShow: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
total: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
isHiddenBackIcon: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
|
||||
const transferList = ref<ITransferListItem[]>([]);
|
||||
const transferTotal = ref<number>(0);
|
||||
const transferSelectedList = ref<ITransferListItem[]>([]);
|
||||
const isTransferSearch = ref(true);
|
||||
const isTransferCustomItem = ref(false);
|
||||
const transferTitle = ref('');
|
||||
const searchValue = ref('');
|
||||
|
||||
watchEffect(() => {
|
||||
if (props.isCustomItem) {
|
||||
for (let index = 0; index < props.list.length; index++) {
|
||||
if (
|
||||
(props.list[index] as any).conversationID.indexOf('@TIM#SYSTEM') > -1
|
||||
) {
|
||||
// eslint-disable-next-line vue/no-mutating-props
|
||||
props.list.splice(index, 1);
|
||||
}
|
||||
transferList.value = props.list as ITransferListItem[];
|
||||
const props = defineProps({
|
||||
list: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
selectedList: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
isSearch: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
isRadio: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
isCustomItem: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
resultShow: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
total: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
isHiddenBackIcon: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
} else {
|
||||
transferList.value = props.list as ITransferListItem[];
|
||||
})
|
||||
|
||||
const transferList = ref<ITransferListItem[]>([])
|
||||
const transferTotal = ref<number>(0)
|
||||
const transferSelectedList = ref<ITransferListItem[]>([])
|
||||
const isTransferSearch = ref(true)
|
||||
const isTransferCustomItem = ref(false)
|
||||
const transferTitle = ref('')
|
||||
const searchValue = ref('')
|
||||
|
||||
watchEffect(() => {
|
||||
if (props.isCustomItem) {
|
||||
for (let index = 0; index < props.list.length; index++) {
|
||||
if (
|
||||
(props.list[index] as any).conversationID.indexOf(
|
||||
'@TIM#SYSTEM'
|
||||
) > -1
|
||||
) {
|
||||
// eslint-disable-next-line vue/no-mutating-props
|
||||
props.list.splice(index, 1)
|
||||
}
|
||||
transferList.value = props.list as ITransferListItem[]
|
||||
}
|
||||
} else {
|
||||
transferList.value = props.list as ITransferListItem[]
|
||||
}
|
||||
transferTotal.value = props.total ? props.total : props.list.length
|
||||
transferSelectedList.value = (
|
||||
props.selectedList && props.selectedList.length > 0
|
||||
? props.selectedList
|
||||
: transferSelectedList.value
|
||||
) as any
|
||||
isTransferSearch.value = props.isSearch
|
||||
isTransferCustomItem.value = props.isCustomItem
|
||||
transferTitle.value = props.title
|
||||
})
|
||||
|
||||
const emit = defineEmits(['search', 'submit', 'cancel', 'getMore'])
|
||||
|
||||
const optional = computed(() =>
|
||||
transferList.value.filter((item: any) => !item.isDisabled)
|
||||
)
|
||||
|
||||
const handleInput = (e: any) => {
|
||||
searchValue.value = e.target.value || e.detail.value
|
||||
emit('search', searchValue.value)
|
||||
}
|
||||
transferTotal.value = props.total ? props.total : props.list.length;
|
||||
transferSelectedList.value = (props.selectedList && props.selectedList.length > 0 ? props.selectedList : transferSelectedList.value) as any;
|
||||
isTransferSearch.value = props.isSearch;
|
||||
isTransferCustomItem.value = props.isCustomItem;
|
||||
transferTitle.value = props.title;
|
||||
});
|
||||
|
||||
const emit = defineEmits(['search', 'submit', 'cancel', 'getMore']);
|
||||
|
||||
const optional = computed(() =>
|
||||
transferList.value.filter((item: any) => !item.isDisabled),
|
||||
);
|
||||
|
||||
const handleInput = (e: any) => {
|
||||
searchValue.value = e.target.value || e.detail.value;
|
||||
emit('search', searchValue.value);
|
||||
};
|
||||
const selected = (item: any) => {
|
||||
if (item.isDisabled) {
|
||||
return;
|
||||
const selected = (item: any) => {
|
||||
if (item.isDisabled) {
|
||||
return
|
||||
}
|
||||
let list: ITransferListItem[] = transferSelectedList.value
|
||||
const index: number = list.indexOf(item)
|
||||
if (index > -1) {
|
||||
return transferSelectedList.value.splice(index, 1)
|
||||
}
|
||||
if (props.isRadio) {
|
||||
list = []
|
||||
}
|
||||
list.push(item)
|
||||
transferSelectedList.value = list
|
||||
}
|
||||
let list: ITransferListItem[] = transferSelectedList.value;
|
||||
const index: number = list.indexOf(item);
|
||||
if (index > -1) {
|
||||
return transferSelectedList.value.splice(index, 1);
|
||||
|
||||
const selectedAll = () => {
|
||||
if (transferSelectedList.value.length === optional.value.length) {
|
||||
transferSelectedList.value = []
|
||||
} else {
|
||||
transferSelectedList.value = [...optional.value]
|
||||
}
|
||||
}
|
||||
if (props.isRadio) {
|
||||
list = [];
|
||||
|
||||
const submit = () => {
|
||||
emit('submit', transferSelectedList.value)
|
||||
searchValue.value = ''
|
||||
}
|
||||
list.push(item);
|
||||
transferSelectedList.value = list;
|
||||
};
|
||||
|
||||
const selectedAll = () => {
|
||||
if (transferSelectedList.value.length === optional.value.length) {
|
||||
transferSelectedList.value = [];
|
||||
} else {
|
||||
transferSelectedList.value = [...optional.value];
|
||||
const cancel = () => {
|
||||
emit('cancel')
|
||||
searchValue.value = ''
|
||||
}
|
||||
};
|
||||
|
||||
const submit = () => {
|
||||
emit('submit', transferSelectedList.value);
|
||||
searchValue.value = '';
|
||||
};
|
||||
|
||||
const cancel = () => {
|
||||
emit('cancel');
|
||||
searchValue.value = '';
|
||||
};
|
||||
|
||||
const getMore = () => {
|
||||
emit('getMore');
|
||||
};
|
||||
const getMore = () => {
|
||||
emit('getMore')
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped src="./style/transfer.scss"></style>
|
||||
<style lang="scss" scoped>
|
||||
.transfer-left-main {
|
||||
// background: #f4f4f4;
|
||||
}
|
||||
.transfer-header {
|
||||
padding: 26rpx 32rpx !important;
|
||||
box-shadow: 0rpx 2rpx 8rpx 0rpx rgba(0, 0, 0, 0.04) !important;
|
||||
margin-bottom: 12rpx !important;
|
||||
}
|
||||
.left-uniapp-input {
|
||||
border-radius: 80rpx !important;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
width: 80rpx !important;
|
||||
height: 80rpx !important;
|
||||
border-radius: 80rpx !important;
|
||||
}
|
||||
|
||||
.transfer-right-footer {
|
||||
button {
|
||||
height: 64rpx;
|
||||
padding: 0 36rpx;
|
||||
line-height: 64rpx;
|
||||
border-radius: 64rpx;
|
||||
&::after {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
.btn-confirm {
|
||||
background: #00D993;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user