提现功能需要添加
This commit is contained in:
19
App.vue
19
App.vue
@@ -1,15 +1,20 @@
|
||||
<script setup lang="ts">
|
||||
import { onLaunch, onShow, onHide } from '@dcloudio/uni-app'
|
||||
import { useTokenStore } from './stores/token'
|
||||
import { reLaunch } from './utils/router'
|
||||
import { useAuthUser } from './composables/useAuthUser'
|
||||
import { useUserStore } from './stores/user'
|
||||
|
||||
import { TUIChatKit } from './TUIKit';
|
||||
TUIChatKit.init();
|
||||
|
||||
const { token } = useAuthUser()
|
||||
const { loginTencentIM } = useUserStore()
|
||||
/** 静默登录逻辑 */
|
||||
const silentLogin = async () => {
|
||||
console.log(token.value, '==')
|
||||
if (token.value) {
|
||||
reLaunch('/pages/news-list/news-list')
|
||||
loginTencentIM()
|
||||
reLaunch('/TUIKit/components/TUIConversation/index')
|
||||
return
|
||||
}
|
||||
|
||||
@@ -34,4 +39,14 @@
|
||||
<style lang="scss">
|
||||
/*每个页面公共css */
|
||||
@import './styles/global.scss';
|
||||
|
||||
/* common css for page */
|
||||
// uni-page-body,
|
||||
// html,
|
||||
// body,
|
||||
// page {
|
||||
// width: 100% !important;
|
||||
// height: 100% !important;
|
||||
// overflow: hidden;
|
||||
// }
|
||||
</style>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="chat" :style="{marginBottom: keywordHight + 'px'}">
|
||||
<div class="chat" :style="{ marginBottom: keywordHight + 'px' }">
|
||||
<div :class="['tui-chat', !isPC && 'tui-chat-h5']">
|
||||
<div
|
||||
v-if="!currentConversationID"
|
||||
@@ -20,7 +20,10 @@
|
||||
<Forward @toggleMultipleSelectMode="toggleMultipleSelectMode" />
|
||||
<MessageList
|
||||
ref="messageListRef"
|
||||
:class="['tui-chat-message-list', !isPC && 'tui-chat-h5-message-list']"
|
||||
:class="[
|
||||
'tui-chat-message-list',
|
||||
!isPC && 'tui-chat-h5-message-list'
|
||||
]"
|
||||
:isGroup="isGroup"
|
||||
:groupID="groupID"
|
||||
:isNotInGroup="isNotInGroup"
|
||||
@@ -33,7 +36,7 @@
|
||||
v-if="isNotInGroup"
|
||||
:class="{
|
||||
'tui-chat-leave-group': true,
|
||||
'tui-chat-leave-group-mobile': isMobile,
|
||||
'tui-chat-leave-group-mobile': isMobile
|
||||
}"
|
||||
>
|
||||
{{ leaveGroupReasonText }}
|
||||
@@ -63,7 +66,7 @@
|
||||
'tui-chat-message-input',
|
||||
!isPC && 'tui-chat-h5-message-input',
|
||||
isUniFrameWork && 'tui-chat-uni-message-input',
|
||||
isWeChat && 'tui-chat-wx-message-input',
|
||||
isWeChat && 'tui-chat-wx-message-input'
|
||||
]"
|
||||
:enableAt="featureConfig.InputMention"
|
||||
:isMuted="false"
|
||||
@@ -78,232 +81,275 @@
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { ref, onMounted, onUnmounted, computed } from '../../adapter-vue';
|
||||
import TUIChatEngine, {
|
||||
TUITranslateService,
|
||||
TUIConversationService,
|
||||
TUIStore,
|
||||
StoreName,
|
||||
IMessageModel,
|
||||
IConversationModel,
|
||||
} from '@tencentcloud/chat-uikit-engine-lite';
|
||||
import TUICore, { TUIConstants, ExtensionInfo } from '@tencentcloud/tui-core-lite';
|
||||
import ChatHeader from './chat-header/index.vue';
|
||||
import MessageList from './message-list/index.vue';
|
||||
import MessageInput from './message-input/index.vue';
|
||||
import MultipleSelectPanel from './mulitple-select-panel/index.vue';
|
||||
import Forward from './forward/index.vue';
|
||||
import MessageInputToolbar from './message-input-toolbar/index.vue';
|
||||
import { isPC, isWeChat, isUniFrameWork, isMobile, isApp } from '../../utils/env';
|
||||
import { ToolbarDisplayType } from '../../interface';
|
||||
import TUIChatConfig from './config';
|
||||
import {
|
||||
ref,
|
||||
onMounted,
|
||||
onUnmounted,
|
||||
computed
|
||||
} from '../../adapter-vue'
|
||||
import TUIChatEngine, {
|
||||
TUITranslateService,
|
||||
TUIConversationService,
|
||||
TUIStore,
|
||||
StoreName,
|
||||
IMessageModel,
|
||||
IConversationModel
|
||||
} from '@tencentcloud/chat-uikit-engine-lite'
|
||||
import TUICore, {
|
||||
TUIConstants,
|
||||
ExtensionInfo
|
||||
} from '@tencentcloud/tui-core-lite'
|
||||
import ChatHeader from './chat-header/index.vue'
|
||||
import MessageList from './message-list/index.vue'
|
||||
import MessageInput from './message-input/index.vue'
|
||||
import MultipleSelectPanel from './mulitple-select-panel/index.vue'
|
||||
import Forward from './forward/index.vue'
|
||||
import MessageInputToolbar from './message-input-toolbar/index.vue'
|
||||
import {
|
||||
isPC,
|
||||
isWeChat,
|
||||
isUniFrameWork,
|
||||
isMobile,
|
||||
isApp
|
||||
} from '../../utils/env'
|
||||
import { ToolbarDisplayType } from '../../interface'
|
||||
import TUIChatConfig from './config'
|
||||
|
||||
// @Start uniapp use Chat only
|
||||
import { onLoad, onUnload } from '@dcloudio/uni-app';
|
||||
import { initChat, logout } from './entry-chat-only.ts';
|
||||
// @Start uniapp use Chat only
|
||||
import { onLoad, onUnload } from '@dcloudio/uni-app'
|
||||
import { initChat, logout } from './entry-chat-only.ts'
|
||||
|
||||
onLoad((options) => {
|
||||
initChat(options);
|
||||
});
|
||||
onLoad(options => {
|
||||
initChat(options)
|
||||
})
|
||||
|
||||
onUnload(() => {
|
||||
// Whether logout is decided by yourself when the page is unloaded. The default is false.
|
||||
logout(false).then(() => {
|
||||
// Handle success result from promise.then when you set true.
|
||||
}).catch(() => {
|
||||
// handle error
|
||||
});
|
||||
});
|
||||
// @End uniapp use Chat only
|
||||
onUnload(() => {
|
||||
// Whether logout is decided by yourself when the page is unloaded. The default is false.
|
||||
logout(false)
|
||||
.then(() => {
|
||||
// Handle success result from promise.then when you set true.
|
||||
})
|
||||
.catch(() => {
|
||||
// handle error
|
||||
})
|
||||
})
|
||||
// @End uniapp use Chat only
|
||||
|
||||
const emits = defineEmits(['closeChat']);
|
||||
const emits = defineEmits(['closeChat'])
|
||||
|
||||
const groupID = ref(undefined);
|
||||
const isGroup = ref(false);
|
||||
const isNotInGroup = ref(false);
|
||||
const notInGroupReason = ref<number>();
|
||||
const currentConversationID = ref();
|
||||
const isMultipleSelectMode = ref(false);
|
||||
const inputToolbarDisplayType = ref<ToolbarDisplayType>('none');
|
||||
const messageInputRef = ref();
|
||||
const messageListRef = ref<InstanceType<typeof MessageList>>();
|
||||
const headerExtensionList = ref<ExtensionInfo[]>([]);
|
||||
const featureConfig = TUIChatConfig.getFeatureConfig();
|
||||
const keywordHight = ref(0);
|
||||
const groupID = ref(undefined)
|
||||
const isGroup = ref(false)
|
||||
const isNotInGroup = ref(false)
|
||||
const notInGroupReason = ref<number>()
|
||||
const currentConversationID = ref()
|
||||
const isMultipleSelectMode = ref(false)
|
||||
const inputToolbarDisplayType = ref<ToolbarDisplayType>('none')
|
||||
const messageInputRef = ref()
|
||||
const messageListRef = ref<InstanceType<typeof MessageList>>()
|
||||
const headerExtensionList = ref<ExtensionInfo[]>([])
|
||||
const featureConfig = TUIChatConfig.getFeatureConfig()
|
||||
const keywordHight = ref(0)
|
||||
|
||||
const systemInfo = uni.getSystemInfoSync();
|
||||
const screenHeight = systemInfo.screenHeight;
|
||||
const systemInfo = uni.getSystemInfoSync()
|
||||
const screenHeight = systemInfo.screenHeight
|
||||
|
||||
const windowResizeCallback = (res) => {
|
||||
const value = screenHeight - res.size.windowHeight;
|
||||
if (value > 0 && inputToolbarDisplayType.value !== 'dialog') {
|
||||
inputToolbarDisplayType.value = 'none';
|
||||
const windowResizeCallback = res => {
|
||||
const value = screenHeight - res.size.windowHeight
|
||||
if (value > 0 && inputToolbarDisplayType.value !== 'dialog') {
|
||||
inputToolbarDisplayType.value = 'none'
|
||||
}
|
||||
uni.$emit('scroll-to-bottom')
|
||||
keywordHight.value = value
|
||||
}
|
||||
uni.$emit('scroll-to-bottom');
|
||||
keywordHight.value = value;
|
||||
};
|
||||
uni.onWindowResize(windowResizeCallback);
|
||||
uni.onWindowResize(windowResizeCallback)
|
||||
|
||||
onMounted(() => {
|
||||
TUIStore.watch(StoreName.CONV, {
|
||||
currentConversation: onCurrentConversationUpdate,
|
||||
});
|
||||
});
|
||||
onMounted(() => {
|
||||
TUIStore.watch(StoreName.CONV, {
|
||||
currentConversation: onCurrentConversationUpdate
|
||||
})
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
TUIStore.unwatch(StoreName.CONV, {
|
||||
currentConversation: onCurrentConversationUpdate,
|
||||
});
|
||||
reset();
|
||||
});
|
||||
onUnmounted(() => {
|
||||
TUIStore.unwatch(StoreName.CONV, {
|
||||
currentConversation: onCurrentConversationUpdate
|
||||
})
|
||||
reset()
|
||||
})
|
||||
|
||||
const isInputToolbarShow = computed<boolean>(() => {
|
||||
return isUniFrameWork ? inputToolbarDisplayType.value !== 'none' : true;
|
||||
});
|
||||
const isInputToolbarShow = computed<boolean>(() => {
|
||||
return isUniFrameWork
|
||||
? inputToolbarDisplayType.value !== 'none'
|
||||
: true
|
||||
})
|
||||
|
||||
const leaveGroupReasonText = computed<string>(() => {
|
||||
let text = '';
|
||||
switch (notInGroupReason.value) {
|
||||
case 4:
|
||||
text = TUITranslateService.t('TUIChat.您已被管理员移出群聊');
|
||||
break;
|
||||
case 5:
|
||||
text = TUITranslateService.t('TUIChat.该群聊已被解散');
|
||||
break;
|
||||
case 8:
|
||||
text = TUITranslateService.t('TUIChat.您已退出该群聊');
|
||||
break;
|
||||
default:
|
||||
text = TUITranslateService.t('TUIChat.您已退出该群聊');
|
||||
break;
|
||||
const leaveGroupReasonText = computed<string>(() => {
|
||||
let text = ''
|
||||
switch (notInGroupReason.value) {
|
||||
case 4:
|
||||
text = TUITranslateService.t('TUIChat.您已被管理员移出群聊')
|
||||
break
|
||||
case 5:
|
||||
text = TUITranslateService.t('TUIChat.该群聊已被解散')
|
||||
break
|
||||
case 8:
|
||||
text = TUITranslateService.t('TUIChat.您已退出该群聊')
|
||||
break
|
||||
default:
|
||||
text = TUITranslateService.t('TUIChat.您已退出该群聊')
|
||||
break
|
||||
}
|
||||
return text
|
||||
})
|
||||
|
||||
const reset = () => {
|
||||
TUIConversationService.switchConversation('')
|
||||
}
|
||||
return text;
|
||||
});
|
||||
|
||||
const reset = () => {
|
||||
TUIConversationService.switchConversation('');
|
||||
};
|
||||
const closeChat = (conversationID: string) => {
|
||||
emits('closeChat', conversationID)
|
||||
reset()
|
||||
}
|
||||
|
||||
const closeChat = (conversationID: string) => {
|
||||
emits('closeChat', conversationID);
|
||||
reset();
|
||||
};
|
||||
const insertEmoji = (emojiObj: object) => {
|
||||
messageInputRef.value?.insertEmoji(emojiObj)
|
||||
}
|
||||
|
||||
const insertEmoji = (emojiObj: object) => {
|
||||
messageInputRef.value?.insertEmoji(emojiObj);
|
||||
};
|
||||
const handleEditor = (message: IMessageModel, type: string) => {
|
||||
if (!message || !type) return
|
||||
switch (type) {
|
||||
case 'reference':
|
||||
// todo
|
||||
break
|
||||
case 'reply':
|
||||
// todo
|
||||
break
|
||||
case 'reedit':
|
||||
if (message?.payload?.text) {
|
||||
messageInputRef?.value?.reEdit(message?.payload?.text)
|
||||
}
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
const handleEditor = (message: IMessageModel, type: string) => {
|
||||
if (!message || !type) return;
|
||||
switch (type) {
|
||||
case 'reference':
|
||||
// todo
|
||||
break;
|
||||
case 'reply':
|
||||
// todo
|
||||
break;
|
||||
case 'reedit':
|
||||
if (message?.payload?.text) {
|
||||
messageInputRef?.value?.reEdit(message?.payload?.text);
|
||||
const handleGroup = () => {
|
||||
headerExtensionList.value[0].listener.onClicked({
|
||||
groupID: groupID.value
|
||||
})
|
||||
}
|
||||
|
||||
function changeToolbarDisplayType(type: ToolbarDisplayType) {
|
||||
setTimeout(() => {
|
||||
inputToolbarDisplayType.value =
|
||||
inputToolbarDisplayType.value === type ? 'none' : type
|
||||
if (inputToolbarDisplayType.value !== 'none' && isUniFrameWork) {
|
||||
uni.$emit('scroll-to-bottom')
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
const handleGroup = () => {
|
||||
headerExtensionList.value[0].listener.onClicked({ groupID: groupID.value });
|
||||
};
|
||||
|
||||
function changeToolbarDisplayType(type: ToolbarDisplayType) {
|
||||
setTimeout(() => {
|
||||
inputToolbarDisplayType.value = inputToolbarDisplayType.value === type ? 'none' : type;
|
||||
if (inputToolbarDisplayType.value !== 'none' && isUniFrameWork) {
|
||||
uni.$emit('scroll-to-bottom');
|
||||
}
|
||||
}, 100)
|
||||
}
|
||||
|
||||
function scrollToLatestMessage() {
|
||||
messageListRef.value?.scrollToLatestMessage();
|
||||
}
|
||||
|
||||
function toggleMultipleSelectMode(visible?: boolean) {
|
||||
isMultipleSelectMode.value = visible === undefined ? !isMultipleSelectMode.value : visible;
|
||||
}
|
||||
|
||||
function mergeForwardMessage() {
|
||||
messageListRef.value?.mergeForwardMessage();
|
||||
}
|
||||
|
||||
function oneByOneForwardMessage() {
|
||||
messageListRef.value?.oneByOneForwardMessage();
|
||||
}
|
||||
|
||||
function updateUIUserNotInGroup(conversation: IConversationModel) {
|
||||
if (conversation?.operationType > 0) {
|
||||
headerExtensionList.value = [];
|
||||
isNotInGroup.value = true;
|
||||
/**
|
||||
* 4 - be removed from the group
|
||||
* 5 - group is dismissed
|
||||
* 8 - quit group
|
||||
*/
|
||||
notInGroupReason.value = conversation?.operationType;
|
||||
} else {
|
||||
isNotInGroup.value = false;
|
||||
notInGroupReason.value = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function onCurrentConversationUpdate(conversation: IConversationModel) {
|
||||
updateUIUserNotInGroup(conversation);
|
||||
// return when currentConversation is null
|
||||
if (!conversation) {
|
||||
return;
|
||||
}
|
||||
// return when currentConversationID.value is the same as conversation.conversationID.
|
||||
if (currentConversationID.value === conversation?.conversationID) {
|
||||
return;
|
||||
}, 100)
|
||||
}
|
||||
|
||||
isGroup.value = false;
|
||||
let conversationType = TUIChatEngine.TYPES.CONV_C2C;
|
||||
const conversationID = conversation.conversationID;
|
||||
if (conversationID.startsWith(TUIChatEngine.TYPES.CONV_GROUP)) {
|
||||
conversationType = TUIChatEngine.TYPES.CONV_GROUP;
|
||||
isGroup.value = true;
|
||||
groupID.value = conversationID.replace(TUIChatEngine.TYPES.CONV_GROUP, '');
|
||||
function scrollToLatestMessage() {
|
||||
messageListRef.value?.scrollToLatestMessage()
|
||||
}
|
||||
|
||||
headerExtensionList.value = [];
|
||||
isMultipleSelectMode.value = false;
|
||||
// Initialize chatType
|
||||
TUIChatConfig.setChatType(conversationType);
|
||||
// While converstaion change success, notify callkit and roomkit、or other components.
|
||||
TUICore.notifyEvent(TUIConstants.TUIChat.EVENT.CHAT_STATE_CHANGED, TUIConstants.TUIChat.EVENT_SUB_KEY.CHAT_OPENED, { groupID: groupID.value });
|
||||
// The TUICustomerServicePlugin plugin determines if the current conversation is a customer service conversation, then sets chatType and activates the conversation.
|
||||
TUICore.callService({
|
||||
serviceName: TUIConstants.TUICustomerServicePlugin.SERVICE.NAME,
|
||||
method: TUIConstants.TUICustomerServicePlugin.SERVICE.METHOD.ACTIVE_CONVERSATION,
|
||||
params: { conversationID: conversationID },
|
||||
});
|
||||
// When open chat in room, close main chat ui and reset theme.
|
||||
if (TUIChatConfig.getChatType() === TUIConstants.TUIChat.TYPE.ROOM) {
|
||||
if (TUIChatConfig.getFeatureConfig(TUIConstants.TUIChat.FEATURE.InputVoice) === true) {
|
||||
TUIChatConfig.setTheme('light');
|
||||
currentConversationID.value = '';
|
||||
return;
|
||||
function toggleMultipleSelectMode(visible?: boolean) {
|
||||
isMultipleSelectMode.value =
|
||||
visible === undefined ? !isMultipleSelectMode.value : visible
|
||||
}
|
||||
|
||||
function mergeForwardMessage() {
|
||||
messageListRef.value?.mergeForwardMessage()
|
||||
}
|
||||
|
||||
function oneByOneForwardMessage() {
|
||||
messageListRef.value?.oneByOneForwardMessage()
|
||||
}
|
||||
|
||||
function updateUIUserNotInGroup(conversation: IConversationModel) {
|
||||
if (conversation?.operationType > 0) {
|
||||
headerExtensionList.value = []
|
||||
isNotInGroup.value = true
|
||||
/**
|
||||
* 4 - be removed from the group
|
||||
* 5 - group is dismissed
|
||||
* 8 - quit group
|
||||
*/
|
||||
notInGroupReason.value = conversation?.operationType
|
||||
} else {
|
||||
isNotInGroup.value = false
|
||||
notInGroupReason.value = undefined
|
||||
}
|
||||
}
|
||||
// Get chat header extensions
|
||||
if (TUIChatConfig.getChatType() === TUIConstants.TUIChat.TYPE.GROUP) {
|
||||
headerExtensionList.value = TUICore.getExtensionList(TUIConstants.TUIChat.EXTENSION.CHAT_HEADER.EXT_ID);
|
||||
|
||||
function onCurrentConversationUpdate(conversation: IConversationModel) {
|
||||
updateUIUserNotInGroup(conversation)
|
||||
// return when currentConversation is null
|
||||
if (!conversation) {
|
||||
return
|
||||
}
|
||||
// return when currentConversationID.value is the same as conversation.conversationID.
|
||||
if (currentConversationID.value === conversation?.conversationID) {
|
||||
return
|
||||
}
|
||||
|
||||
isGroup.value = false
|
||||
let conversationType = TUIChatEngine.TYPES.CONV_C2C
|
||||
const conversationID = conversation.conversationID
|
||||
if (conversationID.startsWith(TUIChatEngine.TYPES.CONV_GROUP)) {
|
||||
conversationType = TUIChatEngine.TYPES.CONV_GROUP
|
||||
isGroup.value = true
|
||||
groupID.value = conversationID.replace(
|
||||
TUIChatEngine.TYPES.CONV_GROUP,
|
||||
''
|
||||
)
|
||||
}
|
||||
|
||||
headerExtensionList.value = []
|
||||
isMultipleSelectMode.value = false
|
||||
// Initialize chatType
|
||||
TUIChatConfig.setChatType(conversationType)
|
||||
// While converstaion change success, notify callkit and roomkit、or other components.
|
||||
TUICore.notifyEvent(
|
||||
TUIConstants.TUIChat.EVENT.CHAT_STATE_CHANGED,
|
||||
TUIConstants.TUIChat.EVENT_SUB_KEY.CHAT_OPENED,
|
||||
{ groupID: groupID.value }
|
||||
)
|
||||
// The TUICustomerServicePlugin plugin determines if the current conversation is a customer service conversation, then sets chatType and activates the conversation.
|
||||
TUICore.callService({
|
||||
serviceName: TUIConstants.TUICustomerServicePlugin.SERVICE.NAME,
|
||||
method:
|
||||
TUIConstants.TUICustomerServicePlugin.SERVICE.METHOD
|
||||
.ACTIVE_CONVERSATION,
|
||||
params: { conversationID: conversationID }
|
||||
})
|
||||
// When open chat in room, close main chat ui and reset theme.
|
||||
if (TUIChatConfig.getChatType() === TUIConstants.TUIChat.TYPE.ROOM) {
|
||||
if (
|
||||
TUIChatConfig.getFeatureConfig(
|
||||
TUIConstants.TUIChat.FEATURE.InputVoice
|
||||
) === true
|
||||
) {
|
||||
TUIChatConfig.setTheme('light')
|
||||
currentConversationID.value = ''
|
||||
return
|
||||
}
|
||||
}
|
||||
// Get chat header extensions
|
||||
if (TUIChatConfig.getChatType() === TUIConstants.TUIChat.TYPE.GROUP) {
|
||||
headerExtensionList.value = TUICore.getExtensionList(
|
||||
TUIConstants.TUIChat.EXTENSION.CHAT_HEADER.EXT_ID
|
||||
)
|
||||
}
|
||||
TUIStore.update(
|
||||
StoreName.CUSTOM,
|
||||
'activeConversation',
|
||||
conversationID
|
||||
)
|
||||
currentConversationID.value = conversationID
|
||||
}
|
||||
TUIStore.update(StoreName.CUSTOM, 'activeConversation', conversationID);
|
||||
currentConversationID.value = conversationID;
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss" src="./style/index.scss"></style>
|
||||
<style scoped lang="scss" src="./style/index.scss">
|
||||
|
||||
</style>
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
uni-page-body,
|
||||
html,
|
||||
body,
|
||||
page {
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@import '../../../assets/styles/common';
|
||||
@import './web';
|
||||
@import './h5';
|
||||
|
||||
@@ -26,64 +26,84 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { deepCopy } from '../../../TUIChat/utils/utils';
|
||||
import ContactListItem from '../contact-list-item/index.vue';
|
||||
import { sortByFirstChar } from '../../utils/sortByFirstChar';
|
||||
import { onMounted, onUnmounted, ref } from '../../../../adapter-vue';
|
||||
import { Friend, StoreName, TUIStore } from '@tencentcloud/chat-uikit-engine-lite';
|
||||
import { FriendListData } from '../../../../interface';
|
||||
import { deepCopy } from '../../../TUIChat/utils/utils'
|
||||
import ContactListItem from '../contact-list-item/index.vue'
|
||||
import { sortByFirstChar } from '../../utils/sortByFirstChar'
|
||||
import { onMounted, onUnmounted, ref } from '../../../../adapter-vue'
|
||||
import {
|
||||
Friend,
|
||||
StoreName,
|
||||
TUIStore
|
||||
} from '@tencentcloud/chat-uikit-engine-lite'
|
||||
import { FriendListData } from '../../../../interface'
|
||||
|
||||
const emits = defineEmits(['enterConversation']);
|
||||
const emits = defineEmits(['enterConversation'])
|
||||
|
||||
const friendListData = ref<FriendListData>({
|
||||
key: 'friendList',
|
||||
title: '我的好友',
|
||||
map: {},
|
||||
});
|
||||
const friendListData = ref<FriendListData>({
|
||||
key: 'friendList',
|
||||
title: '我的好友',
|
||||
map: {}
|
||||
})
|
||||
|
||||
function onFriendListUpdated(friendList: Friend[]) {
|
||||
const { groupedList } = sortByFirstChar(
|
||||
friendList,
|
||||
(friend: Friend) => friend.remark || friend.profile?.nick || friend.userID || '');
|
||||
friendListData.value.map = groupedList;
|
||||
}
|
||||
function onFriendListUpdated(friendList: Friend[]) {
|
||||
const { groupedList } = sortByFirstChar(
|
||||
friendList,
|
||||
(friend: Friend) =>
|
||||
friend.remark || friend.profile?.nick || friend.userID || ''
|
||||
)
|
||||
|
||||
onMounted(() => {
|
||||
TUIStore.watch(StoreName.FRIEND, {
|
||||
friendList: onFriendListUpdated,
|
||||
});
|
||||
});
|
||||
console.log(groupedList, '====222==')
|
||||
friendListData.value.map = {
|
||||
A: [
|
||||
{
|
||||
profile: {
|
||||
nick: '测试第三方',
|
||||
avatar:
|
||||
'https://jckj-1309258891.cos.ap-chengdu.myqcloud.com/common/19101767159307246_.png'
|
||||
}
|
||||
}
|
||||
],
|
||||
...groupedList
|
||||
}
|
||||
}
|
||||
|
||||
onUnmounted(() => {
|
||||
TUIStore.unwatch(StoreName.FRIEND, {
|
||||
friendList: onFriendListUpdated,
|
||||
});
|
||||
});
|
||||
onMounted(() => {
|
||||
TUIStore.watch(StoreName.FRIEND, {
|
||||
friendList: onFriendListUpdated
|
||||
})
|
||||
})
|
||||
|
||||
const enterConversation = (item: any) => {
|
||||
emits('enterConversation', item);
|
||||
};
|
||||
onUnmounted(() => {
|
||||
TUIStore.unwatch(StoreName.FRIEND, {
|
||||
friendList: onFriendListUpdated
|
||||
})
|
||||
})
|
||||
|
||||
const enterConversation = (item: any) => {
|
||||
emits('enterConversation', item)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.friend-list {
|
||||
ul, li {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
.friend-list {
|
||||
ul,
|
||||
li {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.friend-group-title {
|
||||
padding: 8px 16px;
|
||||
background-color: #f8f9fa;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #666;
|
||||
line-height: 20px;
|
||||
}
|
||||
.friend-group-title {
|
||||
padding: 8px 16px;
|
||||
background-color: #f8f9fa;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #666;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.friend-item {
|
||||
margin: 0 15px;
|
||||
padding: 5px 0;
|
||||
}
|
||||
.friend-item {
|
||||
margin: 0 15px;
|
||||
padding: 5px 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -28,7 +28,13 @@
|
||||
</span>
|
||||
</div>
|
||||
<div class="tui-contact-list-item-header-right">
|
||||
<div>{{ TUITranslateService.t(`TUIContact.${contactListObj.title}`) }}</div>
|
||||
<div>
|
||||
{{
|
||||
TUITranslateService.t(
|
||||
`TUIContact.${contactListObj.title}`
|
||||
)
|
||||
}}
|
||||
</div>
|
||||
<Icon
|
||||
:file="currentContactListKey === key ? downSVG : rightSVG"
|
||||
size="20px"
|
||||
@@ -41,7 +47,8 @@
|
||||
</div>
|
||||
<template v-else>
|
||||
<li
|
||||
v-for="contactListItem in contactListMap[currentContactListKey].list"
|
||||
v-for="contactListItem in contactListMap[currentContactListKey]
|
||||
.list"
|
||||
:key="contactListItem.renderKey"
|
||||
class="tui-contact-list-item-main-item"
|
||||
:class="['selected']"
|
||||
@@ -55,19 +62,13 @@
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<ul
|
||||
v-else-if="contactSearchingStatus"
|
||||
class="tui-contact-list"
|
||||
>
|
||||
<ul v-else-if="contactSearchingStatus" class="tui-contact-list">
|
||||
<li
|
||||
v-for="(item, key) in contactSearchResult"
|
||||
:key="key"
|
||||
class="tui-contact-list-item"
|
||||
>
|
||||
<div
|
||||
v-if="item.list[0]"
|
||||
class="tui-contact-search-list"
|
||||
>
|
||||
<div v-if="item.list[0]" class="tui-contact-search-list">
|
||||
<div class="tui-contact-search-list-title">
|
||||
{{ TUITranslateService.t(`TUIContact.${item.label}`) }}
|
||||
</div>
|
||||
@@ -89,288 +90,369 @@
|
||||
v-if="isContactSearchNoResult"
|
||||
class="tui-contact-search-list-default"
|
||||
>
|
||||
{{ TUITranslateService.t("TUIContact.无搜索结果") }}
|
||||
{{ TUITranslateService.t('TUIContact.无搜索结果') }}
|
||||
</div>
|
||||
</ul>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
TUITranslateService,
|
||||
TUIStore,
|
||||
StoreName,
|
||||
TUIFriendService,
|
||||
TUIUserService,
|
||||
} from '@tencentcloud/chat-uikit-engine-lite';
|
||||
import TUICore, { TUIConstants } from '@tencentcloud/tui-core-lite';
|
||||
import { ref, computed, onMounted, onUnmounted, provide } from '../../../adapter-vue';
|
||||
import { isPC } from '../../../utils/env';
|
||||
import { deepCopy } from '../../TUIChat/utils/utils';
|
||||
import {
|
||||
TUITranslateService,
|
||||
TUIStore,
|
||||
StoreName,
|
||||
TUIFriendService,
|
||||
TUIUserService
|
||||
} from '@tencentcloud/chat-uikit-engine-lite'
|
||||
import TUICore, { TUIConstants } from '@tencentcloud/tui-core-lite'
|
||||
import {
|
||||
ref,
|
||||
computed,
|
||||
onMounted,
|
||||
onUnmounted,
|
||||
provide
|
||||
} from '../../../adapter-vue'
|
||||
import { isPC } from '../../../utils/env'
|
||||
import { deepCopy } from '../../TUIChat/utils/utils'
|
||||
|
||||
import Icon from '../../common/Icon.vue';
|
||||
import ContactListItem from './contact-list-item/index.vue';
|
||||
import FriendList from './components/FriendList.vue';
|
||||
import Icon from '../../common/Icon.vue'
|
||||
import ContactListItem from './contact-list-item/index.vue'
|
||||
import FriendList from './components/FriendList.vue'
|
||||
|
||||
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 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 type {
|
||||
IContactList,
|
||||
IContactSearchResult,
|
||||
IBlackListUserItem,
|
||||
IUserStatus,
|
||||
IUserStatusMap,
|
||||
IContactInfoType,
|
||||
} from '../../../interface';
|
||||
import type {
|
||||
IGroupModel,
|
||||
Friend,
|
||||
FriendApplication } from '@tencentcloud/chat-uikit-engine-lite';
|
||||
import type {
|
||||
IContactList,
|
||||
IContactSearchResult,
|
||||
IBlackListUserItem,
|
||||
IUserStatus,
|
||||
IUserStatusMap,
|
||||
IContactInfoType
|
||||
} from '../../../interface'
|
||||
import type {
|
||||
IGroupModel,
|
||||
Friend,
|
||||
FriendApplication
|
||||
} from '@tencentcloud/chat-uikit-engine-lite'
|
||||
|
||||
const currentContactListKey = ref<keyof IContactList>('');
|
||||
const currentContactInfo = ref<IContactInfoType>({} as IContactInfoType);
|
||||
const contactListMap = ref<IContactList>({
|
||||
friendApplicationList: {
|
||||
icon: newContactsSVG,
|
||||
key: 'friendApplicationList',
|
||||
title: '新的联系人',
|
||||
list: [] as FriendApplication[],
|
||||
unreadCount: 0,
|
||||
},
|
||||
groupList: {
|
||||
icon: groupSVG,
|
||||
key: 'groupList',
|
||||
title: '我的群聊',
|
||||
list: [] as IGroupModel[],
|
||||
},
|
||||
blackList: {
|
||||
icon: blackListSVG,
|
||||
key: 'blackList',
|
||||
title: '黑名单',
|
||||
list: [] as IBlackListUserItem[],
|
||||
},
|
||||
});
|
||||
const currentContactListKey = ref<keyof IContactList>('')
|
||||
const currentContactInfo = ref<IContactInfoType>({} as IContactInfoType)
|
||||
const contactListMap = ref<IContactList>({
|
||||
friendApplicationList: {
|
||||
icon: newContactsSVG,
|
||||
key: 'friendApplicationList',
|
||||
title: '新的联系人',
|
||||
list: [] as FriendApplication[],
|
||||
unreadCount: 0
|
||||
},
|
||||
groupList: {
|
||||
icon: groupSVG,
|
||||
key: 'groupList',
|
||||
title: '我的群聊',
|
||||
list: [] as IGroupModel[]
|
||||
},
|
||||
blackList: {
|
||||
icon: blackListSVG,
|
||||
key: 'blackList',
|
||||
title: '黑名单',
|
||||
list: [] as IBlackListUserItem[]
|
||||
}
|
||||
})
|
||||
|
||||
const contactSearchingStatus = ref<boolean>(false);
|
||||
const contactSearchResult = ref<IContactSearchResult>();
|
||||
const displayOnlineStatus = ref<boolean>(false);
|
||||
const userOnlineStatusMap = ref<IUserStatusMap>();
|
||||
const contactSearchingStatus = ref<boolean>(false)
|
||||
const contactSearchResult = ref<IContactSearchResult>()
|
||||
const displayOnlineStatus = ref<boolean>(false)
|
||||
const userOnlineStatusMap = ref<IUserStatusMap>()
|
||||
|
||||
const isContactSearchNoResult = computed((): boolean => (
|
||||
!contactSearchResult?.value?.user?.list[0]
|
||||
&& !contactSearchResult?.value?.group?.list[0]
|
||||
));
|
||||
const isContactSearchNoResult = computed(
|
||||
(): boolean =>
|
||||
!contactSearchResult?.value?.user?.list[0] &&
|
||||
!contactSearchResult?.value?.group?.list[0]
|
||||
)
|
||||
|
||||
onMounted(() => {
|
||||
TUIStore.watch(StoreName.APP, {
|
||||
enabledCustomerServicePlugin: onCustomerServiceCommercialPluginUpdated,
|
||||
});
|
||||
onMounted(() => {
|
||||
TUIStore.watch(StoreName.APP, {
|
||||
enabledCustomerServicePlugin:
|
||||
onCustomerServiceCommercialPluginUpdated
|
||||
})
|
||||
|
||||
TUIStore.watch(StoreName.GRP, {
|
||||
groupList: onGroupListUpdated,
|
||||
});
|
||||
TUIStore.watch(StoreName.GRP, {
|
||||
groupList: onGroupListUpdated
|
||||
})
|
||||
|
||||
TUIStore.watch(StoreName.USER, {
|
||||
userBlacklist: onUserBlacklistUpdated,
|
||||
displayOnlineStatus: onDisplayOnlineStatusUpdated,
|
||||
userStatusList: onUserStatusListUpdated,
|
||||
});
|
||||
TUIStore.watch(StoreName.USER, {
|
||||
userBlacklist: onUserBlacklistUpdated,
|
||||
displayOnlineStatus: onDisplayOnlineStatusUpdated,
|
||||
userStatusList: onUserStatusListUpdated
|
||||
})
|
||||
|
||||
TUIStore.watch(StoreName.FRIEND, {
|
||||
friendApplicationList: onFriendApplicationListUpdated,
|
||||
friendApplicationUnreadCount: onFriendApplicationUnreadCountUpdated,
|
||||
});
|
||||
TUIStore.watch(StoreName.FRIEND, {
|
||||
friendApplicationList: onFriendApplicationListUpdated,
|
||||
friendApplicationUnreadCount: onFriendApplicationUnreadCountUpdated
|
||||
})
|
||||
|
||||
TUIStore.watch(StoreName.CUSTOM, {
|
||||
currentContactSearchingStatus: onCurrentContactSearchingStatusUpdated,
|
||||
currentContactSearchResult: onCurrentContactSearchResultUpdated,
|
||||
currentContactListKey: onCurrentContactListKeyUpdated,
|
||||
currentContactInfo: onCurrentContactInfoUpdated,
|
||||
});
|
||||
});
|
||||
TUIStore.watch(StoreName.CUSTOM, {
|
||||
currentContactSearchingStatus:
|
||||
onCurrentContactSearchingStatusUpdated,
|
||||
currentContactSearchResult: onCurrentContactSearchResultUpdated,
|
||||
currentContactListKey: onCurrentContactListKeyUpdated,
|
||||
currentContactInfo: onCurrentContactInfoUpdated
|
||||
})
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
TUIStore.unwatch(StoreName.APP, {
|
||||
enabledCustomerServicePlugin: onCustomerServiceCommercialPluginUpdated,
|
||||
});
|
||||
onUnmounted(() => {
|
||||
TUIStore.unwatch(StoreName.APP, {
|
||||
enabledCustomerServicePlugin:
|
||||
onCustomerServiceCommercialPluginUpdated
|
||||
})
|
||||
|
||||
TUIStore.unwatch(StoreName.GRP, {
|
||||
groupList: onGroupListUpdated,
|
||||
});
|
||||
TUIStore.unwatch(StoreName.GRP, {
|
||||
groupList: onGroupListUpdated
|
||||
})
|
||||
|
||||
TUIStore.unwatch(StoreName.USER, {
|
||||
userBlacklist: onUserBlacklistUpdated,
|
||||
displayOnlineStatus: onDisplayOnlineStatusUpdated,
|
||||
userStatusList: onUserStatusListUpdated,
|
||||
});
|
||||
TUIStore.unwatch(StoreName.USER, {
|
||||
userBlacklist: onUserBlacklistUpdated,
|
||||
displayOnlineStatus: onDisplayOnlineStatusUpdated,
|
||||
userStatusList: onUserStatusListUpdated
|
||||
})
|
||||
|
||||
TUIStore.unwatch(StoreName.FRIEND, {
|
||||
friendApplicationList: onFriendApplicationListUpdated,
|
||||
friendApplicationUnreadCount: onFriendApplicationUnreadCountUpdated,
|
||||
});
|
||||
TUIStore.unwatch(StoreName.FRIEND, {
|
||||
friendApplicationList: onFriendApplicationListUpdated,
|
||||
friendApplicationUnreadCount: onFriendApplicationUnreadCountUpdated
|
||||
})
|
||||
|
||||
TUIStore.unwatch(StoreName.CUSTOM, {
|
||||
currentContactSearchingStatus: onCurrentContactSearchingStatusUpdated,
|
||||
currentContactSearchResult: onCurrentContactSearchResultUpdated,
|
||||
currentContactListKey: onCurrentContactListKeyUpdated,
|
||||
currentContactInfo: onCurrentContactInfoUpdated,
|
||||
});
|
||||
});
|
||||
TUIStore.unwatch(StoreName.CUSTOM, {
|
||||
currentContactSearchingStatus:
|
||||
onCurrentContactSearchingStatusUpdated,
|
||||
currentContactSearchResult: onCurrentContactSearchResultUpdated,
|
||||
currentContactListKey: onCurrentContactListKeyUpdated,
|
||||
currentContactInfo: onCurrentContactInfoUpdated
|
||||
})
|
||||
})
|
||||
|
||||
function toggleCurrentContactList(key: keyof IContactList) {
|
||||
if (currentContactListKey.value === key) {
|
||||
currentContactListKey.value = '';
|
||||
currentContactInfo.value = {} as IContactInfoType;
|
||||
TUIStore.update(StoreName.CUSTOM, 'currentContactListKey', '');
|
||||
TUIStore.update(StoreName.CUSTOM, 'currentContactInfo', {} as IContactInfoType);
|
||||
} else {
|
||||
currentContactListKey.value = key;
|
||||
TUIStore.update(StoreName.CUSTOM, 'currentContactListKey', key);
|
||||
if (key === 'friendApplicationList') {
|
||||
TUIFriendService.setFriendApplicationRead();
|
||||
function toggleCurrentContactList(key: keyof IContactList) {
|
||||
if (currentContactListKey.value === key) {
|
||||
currentContactListKey.value = ''
|
||||
currentContactInfo.value = {} as IContactInfoType
|
||||
TUIStore.update(StoreName.CUSTOM, 'currentContactListKey', '')
|
||||
TUIStore.update(
|
||||
StoreName.CUSTOM,
|
||||
'currentContactInfo',
|
||||
{} as IContactInfoType
|
||||
)
|
||||
} else {
|
||||
currentContactListKey.value = key
|
||||
TUIStore.update(StoreName.CUSTOM, 'currentContactListKey', key)
|
||||
|
||||
console.log(111)
|
||||
if (key === 'friendApplicationList') {
|
||||
TUIFriendService.setFriendApplicationRead()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function selectItem(item: any) {
|
||||
currentContactInfo.value = item;
|
||||
// For a result in the search list, before viewing the contactInfo details,
|
||||
// it is necessary to update the data for the "already in the group list/already in the friend list" situation to obtain more detailed information
|
||||
if (contactSearchingStatus.value) {
|
||||
let targetListItem;
|
||||
if ((currentContactInfo.value as Friend)?.userID) {
|
||||
targetListItem = contactListMap.value?.friendList?.list?.find(
|
||||
(item: IContactInfoType) => (item as Friend)?.userID === (currentContactInfo.value as Friend)?.userID,
|
||||
);
|
||||
} else if ((currentContactInfo.value as IGroupModel)?.groupID) {
|
||||
targetListItem = contactListMap.value?.groupList?.list?.find(
|
||||
(item: IContactInfoType) => (item as IGroupModel)?.groupID === (currentContactInfo.value as IGroupModel)?.groupID,
|
||||
);
|
||||
function selectItem(item: any) {
|
||||
currentContactInfo.value = item
|
||||
// For a result in the search list, before viewing the contactInfo details,
|
||||
// it is necessary to update the data for the "already in the group list/already in the friend list" situation to obtain more detailed information
|
||||
if (contactSearchingStatus.value) {
|
||||
let targetListItem
|
||||
if ((currentContactInfo.value as Friend)?.userID) {
|
||||
targetListItem = contactListMap.value?.friendList?.list?.find(
|
||||
(item: IContactInfoType) =>
|
||||
(item as Friend)?.userID ===
|
||||
(currentContactInfo.value as Friend)?.userID
|
||||
)
|
||||
} else if ((currentContactInfo.value as IGroupModel)?.groupID) {
|
||||
targetListItem = contactListMap.value?.groupList?.list?.find(
|
||||
(item: IContactInfoType) =>
|
||||
(item as IGroupModel)?.groupID ===
|
||||
(currentContactInfo.value as IGroupModel)?.groupID
|
||||
)
|
||||
}
|
||||
if (targetListItem) {
|
||||
currentContactInfo.value = targetListItem
|
||||
}
|
||||
}
|
||||
if (targetListItem) {
|
||||
currentContactInfo.value = targetListItem;
|
||||
TUIStore.update(
|
||||
StoreName.CUSTOM,
|
||||
'currentContactInfo',
|
||||
currentContactInfo.value
|
||||
)
|
||||
}
|
||||
|
||||
const selectFriend = (item: any) => {
|
||||
TUIStore.update(
|
||||
StoreName.CUSTOM,
|
||||
'currentContactListKey',
|
||||
'friendList'
|
||||
)
|
||||
selectItem(item)
|
||||
}
|
||||
|
||||
function onDisplayOnlineStatusUpdated(status: boolean) {
|
||||
displayOnlineStatus.value = status
|
||||
}
|
||||
|
||||
function onUserStatusListUpdated(list: Map<string, IUserStatus>) {
|
||||
if (list?.size > 0) {
|
||||
userOnlineStatusMap.value = Object.fromEntries(list?.entries())
|
||||
}
|
||||
}
|
||||
TUIStore.update(StoreName.CUSTOM, 'currentContactInfo', currentContactInfo.value);
|
||||
}
|
||||
|
||||
const selectFriend = (item: any) => {
|
||||
TUIStore.update(StoreName.CUSTOM, 'currentContactListKey', 'friendList');
|
||||
selectItem(item);
|
||||
};
|
||||
function onCustomerServiceCommercialPluginUpdated(isEnabled: boolean) {
|
||||
if (!isEnabled) {
|
||||
return
|
||||
}
|
||||
|
||||
function onDisplayOnlineStatusUpdated(status: boolean) {
|
||||
displayOnlineStatus.value = status;
|
||||
}
|
||||
// After the customer purchases the customer service plug-in,
|
||||
// the engine updates the enabledCustomerServicePlugin to true through the commercial capability bit.
|
||||
const contactListExtensionID =
|
||||
TUIConstants.TUIContact.EXTENSION.CONTACT_LIST.EXT_ID
|
||||
const tuiContactExtensionList = TUICore.getExtensionList(
|
||||
contactListExtensionID
|
||||
)
|
||||
|
||||
function onUserStatusListUpdated(list: Map<string, IUserStatus>) {
|
||||
if (list?.size > 0) {
|
||||
userOnlineStatusMap.value = Object.fromEntries(list?.entries());
|
||||
}
|
||||
}
|
||||
const customerData = tuiContactExtensionList.find(
|
||||
(extension: any) => {
|
||||
const { name, accountList = [] } = extension.data || {}
|
||||
return name === 'customer' && accountList.length > 0
|
||||
}
|
||||
)
|
||||
|
||||
function onCustomerServiceCommercialPluginUpdated(isEnabled: boolean) {
|
||||
if (!isEnabled) {
|
||||
return;
|
||||
if (customerData) {
|
||||
const { data, text } = customerData
|
||||
const { accountList } = (data || {}) as { accountList: string[] }
|
||||
|
||||
TUIUserService.getUserProfile({ userIDList: accountList })
|
||||
.then(res => {
|
||||
if (res.data.length > 0) {
|
||||
const customerList = {
|
||||
title: text,
|
||||
list: res.data.map((item: any, index: number) => ({
|
||||
...item,
|
||||
renderKey: generateRenderKey('customerList', item, index),
|
||||
infoKeyList: [],
|
||||
btnKeyList: ['enterC2CConversation']
|
||||
})),
|
||||
key: 'customerList'
|
||||
}
|
||||
contactListMap.value = {
|
||||
...contactListMap.value,
|
||||
customerList
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(() => {})
|
||||
}
|
||||
}
|
||||
|
||||
// After the customer purchases the customer service plug-in,
|
||||
// the engine updates the enabledCustomerServicePlugin to true through the commercial capability bit.
|
||||
const contactListExtensionID = TUIConstants.TUIContact.EXTENSION.CONTACT_LIST.EXT_ID;
|
||||
const tuiContactExtensionList = TUICore.getExtensionList(contactListExtensionID);
|
||||
|
||||
const customerData = tuiContactExtensionList.find((extension: any) => {
|
||||
const { name, accountList = [] } = extension.data || {};
|
||||
return name === 'customer' && accountList.length > 0;
|
||||
});
|
||||
|
||||
if (customerData) {
|
||||
const { data, text } = customerData;
|
||||
const { accountList } = (data || {}) as { accountList: string[] };
|
||||
|
||||
TUIUserService.getUserProfile({ userIDList: accountList })
|
||||
.then((res) => {
|
||||
if (res.data.length > 0) {
|
||||
const customerList = {
|
||||
title: text,
|
||||
list: res.data.map((item: any, index: number) => ({
|
||||
...item,
|
||||
renderKey: generateRenderKey('customerList', item, index),
|
||||
infoKeyList: [],
|
||||
btnKeyList: ['enterC2CConversation'],
|
||||
})),
|
||||
key: 'customerList',
|
||||
};
|
||||
contactListMap.value = { ...contactListMap.value, customerList };
|
||||
}
|
||||
})
|
||||
.catch(() => { });
|
||||
function onGroupListUpdated(groupList: IGroupModel[]) {
|
||||
updateContactListMap('groupList', groupList)
|
||||
}
|
||||
}
|
||||
|
||||
function onGroupListUpdated(groupList: IGroupModel[]) {
|
||||
updateContactListMap('groupList', groupList);
|
||||
}
|
||||
function onUserBlacklistUpdated(userBlacklist: IBlackListUserItem[]) {
|
||||
updateContactListMap('blackList', userBlacklist)
|
||||
}
|
||||
|
||||
function onUserBlacklistUpdated(userBlacklist: IBlackListUserItem[]) {
|
||||
updateContactListMap('blackList', userBlacklist);
|
||||
}
|
||||
|
||||
function onFriendApplicationUnreadCountUpdated(friendApplicationUnreadCount: number) {
|
||||
contactListMap.value.friendApplicationList.unreadCount = friendApplicationUnreadCount;
|
||||
}
|
||||
|
||||
function onFriendApplicationListUpdated(friendApplicationList: FriendApplication[]) {
|
||||
updateContactListMap('friendApplicationList', friendApplicationList);
|
||||
}
|
||||
|
||||
function updateContactListMap(key: keyof IContactList, list: IContactInfoType[]) {
|
||||
contactListMap.value[key].list = list;
|
||||
contactListMap.value[key].list.map((item: IContactInfoType, index: number) => item.renderKey = generateRenderKey(key, item, index));
|
||||
updateCurrentContactInfoFromList(contactListMap.value[key].list, key);
|
||||
}
|
||||
|
||||
function updateCurrentContactInfoFromList(list: IContactInfoType[], type: keyof IContactList) {
|
||||
if (
|
||||
!(currentContactInfo.value as Friend)?.userID
|
||||
&& !(currentContactInfo.value as IGroupModel)?.groupID
|
||||
function onFriendApplicationUnreadCountUpdated(
|
||||
friendApplicationUnreadCount: number
|
||||
) {
|
||||
return;
|
||||
contactListMap.value.friendApplicationList.unreadCount =
|
||||
friendApplicationUnreadCount
|
||||
}
|
||||
if (type === currentContactListKey.value || contactSearchingStatus.value) {
|
||||
currentContactInfo.value = list?.find(
|
||||
(item: any) =>
|
||||
(item?.groupID && item?.groupID === (currentContactInfo.value as IGroupModel)?.groupID) || (item?.userID && item?.userID === (currentContactInfo.value as Friend)?.userID),
|
||||
) || {} as IContactInfoType;
|
||||
TUIStore.update(StoreName.CUSTOM, 'currentContactInfo', currentContactInfo.value);
|
||||
|
||||
function onFriendApplicationListUpdated(
|
||||
friendApplicationList: FriendApplication[]
|
||||
) {
|
||||
updateContactListMap('friendApplicationList', friendApplicationList)
|
||||
}
|
||||
}
|
||||
|
||||
function generateRenderKey(contactListMapKey: keyof IContactList, contactInfo: IContactInfoType, index: number) {
|
||||
return `${contactListMapKey}-${(contactInfo as Friend).userID || (contactInfo as IGroupModel).groupID || (`index${index}`)}`;
|
||||
}
|
||||
function updateContactListMap(
|
||||
key: keyof IContactList,
|
||||
list: IContactInfoType[]
|
||||
) {
|
||||
contactListMap.value[key].list = list
|
||||
contactListMap.value[key].list.map(
|
||||
(item: IContactInfoType, index: number) =>
|
||||
(item.renderKey = generateRenderKey(key, item, index))
|
||||
)
|
||||
updateCurrentContactInfoFromList(contactListMap.value[key].list, key)
|
||||
}
|
||||
|
||||
function onCurrentContactSearchResultUpdated(searchResult: IContactSearchResult) {
|
||||
contactSearchResult.value = searchResult;
|
||||
}
|
||||
function updateCurrentContactInfoFromList(
|
||||
list: IContactInfoType[],
|
||||
type: keyof IContactList
|
||||
) {
|
||||
if (
|
||||
!(currentContactInfo.value as Friend)?.userID &&
|
||||
!(currentContactInfo.value as IGroupModel)?.groupID
|
||||
) {
|
||||
return
|
||||
}
|
||||
if (
|
||||
type === currentContactListKey.value ||
|
||||
contactSearchingStatus.value
|
||||
) {
|
||||
currentContactInfo.value =
|
||||
list?.find(
|
||||
(item: any) =>
|
||||
(item?.groupID &&
|
||||
item?.groupID ===
|
||||
(currentContactInfo.value as IGroupModel)?.groupID) ||
|
||||
(item?.userID &&
|
||||
item?.userID ===
|
||||
(currentContactInfo.value as Friend)?.userID)
|
||||
) || ({} as IContactInfoType)
|
||||
TUIStore.update(
|
||||
StoreName.CUSTOM,
|
||||
'currentContactInfo',
|
||||
currentContactInfo.value
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
function onCurrentContactSearchingStatusUpdated(searchingStatus: boolean) {
|
||||
contactSearchingStatus.value = searchingStatus;
|
||||
TUIStore.update(StoreName.CUSTOM, 'currentContactInfo', {} as IContactInfoType);
|
||||
TUIStore.update(StoreName.CUSTOM, 'currentContactListKey', '');
|
||||
}
|
||||
function generateRenderKey(
|
||||
contactListMapKey: keyof IContactList,
|
||||
contactInfo: IContactInfoType,
|
||||
index: number
|
||||
) {
|
||||
return `${contactListMapKey}-${
|
||||
(contactInfo as Friend).userID ||
|
||||
(contactInfo as IGroupModel).groupID ||
|
||||
`index${index}`
|
||||
}`
|
||||
}
|
||||
|
||||
function onCurrentContactInfoUpdated(contactInfo: IContactInfoType) {
|
||||
currentContactInfo.value = contactInfo;
|
||||
}
|
||||
function onCurrentContactSearchResultUpdated(
|
||||
searchResult: IContactSearchResult
|
||||
) {
|
||||
contactSearchResult.value = searchResult
|
||||
}
|
||||
|
||||
function onCurrentContactListKeyUpdated(contactListKey: string) {
|
||||
currentContactListKey.value = contactListKey;
|
||||
}
|
||||
function onCurrentContactSearchingStatusUpdated(
|
||||
searchingStatus: boolean
|
||||
) {
|
||||
contactSearchingStatus.value = searchingStatus
|
||||
TUIStore.update(
|
||||
StoreName.CUSTOM,
|
||||
'currentContactInfo',
|
||||
{} as IContactInfoType
|
||||
)
|
||||
TUIStore.update(StoreName.CUSTOM, 'currentContactListKey', '')
|
||||
}
|
||||
|
||||
provide('userOnlineStatusMap', userOnlineStatusMap);
|
||||
function onCurrentContactInfoUpdated(contactInfo: IContactInfoType) {
|
||||
currentContactInfo.value = contactInfo
|
||||
}
|
||||
|
||||
function onCurrentContactListKeyUpdated(contactListKey: string) {
|
||||
currentContactListKey.value = contactListKey
|
||||
}
|
||||
|
||||
provide('userOnlineStatusMap', userOnlineStatusMap)
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped src="./style/index.scss"></style>
|
||||
|
||||
@@ -4,19 +4,24 @@
|
||||
v-else-if="isShowContactList"
|
||||
:class="['tui-contact', !isPC && 'tui-contact-h5']"
|
||||
>
|
||||
<Navigation :title="currentContactKey ? contactInfoTitle : TUITranslateService.t('TUIChat.腾讯云 IM')">
|
||||
<Navigation
|
||||
:title="
|
||||
currentContactKey
|
||||
? contactInfoTitle
|
||||
: TUITranslateService.t('TUIChat.腾讯云 IM')
|
||||
"
|
||||
>
|
||||
<template #left>
|
||||
<div v-show="currentContactKey" @click="resetContactType">
|
||||
<Icon
|
||||
:file="backSVG"
|
||||
/>
|
||||
<Icon :file="backSVG" />
|
||||
</div>
|
||||
</template>
|
||||
<template #right>
|
||||
<div v-show="!isShowContactSearch && !currentContactKey" @click="openContactSearch">
|
||||
<Icon
|
||||
:file="addCircle"
|
||||
/>
|
||||
<div
|
||||
v-show="!isShowContactSearch && !currentContactKey"
|
||||
@click="openContactSearch"
|
||||
>
|
||||
<Icon :file="addCircle" />
|
||||
</div>
|
||||
</template>
|
||||
</Navigation>
|
||||
@@ -28,173 +33,204 @@
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
:class="['tui-contact-left', !isPC && 'tui-contact-h5-left']">
|
||||
<ContactSearch
|
||||
v-if="isShowContactSearch"
|
||||
:class="['tui-contact-left', !isPC && 'tui-contact-h5-left']"
|
||||
>
|
||||
<ContactSearch v-if="isShowContactSearch" />
|
||||
<ContactList
|
||||
:class="[
|
||||
'tui-contact-left-list',
|
||||
!isPC && 'tui-contact-h5-left-list'
|
||||
]"
|
||||
/>
|
||||
<ContactList :class="['tui-contact-left-list', !isPC && 'tui-contact-h5-left-list']" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { TUIStore, StoreName, TUITranslateService } from '@tencentcloud/chat-uikit-engine-lite';
|
||||
import { TUIGlobal } from '@tencentcloud/universal-api';
|
||||
import { onMounted, onUnmounted, ref, watchEffect } from '../../adapter-vue';
|
||||
import { isPC, isUniFrameWork } from '../../utils/env';
|
||||
import Navigation from '../common/Navigation/index.vue';
|
||||
import Icon from '../common/Icon.vue';
|
||||
import {
|
||||
TUIStore,
|
||||
StoreName,
|
||||
TUITranslateService
|
||||
} from '@tencentcloud/chat-uikit-engine-lite'
|
||||
import { TUIGlobal } from '@tencentcloud/universal-api'
|
||||
import {
|
||||
onMounted,
|
||||
onUnmounted,
|
||||
ref,
|
||||
watchEffect
|
||||
} from '../../adapter-vue'
|
||||
import { isPC, isUniFrameWork } from '../../utils/env'
|
||||
import Navigation from '../common/Navigation/index.vue'
|
||||
import Icon from '../common/Icon.vue'
|
||||
|
||||
import SelectFriend from './select-friend/index.vue';
|
||||
import ContactSearch from './contact-search/index.vue';
|
||||
import ContactList from './contact-list/index.vue';
|
||||
import ContactInfo from './contact-info/index.vue';
|
||||
import SelectFriend from './select-friend/index.vue'
|
||||
import ContactSearch from './contact-search/index.vue'
|
||||
import ContactList from './contact-list/index.vue'
|
||||
import ContactInfo from './contact-info/index.vue'
|
||||
|
||||
import addCircle from '../../assets/icon/add-circle.svg';
|
||||
import backSVG from '../../assets/icon/back.svg';
|
||||
import { CONTACT_INFO_TITLE } from '../../constant';
|
||||
import addCircle from '../../assets/icon/add-circle.svg'
|
||||
import backSVG from '../../assets/icon/back.svg'
|
||||
import { CONTACT_INFO_TITLE } from '../../constant'
|
||||
|
||||
const emits = defineEmits(['switchConversation']);
|
||||
const emits = defineEmits(['switchConversation'])
|
||||
|
||||
const props = defineProps({
|
||||
// web/h5 single page application display format, uniapp please ignore
|
||||
displayType: {
|
||||
type: String,
|
||||
default: 'contactList', // "contactList" / "selectFriend"
|
||||
require: false,
|
||||
},
|
||||
});
|
||||
|
||||
const displayTypeRef = ref<string>(props.displayType || 'contactList');
|
||||
const isShowSelectFriend = ref(false);
|
||||
const isShowContactList = ref(true);
|
||||
const isShowContactInfo = ref(true);
|
||||
const isShowContactSearch = ref(false);
|
||||
const currentContactKey = ref('');
|
||||
const contactInfoTitle = ref<string>('');
|
||||
|
||||
watchEffect(() => {
|
||||
isShowContactList.value = props?.displayType !== 'selectFriend';
|
||||
});
|
||||
|
||||
const switchConversation = (data: any) => {
|
||||
isUniFrameWork
|
||||
&& TUIGlobal?.navigateTo({
|
||||
url: '/TUIKit/components/TUIChat/index',
|
||||
});
|
||||
emits('switchConversation', data);
|
||||
};
|
||||
|
||||
const openContactSearch = () => {
|
||||
TUIStore.update(
|
||||
StoreName.CUSTOM,
|
||||
'currentContactSearchingStatus',
|
||||
true,
|
||||
);
|
||||
};
|
||||
|
||||
const resetContactType = () => {
|
||||
TUIStore.update(StoreName.CUSTOM, 'currentContactListKey', '');
|
||||
};
|
||||
|
||||
const onCurrentContactSearchingStatusUpdated = (data: boolean) => {
|
||||
isShowContactSearch.value = data;
|
||||
};
|
||||
|
||||
const onIsShowSelectFriendComponentUpdated = (data: any) => {
|
||||
if (!isUniFrameWork && props?.displayType === 'selectFriend') {
|
||||
isShowSelectFriend.value = data;
|
||||
isShowContactList.value = false;
|
||||
return;
|
||||
}
|
||||
if (data) {
|
||||
isShowSelectFriend.value = true;
|
||||
if (isUniFrameWork) {
|
||||
displayTypeRef.value = 'selectFriend';
|
||||
TUIGlobal?.hideTabBar();
|
||||
const props = defineProps({
|
||||
// web/h5 single page application display format, uniapp please ignore
|
||||
displayType: {
|
||||
type: String,
|
||||
default: 'contactList', // "contactList" / "selectFriend"
|
||||
require: false
|
||||
}
|
||||
} else {
|
||||
isShowSelectFriend.value = false;
|
||||
if (isUniFrameWork) {
|
||||
displayTypeRef.value = props.displayType;
|
||||
TUIGlobal?.showTabBar()?.catch(() => { /* ignore */ });
|
||||
})
|
||||
|
||||
const displayTypeRef = ref<string>(props.displayType || 'contactList')
|
||||
const isShowSelectFriend = ref(false)
|
||||
const isShowContactList = ref(true)
|
||||
const isShowContactInfo = ref(true)
|
||||
const isShowContactSearch = ref(false)
|
||||
const currentContactKey = ref('')
|
||||
const contactInfoTitle = ref<string>('')
|
||||
|
||||
watchEffect(() => {
|
||||
isShowContactList.value = props?.displayType !== 'selectFriend'
|
||||
})
|
||||
|
||||
const switchConversation = (data: any) => {
|
||||
isUniFrameWork &&
|
||||
TUIGlobal?.navigateTo({
|
||||
url: '/TUIKit/components/TUIChat/index'
|
||||
})
|
||||
emits('switchConversation', data)
|
||||
}
|
||||
|
||||
const openContactSearch = () => {
|
||||
TUIStore.update(
|
||||
StoreName.CUSTOM,
|
||||
'currentContactSearchingStatus',
|
||||
true
|
||||
)
|
||||
}
|
||||
|
||||
const resetContactType = () => {
|
||||
TUIStore.update(StoreName.CUSTOM, 'currentContactListKey', '')
|
||||
}
|
||||
|
||||
const onCurrentContactSearchingStatusUpdated = (data: boolean) => {
|
||||
isShowContactSearch.value = data
|
||||
}
|
||||
|
||||
const onIsShowSelectFriendComponentUpdated = (data: any) => {
|
||||
if (!isUniFrameWork && props?.displayType === 'selectFriend') {
|
||||
isShowSelectFriend.value = data
|
||||
isShowContactList.value = false
|
||||
return
|
||||
}
|
||||
if (data) {
|
||||
isShowSelectFriend.value = true
|
||||
if (isUniFrameWork) {
|
||||
displayTypeRef.value = 'selectFriend'
|
||||
TUIGlobal?.hideTabBar()
|
||||
}
|
||||
} else {
|
||||
isShowSelectFriend.value = false
|
||||
if (isUniFrameWork) {
|
||||
displayTypeRef.value = props.displayType
|
||||
TUIGlobal?.showTabBar()?.catch(() => {
|
||||
/* ignore */
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const onCurrentContactInfoUpdated = (contactInfo: any) => {
|
||||
isShowContactInfo.value = isPC || (contactInfo && typeof contactInfo === 'object' && Object.keys(contactInfo)?.length > 0);
|
||||
};
|
||||
const onCurrentContactInfoUpdated = (contactInfo: any) => {
|
||||
isShowContactInfo.value =
|
||||
isPC ||
|
||||
(contactInfo &&
|
||||
typeof contactInfo === 'object' &&
|
||||
Object.keys(contactInfo)?.length > 0)
|
||||
}
|
||||
|
||||
const onCurrentContactListKeyUpdated = (key: string) => {
|
||||
currentContactKey.value = key;
|
||||
contactInfoTitle.value = TUITranslateService.t(`TUIContact.${CONTACT_INFO_TITLE[key]}`);
|
||||
};
|
||||
const onCurrentContactListKeyUpdated = (key: string) => {
|
||||
currentContactKey.value = key
|
||||
contactInfoTitle.value = TUITranslateService.t(
|
||||
`TUIContact.${CONTACT_INFO_TITLE[key]}`
|
||||
)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
TUIStore.watch(StoreName.CUSTOM, {
|
||||
currentContactSearchingStatus: onCurrentContactSearchingStatusUpdated,
|
||||
isShowSelectFriendComponent: onIsShowSelectFriendComponentUpdated,
|
||||
currentContactInfo: onCurrentContactInfoUpdated,
|
||||
currentContactListKey: onCurrentContactListKeyUpdated,
|
||||
});
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
TUIStore.unwatch(StoreName.CUSTOM, {
|
||||
currentContactSearchingStatus: onCurrentContactSearchingStatusUpdated,
|
||||
isShowSelectFriendComponent: onIsShowSelectFriendComponentUpdated,
|
||||
currentContactInfo: onCurrentContactInfoUpdated,
|
||||
currentContactListKey: onCurrentContactListKeyUpdated,
|
||||
});
|
||||
});
|
||||
onMounted(() => {
|
||||
TUIStore.watch(StoreName.CUSTOM, {
|
||||
currentContactSearchingStatus:
|
||||
onCurrentContactSearchingStatusUpdated,
|
||||
isShowSelectFriendComponent: onIsShowSelectFriendComponentUpdated,
|
||||
currentContactInfo: onCurrentContactInfoUpdated,
|
||||
currentContactListKey: onCurrentContactListKeyUpdated
|
||||
})
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
TUIStore.unwatch(StoreName.CUSTOM, {
|
||||
currentContactSearchingStatus:
|
||||
onCurrentContactSearchingStatusUpdated,
|
||||
isShowSelectFriendComponent: onIsShowSelectFriendComponentUpdated,
|
||||
currentContactInfo: onCurrentContactInfoUpdated,
|
||||
currentContactListKey: onCurrentContactListKeyUpdated
|
||||
})
|
||||
})
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
@import "../../assets/styles/common";
|
||||
@import '../../assets/styles/common';
|
||||
|
||||
.tui-contact {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
uni-page-body,
|
||||
html,
|
||||
body,
|
||||
page {
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
&-left {
|
||||
min-width: 285px;
|
||||
flex: 0 0 24%;
|
||||
.tui-contact {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
&-right {
|
||||
border-left: 1px solid #f4f5f9;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
&-left {
|
||||
min-width: 285px;
|
||||
flex: 0 0 24%;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.tui-contact-h5 {
|
||||
position: relative;
|
||||
|
||||
&-left,
|
||||
&-right {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
&-right {
|
||||
position: absolute;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
&-left {
|
||||
&-list {
|
||||
overflow-y: auto;
|
||||
&-right {
|
||||
border-left: 1px solid #f4f5f9;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.tui-contact-h5 {
|
||||
position: relative;
|
||||
|
||||
&-left,
|
||||
&-right {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
&-right {
|
||||
position: absolute;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
&-left {
|
||||
&-list {
|
||||
overflow-y: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -18,174 +18,195 @@
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { TUIStore, StoreName } from '@tencentcloud/chat-uikit-engine-lite';
|
||||
import { TUIGlobal } from '@tencentcloud/universal-api';
|
||||
import { ref } from '../../adapter-vue';
|
||||
import TUISearch from '../TUISearch/index.vue';
|
||||
import ConversationList from './conversation-list/index.vue';
|
||||
import ConversationHeader from './conversation-header/index.vue';
|
||||
import ConversationNetwork from './conversation-network/index.vue';
|
||||
import { onHide } from '@dcloudio/uni-app';
|
||||
// #ifdef MP-WEIXIN
|
||||
// uniapp packaged mini-programs are integrated by default, and the default initialization entry file is imported here
|
||||
// TUIChatKit init needs to be encapsulated because uni vue2 will report an error when compiling H5 directly through conditional compilation
|
||||
import './entry.ts';
|
||||
// #endif
|
||||
import {
|
||||
TUIStore,
|
||||
StoreName
|
||||
} from '@tencentcloud/chat-uikit-engine-lite'
|
||||
import { TUIGlobal } from '@tencentcloud/universal-api'
|
||||
import { ref } from '../../adapter-vue'
|
||||
import TUISearch from '../TUISearch/index.vue'
|
||||
import ConversationList from './conversation-list/index.vue'
|
||||
import ConversationHeader from './conversation-header/index.vue'
|
||||
import ConversationNetwork from './conversation-network/index.vue'
|
||||
import { onHide } from '@dcloudio/uni-app'
|
||||
// #ifdef MP-WEIXIN
|
||||
// uniapp packaged mini-programs are integrated by default, and the default initialization entry file is imported here
|
||||
// TUIChatKit init needs to be encapsulated because uni vue2 will report an error when compiling H5 directly through conditional compilation
|
||||
import './entry.ts'
|
||||
// #endif
|
||||
|
||||
const emits = defineEmits(['handleSwitchConversation']);
|
||||
const emits = defineEmits(['handleSwitchConversation'])
|
||||
|
||||
const totalUnreadCount = ref(0);
|
||||
const headerRef = ref<typeof ConversationHeader>();
|
||||
const conversationListDomRef = ref<typeof ConversationList>();
|
||||
const touchX = ref<number>(0);
|
||||
const touchY = ref<number>(0);
|
||||
const isShowConversationHeader = ref<boolean>(true);
|
||||
const totalUnreadCount = ref(0)
|
||||
const headerRef = ref<typeof ConversationHeader>()
|
||||
const conversationListDomRef = ref<typeof ConversationList>()
|
||||
const touchX = ref<number>(0)
|
||||
const touchY = ref<number>(0)
|
||||
const isShowConversationHeader = ref<boolean>(true)
|
||||
|
||||
const getTabBarConfig = () => {
|
||||
try {
|
||||
// 方法1: 从 getApp() 全局数据中获取
|
||||
const app = getApp();
|
||||
if (app?.globalData?.tabBar) {
|
||||
return app.globalData.tabBar;
|
||||
}
|
||||
|
||||
// 方法2: 从 pages.json 配置中读取(如果可访问)
|
||||
// @ts-ignore
|
||||
if (typeof __uniConfig !== 'undefined' && __uniConfig.tabBar) {
|
||||
// @ts-ignore
|
||||
return __uniConfig.tabBar;
|
||||
}
|
||||
|
||||
// 方法3: 尝试调用 uni.getTabBar() 检测是否存在 tabbar
|
||||
const getTabBarConfig = () => {
|
||||
try {
|
||||
const tabBar = uni.getTabBar && uni.getTabBar();
|
||||
if (tabBar) {
|
||||
return { list: tabBar};
|
||||
// 方法1: 从 getApp() 全局数据中获取
|
||||
const app = getApp()
|
||||
if (app?.globalData?.tabBar) {
|
||||
return app.globalData.tabBar
|
||||
}
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return null;
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const getTabBarIndex = () => {
|
||||
try {
|
||||
|
||||
const pages = getCurrentPages();
|
||||
if (!pages || pages.length === 0) return;
|
||||
|
||||
const currentPage = pages[pages.length - 1];
|
||||
const currentRoute = currentPage.route;
|
||||
|
||||
const isTUIConversationPage = currentRoute && (
|
||||
currentRoute.includes('TUIKit/components/TUIConversation/index') ||
|
||||
currentRoute.includes('TUIConversation')
|
||||
);
|
||||
|
||||
if (!isTUIConversationPage) {
|
||||
return;
|
||||
}
|
||||
|
||||
const tabBarConfig = getTabBarConfig();
|
||||
|
||||
if (!tabBarConfig) {
|
||||
return;
|
||||
}
|
||||
|
||||
let tabBarIndex = -1;
|
||||
|
||||
if (tabBarConfig.list && Array.isArray(tabBarConfig.list)) {
|
||||
tabBarIndex = tabBarConfig.list.findIndex((item: any) => {
|
||||
const pagePath = item.pagePath || '';
|
||||
return pagePath.includes('TUIConversation') ||
|
||||
pagePath.includes('TUIKit/components/TUIConversation/index');
|
||||
});
|
||||
}
|
||||
return tabBarIndex;
|
||||
} catch (error) {
|
||||
return -1;
|
||||
}
|
||||
};
|
||||
// 方法2: 从 pages.json 配置中读取(如果可访问)
|
||||
// @ts-ignore
|
||||
if (typeof __uniConfig !== 'undefined' && __uniConfig.tabBar) {
|
||||
// @ts-ignore
|
||||
return __uniConfig.tabBar
|
||||
}
|
||||
|
||||
TUIStore.watch(StoreName.CONV, {
|
||||
totalUnreadCount: (count: number) => {
|
||||
totalUnreadCount.value = count;
|
||||
const tabBarIndex = getTabBarIndex() ?? -1;
|
||||
if (tabBarIndex >= 0) {
|
||||
if (count > 0) {
|
||||
uni.setTabBarBadge({
|
||||
index: tabBarIndex,
|
||||
text: count > 99 ? '99+' : count.toString(),
|
||||
});
|
||||
} else {
|
||||
uni.removeTabBarBadge({
|
||||
index: tabBarIndex,
|
||||
});
|
||||
// 方法3: 尝试调用 uni.getTabBar() 检测是否存在 tabbar
|
||||
try {
|
||||
const tabBar = uni.getTabBar && uni.getTabBar()
|
||||
if (tabBar) {
|
||||
return { list: tabBar }
|
||||
}
|
||||
} catch (e) {
|
||||
return null
|
||||
}
|
||||
|
||||
return null
|
||||
} catch (error) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
const getTabBarIndex = () => {
|
||||
try {
|
||||
const pages = getCurrentPages()
|
||||
if (!pages || pages.length === 0) return
|
||||
|
||||
const currentPage = pages[pages.length - 1]
|
||||
const currentRoute = currentPage.route
|
||||
|
||||
const isTUIConversationPage =
|
||||
currentRoute &&
|
||||
(currentRoute.includes(
|
||||
'TUIKit/components/TUIConversation/index'
|
||||
) ||
|
||||
currentRoute.includes('TUIConversation'))
|
||||
|
||||
if (!isTUIConversationPage) {
|
||||
return
|
||||
}
|
||||
|
||||
const tabBarConfig = getTabBarConfig()
|
||||
|
||||
if (!tabBarConfig) {
|
||||
return
|
||||
}
|
||||
|
||||
let tabBarIndex = -1
|
||||
|
||||
if (tabBarConfig.list && Array.isArray(tabBarConfig.list)) {
|
||||
tabBarIndex = tabBarConfig.list.findIndex((item: any) => {
|
||||
const pagePath = item.pagePath || ''
|
||||
return (
|
||||
pagePath.includes('TUIConversation') ||
|
||||
pagePath.includes('TUIKit/components/TUIConversation/index')
|
||||
)
|
||||
})
|
||||
}
|
||||
return tabBarIndex
|
||||
} catch (error) {
|
||||
return -1
|
||||
}
|
||||
}
|
||||
|
||||
TUIStore.watch(StoreName.CONV, {
|
||||
totalUnreadCount: (count: number) => {
|
||||
totalUnreadCount.value = count
|
||||
const tabBarIndex = getTabBarIndex() ?? -1
|
||||
if (tabBarIndex >= 0) {
|
||||
if (count > 0) {
|
||||
uni.setTabBarBadge({
|
||||
index: tabBarIndex,
|
||||
text: count > 99 ? '99+' : count.toString()
|
||||
})
|
||||
} else {
|
||||
uni.removeTabBarBadge({
|
||||
index: tabBarIndex
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
})
|
||||
|
||||
TUIStore.watch(StoreName.CUSTOM, {
|
||||
isShowConversationHeader: (showStatus: boolean) => {
|
||||
isShowConversationHeader.value = showStatus !== false;
|
||||
},
|
||||
});
|
||||
TUIStore.watch(StoreName.CUSTOM, {
|
||||
isShowConversationHeader: (showStatus: boolean) => {
|
||||
isShowConversationHeader.value = showStatus !== false
|
||||
}
|
||||
})
|
||||
|
||||
const handleSwitchConversation = (conversationID: string) => {
|
||||
TUIGlobal?.navigateTo({
|
||||
url: '/TUIKit/components/TUIChat/index',
|
||||
});
|
||||
emits('handleSwitchConversation', conversationID);
|
||||
};
|
||||
|
||||
const closeChildren = () => {
|
||||
headerRef?.value?.closeChildren();
|
||||
conversationListDomRef?.value?.closeChildren();
|
||||
};
|
||||
|
||||
const handleClickConv = () => {
|
||||
closeChildren();
|
||||
};
|
||||
|
||||
onHide(closeChildren);
|
||||
|
||||
const handleTouchStart = (e: any) => {
|
||||
touchX.value = e.changedTouches[0].clientX;
|
||||
touchY.value = e.changedTouches[0].clientY;
|
||||
};
|
||||
|
||||
const handleTouchEnd = (e: any) => {
|
||||
const x = e.changedTouches[0].clientX;
|
||||
const y = e.changedTouches[0].clientY;
|
||||
let turn = '';
|
||||
if (x - touchX.value > 50 && Math.abs(y - touchY.value) < 50) {
|
||||
// Swipe right
|
||||
turn = 'right';
|
||||
} else if (x - touchX.value < -50 && Math.abs(y - touchY.value) < 50) {
|
||||
// Swipe left
|
||||
turn = 'left';
|
||||
const handleSwitchConversation = (conversationID: string) => {
|
||||
TUIGlobal?.navigateTo({
|
||||
url: '/TUIKit/components/TUIChat/index'
|
||||
})
|
||||
emits('handleSwitchConversation', conversationID)
|
||||
}
|
||||
if (y - touchY.value > 50 && Math.abs(x - touchX.value) < 50) {
|
||||
// Swipe down
|
||||
turn = 'down';
|
||||
} else if (y - touchY.value < -50 && Math.abs(x - touchX.value) < 50) {
|
||||
// Swipe up
|
||||
turn = 'up';
|
||||
}
|
||||
// Operate according to the direction
|
||||
if (turn === 'down' || turn === 'up') {
|
||||
closeChildren();
|
||||
}
|
||||
};
|
||||
|
||||
const getPassingRef = (ref) => {
|
||||
ref.value = conversationListDomRef.value;
|
||||
};
|
||||
const closeChildren = () => {
|
||||
headerRef?.value?.closeChildren()
|
||||
conversationListDomRef?.value?.closeChildren()
|
||||
}
|
||||
|
||||
const handleClickConv = () => {
|
||||
closeChildren()
|
||||
}
|
||||
|
||||
onHide(closeChildren)
|
||||
|
||||
const handleTouchStart = (e: any) => {
|
||||
touchX.value = e.changedTouches[0].clientX
|
||||
touchY.value = e.changedTouches[0].clientY
|
||||
}
|
||||
|
||||
const handleTouchEnd = (e: any) => {
|
||||
const x = e.changedTouches[0].clientX
|
||||
const y = e.changedTouches[0].clientY
|
||||
let turn = ''
|
||||
if (x - touchX.value > 50 && Math.abs(y - touchY.value) < 50) {
|
||||
// Swipe right
|
||||
turn = 'right'
|
||||
} else if (
|
||||
x - touchX.value < -50 &&
|
||||
Math.abs(y - touchY.value) < 50
|
||||
) {
|
||||
// Swipe left
|
||||
turn = 'left'
|
||||
}
|
||||
if (y - touchY.value > 50 && Math.abs(x - touchX.value) < 50) {
|
||||
// Swipe down
|
||||
turn = 'down'
|
||||
} else if (
|
||||
y - touchY.value < -50 &&
|
||||
Math.abs(x - touchX.value) < 50
|
||||
) {
|
||||
// Swipe up
|
||||
turn = 'up'
|
||||
}
|
||||
// Operate according to the direction
|
||||
if (turn === 'down' || turn === 'up') {
|
||||
closeChildren()
|
||||
}
|
||||
}
|
||||
|
||||
const getPassingRef = ref => {
|
||||
ref.value = conversationListDomRef.value
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped src="./style/index.scss"></style>
|
||||
<style lang="scss" scoped src="./style/index.scss">
|
||||
uni-page-body,
|
||||
html,
|
||||
body,
|
||||
page {
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
|
||||
10
api/index.js
10
api/index.js
@@ -34,6 +34,15 @@ export const getUserData = () => {
|
||||
})
|
||||
}
|
||||
|
||||
/** 修改用户信息 */
|
||||
export const updateUserData = data => {
|
||||
return http({
|
||||
url: '/api/user/edit',
|
||||
method: 'put',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
/** 获取用户地址列表 */
|
||||
export const getUserAddress = (data, loading = true) => {
|
||||
return http({
|
||||
@@ -128,3 +137,4 @@ export const getTencentUserSig = () => {
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -50,3 +50,94 @@ export const getUserPayPwd = () => {
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加/修改支付密码
|
||||
* @param {*} data
|
||||
* @param {*} method post 添加 put 修改
|
||||
* @returns
|
||||
*/
|
||||
export const updateUserPayPwd = (data, method = 'post') => {
|
||||
return http({
|
||||
url: '/api/service/userPassword',
|
||||
method,
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加/修改银行卡
|
||||
* @param {*} data
|
||||
* @param {*} method post 添加 put 修改
|
||||
* @returns
|
||||
*/
|
||||
export const addUserPayPwd = (data, method = 'post') => {
|
||||
return http({
|
||||
url: '/api/service/userCard',
|
||||
method,
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
/** 删除银行卡 */
|
||||
export const deleteUserPayPwd = id => {
|
||||
return http({
|
||||
url: `/api/service/userCard/${id}`,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
|
||||
/** 获取银行卡列表 */
|
||||
export const getUserBankList = () => {
|
||||
return http({
|
||||
url: '/api/service/userCard/list',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
/** 获取银行卡详情 */
|
||||
export const getUserBankDetail = id => {
|
||||
return http({
|
||||
url: `/api/service/userCard/details/${id}`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加/修改用户第三方支付账户
|
||||
* @param {*} data 提交数据
|
||||
* @param {*} method post 添加 put 修改
|
||||
* @returns
|
||||
*/
|
||||
export const addUserThirdPay = (data, method = 'post') => {
|
||||
return http({
|
||||
url: '/api/service/userPayment',
|
||||
method,
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
/** 获取用户第三方支付详情 */
|
||||
export const getUserThirdPay = id => {
|
||||
return http({
|
||||
url: `/api/service/userPayment/${id}`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
/** 获取身份证信息 */
|
||||
export const getUserIdCard = () => {
|
||||
return http({
|
||||
url: '/api/service/userVerification/details',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
/** 添加身份证 */
|
||||
export const addUserIdCard = data => {
|
||||
return http({
|
||||
url: '/api/service/userVerification',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
@@ -28,6 +28,12 @@
|
||||
default: () => []
|
||||
})
|
||||
|
||||
/** 图片回显列表 */
|
||||
const fileLists = defineModel('list', {
|
||||
type: Array,
|
||||
default: () => []
|
||||
})
|
||||
|
||||
/** 是否为身份证状态 */
|
||||
const isIdCard = computed(() => props.isFront || props.isBack)
|
||||
|
||||
@@ -61,25 +67,31 @@
|
||||
}
|
||||
|
||||
const onSelect = async e => {
|
||||
e.tempFiles.forEach(v => {
|
||||
uploadFile(v.path)
|
||||
})
|
||||
|
||||
return
|
||||
const upData = e.tempFiles.map(v => v.path)[0]
|
||||
imageValue.value = await uploadSingleFile(upData, {
|
||||
url: '/api/common/admin/upload/up/single'
|
||||
})
|
||||
if (props.limit === '1') {
|
||||
const upData = e.tempFiles.map(v => v.path)[0]
|
||||
imageValue.value = await uploadSingleFile(upData, {
|
||||
url: '/api/common/admin/upload/up/single'
|
||||
})
|
||||
} else {
|
||||
e.tempFiles.forEach(v => {
|
||||
uploadFile(v.path)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const onDelete = e => {
|
||||
imageValue.value.splice(e.index, 1)
|
||||
if (props.limit === '1') {
|
||||
imageValue.value = ''
|
||||
} else {
|
||||
imageValue.value.splice(e.index, 1)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view :class="{ 'file_card-box': isIdCard }" class="cb-file-picker">
|
||||
<uni-file-picker
|
||||
v-model="fileLists"
|
||||
:file-mediatype="props.type"
|
||||
:image-styles="imageStyles"
|
||||
:limit="props.limit"
|
||||
|
||||
63
package-lock.json
generated
63
package-lock.json
generated
@@ -9,7 +9,11 @@
|
||||
"version": "1.0.0",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@tencentcloud/chat-uikit-uniapp": "^3.2.0",
|
||||
"@tencentcloud/chat-uikit-engine-lite": "1.0.3",
|
||||
"@tencentcloud/chat-uikit-uniapp": "3.1.0",
|
||||
"@tencentcloud/tui-core-lite": "1.0.0",
|
||||
"@tencentcloud/universal-api": "^2.4.0",
|
||||
"dayjs": "^1.11.10",
|
||||
"unplugin-vue2-script-setup": "^0.11.4"
|
||||
}
|
||||
},
|
||||
@@ -654,18 +658,18 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tencentcloud/chat-uikit-engine-lite": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://repo.huaweicloud.com/repository/npm/@tencentcloud/chat-uikit-engine-lite/-/chat-uikit-engine-lite-1.0.4.tgz",
|
||||
"integrity": "sha512-YfDuXDkUlz7V1RA6iAEO2PLBI/efz60TRwAVy/Vhlu7oqZOMlphra72J30N3n5WhxbsFfcfTShoqW3OwcatUQg==",
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://repo.huaweicloud.com/repository/npm/@tencentcloud/chat-uikit-engine-lite/-/chat-uikit-engine-lite-1.0.3.tgz",
|
||||
"integrity": "sha512-Zh539tplI+BY1uljuXVoOCzR1k/UuBhX2bx9fZw0yRDKf3KGWWAjnkDzl3erpgYpNGXX6qPd/PhPhLWbEB5ouw==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@tencentcloud/lite-chat": "^1.6.3"
|
||||
"@tencentcloud/lite-chat": "^1.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tencentcloud/chat-uikit-uniapp": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://repo.huaweicloud.com/repository/npm/@tencentcloud/chat-uikit-uniapp/-/chat-uikit-uniapp-3.2.0.tgz",
|
||||
"integrity": "sha512-5qUVRcsndwhWLAidgFMTk/JAhnkl2HrHux9jFJrk5iXZzXPdv6Wn9G2MA2bZcMQrWirYuTd7sAH/umcDYZsk3Q==",
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://repo.huaweicloud.com/repository/npm/@tencentcloud/chat-uikit-uniapp/-/chat-uikit-uniapp-3.1.0.tgz",
|
||||
"integrity": "sha512-cKQBaFGscSlwBK0fjK7feMY675c3K35uoUbntkiFZo2o8UwW2w5Cmp/76HbaQ0DY/wxG/fMTbfU9RQCfVe3SjQ==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@tencentcloud/chat-uikit-engine-lite": "~1.0.3",
|
||||
@@ -680,9 +684,9 @@
|
||||
"unplugin-vue2-script-setup": "^0.11.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@trtc/calls-uikit-vue": "~4.4.0",
|
||||
"@trtc/calls-uikit-vue2.6": "~4.4.0",
|
||||
"@trtc/calls-uikit-wx": "~4.2.0"
|
||||
"@trtc/calls-uikit-vue": "4.2.2",
|
||||
"@trtc/calls-uikit-vue2.6": "4.2.2",
|
||||
"@trtc/calls-uikit-wx": "4.2.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@tencentcloud/lite-chat": {
|
||||
@@ -735,13 +739,13 @@
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/@trtc/call-engine-lite-js": {
|
||||
"version": "3.5.0",
|
||||
"resolved": "https://repo.huaweicloud.com/repository/npm/@trtc/call-engine-lite-js/-/call-engine-lite-js-3.5.0.tgz",
|
||||
"integrity": "sha512-XSKDFBOcs6kITZnbLdECg889bbhTbdmHdg/EQRmGJx/2KakEe/uubfSDydOPBO47UgV2QZ9dGiRVAX5SCY2g6A==",
|
||||
"version": "3.4.8",
|
||||
"resolved": "https://repo.huaweicloud.com/repository/npm/@trtc/call-engine-lite-js/-/call-engine-lite-js-3.4.8.tgz",
|
||||
"integrity": "sha512-VU+2VMhuNUfJg3F8TZ992d6iB33Rjc/xhbez0OkzYyejpaOtU1mExdU25On4xjeAx7XIZ6n80vfU5uEBsPusyw==",
|
||||
"license": "ISC",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@tencentcloud/lite-chat": "^1.6.3",
|
||||
"@tencentcloud/lite-chat": "^1.5.0",
|
||||
"core-js": "^3.8.3",
|
||||
"eventemitter3": "^4.0.7",
|
||||
"rtc-detect": "^0.0.5",
|
||||
@@ -762,41 +766,40 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@trtc/calls-uikit-vue": {
|
||||
"version": "4.4.2",
|
||||
"resolved": "https://repo.huaweicloud.com/repository/npm/@trtc/calls-uikit-vue/-/calls-uikit-vue-4.4.2.tgz",
|
||||
"integrity": "sha512-a5oaNSO4qN0FSsDb65Kl9BEghDdWV0l5RdjMgaWEjrqNVVCuz7p5s7/6AYaB78dpdsIC9U9FXTVbM2+u79r4zw==",
|
||||
"version": "4.2.2",
|
||||
"resolved": "https://repo.huaweicloud.com/repository/npm/@trtc/calls-uikit-vue/-/calls-uikit-vue-4.2.2.tgz",
|
||||
"integrity": "sha512-xAHBo2RXb/PDCcd07sdftgE2HtiW3QC3hXY8g5yWfTkJ3WIPfDi5OsG9lti0KgzF9ZnfMf4i08ptAoC3k72iHQ==",
|
||||
"license": "ISC",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@tencentcloud/lite-chat": "^1.6.3",
|
||||
"@tencentcloud/lite-chat": "^1.5.0",
|
||||
"@tencentcloud/tui-core-lite": "1.0.0",
|
||||
"@trtc/call-engine-lite-js": "~3.5.0"
|
||||
"@trtc/call-engine-lite-js": "~3.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@trtc/calls-uikit-vue2.6": {
|
||||
"version": "4.4.2",
|
||||
"resolved": "https://repo.huaweicloud.com/repository/npm/@trtc/calls-uikit-vue2.6/-/calls-uikit-vue2.6-4.4.2.tgz",
|
||||
"integrity": "sha512-KDHFXQVFMAPCpb0aRrOHAh3LPA4tLlr/LhTMDxyUJbJN6CGhYRIlbDyQ0BHl/z7yHYNkTwXaHkahpCJCmnaPNQ==",
|
||||
"version": "4.2.2",
|
||||
"resolved": "https://repo.huaweicloud.com/repository/npm/@trtc/calls-uikit-vue2.6/-/calls-uikit-vue2.6-4.2.2.tgz",
|
||||
"integrity": "sha512-OjB3R4/vrQiK5uF3FG6UlmUOR+3CvbZ8YMwm6MI0KfXyg27EJOq315JdQKJgX5ZU7HzUstfrMtxYTbGfUJKAcA==",
|
||||
"license": "ISC",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@tencentcloud/lite-chat": "^1.6.3",
|
||||
"@tencentcloud/lite-chat": "^1.5.0",
|
||||
"@tencentcloud/tui-core-lite": "1.0.0",
|
||||
"@trtc/call-engine-lite-js": "~3.5.0",
|
||||
"@trtc/call-engine-lite-wx": "~3.4.7",
|
||||
"@trtc/call-engine-lite-js": "~3.4.0",
|
||||
"@vue/composition-api": "^1.7.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@trtc/calls-uikit-wx": {
|
||||
"version": "4.2.4",
|
||||
"resolved": "https://repo.huaweicloud.com/repository/npm/@trtc/calls-uikit-wx/-/calls-uikit-wx-4.2.4.tgz",
|
||||
"integrity": "sha512-PuqIfEqUrLfwCxip1EjOj2HAObzZBvzN3Gpx8ULvWdPu0hqVZIPobo3aTtfGKUCdJPmNq+/iLVQ3MwneXhtR5A==",
|
||||
"version": "4.2.2",
|
||||
"resolved": "https://repo.huaweicloud.com/repository/npm/@trtc/calls-uikit-wx/-/calls-uikit-wx-4.2.2.tgz",
|
||||
"integrity": "sha512-pW0l3UhiekcTZlLDVPBnsvYyIhrjU0Mod9joL+KlO8gXnK/qeBauAyAz8AaipsveJV4FYUh6AZJkDNN+DzAgig==",
|
||||
"license": "ISC",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@tencentcloud/trtc-component-wx": "^1.0.5",
|
||||
"@tencentcloud/tui-core-lite": "1.0.0",
|
||||
"@trtc/call-engine-lite-wx": "~3.4.7",
|
||||
"@trtc/call-engine-lite-wx": "~3.4.3",
|
||||
"aegis-mp-sdk": "latest"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -10,7 +10,11 @@
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@tencentcloud/chat-uikit-uniapp": "^3.2.0",
|
||||
"unplugin-vue2-script-setup": "^0.11.4"
|
||||
"@tencentcloud/chat-uikit-engine-lite": "1.0.3",
|
||||
"@tencentcloud/chat-uikit-uniapp": "3.1.0",
|
||||
"@tencentcloud/tui-core-lite": "1.0.0",
|
||||
"@tencentcloud/universal-api": "^2.4.0",
|
||||
"unplugin-vue2-script-setup": "^0.11.4",
|
||||
"dayjs": "^1.11.10"
|
||||
}
|
||||
}
|
||||
|
||||
58
pages.json
58
pages.json
@@ -14,6 +14,54 @@
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "TUIKit/components/TUIConversation/index",
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "TUIKit/components/TUIChat/index",
|
||||
"style": {
|
||||
"navigationStyle": "custom",
|
||||
"app-plus": {
|
||||
"softinputMode": "adjustResize"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "TUIKit/components/TUIContact/index",
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "TUIKit/components/TUIChat/video-play",
|
||||
"style": {
|
||||
"navigationBarTitleText": "腾讯云 IM",
|
||||
"navigationBarBackgroundColor": "#EBF0F6"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "TUIKit/components/TUIChat/web-view",
|
||||
"style": {
|
||||
"navigationBarTitleText": "腾讯云 IM",
|
||||
"navigationBarBackgroundColor": "#EBF0F6"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "TUIKit/components/TUIGroup/index",
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "TUIKit/components/TUISearch/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "聊天记录",
|
||||
"navigationBarBackgroundColor": "#EBF0F6"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/login/protocol",
|
||||
"style": {
|
||||
@@ -76,8 +124,8 @@
|
||||
{
|
||||
"path": "pages/my-index/personal-center/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "个人中心",
|
||||
"navigationBarBackgroundColor": "#ffffff"
|
||||
"navigationStyle": "custom",
|
||||
"navigationBarTitleText": "个人中心"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -262,13 +310,13 @@
|
||||
"backgroundColor": "#ffffff",
|
||||
"list": [
|
||||
{
|
||||
"pagePath": "pages/news-list/news-list",
|
||||
"pagePath": "TUIKit/components/TUIConversation/index",
|
||||
"iconPath": "static/images/tabBar/news.png",
|
||||
"selectedIconPath": "static/images/tabBar/newsHL.png",
|
||||
"text": "消息"
|
||||
},
|
||||
{
|
||||
"pagePath": "pages/contacts/contacts",
|
||||
"pagePath": "TUIKit/components/TUIContact/index",
|
||||
"iconPath": "static/images/tabBar/contacts.png",
|
||||
"selectedIconPath": "static/images/tabBar/contactsHL.png",
|
||||
"text": "通讯录"
|
||||
@@ -287,4 +335,4 @@
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,7 +94,7 @@
|
||||
<view class="swipe-box">
|
||||
<view
|
||||
v-if="item.defaultAddress == 0"
|
||||
class="btn-box"
|
||||
class="public-uni-swipe-action-right"
|
||||
@click="onDefault(item)"
|
||||
>
|
||||
<uni-icons
|
||||
@@ -104,7 +104,10 @@
|
||||
></uni-icons>
|
||||
<text class="iocn-name">设为默认</text>
|
||||
</view>
|
||||
<view class="btn-box" @click="onDelete(item)">
|
||||
<view
|
||||
class="public-uni-swipe-action-right"
|
||||
@click="onDelete(item)"
|
||||
>
|
||||
<uni-icons
|
||||
type="trash"
|
||||
size="18"
|
||||
@@ -121,6 +124,8 @@
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '@/styles/global.scss';
|
||||
|
||||
.top-right-name {
|
||||
font-family: PingFang SC, PingFang SC;
|
||||
font-weight: 500;
|
||||
@@ -181,28 +186,6 @@
|
||||
}
|
||||
.swipe-box {
|
||||
display: flex;
|
||||
.btn-box {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: rgb(47, 194, 17);
|
||||
width: 130rpx;
|
||||
.iocn-name {
|
||||
font-family: PingFang SC, PingFang SC;
|
||||
font-weight: 500;
|
||||
font-size: 28rpx;
|
||||
color: #fff;
|
||||
text-align: left;
|
||||
font-style: normal;
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
// 最后一个
|
||||
&:last-child {
|
||||
background: rgb(206, 59, 22);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
})
|
||||
setToken(res.token)
|
||||
await fetchUserInfo()
|
||||
reLaunch('/pages/news-list/news-list')
|
||||
reLaunch('/TUIKit/components/TUIConversation/index')
|
||||
}
|
||||
|
||||
const onRegister = () => {
|
||||
|
||||
@@ -53,6 +53,9 @@
|
||||
:placeholder-style="placeholderStyle"
|
||||
:placeholder="props.placeholder"
|
||||
/>
|
||||
<view v-else class="right-box">
|
||||
<slot name="right"></slot>
|
||||
</view>
|
||||
</view>
|
||||
<view v-if="hasDefaultSlot" class="bottom-slot">
|
||||
<slot></slot>
|
||||
@@ -74,6 +77,10 @@
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
.right-box {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
.bottom-slot {
|
||||
margin-top: 26rpx;
|
||||
|
||||
81
pages/my-index/components/popup-box.vue
Normal file
81
pages/my-index/components/popup-box.vue
Normal file
@@ -0,0 +1,81 @@
|
||||
<script setup>
|
||||
import { ref, watch } from 'vue'
|
||||
|
||||
const show = defineModel({
|
||||
type: Boolean,
|
||||
default: false
|
||||
})
|
||||
|
||||
const name = defineModel('name', {
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
})
|
||||
|
||||
const props = defineProps({
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
/** 是否为性别选项 */
|
||||
isSex: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
})
|
||||
|
||||
const emits = defineEmits(['confirm'])
|
||||
|
||||
const inputDialog = ref(null)
|
||||
|
||||
watch(
|
||||
() => show.value,
|
||||
v => {
|
||||
if (v) {
|
||||
// 'bottom'
|
||||
inputDialog.value.open()
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
const close = () => {
|
||||
show.value = false
|
||||
}
|
||||
const dialogInputConfirm = () => {
|
||||
close()
|
||||
emits('confirm')
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<uni-popup ref="inputDialog" type="dialog">
|
||||
<uni-popup-dialog
|
||||
v-model="name"
|
||||
mode="input"
|
||||
:title="props.title"
|
||||
:placeholder="`请输入${props.title}`"
|
||||
@close="close"
|
||||
@confirm="dialogInputConfirm"
|
||||
>
|
||||
<uni-data-checkbox
|
||||
v-if="props.isSex"
|
||||
v-model="name"
|
||||
:localdata="[
|
||||
{
|
||||
text: '男',
|
||||
value: '0'
|
||||
},
|
||||
{
|
||||
text: '女',
|
||||
value: '1'
|
||||
},
|
||||
{
|
||||
text: '未知',
|
||||
value: '2'
|
||||
}
|
||||
]"
|
||||
></uni-data-checkbox>
|
||||
</uni-popup-dialog>
|
||||
</uni-popup>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
@@ -52,10 +52,12 @@
|
||||
>
|
||||
<view class="left-box">
|
||||
<image
|
||||
src="https://wx1.sinaimg.cn/mw690/92eeb099gy1i29hl0ne80j21jk2bcash.jpg"
|
||||
mode="scaleToFill"
|
||||
v-if="userInfo?.avatar"
|
||||
:src="userInfo?.avatar"
|
||||
mode="aspectFill"
|
||||
class="avatar"
|
||||
></image>
|
||||
<uni-icons v-else type="contact-filled" size="70"></uni-icons>
|
||||
<view class="nickname">
|
||||
<text class="name">{{ userInfo?.userName || '' }}</text>
|
||||
<text class="name">ID:{{ userInfo?.userId || '' }}</text>
|
||||
|
||||
@@ -1,27 +1,84 @@
|
||||
<script setup>
|
||||
import { reactive } from 'vue'
|
||||
import { useAuthUser } from '@/composables/useAuthUser'
|
||||
import PopupBox from '../components/popup-box.vue'
|
||||
import { chooseImage } from '@/utils/media.js'
|
||||
import { uploadSingleFile } from '@/utils/uploadFile'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
|
||||
const { updateUserInfo } = useUserStore()
|
||||
|
||||
const itemList = [
|
||||
{ title: '我的二维码', key: '1', value: '' },
|
||||
{ title: 'ID', key: '2', value: 'userId' },
|
||||
{ title: '昵称', key: '3', value: 'userName' },
|
||||
{ title: '性别', key: '4', value: '' },
|
||||
{ title: '性别', key: '4', value: 'sex' },
|
||||
{ title: '手机号码', key: '5', value: 'mobile' },
|
||||
{ title: '个性签名', key: '6', value: '' }
|
||||
{ title: '个性签名', key: '6', value: 'perSignature' }
|
||||
]
|
||||
/** 可修改的 key */
|
||||
const MODIFY_KEY = ['3', '4', '6']
|
||||
|
||||
const { userInfo } = useAuthUser()
|
||||
|
||||
const popupData = reactive({
|
||||
show: false,
|
||||
title: '修改信息',
|
||||
name: '',
|
||||
key: '',
|
||||
value: ''
|
||||
})
|
||||
const formData = reactive({
|
||||
avatar: '',
|
||||
userName: '',
|
||||
sex: '',
|
||||
perSignature: ''
|
||||
})
|
||||
const upInfo = (key, value) => {
|
||||
if (MODIFY_KEY.includes(key)) {
|
||||
const titleData = {
|
||||
3: '昵称',
|
||||
4: '性别',
|
||||
6: '个性签名'
|
||||
}[key]
|
||||
popupData.value = value
|
||||
popupData.key = key
|
||||
popupData.title = titleData
|
||||
popupData.name = userInfo.value[value]
|
||||
popupData.show = true
|
||||
}
|
||||
}
|
||||
|
||||
const editAvatar = async () => {
|
||||
const paths = await chooseImage({ count: 1 })
|
||||
const url = await uploadSingleFile(paths[0], {
|
||||
url: '/api/common/admin/upload/up/single'
|
||||
})
|
||||
formData.avatar = url
|
||||
updateUserInfo({ avatar: url })
|
||||
}
|
||||
|
||||
const onConfirm = () => {
|
||||
if (MODIFY_KEY.includes(popupData.key)) {
|
||||
if (popupData.name === userInfo.value[popupData.value]) return
|
||||
if (popupData.name === '') return
|
||||
updateUserInfo({ [popupData.value]: popupData.name })
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<nav-bar isTopBg isPlaceholder title="个人中心"></nav-bar>
|
||||
<view class="personal-center">
|
||||
<view class="public-card">
|
||||
<view class="public-card" @click="editAvatar">
|
||||
<view class="left-img">
|
||||
<image
|
||||
src="https://p4.itc.cn/images01/20220619/46660ed163164c14be90e605a73ee5e8.jpeg"
|
||||
v-if="userInfo.avatar"
|
||||
:src="userInfo.avatar"
|
||||
mode="aspectFill"
|
||||
class="avatar"
|
||||
></image>
|
||||
<uni-icons v-else type="contact-filled" size="60"></uni-icons>
|
||||
</view>
|
||||
<view class="right-box">
|
||||
<text class="value">换头像</text>
|
||||
@@ -33,20 +90,37 @@
|
||||
v-for="(item, index) in itemList"
|
||||
:key="index"
|
||||
class="public-card"
|
||||
@click="upInfo(item.key, item.value)"
|
||||
>
|
||||
<view class="left-box">
|
||||
<text>{{ item.title }}</text>
|
||||
<text v-if="item.key === '6'" class="text">
|
||||
这个人很懒,什么也没有
|
||||
{{ userInfo[item.value] || '这个人很懒,什么也没有' }}
|
||||
</text>
|
||||
</view>
|
||||
<view class="right-box">
|
||||
<text v-if="!['1', '6'].includes(item.key)" class="value">
|
||||
{{ item.value ? userInfo[item.value] : '' }}
|
||||
</text>
|
||||
<view v-if="!['1', '6'].includes(item.key)" class="value">
|
||||
<text v-if="item.key === '4'">
|
||||
{{
|
||||
userInfo[item.value] === '2'
|
||||
? '未设置'
|
||||
: userInfo[item.value] === '0'
|
||||
? '男'
|
||||
: '女'
|
||||
}}
|
||||
</text>
|
||||
<text v-else>{{ item.value ? userInfo[item.value] : '' }}</text>
|
||||
</view>
|
||||
<uni-icons type="right" size="16" color="#999999"></uni-icons>
|
||||
</view>
|
||||
</view>
|
||||
<popup-box
|
||||
v-model="popupData.show"
|
||||
v-model:name="popupData.name"
|
||||
:isSex="popupData.key === '4'"
|
||||
:title="popupData.title"
|
||||
@confirm="onConfirm"
|
||||
/>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ page {
|
||||
}
|
||||
}
|
||||
.right-box {
|
||||
align-items: flex-end;
|
||||
align-items: center;
|
||||
.value {
|
||||
font-weight: 600;
|
||||
font-size: 28rpx;
|
||||
|
||||
@@ -1,14 +1,30 @@
|
||||
<script setup>
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import { reactive } from 'vue'
|
||||
import { useUI } from '@/utils/use-ui'
|
||||
import { navigateBack } from '@/utils/router'
|
||||
import {
|
||||
addUserThirdPay,
|
||||
addUserPayPwd,
|
||||
getUserThirdPay,
|
||||
getUserBankDetail
|
||||
} from '@/api/my-index'
|
||||
import CardInput from '../../components/card-input.vue'
|
||||
|
||||
/** 是否第三方进入 */
|
||||
const IS_THIRD_PAY = ['101', '102']
|
||||
|
||||
const { showToast } = useUI()
|
||||
|
||||
const stateData = reactive({
|
||||
title: '',
|
||||
state: '0'
|
||||
state: '0',
|
||||
loading: true
|
||||
})
|
||||
|
||||
const formData = reactive({
|
||||
// 修改id
|
||||
id: '',
|
||||
// 银行卡名称
|
||||
name: '',
|
||||
// 开户行名称
|
||||
@@ -18,34 +34,131 @@
|
||||
// 微信/支付宝号
|
||||
codeName: '',
|
||||
// 图片链接
|
||||
img: ''
|
||||
img: '',
|
||||
// 银行卡类型
|
||||
cardType: '',
|
||||
// 图片回显列表
|
||||
imgList: []
|
||||
})
|
||||
|
||||
const onAdd = () => {
|
||||
console.log(formData)
|
||||
const data = {
|
||||
bankName: formData.name,
|
||||
cardNumber: formData.cardNum,
|
||||
bankName: formData.khName
|
||||
const onAdd = async () => {
|
||||
let data = {}
|
||||
const name = formData.id ? '修改' : '添加'
|
||||
const method = formData.id ? 'put' : 'post'
|
||||
if (IS_THIRD_PAY.includes(stateData.state)) {
|
||||
if (!formData.codeName) {
|
||||
showToast('请输入账号')
|
||||
return
|
||||
}
|
||||
if (!formData.img) {
|
||||
showToast('请上传收款码')
|
||||
return
|
||||
}
|
||||
data = {
|
||||
paymentType: stateData.state === '101' ? 2 : 1,
|
||||
qrCodeUrl: formData.img,
|
||||
accountNumber: formData.codeName,
|
||||
paymentId: formData.id
|
||||
}
|
||||
await addUserThirdPay(data, method)
|
||||
} else {
|
||||
if (!formData.name) {
|
||||
showToast('请输入银行名称')
|
||||
return
|
||||
}
|
||||
if (!formData.khName) {
|
||||
showToast('请输入持卡人姓名')
|
||||
return
|
||||
}
|
||||
if (!formData.cardNum) {
|
||||
showToast('请输入银行卡号')
|
||||
return
|
||||
}
|
||||
if (!formData.cardType) {
|
||||
showToast('请选择银行卡类型')
|
||||
return
|
||||
}
|
||||
data = {
|
||||
bankName: formData.name,
|
||||
cardHolder: formData.khName,
|
||||
cardNumber: formData.cardNum,
|
||||
cardType: formData.cardType,
|
||||
cardId: formData.id
|
||||
}
|
||||
await addUserPayPwd(data, method)
|
||||
}
|
||||
await showToast(`${name}成功`, 'success')
|
||||
navigateBack()
|
||||
}
|
||||
|
||||
/** 获取详情 */
|
||||
const getDetail = async () => {
|
||||
stateData.loading = true
|
||||
const res = await getUserThirdPay(
|
||||
stateData.state === '101' ? '2' : '1'
|
||||
)
|
||||
let titltData = ''
|
||||
if (res?.data) {
|
||||
const { paymentId, accountNumber, qrCodeUrl } = res.data
|
||||
formData.codeName = accountNumber
|
||||
formData.img = qrCodeUrl
|
||||
formData.id = paymentId
|
||||
formData.imgList = [
|
||||
{
|
||||
url: qrCodeUrl
|
||||
}
|
||||
]
|
||||
titltData = {
|
||||
101: '修改支付宝账户',
|
||||
102: '修改微信账户'
|
||||
}[stateData.state]
|
||||
} else {
|
||||
titltData = {
|
||||
101: '添加支付宝账户',
|
||||
102: '添加微信账户'
|
||||
}[stateData.state]
|
||||
}
|
||||
stateData.title = titltData
|
||||
stateData.loading = false
|
||||
}
|
||||
|
||||
/** 获取对应银行卡详情 */
|
||||
const getBankDetail = async () => {
|
||||
stateData.loading = true
|
||||
const res = await getUserBankDetail(stateData.state)
|
||||
const { cardId, bankName, cardHolder, cardNumber, cardType } =
|
||||
res.data
|
||||
formData.id = cardId
|
||||
formData.name = bankName
|
||||
formData.khName = cardHolder
|
||||
formData.cardNum = cardNumber
|
||||
formData.cardType = cardType
|
||||
stateData.title = '修改银行卡'
|
||||
stateData.loading = false
|
||||
}
|
||||
|
||||
onLoad(e => {
|
||||
const titltData = {
|
||||
0: '添加银行卡',
|
||||
101: '添加支付宝账户',
|
||||
102: '添加微信账户'
|
||||
}[e.key]
|
||||
stateData.title = titltData
|
||||
stateData.state = e.key
|
||||
if (IS_THIRD_PAY.includes(e.key)) {
|
||||
getDetail()
|
||||
} else {
|
||||
if (e.key === '0') {
|
||||
stateData.title = '添加银行卡'
|
||||
stateData.loading = false
|
||||
} else {
|
||||
getBankDetail()
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view>
|
||||
<view v-if="!stateData.loading">
|
||||
<nav-bar isTopBg isPlaceholder :title="stateData.title">
|
||||
<template #right>
|
||||
<text class="public-navbar__right-btn" @click="onAdd">添加</text>
|
||||
<text class="public-navbar__right-btn" @click="onAdd">
|
||||
{{ formData.id ? '修改' : '添加' }}
|
||||
</text>
|
||||
</template>
|
||||
</nav-bar>
|
||||
|
||||
@@ -54,8 +167,26 @@
|
||||
class="card-details"
|
||||
>
|
||||
<CardInput v-model="formData.name" title="银行名称"></CardInput>
|
||||
<CardInput v-model="formData.khName" title="开户行"></CardInput>
|
||||
<CardInput v-model="formData.khName" title="持卡人姓名"></CardInput>
|
||||
<CardInput v-model="formData.cardNum" title="银行卡号"></CardInput>
|
||||
<CardInput :isInput="false" title="银行卡类型">
|
||||
<template #right>
|
||||
<uni-data-checkbox
|
||||
v-model="formData.cardType"
|
||||
:localdata="[
|
||||
{
|
||||
text: '借记卡',
|
||||
value: 1
|
||||
},
|
||||
{
|
||||
text: '信用卡',
|
||||
value: 2
|
||||
}
|
||||
]"
|
||||
class="card-checkbox"
|
||||
></uni-data-checkbox>
|
||||
</template>
|
||||
</CardInput>
|
||||
</view>
|
||||
|
||||
<view v-else class="card-details">
|
||||
@@ -65,8 +196,16 @@
|
||||
stateData.state === '101' ? '支付宝账号' : '微信账号'
|
||||
}`"
|
||||
></CardInput>
|
||||
<CardInput :is-input="false" title="收款码">
|
||||
<cb-file-picker v-model="formData.img"></cb-file-picker>
|
||||
<CardInput
|
||||
:is-input="false"
|
||||
:title="`${
|
||||
stateData.state === '101' ? '支付宝' : '微信'
|
||||
}收款码(必传)`"
|
||||
>
|
||||
<cb-file-picker
|
||||
v-model="formData.img"
|
||||
v-model:list="formData.imgList"
|
||||
></cb-file-picker>
|
||||
</CardInput>
|
||||
</view>
|
||||
</view>
|
||||
@@ -77,4 +216,10 @@
|
||||
.card-details {
|
||||
padding: 32rpx 24rpx;
|
||||
}
|
||||
.card-checkbox {
|
||||
:deep(.uni-label-pointer) {
|
||||
margin: 0 !important;
|
||||
margin-left: 60rpx !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,56 +1,107 @@
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { navigateTo } from '@/utils/router'
|
||||
import { onShow } from '@dcloudio/uni-app'
|
||||
import { getUserBankList, deleteUserPayPwd } from '@/api/my-index'
|
||||
import { useUI } from '@/utils/use-ui'
|
||||
|
||||
const itemList = ref([
|
||||
{
|
||||
title: '支付宝',
|
||||
key: '101',
|
||||
icon: '/static/images/my-index/zfb.png'
|
||||
},
|
||||
{ title: '微信', key: '102', icon: '/static/images/my-index/wx.png' }
|
||||
])
|
||||
/** 是否第三方进入 */
|
||||
const IS_THIRD_PAY = ['0', '101', '102']
|
||||
const itemList = ref([])
|
||||
const { showToast, showDialog } = useUI()
|
||||
|
||||
const onAddCard = key => {
|
||||
navigateTo('/pages/my-index/wallet/bank-card/card-details', { key })
|
||||
}
|
||||
|
||||
const getList = async type => {
|
||||
itemList.value = []
|
||||
const res = await getUserBankList()
|
||||
|
||||
itemList.value = [
|
||||
{
|
||||
bankName: '支付宝',
|
||||
key: '101',
|
||||
icon: '/static/images/my-index/zfb.png'
|
||||
},
|
||||
{
|
||||
bankName: '微信',
|
||||
key: '102',
|
||||
icon: '/static/images/my-index/wx.png'
|
||||
},
|
||||
...res.data
|
||||
]
|
||||
if (type === 1) {
|
||||
showToast('删除成功', 'success')
|
||||
}
|
||||
}
|
||||
|
||||
const onDelete = async id => {
|
||||
const res = await showDialog('提示', '确定要删除吗?')
|
||||
if (!res) return
|
||||
await deleteUserPayPwd(id)
|
||||
await getList(1)
|
||||
}
|
||||
|
||||
onShow(() => {
|
||||
getList()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view class="bank-card">
|
||||
<view
|
||||
v-for="(item, index) in itemList"
|
||||
:key="index"
|
||||
class="public-card"
|
||||
@click="onAddCard(item.key)"
|
||||
>
|
||||
<view class="left-img">
|
||||
<image
|
||||
:src="
|
||||
item.icon
|
||||
? item.icon
|
||||
: 'https://nimg.ws.126.net/?url=http%3A%2F%2Fdingyue.ws.126.net%2F2024%2F1227%2F842e0e65j00sp543n001fd000i700iim.jpg&thumbnail=660x2147483647&quality=80&type=jpg'
|
||||
"
|
||||
mode="aspectFill"
|
||||
class="card"
|
||||
></image>
|
||||
<text>{{ item.title }}</text>
|
||||
</view>
|
||||
<view class="right-box">
|
||||
<uni-icons type="right" size="16" color="#999999"></uni-icons>
|
||||
</view>
|
||||
</view>
|
||||
<uni-swipe-action>
|
||||
<uni-swipe-action-item
|
||||
v-for="(item, index) in itemList"
|
||||
:key="index"
|
||||
:disabled="!!item.key"
|
||||
class="card-box"
|
||||
>
|
||||
<view
|
||||
class="public-card"
|
||||
@click="onAddCard(item.key || item.cardId)"
|
||||
>
|
||||
<view class="left-img">
|
||||
<image
|
||||
v-if="item.icon"
|
||||
:src="item.icon"
|
||||
mode="aspectFill"
|
||||
class="card"
|
||||
></image>
|
||||
<text>{{ item.bankName }}</text>
|
||||
</view>
|
||||
<view class="right-box">
|
||||
<text v-if="item.cardNumber">{{ item.cardNumber }}</text>
|
||||
<uni-icons type="right" size="16" color="#999999"></uni-icons>
|
||||
</view>
|
||||
</view>
|
||||
<template v-slot:right>
|
||||
<view
|
||||
class="public-uni-swipe-action-right"
|
||||
@click="onDelete(item.cardId)"
|
||||
>
|
||||
<uni-icons type="trash" size="18" color="#ffffff"></uni-icons>
|
||||
<text class="iocn-name">删除</text>
|
||||
</view>
|
||||
</template>
|
||||
</uni-swipe-action-item>
|
||||
</uni-swipe-action>
|
||||
|
||||
<!-- 底部按钮 -->
|
||||
<bottom-view>
|
||||
<bottom-view v-if="itemList.length > 0">
|
||||
<cb-button @click="onAddCard('0')">+添加银行卡</cb-button>
|
||||
</bottom-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '@/styles/global.scss';
|
||||
@import '../../styles/index.scss';
|
||||
.bank-card {
|
||||
padding: 38rpx 24rpx;
|
||||
}
|
||||
|
||||
.card-box + .card-box {
|
||||
margin-top: 16rpx;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
<script setup>
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import { reactive } from 'vue'
|
||||
import CardInput from '../components/card-input.vue'
|
||||
import { useUI } from '@/utils/use-ui'
|
||||
import { navigateBack } from '@/utils/router'
|
||||
import { validateTransactionPassword } from '@/utils/validate'
|
||||
import { updateUserPayPwd, getUserPayPwd } from '@/api/my-index'
|
||||
|
||||
const { showToast } = useUI()
|
||||
|
||||
const formData = reactive({
|
||||
id: '',
|
||||
state: '0',
|
||||
// 旧密码
|
||||
password: '',
|
||||
// 新密码
|
||||
@@ -14,41 +19,72 @@
|
||||
// 确认密码
|
||||
confirmPassword: ''
|
||||
})
|
||||
const onEdit = () => {
|
||||
const passwordValue = validateTransactionPassword(
|
||||
formData.password,
|
||||
'旧密码'
|
||||
)
|
||||
if (!passwordValue.valid) {
|
||||
showToast(passwordValue.message)
|
||||
return
|
||||
const onEdit = async () => {
|
||||
let data = {}
|
||||
if (formData.state === '1') {
|
||||
const passwordValue = validateTransactionPassword(
|
||||
formData.password,
|
||||
'旧密码'
|
||||
)
|
||||
if (!passwordValue.valid) {
|
||||
showToast(passwordValue.message)
|
||||
return
|
||||
}
|
||||
|
||||
const newPasswordValue = validateTransactionPassword(
|
||||
formData.newPassword,
|
||||
'新的交易密码'
|
||||
)
|
||||
if (!newPasswordValue.valid) {
|
||||
showToast(newPasswordValue.message)
|
||||
return
|
||||
}
|
||||
|
||||
const confirmPasswordValue = validateTransactionPassword(
|
||||
formData.confirmPassword,
|
||||
'确认交易密码'
|
||||
)
|
||||
if (!confirmPasswordValue.valid) {
|
||||
showToast(confirmPasswordValue.message)
|
||||
return
|
||||
}
|
||||
|
||||
if (formData.newPassword !== formData.confirmPassword) {
|
||||
showToast('两次密码不一致')
|
||||
return
|
||||
}
|
||||
|
||||
data = {
|
||||
id: formData.id,
|
||||
oldPassword: formData.password,
|
||||
password: formData.confirmPassword
|
||||
}
|
||||
} else {
|
||||
if (!formData.password) {
|
||||
showToast('请输入设置交易密码')
|
||||
return
|
||||
}
|
||||
data = {
|
||||
password: formData.password
|
||||
}
|
||||
}
|
||||
|
||||
const newPasswordValue = validateTransactionPassword(
|
||||
formData.newPassword,
|
||||
'新的交易密码'
|
||||
)
|
||||
if (!newPasswordValue.valid) {
|
||||
showToast(newPasswordValue.message)
|
||||
return
|
||||
}
|
||||
|
||||
const confirmPasswordValue = validateTransactionPassword(
|
||||
formData.confirmPassword,
|
||||
'确认交易密码'
|
||||
)
|
||||
if (!confirmPasswordValue.valid) {
|
||||
showToast(confirmPasswordValue.message)
|
||||
return
|
||||
}
|
||||
|
||||
if (formData.newPassword !== formData.confirmPassword) {
|
||||
showToast('两次密码不一致')
|
||||
return
|
||||
}
|
||||
|
||||
console.log('修改密码:', formData)
|
||||
await updateUserPayPwd(data, formData.id ? 'put' : 'post')
|
||||
await showToast(`${formData.id ? '修改' : '添加'}成功`, 'success')
|
||||
navigateBack()
|
||||
}
|
||||
|
||||
const getData = async () => {
|
||||
const res = await getUserPayPwd()
|
||||
formData.id = res.data.id
|
||||
}
|
||||
|
||||
onLoad(e => {
|
||||
formData.state = e.type
|
||||
if (e.type === '1') {
|
||||
getData()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -59,7 +95,7 @@
|
||||
</template>
|
||||
</nav-bar>
|
||||
|
||||
<view class="input-box">
|
||||
<view v-if="formData.state === '1'" class="input-box">
|
||||
<CardInput
|
||||
v-model="formData.password"
|
||||
title="旧密码"
|
||||
@@ -76,6 +112,13 @@
|
||||
type="password"
|
||||
></CardInput>
|
||||
</view>
|
||||
<view v-else class="input-box">
|
||||
<CardInput
|
||||
v-model="formData.password"
|
||||
title="设置交易密码"
|
||||
type="password"
|
||||
></CardInput>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -1,26 +1,38 @@
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { navigateTo } from '@/utils/router'
|
||||
import { onShow } from '@dcloudio/uni-app'
|
||||
import { getUserPayPwd } from '@/api/my-index'
|
||||
import { useAuthUser } from '@/composables/useAuthUser'
|
||||
|
||||
const itemList = [
|
||||
{
|
||||
title: '提现卡',
|
||||
key: '1',
|
||||
url: '/pages/my-index/wallet/bank-card/index'
|
||||
},
|
||||
{ title: '交易记录', key: '2', url: '' },
|
||||
{
|
||||
title: '修改支付密码',
|
||||
key: '3',
|
||||
url: '/pages/my-index/wallet/edit-password'
|
||||
},
|
||||
{ title: '实名认证', key: '4', url: '/pages/my-index/wallet/real-id' }
|
||||
]
|
||||
const { userInfo } = useAuthUser()
|
||||
|
||||
const itemList = ref([])
|
||||
|
||||
const getData = async () => {
|
||||
itemList.value = []
|
||||
const res = await getUserPayPwd()
|
||||
console.log(res, '===')
|
||||
const isSetPayPwd = res?.data ? '修改支付密码' : '设置支付密码'
|
||||
|
||||
itemList.value = [
|
||||
{
|
||||
title: '提现卡',
|
||||
key: '1',
|
||||
url: '/pages/my-index/wallet/bank-card/index'
|
||||
},
|
||||
{ title: '交易记录', key: '2', url: '' },
|
||||
{
|
||||
title: isSetPayPwd,
|
||||
key: '3',
|
||||
isType: !!res?.data,
|
||||
url: '/pages/my-index/wallet/edit-password'
|
||||
},
|
||||
{
|
||||
title: '实名认证',
|
||||
key: '4',
|
||||
url: '/pages/my-index/wallet/real-id'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
onShow(() => {
|
||||
@@ -34,7 +46,7 @@
|
||||
<view class="top-card">
|
||||
<view class="left-box">
|
||||
<text>我的资产</text>
|
||||
<text>1222</text>
|
||||
<text>{{ userInfo?.totalPoints }}</text>
|
||||
</view>
|
||||
<view class="right-box">
|
||||
<button>充值</button>
|
||||
@@ -46,7 +58,13 @@
|
||||
v-for="(item, index) in itemList"
|
||||
:key="index"
|
||||
class="public-card"
|
||||
@click="item.url && navigateTo(item.url)"
|
||||
@click="
|
||||
item.url &&
|
||||
navigateTo(
|
||||
item.url,
|
||||
item.key === '3' ? { type: item.isType ? 1 : 0 } : null
|
||||
)
|
||||
"
|
||||
>
|
||||
<view class="left-box">
|
||||
<text>{{ item.title }}</text>
|
||||
|
||||
@@ -1,44 +1,121 @@
|
||||
<script setup>
|
||||
import { reactive } from 'vue'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import { ref, reactive } from 'vue'
|
||||
import CardInput from '../components/card-input.vue'
|
||||
import { useUI } from '@/utils/use-ui'
|
||||
import { navigateBack } from '@/utils/router'
|
||||
import { getUserIdCard, addUserIdCard } from '@/api/my-index'
|
||||
import { validateIdCard } from '@/utils/validate'
|
||||
|
||||
const { showToast } = useUI()
|
||||
|
||||
const loading = ref(true)
|
||||
const formData = reactive({
|
||||
// 修改id
|
||||
id: '',
|
||||
// 正面
|
||||
front: '',
|
||||
// 背面
|
||||
back: '',
|
||||
// 手机号
|
||||
phone: '',
|
||||
// 姓名
|
||||
realName: '',
|
||||
// 身份证号码
|
||||
idCard: ''
|
||||
idCard: '',
|
||||
frontList: [],
|
||||
backList: []
|
||||
})
|
||||
|
||||
const getData = async () => {
|
||||
loading.value = true
|
||||
const res = await getUserIdCard()
|
||||
if (res?.data) {
|
||||
formData.id = res.data.id
|
||||
formData.front = res.data.idCardFrontUrl
|
||||
formData.back = res.data.idCardBackUrl
|
||||
formData.realName = res.data.realName
|
||||
formData.idCard = res.data.idCardNumber
|
||||
|
||||
formData.frontList = [{ url: res.data.idCardFrontUrl }]
|
||||
formData.backList = [{ url: res.data.idCardBackUrl }]
|
||||
}
|
||||
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
const onAddCode = async () => {
|
||||
if (!formData.front) {
|
||||
showToast('请上传身份证人像面')
|
||||
return
|
||||
}
|
||||
if (!formData.back) {
|
||||
showToast('请上传身份证国徽面')
|
||||
return
|
||||
}
|
||||
if (!formData.realName) {
|
||||
showToast('请输入姓名')
|
||||
return
|
||||
}
|
||||
const codeData = validateIdCard(formData.idCard)
|
||||
if (!codeData.valid) {
|
||||
showToast(codeData.message)
|
||||
return
|
||||
}
|
||||
|
||||
const data = {
|
||||
idCardFrontUrl: formData.front,
|
||||
idCardBackUrl: formData.back,
|
||||
realName: formData.realName,
|
||||
idCardNumber: formData.idCard
|
||||
}
|
||||
|
||||
await addUserIdCard(data)
|
||||
await showToast(`添加成功`, 'success')
|
||||
navigateBack()
|
||||
}
|
||||
|
||||
onLoad(() => {
|
||||
getData()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view class="real-id">
|
||||
<view v-if="!loading" class="real-id">
|
||||
<!-- 说明 -->
|
||||
<text class="top-text">*为保证您的账户安全,请先完成实名认证</text>
|
||||
|
||||
<CardInput :is-input="false" title="收款码">
|
||||
<view class="qrcode-box">
|
||||
<cb-file-picker v-model="formData.front" isFront></cb-file-picker>
|
||||
<cb-file-picker v-model="formData.back" isBack></cb-file-picker>
|
||||
<cb-file-picker
|
||||
v-model="formData.front"
|
||||
v-model:list="formData.frontList"
|
||||
isFront
|
||||
></cb-file-picker>
|
||||
<cb-file-picker
|
||||
v-model="formData.back"
|
||||
v-model:list="formData.backList"
|
||||
isBack
|
||||
></cb-file-picker>
|
||||
</view>
|
||||
</CardInput>
|
||||
|
||||
<CardInput
|
||||
v-model="formData.phone"
|
||||
title="手机号"
|
||||
type="tel"
|
||||
placeholder="请输入手机号"
|
||||
v-model="formData.realName"
|
||||
title="姓名"
|
||||
placeholder="请输入姓名"
|
||||
></CardInput>
|
||||
|
||||
<CardInput
|
||||
v-model="formData.idCard"
|
||||
type="text"
|
||||
title="身份证号"
|
||||
placeholder="请输入身份证号"
|
||||
></CardInput>
|
||||
|
||||
<!-- 底部按钮 -->
|
||||
<bottom-view>
|
||||
<cb-button @click="onAddCode">
|
||||
确认{{ formData.id ? '修改' : '添加' }}
|
||||
</cb-button>
|
||||
</bottom-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -2,9 +2,6 @@
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import { useAuthUser } from '@/composables/useAuthUser'
|
||||
|
||||
import { TUIConversation } from '@/TUIKit'
|
||||
|
||||
|
||||
const { tencentUserSig } = useAuthUser()
|
||||
onLoad(() => {
|
||||
console.log(tencentUserSig.value, '===222===')
|
||||
@@ -17,8 +14,7 @@
|
||||
<template #right>右侧按钮</template>
|
||||
</nav-bar>
|
||||
<cb-search placeholder="搜索"></cb-search> -->
|
||||
<TUIConversation></TUIConversation>
|
||||
|
||||
222
|
||||
</view>
|
||||
<!-- -->
|
||||
</template>
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
removeSig
|
||||
} from '@/utils/storage'
|
||||
import { useTokenStore } from './token'
|
||||
import { getUserData, userLogout } from '@/api'
|
||||
import { getUserData, userLogout, updateUserData } from '@/api'
|
||||
import { ref } from 'vue'
|
||||
import { useUI } from '@/utils/use-ui'
|
||||
import { reLaunch } from '@/utils/router'
|
||||
@@ -62,6 +62,7 @@ export const useUserStore = defineStore('user', () => {
|
||||
* 登录腾讯 IM
|
||||
*/
|
||||
const loginTencentIM = async () => {
|
||||
console.log(11111111111111111111111)
|
||||
await TUILogin.login({
|
||||
SDKAppID: tencentUserSig.value.sdkappID,
|
||||
userID: tencentUserSig.value.userId,
|
||||
@@ -90,16 +91,17 @@ export const useUserStore = defineStore('user', () => {
|
||||
/** 刷新用户信息(如用户信息被修改) */
|
||||
const refreshUserInfo = async () => {
|
||||
const res = await getUserData()
|
||||
await setUserInfo(res.data)
|
||||
await setUserInfoData(res.data)
|
||||
userInfo.value = res.data
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新部分用户信息(例如昵称、头像)
|
||||
*/
|
||||
const updateUserInfo = partialData => {
|
||||
const updateUserInfo = async partialData => {
|
||||
if (!userInfo.value) return
|
||||
userInfo.value = { ...userInfo.value, ...partialData }
|
||||
setUserInfoData(userInfo.value)
|
||||
await updateUserData(partialData)
|
||||
await refreshUserInfo()
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -107,6 +109,7 @@ export const useUserStore = defineStore('user', () => {
|
||||
tencentUserSig,
|
||||
refreshUserInfo,
|
||||
fetchUserInfo,
|
||||
loginTencentIM,
|
||||
setUserInfo,
|
||||
clearUserInfo,
|
||||
updateUserInfo
|
||||
|
||||
@@ -15,3 +15,25 @@ page {
|
||||
padding: 12rpx 36rpx;
|
||||
border-radius: 8rpx;
|
||||
}
|
||||
|
||||
/** uni-swipe-action 滑动右边样式 */
|
||||
.public-uni-swipe-action-right {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: rgb(47, 194, 17);
|
||||
width: 130rpx;
|
||||
.iocn-name {
|
||||
font-family: PingFang SC, PingFang SC;
|
||||
font-weight: 500;
|
||||
font-size: 28rpx;
|
||||
color: #fff;
|
||||
text-align: left;
|
||||
font-style: normal;
|
||||
text-transform: none;
|
||||
}
|
||||
&:last-child {
|
||||
background: rgb(206, 59, 22);
|
||||
}
|
||||
}
|
||||
|
||||
29
uni_modules/TencentCloud-Push/changelog.md
Normal file
29
uni_modules/TencentCloud-Push/changelog.md
Normal file
@@ -0,0 +1,29 @@
|
||||
## 1.2.0(2025-03-31)
|
||||
- 适配出海手机支持 FCM 推送。
|
||||
## 1.1.0(2024-12-11)
|
||||
- 大幅减小插件包体积,优化产品体验。
|
||||
- 兼容 HBuilderX 4.36 的 Breaking changes。如果您需要 vivo/荣耀 的厂商推送,请参考 [文档](https://cloud.tencent.com/document/product/269/103522),正确配置 `manifestPlaceholders.json` 和 `mcn-services.json`。
|
||||
|
||||
## 1.0.0(2024-11-29)
|
||||
- 优化和 [TencentCloud-TUICallKit 插件](https://ext.dcloud.net.cn/plugin?id=9035) 融合时的产品体验。
|
||||
- 新增点击通知栏事件 NOTIFICATION_CLICKED,支持获取推送扩展信息。
|
||||
- 在线通道支持自定义铃音功能。
|
||||
|
||||
## 0.5.1(2024-11-07)
|
||||
- 优化和 [@tencentcloud/chat-uikit-uniapp](https://cloud.tencent.com/document/product/269/64507) 融合时的产品体验。
|
||||
- 优化和 [TencentCloud-TUICallKit 插件](https://ext.dcloud.net.cn/plugin?id=9035) 融合时的产品体验。
|
||||
- 新增接口 disablePostNotificationInForeground,此接口可实现应用在前台时,开/关通知栏通知(默认开)。
|
||||
- 新增接口 createNotificationChannel,支持 FCM/OPPO 自定义铃音。
|
||||
|
||||
## 0.4.0(2024-10-17)
|
||||
- 支持与 [TencentCloud-TUICallKit 插件](https://ext.dcloud.net.cn/plugin?id=9035) 融合打包。
|
||||
|
||||
## 0.3.0(2024-10-12)
|
||||
- 新增接口 addPushListener/removePushListener,支持获取在线推送消息,支持推送消息撤回通知。
|
||||
|
||||
## 0.2.0(2024-09-18)
|
||||
- 支持 FCM
|
||||
- 支持 hihonor
|
||||
|
||||
## 0.1.0(2024-09-10)
|
||||
- 使用 uts 开发,基于腾讯云推送服务(Push),支持 iOS 和 Android 推送,同时适配各大厂商推送。
|
||||
0
uni_modules/TencentCloud-Push/index.js
Normal file
0
uni_modules/TencentCloud-Push/index.js
Normal file
90
uni_modules/TencentCloud-Push/package.json
Normal file
90
uni_modules/TencentCloud-Push/package.json
Normal file
@@ -0,0 +1,90 @@
|
||||
{
|
||||
"name": "@tencentcloud/uni-app-push",
|
||||
"id": "TencentCloud-Push",
|
||||
"main": "index.js",
|
||||
"displayName": "【官方】uni-app 腾讯云推送服务(Push)",
|
||||
"version": "1.2.0",
|
||||
"description": "使用 uts 开发,基于腾讯云推送服务(Push),支持 iOS 和 Android 推送,同时适配各大厂商推送。",
|
||||
"license": "ISC",
|
||||
"keywords": [
|
||||
"腾讯云",
|
||||
"Push",
|
||||
"推送",
|
||||
"Android/iOS",
|
||||
"谷歌FCM"
|
||||
],
|
||||
"repository": "",
|
||||
"engines": {
|
||||
"HBuilderX": "^3.6.8"
|
||||
},
|
||||
"dcloudext": {
|
||||
"type": "uts",
|
||||
"sale": {
|
||||
"regular": {
|
||||
"price": "0.00"
|
||||
},
|
||||
"sourcecode": {
|
||||
"price": "0.00"
|
||||
}
|
||||
},
|
||||
"contact": {
|
||||
"qq": ""
|
||||
},
|
||||
"declaration": {
|
||||
"ads": "无",
|
||||
"data": "腾讯云即时通信IM隐私保护指引: https://web.sdk.qcloud.com/document/Tencent-IM-Privacy-Protection-Guidelines.html\n移动推送隐私保护指引: https://privacy.qq.com/document/preview/8565a4a2d26e480187ed86b0cc81d727",
|
||||
"permissions": "本地存储空间"
|
||||
},
|
||||
"npmurl": ""
|
||||
},
|
||||
"uni_modules": {
|
||||
"dependencies": [],
|
||||
"encrypt": [],
|
||||
"platforms": {
|
||||
"cloud": {
|
||||
"tcb": "y",
|
||||
"aliyun": "y",
|
||||
"alipay": "y"
|
||||
},
|
||||
"client": {
|
||||
"Vue": {
|
||||
"vue2": "y",
|
||||
"vue3": "y"
|
||||
},
|
||||
"App": {
|
||||
"app-android": "y",
|
||||
"app-ios": "y",
|
||||
"app-harmony": "u"
|
||||
},
|
||||
"H5-mobile": {
|
||||
"Safari": "u",
|
||||
"Android Browser": "u",
|
||||
"微信浏览器(Android)": "u",
|
||||
"QQ浏览器(Android)": "u"
|
||||
},
|
||||
"H5-pc": {
|
||||
"Chrome": "u",
|
||||
"IE": "u",
|
||||
"Edge": "u",
|
||||
"Firefox": "u",
|
||||
"Safari": "u"
|
||||
},
|
||||
"小程序": {
|
||||
"微信": "u",
|
||||
"阿里": "u",
|
||||
"百度": "u",
|
||||
"字节跳动": "u",
|
||||
"QQ": "u",
|
||||
"钉钉": "u",
|
||||
"快手": "u",
|
||||
"飞书": "u",
|
||||
"京东": "u"
|
||||
},
|
||||
"快应用": {
|
||||
"华为": "u",
|
||||
"联盟": "u"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
299
uni_modules/TencentCloud-Push/readme-npm.md
Normal file
299
uni_modules/TencentCloud-Push/readme-npm.md
Normal file
@@ -0,0 +1,299 @@
|
||||
# TencentCloud-Push
|
||||
|
||||
## 简介
|
||||
|
||||
使用 uts 开发,基于腾讯云推送服务(Push),支持 iOS 和 Android 推送,同时适配各大厂商推送。
|
||||
|
||||
腾讯云推送服务(Push)提供一站式 App 推送解决方案,助您轻松提升用户留存和互动活跃度,支持与腾讯云即时通信 IM SDK、实时音视频 TRTC SDK、音视频通话 SDK、直播 SDK等音视频终端产品协同集成,在不同场景联合使用,提升业务整体功能体验。
|
||||
|
||||
<img src="https://qcloudimg.tencent-cloud.cn/image/document/60d714484e54b284cfa440adcc885349.png" width="618" height="456">
|
||||
<img src="https://qcloudimg.tencent-cloud.cn/image/document/864c391ecf6f2724d26e368e4f09e466.png" width="618" height="444">
|
||||
<img src="https://qcloudimg.tencent-cloud.cn/image/document/6af60f4b20dd46323e8f901a161a80a9.png" width="618" height="454">
|
||||
|
||||
#### 数据可视化,辅助运营策略
|
||||
|
||||
<img src="https://qcloudimg.tencent-cloud.cn/image/document/6c422f64900053c38a6bf66fe1103b3f.png" width="618" height="334">
|
||||
|
||||
#### 支持推送消息全链路问题排查
|
||||
|
||||
<img src="https://qcloudimg.tencent-cloud.cn/image/document/156d43ed48971f9bf865ad0c4e2342e3.png" width="618" height="443">
|
||||
|
||||
#### 六地服务部署,严守数据安全
|
||||
|
||||
提供了中国、东南亚(新加坡、印尼雅加达)、东北亚(韩国首尔)、欧洲(德国法兰克福)以及北美(美国硅谷)数据存储中心供选择,每个数据中心均支持全球接入。如果您的应用在境外上线且用户主要在境外,您可以根据消息传输需求及合规要求,选择适合您业务的境外数据中心,保障您的数据安全。
|
||||
<img src="https://qcloudimg.tencent-cloud.cn/image/document/2ffc1a103a42d9c01cfb819cd92bbd1d.png" widht="618" height="308">
|
||||
|
||||
## 快速跑通
|
||||
|
||||
### 步骤1:创建应用
|
||||
|
||||
进入 [控制台](https://console.cloud.tencent.com/im) ,单击创建应用,填写应用名称,选择数据中心,单击确定,完成应用创建。
|
||||
|
||||

|
||||
|
||||
### 步骤2:开通推送服务 Push
|
||||
|
||||
进入 [推送服务 Push](https://console.cloud.tencent.com/im/push-plugin-push-identifier),单击立即购买或免费试用 。(每个应用可免费试用一次,有效期7天)
|
||||
|
||||

|
||||
|
||||
### 步骤3:下载腾讯云推送服务(Push)并复制 Push SDK 到您的项目中
|
||||
|
||||
1. 下载腾讯云推送服务(Push)。
|
||||
```
|
||||
npm install @tencentcloud/uni-app-push
|
||||
```
|
||||
|
||||
2. 复制 Push SDK 到您的项目中。
|
||||
|
||||
【macOS 端】
|
||||
|
||||
``` bash
|
||||
mkdir -p ./uni_modules/TencentCloud-Push && rsync -av ./node_modules/@tencentcloud/uni-app-push/ ./uni_modules/TencentCloud-Push
|
||||
```
|
||||
【Window 端】
|
||||
|
||||
``` bash
|
||||
xcopy .\node_modules\@tencentcloud\uni-app-push .\uni_modules\TencentCloud-Push /i /e
|
||||
```
|
||||
|
||||
### 步骤4:在 App.vue 中引入并注册腾讯云推送服务(Push)
|
||||
|
||||
将 SDKAppID 和 appKey 替换为您在IM 控制台 - 推送服务 Push - 接入设置页面 获取的应用的信息。如图所示:
|
||||
|
||||

|
||||
|
||||
```ts
|
||||
// 集成 TencentCloud-Push
|
||||
import * as Push from '@/uni_modules/TencentCloud-Push';
|
||||
const SDKAppID = 0; // 您的 SDKAppID
|
||||
const appKey = ''; // 客户端密钥
|
||||
Push.registerPush(SDKAppID, appKey, (data) => {
|
||||
console.log('registerPush ok', data);
|
||||
Push.getRegistrationID((registrationID) => {
|
||||
console.log('getRegistrationID ok', registrationID);
|
||||
});
|
||||
}, (errCode, errMsg) => {
|
||||
console.error('registerPush failed', errCode, errMsg);
|
||||
}
|
||||
);
|
||||
|
||||
// 监听通知栏点击事件,获取推送扩展信息
|
||||
Push.addPushListener(Push.EVENT.NOTIFICATION_CLICKED, (res) => {
|
||||
// res 为推送扩展信息
|
||||
console.log('notification clicked', res);
|
||||
});
|
||||
|
||||
// 监听在线推送
|
||||
Push.addPushListener(Push.EVENT.MESSAGE_RECEIVED, (res) => {
|
||||
// res 为消息内容
|
||||
console.log('message received', res);
|
||||
});
|
||||
|
||||
// 监听在线推送被撤回
|
||||
Push.addPushListener(Push.EVENT.MESSAGE_REVOKED, (res) => {
|
||||
// res 为被撤回的消息 ID
|
||||
console.log('message revoked', res);
|
||||
});
|
||||
```
|
||||
|
||||
### <span id="step5">步骤5:测试推送(测试前请务必打开手机通知权限,允许应用通知。)</span>
|
||||
|
||||
单击 HBuilderX 的 【运行 > 运行到手机或模拟器 > 制作自定义调试基座】,使用云端证书制作 Android 或 iOS 自定义调试基座。
|
||||
|
||||

|
||||
|
||||
自定义调试基座打好后,安装到手机运行。
|
||||
|
||||
[登录](https://console.cloud.tencent.com/im/push-plugin-push-check) 控制台,使用测试工具进行在线推送测试。
|
||||

|
||||
|
||||
## 厂商推送配置
|
||||
> - 请注意!HBuilderX 4.36 发布了不向下兼容的更新,如果您使用的是 HBuilderX 4.36 或者更高版本,且需要 vivo/荣耀 的厂商推送,
|
||||
请升级推送版本到 1.1.0 或更高版本,并参考文档正确配置 `manifestPlaceholders.json` 和 `mcs-services.json`。
|
||||
> - 请在 `nativeResources` 目录下进行推送配置。若项目根目录尚未创建该文件夹,请新建一个名为 `nativeResources` 的文件夹。
|
||||
> - 离线推送厂商配置完成后,需要打包自定义基座。参考:[[快速跑通]>[步骤5:测试推送(测试前请务必打开手机通知权限,允许应用通知。)]](#user-content-step5)
|
||||
|
||||
### 【Android】
|
||||
|
||||
1. 新建 nativeResources/android/assets 目录。
|
||||
|
||||
2. 在 [推送服务 Push > 接入设置 > 一键式快速配置](https://console.cloud.tencent.com/im/push-plugin-push-identifier) 下载 `timpush-configs.json` 文件,配置到 nativeResources/android/assets 目录下。
|
||||
|
||||
3. For 华为:
|
||||
|
||||
配置 `agconnect-services.json` (此文件获取详见 [厂商配置 > uniapp > 华为 > 步骤4:获取应用信息](https://cloud.tencent.com/document/product/269/103769))到 nativeResources/android 目录下。
|
||||
|
||||
4. For Google FCM:
|
||||
|
||||
4.1. 编辑 uni_modules/TencentCloud-Push/utssdk/app-android/config.json 的 `project.plugins`,添加 `"com.google.gms.google-services"`,如下:
|
||||
```
|
||||
{
|
||||
...
|
||||
"project": {
|
||||
"plugins": [
|
||||
...
|
||||
"com.google.gms.google-services"
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
4.2. 配置 `google-services.json` 文件到 nativeResources/android/ 目录。
|
||||
|
||||
5. For 荣耀:
|
||||
|
||||
5.1. 编辑 uni_modules/TencentCloud-Push/utssdk/app-android/config.json 的 `dependencies`,添加 `"com.tencent.timpush:honor:8.3.6498"`,如下:
|
||||
```
|
||||
{
|
||||
...
|
||||
"dependencies": [
|
||||
...
|
||||
"com.tencent.timpush:honor:8.3.6498"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
5.2. 配置 `mcs-services.json` 文件到 nativeResources/android 目录下。
|
||||
|
||||
5.3. 配置 `appID` 到 nativeResources/android/manifestPlaceholders.json 中的 `"HONOR_APPID"`,如下:
|
||||
```
|
||||
{
|
||||
"HONOR_APPID": ""
|
||||
}
|
||||
```
|
||||
|
||||
6. For vivo:
|
||||
|
||||
6.1. 编辑 uni_modules/TencentCloud-Push/utssdk/app-android/config.json 的 `dependencies`,添加 `"com.tencent.timpush:vivo:8.3.6498"`,如下:
|
||||
```
|
||||
{
|
||||
...
|
||||
"dependencies": [
|
||||
...
|
||||
"com.tencent.timpush:vivo:8.3.6498"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
6.2. 配置 `appID` 和 `appKey` 到 nativeResources/android/manifestPlaceholders.json 中的 `VIVO_APPKEY` 和 `VIVO_APPID`,如下:
|
||||
```
|
||||
{
|
||||
"VIVO_APPKEY": "",
|
||||
"VIVO_APPID": "",
|
||||
}
|
||||
```
|
||||
|
||||
### 【iOS】
|
||||
|
||||
1. 新建 nativeResources/ios/Resources 目录。
|
||||
|
||||
2. 在 nativeResources/ios/Resources 中**新建 timpush-configs.json 文件**。
|
||||
|
||||
3. 并将在 [IM控制台 > 推送服务 Push > 接入设置](https://console.cloud.tencent.com/im/push-plugin-push-identifier) 获取的证书ID,补充到 timpush-configs.json 文件中。
|
||||
|
||||
```
|
||||
{
|
||||
"businessID":"xxx"
|
||||
}
|
||||
```
|
||||
|
||||
## 接口
|
||||
|
||||
| API | 描述|
|
||||
|----|---|
|
||||
| registerPush | 注册推送服务 (必须在 App 用户同意了隐私政策,并且允许为 App 用户提供推送服务后,再调用该接口使用推送服务)。<br>首次注册成功后,TencentCloud-Push SDK 生成该设备的标识 - RegistrationID。<br> 业务侧可以把这个 RegistrationID 保存到业务服务器。业务侧根据 RegistrationID 向设备推送消息或者通知。|
|
||||
| unRegisterPush | 反注册关闭推送服务。|
|
||||
| setRegistrationID | 设置注册推送服务使用的推送 ID 标识,即 RegistrationID。<br/>如果业务侧期望业务账号 ID 和推送 ID 一致,方便使用,可使用此接口,此时需注意,此接口需在 registerPush(注册推送服务)之前调用。|
|
||||
| getRegistrationID | 注册推送服务成功后,获取推送 ID 标识,即 RegistrationID。|
|
||||
| getNotificationExtInfo | 获取推送扩展信息。|
|
||||
| getNotificationExtInfo | 收到离线推送时,点击通知栏拉起 app,调用此接口可获取推送扩展信息。|
|
||||
| addPushListener | 添加 Push 监听器。|
|
||||
| removePushListener | 移除 Push 监听器。|
|
||||
| disablePostNotificationInForeground | 应用在前台时,开/关通知栏通知。|
|
||||
| createNotificationChannel | 创建客户端通知 channel。|
|
||||
|
||||
|
||||
```ts
|
||||
registerPush(SDKAppID: number, appKey: string, onSuccess: (data: string) => void, onError?: (errCode: number, errMsg: string) => void);
|
||||
```
|
||||
|
||||
|属性|类型|必填|说明|
|
||||
|----|---|----|----|
|
||||
|SDKAppID|number|是|推送(Push)应用 ID|
|
||||
|appKey|string|是|推送(Push)应用客户端密钥|
|
||||
|onSuccess|function|是|接口调用成功的回调函数|
|
||||
|onError|function|否|接口调用失败的回调函数|
|
||||
|
||||
```ts
|
||||
unRegisterPush(onSuccess: () => void, onError?: (errCode: number, errMsg: string) => void): void;
|
||||
```
|
||||
|
||||
|属性|类型|必填|说明|
|
||||
|----|---|----|----|
|
||||
|onSuccess|function|是|接口调用成功的回调函数|
|
||||
|onError|function|否|接口调用失败的回调函数|
|
||||
|
||||
```ts
|
||||
setRegistrationID(registrationID: string, onSuccess: () => void): void;
|
||||
```
|
||||
|
||||
|属性|类型|必填|说明|
|
||||
|----|---|----|----|
|
||||
|registrationID|string|是|设备的推送标识 ID,卸载重装会改变。|
|
||||
|onSuccess|function|是|接口调用成功的回调函数|
|
||||
|
||||
|
||||
```ts
|
||||
getRegistrationID(onSuccess: (registrationID: string) => void): void;
|
||||
```
|
||||
|
||||
|属性|类型|必填|说明|
|
||||
|----|---|----|----|
|
||||
|onSuccess|function|是|接口调用成功的回调函数|
|
||||
|
||||
```ts
|
||||
getNotificationExtInfo(onSuccess: (extInfo: string) => void): void;
|
||||
```
|
||||
|
||||
|属性|类型|必填|说明|
|
||||
|----|---|----|----|
|
||||
|onSuccess|function|是|接口调用成功的回调函数|
|
||||
|
||||
```ts
|
||||
addPushListener(eventName: string, listener: (data: any) => void);
|
||||
```
|
||||
|
||||
|属性|类型|必填|说明|
|
||||
|----|---|----|----|
|
||||
|eventName|string|是|推送事件类型|
|
||||
|listener|function|是|推送事件处理方法|
|
||||
|
||||
```ts
|
||||
removePushListener(eventName: string, listener?: (data: any) => void);
|
||||
```
|
||||
|
||||
|属性|类型|必填|说明|
|
||||
|----|---|----|----|
|
||||
|eventName|string|是|推送事件类型|
|
||||
|listener|function|否|推送事件处理方法|
|
||||
|
||||
```ts
|
||||
disablePostNotificationInForeground(disable: boolean);
|
||||
```
|
||||
|
||||
|属性|类型|必填|说明|
|
||||
|----|---|----|----|
|
||||
|disable|boolean|是|应用在前台时,开/关通知栏通知,默认开<br/> - true: 应用在前台时,关闭通知栏通知。<br/> - false: 应用在前台时,开启通知栏通知。|
|
||||
|
||||
```ts
|
||||
createNotificationChannel(options: any, listener: (data: any) => void);
|
||||
```
|
||||
|
||||
|属性|类型|必填|说明|
|
||||
|----|---|----|----|
|
||||
|options.channelID|string|是|自定义 channel 的 ID|
|
||||
|options.channelName|string|是|自定义 channel 的名称|
|
||||
|options.channelDesc|string|否|自定义 channel 的描述|
|
||||
|options.channelSound|string|否|自定义 channel 的铃音,音频文件名,不带后缀,音频文件需要放到 xxx/nativeResources/android/res/raw 中。<br/> 例如:<br/> `options.channelSound = private_ring`,即设置 `xxx/nativeResources/android/res/raw/private_ring.mp3` 为自定义铃音|
|
||||
|listener|function|是|接口调用成功的回调函数|
|
||||
285
uni_modules/TencentCloud-Push/readme.md
Normal file
285
uni_modules/TencentCloud-Push/readme.md
Normal file
@@ -0,0 +1,285 @@
|
||||
# TencentCloud-Push
|
||||
|
||||
## 简介
|
||||
|
||||
使用 uts 开发,基于腾讯云推送服务(Push),支持 iOS 和 Android 推送,同时适配各大厂商推送。
|
||||
|
||||
腾讯云推送服务(Push)提供一站式 App 推送解决方案,助您轻松提升用户留存和互动活跃度,支持与腾讯云即时通信 IM SDK、实时音视频 TRTC SDK、音视频通话 SDK、直播 SDK等音视频终端产品协同集成,在不同场景联合使用,提升业务整体功能体验。
|
||||
|
||||
<img src="https://qcloudimg.tencent-cloud.cn/image/document/60d714484e54b284cfa440adcc885349.png" width="618" height="456">
|
||||
<img src="https://qcloudimg.tencent-cloud.cn/image/document/864c391ecf6f2724d26e368e4f09e466.png" width="618" height="444">
|
||||
<img src="https://qcloudimg.tencent-cloud.cn/image/document/6af60f4b20dd46323e8f901a161a80a9.png" width="618" height="454">
|
||||
|
||||
#### 数据可视化,辅助运营策略
|
||||
|
||||
<img src="https://qcloudimg.tencent-cloud.cn/image/document/6c422f64900053c38a6bf66fe1103b3f.png" width="618" height="334">
|
||||
|
||||
#### 支持推送消息全链路问题排查
|
||||
|
||||
<img src="https://qcloudimg.tencent-cloud.cn/image/document/156d43ed48971f9bf865ad0c4e2342e3.png" width="618" height="443">
|
||||
|
||||
#### 六地服务部署,严守数据安全
|
||||
|
||||
提供了中国、东南亚(新加坡、印尼雅加达)、东北亚(韩国首尔)、欧洲(德国法兰克福)以及北美(美国硅谷)数据存储中心供选择,每个数据中心均支持全球接入。如果您的应用在境外上线且用户主要在境外,您可以根据消息传输需求及合规要求,选择适合您业务的境外数据中心,保障您的数据安全。
|
||||
<img src="https://qcloudimg.tencent-cloud.cn/image/document/2ffc1a103a42d9c01cfb819cd92bbd1d.png" widht="618" height="308">
|
||||
|
||||
## 快速跑通
|
||||
|
||||
### 步骤1:创建应用
|
||||
|
||||
进入 [控制台](https://console.cloud.tencent.com/im) ,单击创建应用,填写应用名称,选择数据中心,单击确定,完成应用创建。
|
||||
|
||||

|
||||
|
||||
### 步骤2:开通推送服务 Push
|
||||
|
||||
进入 [推送服务 Push](https://console.cloud.tencent.com/im/push-plugin-push-identifier),单击立即购买或免费试用 。(每个应用可免费试用一次,有效期7天)
|
||||
|
||||

|
||||
|
||||
### 步骤3:将 [uni-app 腾讯云推送服务(Push)](https://ext.dcloud.net.cn/plugin?id=20169)插件导入 HbuilderX 中的工程。如图所示:
|
||||
|
||||

|
||||

|
||||

|
||||
|
||||
### 步骤4:在 App.vue 中引入并注册腾讯云推送服务(Push)
|
||||
|
||||
将 SDKAppID 和 appKey 替换为您在IM 控制台 - 推送服务 Push - 接入设置页面 获取的应用的信息。如图所示:
|
||||
|
||||

|
||||
|
||||
```ts
|
||||
// 集成 TencentCloud-Push
|
||||
import * as Push from '@/uni_modules/TencentCloud-Push';
|
||||
const SDKAppID = 0; // 您的 SDKAppID
|
||||
const appKey = ''; // 客户端密钥
|
||||
Push.registerPush(SDKAppID, appKey, (data) => {
|
||||
console.log('registerPush ok', data);
|
||||
Push.getRegistrationID((registrationID) => {
|
||||
console.log('getRegistrationID ok', registrationID);
|
||||
});
|
||||
}, (errCode, errMsg) => {
|
||||
console.error('registerPush failed', errCode, errMsg);
|
||||
}
|
||||
);
|
||||
|
||||
// 监听通知栏点击事件,获取推送扩展信息
|
||||
Push.addPushListener(Push.EVENT.NOTIFICATION_CLICKED, (res) => {
|
||||
// res 为推送扩展信息
|
||||
console.log('notification clicked', res);
|
||||
});
|
||||
|
||||
// 监听在线推送
|
||||
Push.addPushListener(Push.EVENT.MESSAGE_RECEIVED, (res) => {
|
||||
// res 为消息内容
|
||||
console.log('message received', res);
|
||||
});
|
||||
|
||||
// 监听在线推送被撤回
|
||||
Push.addPushListener(Push.EVENT.MESSAGE_REVOKED, (res) => {
|
||||
// res 为被撤回的消息 ID
|
||||
console.log('message revoked', res);
|
||||
});
|
||||
```
|
||||
|
||||
### <span id="step5">步骤5:测试推送(测试前请务必打开手机通知权限,允许应用通知。)</span>
|
||||
|
||||
单击 HBuilderX 的 【运行 > 运行到手机或模拟器 > 制作自定义调试基座】,使用云端证书制作 Android 或 iOS 自定义调试基座。
|
||||
|
||||

|
||||
|
||||
自定义调试基座打好后,安装到手机运行。
|
||||
|
||||
[登录](https://console.cloud.tencent.com/im/push-plugin-push-check) 控制台,使用测试工具进行在线推送测试。
|
||||

|
||||
|
||||
|
||||
## 厂商推送配置
|
||||
|
||||
> - 请注意!HBuilderX 4.36 发布了不向下兼容的更新,如果您使用的是 HBuilderX 4.36 或者更高版本,且需要 vivo/荣耀 的厂商推送,
|
||||
请升级推送版本到 1.1.0 或更高版本,并参考文档正确配置 `manifestPlaceholders.json` 和 `mcs-services.json`。
|
||||
> - 请在 `nativeResources` 目录下进行推送配置。若项目根目录尚未创建该文件夹,请新建一个名为 `nativeResources` 的文件夹。
|
||||
> - 厂商推送配置完成后,需要打包自定义基座。参考:[[快速跑通]>[步骤5:测试推送(测试前请务必打开手机通知权限,允许应用通知。)]](#step5)
|
||||
|
||||
#### 【Android】
|
||||
|
||||
1. 新建 nativeResources/android/assets 目录。
|
||||
|
||||
2. 在 [推送服务 Push > 接入设置 > 一键式快速配置](https://console.cloud.tencent.com/im/push-plugin-push-identifier) 下载 `timpush-configs.json` 文件,配置到 nativeResources/android/assets 目录下。
|
||||
|
||||
3. For 华为:
|
||||
|
||||
配置 `agconnect-services.json` (此文件获取详见 [厂商配置 > uniapp > 华为 > 步骤4:获取应用信息](https://cloud.tencent.com/document/product/269/103769))到 nativeResources/android 目录下。
|
||||
|
||||
4. For Google FCM:
|
||||
|
||||
4.1. 编辑 uni_modules/TencentCloud-Push/utssdk/app-android/config.json 的 `project.plugins`,添加 `"com.google.gms.google-services"`,如下:
|
||||
```
|
||||
{
|
||||
...
|
||||
"project": {
|
||||
"plugins": [
|
||||
...
|
||||
"com.google.gms.google-services"
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
4.2. 配置 `google-services.json` 文件到 nativeResources/android/ 目录。
|
||||
|
||||
5. For 荣耀:
|
||||
|
||||
5.1. 编辑 uni_modules/TencentCloud-Push/utssdk/app-android/config.json 的 `dependencies`,添加 `"com.tencent.timpush:honor:8.3.6498"`,如下:
|
||||
```
|
||||
{
|
||||
...
|
||||
"dependencies": [
|
||||
...
|
||||
"com.tencent.timpush:honor:8.3.6498"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
5.2. 配置 `mcs-services.json` 文件到 nativeResources/android 目录下。
|
||||
|
||||
5.3. 配置 `appID` 到 nativeResources/android/manifestPlaceholders.json 中的 `"HONOR_APPID"`,如下:
|
||||
```
|
||||
{
|
||||
"HONOR_APPID": ""
|
||||
}
|
||||
```
|
||||
|
||||
6. For vivo:
|
||||
|
||||
6.1. 编辑 uni_modules/TencentCloud-Push/utssdk/app-android/config.json 的 `dependencies`,添加 `"com.tencent.timpush:vivo:8.3.6498"`,如下:
|
||||
```
|
||||
{
|
||||
...
|
||||
"dependencies": [
|
||||
...
|
||||
"com.tencent.timpush:vivo:8.3.6498"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
6.2. 配置 `appID` 和 `appKey` 到 nativeResources/android/manifestPlaceholders.json 中的 `VIVO_APPKEY` 和 `VIVO_APPID`,如下:
|
||||
```
|
||||
{
|
||||
"VIVO_APPKEY": "",
|
||||
"VIVO_APPID": "",
|
||||
}
|
||||
```
|
||||
|
||||
#### 【iOS】
|
||||
|
||||
1. 新建 nativeResources/ios/Resources 目录。
|
||||
|
||||
2. 在 nativeResources/ios/Resources 目录下新建 `timpush-configs.json` 文件。
|
||||
|
||||
3. 将在 [IM控制台 > 推送服务 Push > 接入设置](https://console.cloud.tencent.com/im/push-plugin-push-identifier) 获取的证书ID,补充到 `timpush-configs.json` 文件中。
|
||||
|
||||
```
|
||||
{
|
||||
"businessID":"xxx"
|
||||
}
|
||||
```
|
||||
|
||||
## 接口
|
||||
|
||||
| API | 描述|
|
||||
|----|---|
|
||||
| registerPush | 注册推送服务 (必须在 App 用户同意了隐私政策,并且允许为 App 用户提供推送服务后,再调用该接口使用推送服务)。<br>首次注册成功后,TencentCloud-Push SDK 生成该设备的标识 - RegistrationID。<br> 业务侧可以把这个 RegistrationID 保存到业务服务器。业务侧根据 RegistrationID 向设备推送消息或者通知。|
|
||||
| unRegisterPush | 反注册关闭推送服务。|
|
||||
| setRegistrationID | 设置注册推送服务使用的推送 ID 标识,即 RegistrationID。<br/>如果业务侧期望业务账号 ID 和推送 ID 一致,方便使用,可使用此接口,此时需注意,此接口需在 registerPush(注册推送服务)之前调用。|
|
||||
| getRegistrationID | 注册推送服务成功后,获取推送 ID 标识,即 RegistrationID。|
|
||||
| getNotificationExtInfo | 收到离线推送时,点击通知栏拉起 app,调用此接口可获取推送扩展信息。|
|
||||
| addPushListener | 添加 Push 监听器。|
|
||||
| removePushListener | 移除 Push 监听器。|
|
||||
| disablePostNotificationInForeground | 应用在前台时,开/关通知栏通知(默认开)。|
|
||||
| createNotificationChannel | 创建客户端通知 channel。|
|
||||
|
||||
```ts
|
||||
registerPush(SDKAppID: number, appKey: string, onSuccess: (data: string) => void, onError?: (errCode: number, errMsg: string) => void);
|
||||
```
|
||||
|
||||
|属性|类型|必填|说明|
|
||||
|----|---|----|----|
|
||||
|SDKAppID|number|是|推送(Push)应用 ID|
|
||||
|appKey|string|是|推送(Push)应用客户端密钥|
|
||||
|onSuccess|function|是|接口调用成功的回调函数|
|
||||
|onError|function|否|接口调用失败的回调函数|
|
||||
|
||||
```ts
|
||||
unRegisterPush(onSuccess: () => void, onError?: (errCode: number, errMsg: string) => void): void;
|
||||
```
|
||||
|
||||
|属性|类型|必填|说明|
|
||||
|----|---|----|----|
|
||||
|onSuccess|function|是|接口调用成功的回调函数|
|
||||
|onError|function|否|接口调用失败的回调函数|
|
||||
|
||||
```ts
|
||||
setRegistrationID(registrationID: string, onSuccess: () => void): void;
|
||||
```
|
||||
|
||||
|属性|类型|必填|说明|
|
||||
|----|---|----|----|
|
||||
|registrationID|string|是|设备的推送标识 ID,卸载重装会改变。|
|
||||
|onSuccess|function|是|接口调用成功的回调函数|
|
||||
|
||||
|
||||
```ts
|
||||
getRegistrationID(onSuccess: (registrationID: string) => void): void;
|
||||
```
|
||||
|
||||
|属性|类型|必填|说明|
|
||||
|----|---|----|----|
|
||||
|onSuccess|function|是|接口调用成功的回调函数|
|
||||
|
||||
```ts
|
||||
getNotificationExtInfo(onSuccess: (extInfo: string) => void): void;
|
||||
```
|
||||
|
||||
|属性|类型|必填|说明|
|
||||
|----|---|----|----|
|
||||
|onSuccess|function|是|接口调用成功的回调函数|
|
||||
|
||||
```ts
|
||||
addPushListener(eventName: string, listener: (data: any) => void);
|
||||
```
|
||||
|
||||
|属性|类型|必填|说明|
|
||||
|----|---|----|----|
|
||||
|eventName|string|是|推送事件类型|
|
||||
|listener|function|是|推送事件处理方法|
|
||||
|
||||
```ts
|
||||
removePushListener(eventName: string, listener?: (data: any) => void);
|
||||
```
|
||||
|
||||
|属性|类型|必填|说明|
|
||||
|----|---|----|----|
|
||||
|eventName|string|是|推送事件类型|
|
||||
|listener|function|否|推送事件处理方法|
|
||||
|
||||
```ts
|
||||
disablePostNotificationInForeground(disable: boolean);
|
||||
```
|
||||
|
||||
|属性|类型|必填|说明|
|
||||
|----|---|----|----|
|
||||
|disable|boolean|是|应用在前台时,开/关通知栏通知,默认开<br/> - true: 应用在前台时,关闭通知栏通知。<br/> - false: 应用在前台时,开启通知栏通知。|
|
||||
|
||||
```ts
|
||||
createNotificationChannel(options: any, listener: (data: any) => void);
|
||||
```
|
||||
|
||||
|属性|类型|必填|说明|
|
||||
|----|---|----|----|
|
||||
|options.channelID|string|是|自定义 channel 的 ID|
|
||||
|options.channelName|string|是|自定义 channel 的名称|
|
||||
|options.channelDesc|string|否|自定义 channel 的描述|
|
||||
|options.channelSound|string|否|自定义 channel 的铃音,音频文件名,不带后缀,音频文件需要放到 xxx/nativeResources/android/res/raw 中。<br/> 例如:<br/> `options.channelSound = private_ring`,即设置 `xxx/nativeResources/android/res/raw/private_ring.mp3` 为自定义铃音|
|
||||
|listener|function|是|接口调用成功的回调函数|
|
||||
27
uni_modules/TencentCloud-Push/utssdk/app-android/config.json
Normal file
27
uni_modules/TencentCloud-Push/utssdk/app-android/config.json
Normal file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"minSdkVersion": "21",
|
||||
"dependencies": [
|
||||
"com.google.android.material:material:1.3.0",
|
||||
"com.google.code.gson:gson:2.9.1",
|
||||
"commons-codec:commons-codec:1.15",
|
||||
"com.github.bumptech.glide:glide:4.12.0",
|
||||
"com.tencent.timpush:timpush:8.5.6864",
|
||||
"com.tencent.liteav.tuikit:tuicore:8.5.6864",
|
||||
"com.tencent.timpush:huawei:8.5.6864",
|
||||
"com.tencent.timpush:xiaomi:8.5.6864",
|
||||
"com.tencent.timpush:oppo:8.5.6864",
|
||||
"com.tencent.timpush:meizu:8.5.6864",
|
||||
"com.tencent.timpush:fcm:8.5.6864"
|
||||
],
|
||||
"project": {
|
||||
"plugins": [
|
||||
"com.huawei.agconnect",
|
||||
"com.hihonor.mcs.asplugin"
|
||||
],
|
||||
"dependencies": [
|
||||
"com.huawei.agconnect:agcp:1.9.1.301",
|
||||
"com.google.gms:google-services:4.3.15",
|
||||
"com.hihonor.mcs:asplugin:2.0.1.300"
|
||||
]
|
||||
}
|
||||
}
|
||||
152
uni_modules/TencentCloud-Push/utssdk/app-android/index.uts
Normal file
152
uni_modules/TencentCloud-Push/utssdk/app-android/index.uts
Normal file
@@ -0,0 +1,152 @@
|
||||
import { UTSAndroid } from 'io.dcloud.uts';
|
||||
import Context from 'android.content.Context';
|
||||
import TIMPushManager from 'com.tencent.qcloud.tim.push.TIMPushManager';
|
||||
import TIMPushConfig from 'com.tencent.qcloud.tim.push.config.TIMPushConfig';
|
||||
import { PushCallbackOptions } from './push-callback-options.uts';
|
||||
import { PushListenerOptions } from './push-listener-options.uts';
|
||||
import PushCallback from './push-callback.uts';
|
||||
import PushListener from './push-listener.uts';
|
||||
|
||||
const context: Context | null = UTSAndroid.getAppContext();
|
||||
console.warn('Push | package.name:', context?.getPackageName());
|
||||
TIMPushConfig.getInstance().setRunningPlatform(2);
|
||||
const Push = TIMPushManager.getInstance();
|
||||
|
||||
export class EVENT {
|
||||
static MESSAGE_RECEIVED: string = 'message_received'
|
||||
static MESSAGE_REVOKED: string = 'message_revoked'
|
||||
static NOTIFICATION_CLICKED: string = 'notification_clicked'
|
||||
}
|
||||
|
||||
let disableNotification = false;
|
||||
export function disablePostNotificationInForeground(disable: boolean): void {
|
||||
console.log('Push | disablePostNotificationInForeground', disable);
|
||||
disableNotification = disable;
|
||||
Push.disablePostNotificationInForeground(disableNotification);
|
||||
}
|
||||
|
||||
export function registerPush(SDKAppID: number, appKey: string, onSuccess: (data: string) => void, onError?: (errCode: number, errMsg: string) => void): void {
|
||||
if (SDKAppID == 0) {
|
||||
onError?.(9010001, 'Invalid SDKAppID');
|
||||
} else if (appKey == '') {
|
||||
onError?.(9010002, 'Invalid appKey');
|
||||
}
|
||||
const pushCbOptions: PushCallbackOptions = {
|
||||
apiName: 'registerPush',
|
||||
success: (res?: any) => {
|
||||
Push.disablePostNotificationInForeground(disableNotification);
|
||||
// 强转下类型,避免类型推断错误
|
||||
let token: string = res as string;
|
||||
onSuccess(token);
|
||||
},
|
||||
fail: (errCode: number, errMsg: string) => {
|
||||
onError?.(errCode, errMsg);
|
||||
}
|
||||
};
|
||||
// 注意!!! 这里不要写成 new PushCallback({ api, success, fail }),否则会因类型推断不一致导致编译错误
|
||||
Push.registerPush(context, SDKAppID.toInt(), appKey, new PushCallback(pushCbOptions));
|
||||
}
|
||||
|
||||
export function setRegistrationID(registrationID: string, onSuccess: () => void): void {
|
||||
const pushCbOptions: PushCallbackOptions = {
|
||||
apiName: 'setRegistrationID',
|
||||
success: (res?: any) => {
|
||||
onSuccess();
|
||||
},
|
||||
fail: (errCode: number, errMsg: string) => {
|
||||
// 空实现
|
||||
}
|
||||
};
|
||||
// 注意!!! 这里不要写成 new PushCallback({ api, success, fail }),否则会因类型推断不一致导致编译错误
|
||||
Push.setRegistrationID(registrationID, new PushCallback(pushCbOptions));
|
||||
}
|
||||
|
||||
export function getRegistrationID(onSuccess: (registrationID: string) => void): void {
|
||||
const pushCbOptions: PushCallbackOptions = {
|
||||
apiName: 'getRegistrationID',
|
||||
success: (res?: any) => {
|
||||
// 强转下类型,避免类型推断错误
|
||||
let registrationID: string = res as string;
|
||||
onSuccess(registrationID);
|
||||
},
|
||||
fail: (errCode: number, errMsg: string) => {
|
||||
// 空实现
|
||||
}
|
||||
};
|
||||
// 注意!!! 这里不要写成 new PushCallback({ api, success, fail }),否则会因类型推断不一致导致编译错误
|
||||
Push.getRegistrationID(new PushCallback(pushCbOptions));
|
||||
}
|
||||
|
||||
export function unRegisterPush(onSuccess: () => void, onError?: (errCode: number, errMsg: string) => void): void {
|
||||
const pushCbOptions: PushCallbackOptions = {
|
||||
apiName: 'unRegisterPush',
|
||||
success: (res?: any) => {
|
||||
onSuccess();
|
||||
},
|
||||
fail: (errCode: number, errMsg: string) => {
|
||||
// 空实现
|
||||
},
|
||||
};
|
||||
// 注意!!! 这里不要写成 new PushCallback({ api, success, fail }),否则会因类型推断不一致导致编译错误
|
||||
Push.unRegisterPush(new PushCallback(pushCbOptions));
|
||||
}
|
||||
|
||||
export function createNotificationChannel(options: any, onSuccess: (extInfo: string) => void): void {
|
||||
const pushCbOptions: PushCallbackOptions = {
|
||||
apiName: 'createNotificationChannel',
|
||||
success: (res?: any) => {
|
||||
let ret: string = res as string;
|
||||
onSuccess(ret);
|
||||
},
|
||||
fail: (errCode: number, errMsg: string) => {
|
||||
// 空实现
|
||||
},
|
||||
};
|
||||
Push.callExperimentalAPI('createNotificationChannel', JSON.stringify(options), new PushCallback(pushCbOptions));
|
||||
}
|
||||
|
||||
export function getNotificationExtInfo(onSuccess: (extInfo: string) => void): void {
|
||||
const pushCbOptions: PushCallbackOptions = {
|
||||
apiName: 'getNotificationExtInfo',
|
||||
success: (res?: any) => {
|
||||
let ret: string = res as string;
|
||||
onSuccess(ret);
|
||||
},
|
||||
fail: (errCode: number, errMsg: string) => {
|
||||
// 空实现
|
||||
},
|
||||
};
|
||||
Push.callExperimentalAPI('getNotificationExtInfo', null, new PushCallback(pushCbOptions));
|
||||
}
|
||||
|
||||
const listenerMap = new Map<string, Array<(res: any) => void>>();
|
||||
|
||||
const pushListenerOptions: PushListenerOptions = {
|
||||
listener: (eventName: string, data: any) => {
|
||||
listenerMap.get(eventName)?.forEach(item => {
|
||||
item(data);
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
const pushListener = new PushListener(pushListenerOptions);
|
||||
|
||||
@UTSJS.keepAlive
|
||||
export function addPushListener(eventName: string, listener: (res: any) => void): void {
|
||||
if(listenerMap.size === 0) {
|
||||
Push.addPushListener(pushListener);
|
||||
}
|
||||
const listeners:Array<(res: any) => void> = [listener];
|
||||
listenerMap.get(eventName)?.forEach(item => {
|
||||
listeners.push(item);
|
||||
})
|
||||
listenerMap.set(eventName, listeners);
|
||||
}
|
||||
|
||||
|
||||
export function removePushListener(eventName: string, listener?: (res: any) => void): void {
|
||||
listenerMap.delete(eventName);
|
||||
if(listenerMap.size === 0) {
|
||||
Push.removePushListener(pushListener);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
export type PushCallbackOptions = {
|
||||
apiName: string
|
||||
success: (res?: any) => void
|
||||
fail: (errCode: number, errMsg: string) => void
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import TIMPushCallback from 'com.tencent.qcloud.tim.push.TIMPushCallback';
|
||||
import { PushCallbackOptions } from './push-callback-options.uts';
|
||||
|
||||
const LOG_PREFIX: string = 'Push |';
|
||||
export default class PushCallback implements TIMPushCallback<any> {
|
||||
private apiName: string;
|
||||
private success: (data?: any) => void;
|
||||
private fail: (errCode: number, errMsg: string) => void;
|
||||
|
||||
constructor(options: PushCallbackOptions) {
|
||||
this.apiName = options.apiName;
|
||||
this.success = options.success;
|
||||
this.fail = options.fail;
|
||||
}
|
||||
|
||||
override onSuccess(data?: any) {
|
||||
console.log(`${LOG_PREFIX} ${this.apiName} ok, data:`, data);
|
||||
if (data == null) {
|
||||
this.success?.('');
|
||||
} else {
|
||||
this.success?.(data);
|
||||
}
|
||||
}
|
||||
|
||||
override onError(errCode: Int, errMsg: string, data?: any) {
|
||||
this.fail?.(errCode as number, errMsg);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export type PushListenerOptions = {
|
||||
listener: (eventType: string, data: any) => void
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import TIMPushListener from 'com.tencent.qcloud.tim.push.TIMPushListener';
|
||||
import TIMPushMessage from 'com.tencent.qcloud.tim.push.TIMPushMessage';
|
||||
import { PushListenerOptions } from './push-listener-options.uts';
|
||||
|
||||
const LOG_PREFIX: string = 'Push | PushListener';
|
||||
export default class PushListener implements TIMPushListener {
|
||||
private listener: (eventType: string, data: any) => void;
|
||||
|
||||
constructor(options: PushListenerOptions) {
|
||||
this.listener = options.listener;
|
||||
console.log(`${LOG_PREFIX} ok`);
|
||||
}
|
||||
|
||||
override onRecvPushMessage(message: TIMPushMessage) {
|
||||
this.listener('message_received', { data: message });
|
||||
}
|
||||
|
||||
override onRevokePushMessage(messageID: string) {
|
||||
this.listener('message_revoked', { data: messageID });
|
||||
}
|
||||
|
||||
override onNotificationClicked(ext: string) {
|
||||
this.listener('notification_clicked', { data: ext });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>aps-environment</key>
|
||||
<string>development</string>
|
||||
</dict>
|
||||
</plist>
|
||||
11
uni_modules/TencentCloud-Push/utssdk/app-ios/config.json
Normal file
11
uni_modules/TencentCloud-Push/utssdk/app-ios/config.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"deploymentTarget": "9.0",
|
||||
"dependencies-pods": [
|
||||
{
|
||||
"name": "TXIMSDK_Plus_iOS_XCFramework",
|
||||
"version": "8.5.6864"
|
||||
}, {
|
||||
"name": "TIMPush",
|
||||
"version": "8.5.6864"
|
||||
}]
|
||||
}
|
||||
125
uni_modules/TencentCloud-Push/utssdk/app-ios/index.uts
Normal file
125
uni_modules/TencentCloud-Push/utssdk/app-ios/index.uts
Normal file
@@ -0,0 +1,125 @@
|
||||
import { TIMPushManager } from "TIMPush"
|
||||
import { NSObject } from "DCloudUTSFoundation"
|
||||
import PushListener from './push-listener.uts'
|
||||
import { PushListenerOptions } from './push-listener-options.uts'
|
||||
|
||||
const LOG_PREFIX = 'Push |';
|
||||
|
||||
export class EVENT {
|
||||
static MESSAGE_RECEIVED: string = 'message_received'
|
||||
static MESSAGE_REVOKED: string = 'message_revoked'
|
||||
static NOTIFICATION_CLICKED: string = 'notification_clicked'
|
||||
}
|
||||
|
||||
function setRunningPlatform(): void {
|
||||
console.log(LOG_PREFIX, 'setRunningPlatform');
|
||||
const param = new NSString("{\"runningPlatform\":2}");
|
||||
TIMPushManager.callExperimentalAPI('setPushConfig', param = param, succ = (ext?: NSObject): void => {
|
||||
let platform: string = ext as string;
|
||||
console.log(LOG_PREFIX, 'setRunningPlatform ok. platform:', platform);
|
||||
}, fail = (code?: Int32 ,desc?:String): void => {
|
||||
console.log(LOG_PREFIX, `setRunningPlatform fail. code: ${code}, desc: ${desc}`);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
let disableNotification = false;
|
||||
|
||||
export function disablePostNotificationInForeground(_disable: boolean): void {
|
||||
console.log(LOG_PREFIX, 'disablePostNotificationInForeground', _disable);
|
||||
disableNotification = _disable;
|
||||
TIMPushManager.disablePostNotificationInForeground(disable = disableNotification);
|
||||
}
|
||||
|
||||
export function registerPush(SDKAppID: number, appKey: string, onSuccess: (data: string) => void, onError?: (errCode: number, errMsg: string) => void): void {
|
||||
if (SDKAppID == 0) {
|
||||
onError?.(9010001, 'Invalid SDKAppID');
|
||||
} else if (appKey == '') {
|
||||
onError?.(9010002, 'Invalid appKey');
|
||||
}
|
||||
setRunningPlatform();
|
||||
TIMPushManager.registerPush(SDKAppID.toInt32(), appKey = appKey, succ = (deviceToken?: Data): void => {
|
||||
TIMPushManager.disablePostNotificationInForeground(disable = disableNotification);
|
||||
console.log('devicetoken ->', deviceToken, deviceToken?.count);
|
||||
onSuccess('');
|
||||
}, fail = (code?: Int32 ,desc?:String): void => {
|
||||
onError?.(code as number, desc as string);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
export function unRegisterPush(onSuccess: () => void, onError: (errCode: number, errMsg: string) => void): void {
|
||||
TIMPushManager.unRegisterPush((): void => {
|
||||
onSuccess();
|
||||
}, fail = (code?: Int32 ,desc?:String): void => {
|
||||
onError(code as number, desc as string);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
export function setRegistrationID(registrationID: string, onSuccess: () => void): void {
|
||||
console.log(LOG_PREFIX, 'setRegistrationID', `registrationID:${registrationID}`);
|
||||
TIMPushManager.setRegistrationID(registrationID, callback = (): void => {
|
||||
console.log(LOG_PREFIX, 'setRegistrationID ok');
|
||||
onSuccess();
|
||||
});
|
||||
}
|
||||
|
||||
export function getRegistrationID(onSuccess: (registrationID: string) => void): void {
|
||||
TIMPushManager.getRegistrationID((value ?: string): void => {
|
||||
// 这里需要转一下,否则会有问题
|
||||
let ret: string = value as string;
|
||||
onSuccess(ret);
|
||||
});
|
||||
}
|
||||
|
||||
export function createNotificationChannel(options: any, onSuccess: (data: string) => void): void {
|
||||
// 空实现
|
||||
}
|
||||
|
||||
// 注意!!!这里的 extInfo 不能写成 ext,否则会跟内部的 ext?:NSObject 有冲突;也不能写成 extension,否则会导致编译错误
|
||||
export function getNotificationExtInfo(onSuccess: (extInfo: string) => void): void {
|
||||
console.log(LOG_PREFIX, 'getNotificationExtInfo');
|
||||
TIMPushManager.callExperimentalAPI('getNotificationExtInfo', param = {}, succ = (ext?: NSObject): void => {
|
||||
let str: string = ext as string;
|
||||
console.log(LOG_PREFIX, 'getNotificationExtInfo ok. ext:', str);
|
||||
onSuccess(str);
|
||||
}, fail = (code?: Int32 ,desc?:String): void => {
|
||||
// 空实现
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
const listenerMap = new Map<string, Array<(res: any) => void>>();
|
||||
|
||||
const pushListenerOptions: PushListenerOptions = {
|
||||
listener: (eventName: string, data: any) => {
|
||||
listenerMap.get(eventName)?.forEach(item => {
|
||||
item(data);
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
const pushListener = new PushListener(pushListenerOptions);
|
||||
|
||||
@UTSJS.keepAlive
|
||||
export function addPushListener(eventName: string, _listener: (res: any) => void): void {
|
||||
console.log(LOG_PREFIX, 'addPushListener', eventName);
|
||||
if(listenerMap.size === 0) {
|
||||
TIMPushManager.addPushListener(listener = pushListener);
|
||||
}
|
||||
const listeners:Array<(res: any) => void> = [_listener];
|
||||
listenerMap.get(eventName)?.forEach(item => {
|
||||
listeners.push(item);
|
||||
})
|
||||
listenerMap.set(eventName, listeners);
|
||||
}
|
||||
|
||||
export function removePushListener(eventName: string, _listener?: (res: any) => void): void {
|
||||
console.log(LOG_PREFIX, 'removePushListener', eventName);
|
||||
listenerMap.delete(eventName);
|
||||
if(listenerMap.size === 0) {
|
||||
TIMPushManager.removePushListener(listener = pushListener);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export type PushListenerOptions = {
|
||||
listener: (eventType: string, data: any) => void
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import { TIMPushListener, TIMPushMessage} from "TIMPush"
|
||||
import { PushListenerOptions } from './push-listener-options.uts';
|
||||
|
||||
const LOG_PREFIX: string = 'Push | PushListener';
|
||||
export default class PushListener implements TIMPushListener {
|
||||
private listener: (eventType: string, data: any) => void;
|
||||
|
||||
constructor(options: PushListenerOptions) {
|
||||
this.listener = options.listener;
|
||||
console.log(`${LOG_PREFIX} ok`);
|
||||
}
|
||||
|
||||
onRecvPushMessage(message: TIMPushMessage) {
|
||||
this.listener('message_received', { data: message });
|
||||
}
|
||||
|
||||
onRevokePushMessage(messageID: string) {
|
||||
this.listener('message_revoked', { data: messageID });
|
||||
}
|
||||
|
||||
onNotificationClicked(ext: string) {
|
||||
this.listener('notification_clicked', { data: ext });
|
||||
}
|
||||
}
|
||||
11
uni_modules/TencentCloud-Push/utssdk/interface.uts
Normal file
11
uni_modules/TencentCloud-Push/utssdk/interface.uts
Normal file
@@ -0,0 +1,11 @@
|
||||
interface Push {
|
||||
setRegistrationID(registrationID: string, onSuccess: () => void): void,
|
||||
registerPush(SDKAppID: number, appKey: string, onSuccess: (data: string) => void, onError?: (errCode: number, errMsg: string) => void): void,
|
||||
getRegistrationID(onSuccess: (registrationID: string) => void): void,
|
||||
unRegisterPush(onSuccess: () => void, onError?: (errCode: number, errMsg: string) => void): void,
|
||||
getNotificationExtInfo(onSuccess: (extInfo: string) => void): void
|
||||
addPushListener(eventName: string, listener: (res: any) => void): void
|
||||
removePushListener(eventName: string, listener?: (res: any) => void): void
|
||||
disablePostNotificationInForeground(disable: boolean): void
|
||||
createNotificationChannel(options: any, onSuccess: (data: string) => void): void
|
||||
}
|
||||
17
uni_modules/uni-combox/changelog.md
Normal file
17
uni_modules/uni-combox/changelog.md
Normal file
@@ -0,0 +1,17 @@
|
||||
## 1.0.2(2024-09-21)
|
||||
- 新增 clearAble属性
|
||||
## 1.0.1(2021-11-23)
|
||||
- 优化 label、label-width 属性
|
||||
## 1.0.0(2021-11-19)
|
||||
- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource)
|
||||
- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-combox](https://uniapp.dcloud.io/component/uniui/uni-combox)
|
||||
## 0.1.0(2021-07-30)
|
||||
- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)
|
||||
## 0.0.6(2021-05-12)
|
||||
- 新增 组件示例地址
|
||||
## 0.0.5(2021-04-21)
|
||||
- 优化 添加依赖 uni-icons, 导入后自动下载依赖
|
||||
## 0.0.4(2021-02-05)
|
||||
- 优化 组件引用关系,通过uni_modules引用组件
|
||||
## 0.0.3(2021-02-04)
|
||||
- 调整为uni_modules目录规范
|
||||
284
uni_modules/uni-combox/components/uni-combox/uni-combox.vue
Normal file
284
uni_modules/uni-combox/components/uni-combox/uni-combox.vue
Normal file
@@ -0,0 +1,284 @@
|
||||
<template>
|
||||
<view class="uni-combox" :class="border ? '' : 'uni-combox__no-border'">
|
||||
<view v-if="label" class="uni-combox__label" :style="labelStyle">
|
||||
<text>{{label}}</text>
|
||||
</view>
|
||||
<view class="uni-combox__input-box">
|
||||
<input class="uni-combox__input" type="text" :placeholder="placeholder" placeholder-class="uni-combox__input-plac"
|
||||
v-model="inputVal" @input="onInput" @focus="onFocus" @blur="onBlur" />
|
||||
<uni-icons v-if="!inputVal || !clearAble" :type="showSelector? 'top' : 'bottom'" size="14" color="#999" @click="toggleSelector">
|
||||
</uni-icons>
|
||||
<uni-icons v-if="inputVal && clearAble" type="clear" size="24" color="#999" @click="clean">
|
||||
</uni-icons>
|
||||
</view>
|
||||
<view class="uni-combox__selector" v-if="showSelector">
|
||||
<view class="uni-popper__arrow"></view>
|
||||
<scroll-view scroll-y="true" class="uni-combox__selector-scroll">
|
||||
<view class="uni-combox__selector-empty" v-if="filterCandidatesLength === 0">
|
||||
<text>{{emptyTips}}</text>
|
||||
</view>
|
||||
<view class="uni-combox__selector-item" v-for="(item,index) in filterCandidates" :key="index"
|
||||
@click="onSelectorClick(index)">
|
||||
<text>{{item}}</text>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* Combox 组合输入框
|
||||
* @description 组合输入框一般用于既可以输入也可以选择的场景
|
||||
* @tutorial https://ext.dcloud.net.cn/plugin?id=1261
|
||||
* @property {String} label 左侧文字
|
||||
* @property {String} labelWidth 左侧内容宽度
|
||||
* @property {String} placeholder 输入框占位符
|
||||
* @property {Array} candidates 候选项列表
|
||||
* @property {String} emptyTips 筛选结果为空时显示的文字
|
||||
* @property {String} value 组合框的值
|
||||
*/
|
||||
export default {
|
||||
name: 'uniCombox',
|
||||
emits: ['input', 'update:modelValue'],
|
||||
props: {
|
||||
clearAble: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
border: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
label: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
labelWidth: {
|
||||
type: String,
|
||||
default: 'auto'
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
candidates: {
|
||||
type: Array,
|
||||
default () {
|
||||
return []
|
||||
}
|
||||
},
|
||||
emptyTips: {
|
||||
type: String,
|
||||
default: '无匹配项'
|
||||
},
|
||||
// #ifndef VUE3
|
||||
value: {
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
},
|
||||
// #endif
|
||||
// #ifdef VUE3
|
||||
modelValue: {
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
},
|
||||
// #endif
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showSelector: false,
|
||||
inputVal: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
labelStyle() {
|
||||
if (this.labelWidth === 'auto') {
|
||||
return ""
|
||||
}
|
||||
return `width: ${this.labelWidth}`
|
||||
},
|
||||
filterCandidates() {
|
||||
return this.candidates.filter((item) => {
|
||||
return item.toString().indexOf(this.inputVal) > -1
|
||||
})
|
||||
},
|
||||
filterCandidatesLength() {
|
||||
return this.filterCandidates.length
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
// #ifndef VUE3
|
||||
value: {
|
||||
handler(newVal) {
|
||||
this.inputVal = newVal
|
||||
},
|
||||
immediate: true
|
||||
},
|
||||
// #endif
|
||||
// #ifdef VUE3
|
||||
modelValue: {
|
||||
handler(newVal) {
|
||||
this.inputVal = newVal
|
||||
},
|
||||
immediate: true
|
||||
},
|
||||
// #endif
|
||||
},
|
||||
methods: {
|
||||
toggleSelector() {
|
||||
this.showSelector = !this.showSelector
|
||||
},
|
||||
onFocus() {
|
||||
this.showSelector = true
|
||||
},
|
||||
onBlur() {
|
||||
setTimeout(() => {
|
||||
this.showSelector = false
|
||||
}, 153)
|
||||
},
|
||||
onSelectorClick(index) {
|
||||
this.inputVal = this.filterCandidates[index]
|
||||
this.showSelector = false
|
||||
this.$emit('input', this.inputVal)
|
||||
this.$emit('update:modelValue', this.inputVal)
|
||||
},
|
||||
onInput() {
|
||||
setTimeout(() => {
|
||||
this.$emit('input', this.inputVal)
|
||||
this.$emit('update:modelValue', this.inputVal)
|
||||
})
|
||||
},
|
||||
clean() {
|
||||
this.inputVal = ''
|
||||
this.onInput()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.uni-combox {
|
||||
font-size: 14px;
|
||||
border: 1px solid #DCDFE6;
|
||||
border-radius: 4px;
|
||||
padding: 6px 10px;
|
||||
position: relative;
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
// height: 40px;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
// border-bottom: solid 1px #DDDDDD;
|
||||
}
|
||||
|
||||
.uni-combox__label {
|
||||
font-size: 16px;
|
||||
line-height: 22px;
|
||||
padding-right: 10px;
|
||||
color: #999999;
|
||||
}
|
||||
|
||||
.uni-combox__input-box {
|
||||
position: relative;
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex: 1;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.uni-combox__input {
|
||||
flex: 1;
|
||||
font-size: 14px;
|
||||
height: 22px;
|
||||
line-height: 22px;
|
||||
}
|
||||
|
||||
.uni-combox__input-plac {
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.uni-combox__selector {
|
||||
/* #ifndef APP-NVUE */
|
||||
box-sizing: border-box;
|
||||
/* #endif */
|
||||
position: absolute;
|
||||
top: calc(100% + 12px);
|
||||
left: 0;
|
||||
width: 100%;
|
||||
background-color: #FFFFFF;
|
||||
border: 1px solid #EBEEF5;
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
||||
z-index: 2;
|
||||
padding: 4px 0;
|
||||
}
|
||||
|
||||
.uni-combox__selector-scroll {
|
||||
/* #ifndef APP-NVUE */
|
||||
max-height: 200px;
|
||||
box-sizing: border-box;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.uni-combox__selector-empty,
|
||||
.uni-combox__selector-item {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
cursor: pointer;
|
||||
/* #endif */
|
||||
line-height: 36px;
|
||||
font-size: 14px;
|
||||
text-align: center;
|
||||
// border-bottom: solid 1px #DDDDDD;
|
||||
padding: 0px 10px;
|
||||
}
|
||||
|
||||
.uni-combox__selector-item:hover {
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
|
||||
.uni-combox__selector-empty:last-child,
|
||||
.uni-combox__selector-item:last-child {
|
||||
/* #ifndef APP-NVUE */
|
||||
border-bottom: none;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
// picker 弹出层通用的指示小三角
|
||||
.uni-popper__arrow,
|
||||
.uni-popper__arrow::after {
|
||||
position: absolute;
|
||||
display: block;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-color: transparent;
|
||||
border-style: solid;
|
||||
border-width: 6px;
|
||||
}
|
||||
|
||||
.uni-popper__arrow {
|
||||
filter: drop-shadow(0 2px 12px rgba(0, 0, 0, 0.03));
|
||||
top: -6px;
|
||||
left: 10%;
|
||||
margin-right: 3px;
|
||||
border-top-width: 0;
|
||||
border-bottom-color: #EBEEF5;
|
||||
}
|
||||
|
||||
.uni-popper__arrow::after {
|
||||
content: " ";
|
||||
top: 1px;
|
||||
margin-left: -6px;
|
||||
border-top-width: 0;
|
||||
border-bottom-color: #fff;
|
||||
}
|
||||
|
||||
.uni-combox__no-border {
|
||||
border: none;
|
||||
}
|
||||
</style>
|
||||
88
uni_modules/uni-combox/package.json
Normal file
88
uni_modules/uni-combox/package.json
Normal file
@@ -0,0 +1,88 @@
|
||||
{
|
||||
"id": "uni-combox",
|
||||
"displayName": "uni-combox 组合框",
|
||||
"version": "1.0.2",
|
||||
"description": "可以选择也可以输入的表单项 ",
|
||||
"keywords": [
|
||||
"uni-ui",
|
||||
"uniui",
|
||||
"combox",
|
||||
"组合框",
|
||||
"select"
|
||||
],
|
||||
"repository": "https://github.com/dcloudio/uni-ui",
|
||||
"engines": {
|
||||
"HBuilderX": ""
|
||||
},
|
||||
"directories": {
|
||||
"example": "../../temps/example_temps"
|
||||
},
|
||||
"dcloudext": {
|
||||
"sale": {
|
||||
"regular": {
|
||||
"price": "0.00"
|
||||
},
|
||||
"sourcecode": {
|
||||
"price": "0.00"
|
||||
}
|
||||
},
|
||||
"contact": {
|
||||
"qq": ""
|
||||
},
|
||||
"declaration": {
|
||||
"ads": "无",
|
||||
"data": "无",
|
||||
"permissions": "无"
|
||||
},
|
||||
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui",
|
||||
"type": "component-vue"
|
||||
},
|
||||
"uni_modules": {
|
||||
"dependencies": [
|
||||
"uni-scss",
|
||||
"uni-icons"
|
||||
],
|
||||
"encrypt": [],
|
||||
"platforms": {
|
||||
"cloud": {
|
||||
"tcb": "y",
|
||||
"aliyun": "y",
|
||||
"alipay": "n"
|
||||
},
|
||||
"client": {
|
||||
"App": {
|
||||
"app-vue": "y",
|
||||
"app-nvue": "n"
|
||||
},
|
||||
"H5-mobile": {
|
||||
"Safari": "y",
|
||||
"Android Browser": "y",
|
||||
"微信浏览器(Android)": "y",
|
||||
"QQ浏览器(Android)": "y"
|
||||
},
|
||||
"H5-pc": {
|
||||
"Chrome": "y",
|
||||
"IE": "y",
|
||||
"Edge": "y",
|
||||
"Firefox": "y",
|
||||
"Safari": "y"
|
||||
},
|
||||
"小程序": {
|
||||
"微信": "y",
|
||||
"阿里": "y",
|
||||
"百度": "y",
|
||||
"字节跳动": "y",
|
||||
"QQ": "y"
|
||||
},
|
||||
"快应用": {
|
||||
"华为": "u",
|
||||
"联盟": "u"
|
||||
},
|
||||
"Vue": {
|
||||
"vue2": "y",
|
||||
"vue3": "y"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
11
uni_modules/uni-combox/readme.md
Normal file
11
uni_modules/uni-combox/readme.md
Normal file
@@ -0,0 +1,11 @@
|
||||
|
||||
|
||||
## Combox 组合框
|
||||
> **组件名:uni-combox**
|
||||
> 代码块: `uCombox`
|
||||
|
||||
|
||||
组合框组件。
|
||||
|
||||
### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-combox)
|
||||
#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839
|
||||
51
uni_modules/uni-data-checkbox/changelog.md
Normal file
51
uni_modules/uni-data-checkbox/changelog.md
Normal file
@@ -0,0 +1,51 @@
|
||||
## 1.0.6(2024-10-22)
|
||||
- 新增 当 multiple 为 false 且传递的 value 为 数组时,使用数组第一项用作反显
|
||||
## 1.0.5(2024-03-20)
|
||||
- 修复 单选模式下选中样式不生效的bug
|
||||
## 1.0.4(2024-01-27)
|
||||
- 修复 修复错别字chagne为change
|
||||
## 1.0.3(2022-09-16)
|
||||
- 可以使用 uni-scss 控制主题色
|
||||
## 1.0.2(2022-06-30)
|
||||
- 优化 在 uni-forms 中的依赖注入方式
|
||||
## 1.0.1(2022-02-07)
|
||||
- 修复 multiple 为 true 时,v-model 的值为 null 报错的 bug
|
||||
## 1.0.0(2021-11-19)
|
||||
- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource)
|
||||
- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-data-checkbox](https://uniapp.dcloud.io/component/uniui/uni-data-checkbox)
|
||||
## 0.2.5(2021-08-23)
|
||||
- 修复 在uni-forms中 modelValue 中不存在当前字段,当前字段必填写也不参与校验的问题
|
||||
## 0.2.4(2021-08-17)
|
||||
- 修复 单选 list 模式下 ,icon 为 left 时,选中图标不显示的问题
|
||||
## 0.2.3(2021-08-11)
|
||||
- 修复 在 uni-forms 中重置表单,错误信息无法清除的问题
|
||||
## 0.2.2(2021-07-30)
|
||||
- 优化 在uni-forms组件,与label不对齐的问题
|
||||
## 0.2.1(2021-07-27)
|
||||
- 修复 单选默认值为0不能选中的Bug
|
||||
## 0.2.0(2021-07-13)
|
||||
- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)
|
||||
## 0.1.11(2021-07-06)
|
||||
- 优化 删除无用日志
|
||||
## 0.1.10(2021-07-05)
|
||||
- 修复 由 0.1.9 引起的非 nvue 端图标不显示的问题
|
||||
## 0.1.9(2021-07-05)
|
||||
- 修复 nvue 黑框样式问题
|
||||
## 0.1.8(2021-06-28)
|
||||
- 修复 selectedTextColor 属性不生效的Bug
|
||||
## 0.1.7(2021-06-02)
|
||||
- 新增 map 属性,可以方便映射text/value属性
|
||||
## 0.1.6(2021-05-26)
|
||||
- 修复 不关联服务空间的情况下组件报错的Bug
|
||||
## 0.1.5(2021-05-12)
|
||||
- 新增 组件示例地址
|
||||
## 0.1.4(2021-04-09)
|
||||
- 修复 nvue 下无法选中的问题
|
||||
## 0.1.3(2021-03-22)
|
||||
- 新增 disabled属性
|
||||
## 0.1.2(2021-02-24)
|
||||
- 优化 默认颜色显示
|
||||
## 0.1.1(2021-02-24)
|
||||
- 新增 支持nvue
|
||||
## 0.1.0(2021-02-18)
|
||||
- “暂无数据”显示居中
|
||||
@@ -0,0 +1,316 @@
|
||||
|
||||
const events = {
|
||||
load: 'load',
|
||||
error: 'error'
|
||||
}
|
||||
const pageMode = {
|
||||
add: 'add',
|
||||
replace: 'replace'
|
||||
}
|
||||
|
||||
const attrs = [
|
||||
'pageCurrent',
|
||||
'pageSize',
|
||||
'collection',
|
||||
'action',
|
||||
'field',
|
||||
'getcount',
|
||||
'orderby',
|
||||
'where'
|
||||
]
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
listData: this.getone ? {} : [],
|
||||
paginationInternal: {
|
||||
current: this.pageCurrent,
|
||||
size: this.pageSize,
|
||||
count: 0
|
||||
},
|
||||
errorMessage: ''
|
||||
}
|
||||
},
|
||||
created() {
|
||||
let db = null;
|
||||
let dbCmd = null;
|
||||
|
||||
if(this.collection){
|
||||
this.db = uniCloud.database();
|
||||
this.dbCmd = this.db.command;
|
||||
}
|
||||
|
||||
this._isEnded = false
|
||||
|
||||
this.$watch(() => {
|
||||
let al = []
|
||||
attrs.forEach(key => {
|
||||
al.push(this[key])
|
||||
})
|
||||
return al
|
||||
}, (newValue, oldValue) => {
|
||||
this.paginationInternal.pageSize = this.pageSize
|
||||
|
||||
let needReset = false
|
||||
for (let i = 2; i < newValue.length; i++) {
|
||||
if (newValue[i] != oldValue[i]) {
|
||||
needReset = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if (needReset) {
|
||||
this.clear()
|
||||
this.reset()
|
||||
}
|
||||
if (newValue[0] != oldValue[0]) {
|
||||
this.paginationInternal.current = this.pageCurrent
|
||||
}
|
||||
|
||||
this._execLoadData()
|
||||
})
|
||||
|
||||
// #ifdef H5
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
this._debugDataList = []
|
||||
if (!window.unidev) {
|
||||
window.unidev = {
|
||||
clientDB: {
|
||||
data: []
|
||||
}
|
||||
}
|
||||
}
|
||||
unidev.clientDB.data.push(this._debugDataList)
|
||||
}
|
||||
// #endif
|
||||
|
||||
// #ifdef MP-TOUTIAO
|
||||
let changeName
|
||||
let events = this.$scope.dataset.eventOpts
|
||||
for (let i = 0; i < events.length; i++) {
|
||||
let event = events[i]
|
||||
if (event[0].includes('^load')) {
|
||||
changeName = event[1][0][0]
|
||||
}
|
||||
}
|
||||
if (changeName) {
|
||||
let parent = this.$parent
|
||||
let maxDepth = 16
|
||||
this._changeDataFunction = null
|
||||
while (parent && maxDepth > 0) {
|
||||
let fun = parent[changeName]
|
||||
if (fun && typeof fun === 'function') {
|
||||
this._changeDataFunction = fun
|
||||
maxDepth = 0
|
||||
break
|
||||
}
|
||||
parent = parent.$parent
|
||||
maxDepth--;
|
||||
}
|
||||
}
|
||||
// #endif
|
||||
|
||||
// if (!this.manual) {
|
||||
// this.loadData()
|
||||
// }
|
||||
},
|
||||
// #ifdef H5
|
||||
beforeDestroy() {
|
||||
if (process.env.NODE_ENV === 'development' && window.unidev) {
|
||||
let cd = this._debugDataList
|
||||
let dl = unidev.clientDB.data
|
||||
for (let i = dl.length - 1; i >= 0; i--) {
|
||||
if (dl[i] === cd) {
|
||||
dl.splice(i, 1)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
// #endif
|
||||
methods: {
|
||||
loadData(args1, args2) {
|
||||
let callback = null
|
||||
if (typeof args1 === 'object') {
|
||||
if (args1.clear) {
|
||||
this.clear()
|
||||
this.reset()
|
||||
}
|
||||
if (args1.current !== undefined) {
|
||||
this.paginationInternal.current = args1.current
|
||||
}
|
||||
if (typeof args2 === 'function') {
|
||||
callback = args2
|
||||
}
|
||||
} else if (typeof args1 === 'function') {
|
||||
callback = args1
|
||||
}
|
||||
|
||||
this._execLoadData(callback)
|
||||
},
|
||||
loadMore() {
|
||||
if (this._isEnded) {
|
||||
return
|
||||
}
|
||||
this._execLoadData()
|
||||
},
|
||||
refresh() {
|
||||
this.clear()
|
||||
this._execLoadData()
|
||||
},
|
||||
clear() {
|
||||
this._isEnded = false
|
||||
this.listData = []
|
||||
},
|
||||
reset() {
|
||||
this.paginationInternal.current = 1
|
||||
},
|
||||
remove(id, {
|
||||
action,
|
||||
callback,
|
||||
confirmTitle,
|
||||
confirmContent
|
||||
} = {}) {
|
||||
if (!id || !id.length) {
|
||||
return
|
||||
}
|
||||
uni.showModal({
|
||||
title: confirmTitle || '提示',
|
||||
content: confirmContent || '是否删除该数据',
|
||||
showCancel: true,
|
||||
success: (res) => {
|
||||
if (!res.confirm) {
|
||||
return
|
||||
}
|
||||
this._execRemove(id, action, callback)
|
||||
}
|
||||
})
|
||||
},
|
||||
_execLoadData(callback) {
|
||||
if (this.loading) {
|
||||
return
|
||||
}
|
||||
this.loading = true
|
||||
this.errorMessage = ''
|
||||
|
||||
this._getExec().then((res) => {
|
||||
this.loading = false
|
||||
const {
|
||||
data,
|
||||
count
|
||||
} = res.result
|
||||
this._isEnded = data.length < this.pageSize
|
||||
|
||||
callback && callback(data, this._isEnded)
|
||||
this._dispatchEvent(events.load, data)
|
||||
|
||||
if (this.getone) {
|
||||
this.listData = data.length ? data[0] : undefined
|
||||
} else if (this.pageData === pageMode.add) {
|
||||
this.listData.push(...data)
|
||||
if (this.listData.length) {
|
||||
this.paginationInternal.current++
|
||||
}
|
||||
} else if (this.pageData === pageMode.replace) {
|
||||
this.listData = data
|
||||
this.paginationInternal.count = count
|
||||
}
|
||||
|
||||
// #ifdef H5
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
this._debugDataList.length = 0
|
||||
this._debugDataList.push(...JSON.parse(JSON.stringify(this.listData)))
|
||||
}
|
||||
// #endif
|
||||
}).catch((err) => {
|
||||
this.loading = false
|
||||
this.errorMessage = err
|
||||
callback && callback()
|
||||
this.$emit(events.error, err)
|
||||
})
|
||||
},
|
||||
_getExec() {
|
||||
let exec = this.db
|
||||
if (this.action) {
|
||||
exec = exec.action(this.action)
|
||||
}
|
||||
|
||||
exec = exec.collection(this.collection)
|
||||
|
||||
if (!(!this.where || !Object.keys(this.where).length)) {
|
||||
exec = exec.where(this.where)
|
||||
}
|
||||
if (this.field) {
|
||||
exec = exec.field(this.field)
|
||||
}
|
||||
if (this.orderby) {
|
||||
exec = exec.orderBy(this.orderby)
|
||||
}
|
||||
|
||||
const {
|
||||
current,
|
||||
size
|
||||
} = this.paginationInternal
|
||||
exec = exec.skip(size * (current - 1)).limit(size).get({
|
||||
getCount: this.getcount
|
||||
})
|
||||
|
||||
return exec
|
||||
},
|
||||
_execRemove(id, action, callback) {
|
||||
if (!this.collection || !id) {
|
||||
return
|
||||
}
|
||||
|
||||
const ids = Array.isArray(id) ? id : [id]
|
||||
if (!ids.length) {
|
||||
return
|
||||
}
|
||||
|
||||
uni.showLoading({
|
||||
mask: true
|
||||
})
|
||||
|
||||
let exec = this.db
|
||||
if (action) {
|
||||
exec = exec.action(action)
|
||||
}
|
||||
|
||||
exec.collection(this.collection).where({
|
||||
_id: dbCmd.in(ids)
|
||||
}).remove().then((res) => {
|
||||
callback && callback(res.result)
|
||||
if (this.pageData === pageMode.replace) {
|
||||
this.refresh()
|
||||
} else {
|
||||
this.removeData(ids)
|
||||
}
|
||||
}).catch((err) => {
|
||||
uni.showModal({
|
||||
content: err.message,
|
||||
showCancel: false
|
||||
})
|
||||
}).finally(() => {
|
||||
uni.hideLoading()
|
||||
})
|
||||
},
|
||||
removeData(ids) {
|
||||
let il = ids.slice(0)
|
||||
let dl = this.listData
|
||||
for (let i = dl.length - 1; i >= 0; i--) {
|
||||
let index = il.indexOf(dl[i]._id)
|
||||
if (index >= 0) {
|
||||
dl.splice(i, 1)
|
||||
il.splice(index, 1)
|
||||
}
|
||||
}
|
||||
},
|
||||
_dispatchEvent(type, data) {
|
||||
if (this._changeDataFunction) {
|
||||
this._changeDataFunction(data, this._isEnded)
|
||||
} else {
|
||||
this.$emit(type, data, this._isEnded)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,853 @@
|
||||
<template>
|
||||
<view class="uni-data-checklist" :style="{'margin-top':isTop+'px'}">
|
||||
<template v-if="!isLocal">
|
||||
<view class="uni-data-loading">
|
||||
<uni-load-more v-if="!mixinDatacomErrorMessage" status="loading" iconType="snow" :iconSize="18"
|
||||
:content-text="contentText"></uni-load-more>
|
||||
<text v-else>{{mixinDatacomErrorMessage}}</text>
|
||||
</view>
|
||||
</template>
|
||||
<template v-else>
|
||||
<checkbox-group v-if="multiple" class="checklist-group" :class="{'is-list':mode==='list' || wrap}"
|
||||
@change="change">
|
||||
<label class="checklist-box"
|
||||
:class="['is--'+mode,item.selected?'is-checked':'',(disabled || !!item.disabled)?'is-disable':'',index!==0&&mode==='list'?'is-list-border':'']"
|
||||
:style="item.styleBackgroud" v-for="(item,index) in dataList" :key="index">
|
||||
<checkbox class="hidden" hidden :disabled="disabled || !!item.disabled" :value="item[map.value]+''"
|
||||
:checked="item.selected" />
|
||||
<view v-if="(mode !=='tag' && mode !== 'list') || ( mode === 'list' && icon === 'left')"
|
||||
class="checkbox__inner" :style="item.styleIcon">
|
||||
<view class="checkbox__inner-icon"></view>
|
||||
</view>
|
||||
<view class="checklist-content" :class="{'list-content':mode === 'list' && icon ==='left'}">
|
||||
<text class="checklist-text" :style="item.styleIconText">{{item[map.text]}}</text>
|
||||
<view v-if="mode === 'list' && icon === 'right'" class="checkobx__list" :style="item.styleBackgroud"></view>
|
||||
</view>
|
||||
</label>
|
||||
</checkbox-group>
|
||||
<radio-group v-else class="checklist-group" :class="{'is-list':mode==='list','is-wrap':wrap}" @change="change">
|
||||
<label class="checklist-box"
|
||||
:class="['is--'+mode,item.selected?'is-checked':'',(disabled || !!item.disabled)?'is-disable':'',index!==0&&mode==='list'?'is-list-border':'']"
|
||||
:style="item.styleBackgroud" v-for="(item,index) in dataList" :key="index">
|
||||
<radio class="hidden" hidden :disabled="disabled || item.disabled" :value="item[map.value]+''"
|
||||
:checked="item.selected" />
|
||||
<view v-if="(mode !=='tag' && mode !== 'list') || ( mode === 'list' && icon === 'left')" class="radio__inner"
|
||||
:style="item.styleBackgroud">
|
||||
<view class="radio__inner-icon" :style="item.styleIcon"></view>
|
||||
</view>
|
||||
<view class="checklist-content" :class="{'list-content':mode === 'list' && icon ==='left'}">
|
||||
<text class="checklist-text" :style="item.styleIconText">{{item[map.text]}}</text>
|
||||
<view v-if="mode === 'list' && icon === 'right'" :style="item.styleRightIcon" class="checkobx__list"></view>
|
||||
</view>
|
||||
</label>
|
||||
</radio-group>
|
||||
</template>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* DataChecklist 数据选择器
|
||||
* @description 通过数据渲染 checkbox 和 radio
|
||||
* @tutorial https://ext.dcloud.net.cn/plugin?id=xxx
|
||||
* @property {String} mode = [default| list | button | tag] 显示模式
|
||||
* @value default 默认横排模式
|
||||
* @value list 列表模式
|
||||
* @value button 按钮模式
|
||||
* @value tag 标签模式
|
||||
* @property {Boolean} multiple = [true|false] 是否多选
|
||||
* @property {Array|String|Number} value 默认值
|
||||
* @property {Array} localdata 本地数据 ,格式 [{text:'',value:''}]
|
||||
* @property {Number|String} min 最小选择个数 ,multiple为true时生效
|
||||
* @property {Number|String} max 最大选择个数 ,multiple为true时生效
|
||||
* @property {Boolean} wrap 是否换行显示
|
||||
* @property {String} icon = [left|right] list 列表模式下icon显示位置
|
||||
* @property {Boolean} selectedColor 选中颜色
|
||||
* @property {Boolean} emptyText 没有数据时显示的文字 ,本地数据无效
|
||||
* @property {Boolean} selectedTextColor 选中文本颜色,如不填写则自动显示
|
||||
* @property {Object} map 字段映射, 默认 map={text:'text',value:'value'}
|
||||
* @value left 左侧显示
|
||||
* @value right 右侧显示
|
||||
* @event {Function} change 选中发生变化触发
|
||||
*/
|
||||
|
||||
export default {
|
||||
name: 'uniDataChecklist',
|
||||
mixins: [uniCloud.mixinDatacom || {}],
|
||||
emits: ['input', 'update:modelValue', 'change'],
|
||||
props: {
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'default'
|
||||
},
|
||||
|
||||
multiple: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
value: {
|
||||
type: [Array, String, Number],
|
||||
default () {
|
||||
return ''
|
||||
}
|
||||
},
|
||||
// TODO vue3
|
||||
modelValue: {
|
||||
type: [Array, String, Number],
|
||||
default () {
|
||||
return '';
|
||||
}
|
||||
},
|
||||
localdata: {
|
||||
type: Array,
|
||||
default () {
|
||||
return []
|
||||
}
|
||||
},
|
||||
min: {
|
||||
type: [Number, String],
|
||||
default: ''
|
||||
},
|
||||
max: {
|
||||
type: [Number, String],
|
||||
default: ''
|
||||
},
|
||||
wrap: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
icon: {
|
||||
type: String,
|
||||
default: 'left'
|
||||
},
|
||||
selectedColor: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
selectedTextColor: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
emptyText: {
|
||||
type: String,
|
||||
default: '暂无数据'
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
map: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {
|
||||
text: 'text',
|
||||
value: 'value'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
localdata: {
|
||||
handler(newVal) {
|
||||
this.range = newVal
|
||||
this.dataList = this.getDataList(this.getSelectedValue(newVal))
|
||||
},
|
||||
deep: true
|
||||
},
|
||||
mixinDatacomResData(newVal) {
|
||||
this.range = newVal
|
||||
this.dataList = this.getDataList(this.getSelectedValue(newVal))
|
||||
},
|
||||
value(newVal) {
|
||||
this.dataList = this.getDataList(newVal)
|
||||
// fix by mehaotian is_reset 在 uni-forms 中定义
|
||||
// if(!this.is_reset){
|
||||
// this.is_reset = false
|
||||
// this.formItem && this.formItem.setValue(newVal)
|
||||
// }
|
||||
},
|
||||
modelValue(newVal) {
|
||||
this.dataList = this.getDataList(newVal);
|
||||
// if(!this.is_reset){
|
||||
// this.is_reset = false
|
||||
// this.formItem && this.formItem.setValue(newVal)
|
||||
// }
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
dataList: [],
|
||||
range: [],
|
||||
contentText: {
|
||||
contentdown: '查看更多',
|
||||
contentrefresh: '加载中',
|
||||
contentnomore: '没有更多'
|
||||
},
|
||||
isLocal: true,
|
||||
styles: {
|
||||
selectedColor: '#2979ff',
|
||||
selectedTextColor: '#666',
|
||||
},
|
||||
isTop: 0
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
dataValue() {
|
||||
if (this.value === '') return this.modelValue
|
||||
if (this.modelValue === '') return this.value
|
||||
return this.value
|
||||
}
|
||||
},
|
||||
created() {
|
||||
// this.form = this.getForm('uniForms')
|
||||
// this.formItem = this.getForm('uniFormsItem')
|
||||
// this.formItem && this.formItem.setValue(this.value)
|
||||
|
||||
// if (this.formItem) {
|
||||
// this.isTop = 6
|
||||
// if (this.formItem.name) {
|
||||
// // 如果存在name添加默认值,否则formData 中不存在这个字段不校验
|
||||
// if(!this.is_reset){
|
||||
// this.is_reset = false
|
||||
// this.formItem.setValue(this.dataValue)
|
||||
// }
|
||||
// this.rename = this.formItem.name
|
||||
// this.form.inputChildrens.push(this)
|
||||
// }
|
||||
// }
|
||||
|
||||
if (this.localdata && this.localdata.length !== 0) {
|
||||
this.isLocal = true
|
||||
this.range = this.localdata
|
||||
this.dataList = this.getDataList(this.getSelectedValue(this.range))
|
||||
} else {
|
||||
if (this.collection) {
|
||||
this.isLocal = false
|
||||
this.loadData()
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
loadData() {
|
||||
this.mixinDatacomGet().then(res => {
|
||||
this.mixinDatacomResData = res.result.data
|
||||
if (this.mixinDatacomResData.length === 0) {
|
||||
this.isLocal = false
|
||||
this.mixinDatacomErrorMessage = this.emptyText
|
||||
} else {
|
||||
this.isLocal = true
|
||||
}
|
||||
}).catch(err => {
|
||||
this.mixinDatacomErrorMessage = err.message
|
||||
})
|
||||
},
|
||||
/**
|
||||
* 获取父元素实例
|
||||
*/
|
||||
getForm(name = 'uniForms') {
|
||||
let parent = this.$parent;
|
||||
let parentName = parent.$options.name;
|
||||
while (parentName !== name) {
|
||||
parent = parent.$parent;
|
||||
if (!parent) return false
|
||||
parentName = parent.$options.name;
|
||||
}
|
||||
return parent;
|
||||
},
|
||||
change(e) {
|
||||
const values = e.detail.value
|
||||
|
||||
let detail = {
|
||||
value: [],
|
||||
data: []
|
||||
}
|
||||
|
||||
if (this.multiple) {
|
||||
this.range.forEach(item => {
|
||||
|
||||
if (values.includes(item[this.map.value] + '')) {
|
||||
detail.value.push(item[this.map.value])
|
||||
detail.data.push(item)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
const range = this.range.find(item => (item[this.map.value] + '') === values)
|
||||
if (range) {
|
||||
detail = {
|
||||
value: range[this.map.value],
|
||||
data: range
|
||||
}
|
||||
}
|
||||
}
|
||||
// this.formItem && this.formItem.setValue(detail.value)
|
||||
// TODO 兼容 vue2
|
||||
this.$emit('input', detail.value);
|
||||
// // TOTO 兼容 vue3
|
||||
this.$emit('update:modelValue', detail.value);
|
||||
this.$emit('change', {
|
||||
detail
|
||||
})
|
||||
if (this.multiple) {
|
||||
// 如果 v-model 没有绑定 ,则走内部逻辑
|
||||
// if (this.value.length === 0) {
|
||||
this.dataList = this.getDataList(detail.value, true)
|
||||
// }
|
||||
} else {
|
||||
this.dataList = this.getDataList(detail.value)
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取渲染的新数组
|
||||
* @param {Object} value 选中内容
|
||||
*/
|
||||
getDataList(value) {
|
||||
// 解除引用关系,破坏原引用关系,避免污染源数据
|
||||
let dataList = JSON.parse(JSON.stringify(this.range))
|
||||
let list = []
|
||||
if (this.multiple) {
|
||||
if (!Array.isArray(value)) {
|
||||
value = []
|
||||
}
|
||||
} else {
|
||||
if (Array.isArray(value) && value.length) {
|
||||
value = value[0]
|
||||
}
|
||||
}
|
||||
dataList.forEach((item, index) => {
|
||||
item.disabled = item.disable || item.disabled || false
|
||||
if (this.multiple) {
|
||||
if (value.length > 0) {
|
||||
let have = value.find(val => val === item[this.map.value])
|
||||
item.selected = have !== undefined
|
||||
} else {
|
||||
item.selected = false
|
||||
}
|
||||
} else {
|
||||
item.selected = value === item[this.map.value]
|
||||
}
|
||||
|
||||
list.push(item)
|
||||
})
|
||||
return this.setRange(list)
|
||||
},
|
||||
/**
|
||||
* 处理最大最小值
|
||||
* @param {Object} list
|
||||
*/
|
||||
setRange(list) {
|
||||
let selectList = list.filter(item => item.selected)
|
||||
let min = Number(this.min) || 0
|
||||
let max = Number(this.max) || ''
|
||||
list.forEach((item, index) => {
|
||||
if (this.multiple) {
|
||||
if (selectList.length <= min) {
|
||||
let have = selectList.find(val => val[this.map.value] === item[this.map.value])
|
||||
if (have !== undefined) {
|
||||
item.disabled = true
|
||||
}
|
||||
}
|
||||
|
||||
if (selectList.length >= max && max !== '') {
|
||||
let have = selectList.find(val => val[this.map.value] === item[this.map.value])
|
||||
if (have === undefined) {
|
||||
item.disabled = true
|
||||
}
|
||||
}
|
||||
}
|
||||
this.setStyles(item, index)
|
||||
list[index] = item
|
||||
})
|
||||
return list
|
||||
},
|
||||
/**
|
||||
* 设置 class
|
||||
* @param {Object} item
|
||||
* @param {Object} index
|
||||
*/
|
||||
setStyles(item, index) {
|
||||
// 设置自定义样式
|
||||
item.styleBackgroud = this.setStyleBackgroud(item)
|
||||
item.styleIcon = this.setStyleIcon(item)
|
||||
item.styleIconText = this.setStyleIconText(item)
|
||||
item.styleRightIcon = this.setStyleRightIcon(item)
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取选中值
|
||||
* @param {Object} range
|
||||
*/
|
||||
getSelectedValue(range) {
|
||||
if (!this.multiple) return this.dataValue
|
||||
let selectedArr = []
|
||||
range.forEach((item) => {
|
||||
if (item.selected) {
|
||||
selectedArr.push(item[this.map.value])
|
||||
}
|
||||
})
|
||||
return this.dataValue.length > 0 ? this.dataValue : selectedArr
|
||||
},
|
||||
|
||||
/**
|
||||
* 设置背景样式
|
||||
*/
|
||||
setStyleBackgroud(item) {
|
||||
let styles = {}
|
||||
let selectedColor = this.selectedColor ? this.selectedColor : '#2979ff'
|
||||
if (this.selectedColor) {
|
||||
if (this.mode !== 'list') {
|
||||
styles['border-color'] = item.selected ? selectedColor : '#DCDFE6'
|
||||
}
|
||||
if (this.mode === 'tag') {
|
||||
styles['background-color'] = item.selected ? selectedColor : '#f5f5f5'
|
||||
}
|
||||
}
|
||||
let classles = ''
|
||||
for (let i in styles) {
|
||||
classles += `${i}:${styles[i]};`
|
||||
}
|
||||
return classles
|
||||
},
|
||||
setStyleIcon(item) {
|
||||
let styles = {}
|
||||
let classles = ''
|
||||
if (this.selectedColor) {
|
||||
let selectedColor = this.selectedColor ? this.selectedColor : '#2979ff'
|
||||
styles['background-color'] = item.selected ? selectedColor : '#fff'
|
||||
styles['border-color'] = item.selected ? selectedColor : '#DCDFE6'
|
||||
|
||||
if (!item.selected && item.disabled) {
|
||||
styles['background-color'] = '#F2F6FC'
|
||||
styles['border-color'] = item.selected ? selectedColor : '#DCDFE6'
|
||||
}
|
||||
}
|
||||
for (let i in styles) {
|
||||
classles += `${i}:${styles[i]};`
|
||||
}
|
||||
return classles
|
||||
},
|
||||
setStyleIconText(item) {
|
||||
let styles = {}
|
||||
let classles = ''
|
||||
if (this.selectedColor) {
|
||||
let selectedColor = this.selectedColor ? this.selectedColor : '#2979ff'
|
||||
if (this.mode === 'tag') {
|
||||
styles.color = item.selected ? (this.selectedTextColor ? this.selectedTextColor : '#fff') : '#666'
|
||||
} else {
|
||||
styles.color = item.selected ? (this.selectedTextColor ? this.selectedTextColor : selectedColor) : '#666'
|
||||
}
|
||||
if (!item.selected && item.disabled) {
|
||||
styles.color = '#999'
|
||||
}
|
||||
}
|
||||
for (let i in styles) {
|
||||
classles += `${i}:${styles[i]};`
|
||||
}
|
||||
return classles
|
||||
},
|
||||
setStyleRightIcon(item) {
|
||||
let styles = {}
|
||||
let classles = ''
|
||||
if (this.mode === 'list') {
|
||||
styles['border-color'] = item.selected ? this.styles.selectedColor : '#DCDFE6'
|
||||
}
|
||||
for (let i in styles) {
|
||||
classles += `${i}:${styles[i]};`
|
||||
}
|
||||
|
||||
return classles
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
$uni-primary: #2979ff !default;
|
||||
$border-color: #DCDFE6;
|
||||
$disable: 0.4;
|
||||
|
||||
@mixin flex {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.uni-data-loading {
|
||||
@include flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 36px;
|
||||
padding-left: 10px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.uni-data-checklist {
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
flex: 1;
|
||||
|
||||
// 多选样式
|
||||
.checklist-group {
|
||||
@include flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
|
||||
&.is-list {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.checklist-box {
|
||||
@include flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
margin: 5px 0;
|
||||
margin-right: 25px;
|
||||
|
||||
.hidden {
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
// 文字样式
|
||||
.checklist-content {
|
||||
@include flex;
|
||||
flex: 1;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
.checklist-text {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
margin-left: 5px;
|
||||
line-height: 14px;
|
||||
}
|
||||
|
||||
.checkobx__list {
|
||||
border-right-width: 1px;
|
||||
border-right-color: #007aff;
|
||||
border-right-style: solid;
|
||||
border-bottom-width: 1px;
|
||||
border-bottom-color: #007aff;
|
||||
border-bottom-style: solid;
|
||||
height: 12px;
|
||||
width: 6px;
|
||||
left: -5px;
|
||||
transform-origin: center;
|
||||
transform: rotate(45deg);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
// 多选样式
|
||||
.checkbox__inner {
|
||||
/* #ifndef APP-NVUE */
|
||||
flex-shrink: 0;
|
||||
box-sizing: border-box;
|
||||
/* #endif */
|
||||
position: relative;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border: 1px solid $border-color;
|
||||
border-radius: 4px;
|
||||
background-color: #fff;
|
||||
z-index: 1;
|
||||
|
||||
.checkbox__inner-icon {
|
||||
position: absolute;
|
||||
/* #ifdef APP-NVUE */
|
||||
top: 2px;
|
||||
/* #endif */
|
||||
/* #ifndef APP-NVUE */
|
||||
top: 1px;
|
||||
/* #endif */
|
||||
left: 5px;
|
||||
height: 8px;
|
||||
width: 4px;
|
||||
border-right-width: 1px;
|
||||
border-right-color: #fff;
|
||||
border-right-style: solid;
|
||||
border-bottom-width: 1px;
|
||||
border-bottom-color: #fff;
|
||||
border-bottom-style: solid;
|
||||
opacity: 0;
|
||||
transform-origin: center;
|
||||
transform: rotate(40deg);
|
||||
}
|
||||
}
|
||||
|
||||
// 单选样式
|
||||
.radio__inner {
|
||||
@include flex;
|
||||
/* #ifndef APP-NVUE */
|
||||
flex-shrink: 0;
|
||||
box-sizing: border-box;
|
||||
/* #endif */
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border: 1px solid $border-color;
|
||||
border-radius: 16px;
|
||||
background-color: #fff;
|
||||
z-index: 1;
|
||||
|
||||
.radio__inner-icon {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 10px;
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
// 默认样式
|
||||
&.is--default {
|
||||
|
||||
// 禁用
|
||||
&.is-disable {
|
||||
/* #ifdef H5 */
|
||||
cursor: not-allowed;
|
||||
|
||||
/* #endif */
|
||||
.checkbox__inner {
|
||||
background-color: #F2F6FC;
|
||||
border-color: $border-color;
|
||||
/* #ifdef H5 */
|
||||
cursor: not-allowed;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.radio__inner {
|
||||
background-color: #F2F6FC;
|
||||
border-color: $border-color;
|
||||
}
|
||||
|
||||
.checklist-text {
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
// 选中
|
||||
&.is-checked {
|
||||
.checkbox__inner {
|
||||
border-color: $uni-primary;
|
||||
background-color: $uni-primary;
|
||||
|
||||
.checkbox__inner-icon {
|
||||
opacity: 1;
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
}
|
||||
|
||||
.radio__inner {
|
||||
border-color: $uni-primary;
|
||||
|
||||
.radio__inner-icon {
|
||||
opacity: 1;
|
||||
background-color: $uni-primary;
|
||||
}
|
||||
}
|
||||
|
||||
.checklist-text {
|
||||
color: $uni-primary;
|
||||
}
|
||||
|
||||
// 选中禁用
|
||||
&.is-disable {
|
||||
.checkbox__inner {
|
||||
opacity: $disable;
|
||||
}
|
||||
|
||||
.checklist-text {
|
||||
opacity: $disable;
|
||||
}
|
||||
|
||||
.radio__inner {
|
||||
opacity: $disable;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 按钮样式
|
||||
&.is--button {
|
||||
margin-right: 10px;
|
||||
padding: 5px 10px;
|
||||
border: 1px $border-color solid;
|
||||
border-radius: 3px;
|
||||
transition: border-color 0.2s;
|
||||
|
||||
// 禁用
|
||||
&.is-disable {
|
||||
/* #ifdef H5 */
|
||||
cursor: not-allowed;
|
||||
/* #endif */
|
||||
border: 1px #eee solid;
|
||||
opacity: $disable;
|
||||
|
||||
.checkbox__inner {
|
||||
background-color: #F2F6FC;
|
||||
border-color: $border-color;
|
||||
/* #ifdef H5 */
|
||||
cursor: not-allowed;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.radio__inner {
|
||||
background-color: #F2F6FC;
|
||||
border-color: $border-color;
|
||||
/* #ifdef H5 */
|
||||
cursor: not-allowed;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.checklist-text {
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
&.is-checked {
|
||||
border-color: $uni-primary;
|
||||
|
||||
.checkbox__inner {
|
||||
border-color: $uni-primary;
|
||||
background-color: $uni-primary;
|
||||
|
||||
.checkbox__inner-icon {
|
||||
opacity: 1;
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
}
|
||||
|
||||
.radio__inner {
|
||||
border-color: $uni-primary;
|
||||
|
||||
.radio__inner-icon {
|
||||
opacity: 1;
|
||||
background-color: $uni-primary;
|
||||
}
|
||||
}
|
||||
|
||||
.checklist-text {
|
||||
color: $uni-primary;
|
||||
}
|
||||
|
||||
// 选中禁用
|
||||
&.is-disable {
|
||||
opacity: $disable;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 标签样式
|
||||
&.is--tag {
|
||||
margin-right: 10px;
|
||||
padding: 5px 10px;
|
||||
border: 1px $border-color solid;
|
||||
border-radius: 3px;
|
||||
background-color: #f5f5f5;
|
||||
|
||||
.checklist-text {
|
||||
margin: 0;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
// 禁用
|
||||
&.is-disable {
|
||||
/* #ifdef H5 */
|
||||
cursor: not-allowed;
|
||||
/* #endif */
|
||||
opacity: $disable;
|
||||
}
|
||||
|
||||
&.is-checked {
|
||||
background-color: $uni-primary;
|
||||
border-color: $uni-primary;
|
||||
|
||||
.checklist-text {
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 列表样式
|
||||
&.is--list {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
padding: 10px 15px;
|
||||
padding-left: 0;
|
||||
margin: 0;
|
||||
|
||||
&.is-list-border {
|
||||
border-top: 1px #eee solid;
|
||||
}
|
||||
|
||||
// 禁用
|
||||
&.is-disable {
|
||||
/* #ifdef H5 */
|
||||
cursor: not-allowed;
|
||||
|
||||
/* #endif */
|
||||
.checkbox__inner {
|
||||
background-color: #F2F6FC;
|
||||
border-color: $border-color;
|
||||
/* #ifdef H5 */
|
||||
cursor: not-allowed;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.checklist-text {
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
&.is-checked {
|
||||
.checkbox__inner {
|
||||
border-color: $uni-primary;
|
||||
background-color: $uni-primary;
|
||||
|
||||
.checkbox__inner-icon {
|
||||
opacity: 1;
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
}
|
||||
|
||||
.radio__inner {
|
||||
border-color: $uni-primary;
|
||||
.radio__inner-icon {
|
||||
opacity: 1;
|
||||
background-color: $uni-primary;
|
||||
}
|
||||
}
|
||||
|
||||
.checklist-text {
|
||||
color: $uni-primary;
|
||||
}
|
||||
|
||||
.checklist-content {
|
||||
.checkobx__list {
|
||||
opacity: 1;
|
||||
border-color: $uni-primary;
|
||||
}
|
||||
}
|
||||
|
||||
// 选中禁用
|
||||
&.is-disable {
|
||||
.checkbox__inner {
|
||||
opacity: $disable;
|
||||
}
|
||||
|
||||
.checklist-text {
|
||||
opacity: $disable;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
87
uni_modules/uni-data-checkbox/package.json
Normal file
87
uni_modules/uni-data-checkbox/package.json
Normal file
@@ -0,0 +1,87 @@
|
||||
{
|
||||
"id": "uni-data-checkbox",
|
||||
"displayName": "uni-data-checkbox 数据选择器",
|
||||
"version": "1.0.6",
|
||||
"description": "通过数据驱动的单选框和复选框",
|
||||
"keywords": [
|
||||
"uni-ui",
|
||||
"checkbox",
|
||||
"单选",
|
||||
"多选",
|
||||
"单选多选"
|
||||
],
|
||||
"repository": "https://github.com/dcloudio/uni-ui",
|
||||
"engines": {
|
||||
"HBuilderX": "^3.1.1"
|
||||
},
|
||||
"directories": {
|
||||
"example": "../../temps/example_temps"
|
||||
},
|
||||
"dcloudext": {
|
||||
"sale": {
|
||||
"regular": {
|
||||
"price": "0.00"
|
||||
},
|
||||
"sourcecode": {
|
||||
"price": "0.00"
|
||||
}
|
||||
},
|
||||
"contact": {
|
||||
"qq": ""
|
||||
},
|
||||
"declaration": {
|
||||
"ads": "无",
|
||||
"data": "无",
|
||||
"permissions": "无"
|
||||
},
|
||||
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui",
|
||||
"type": "component-vue"
|
||||
},
|
||||
"uni_modules": {
|
||||
"dependencies": ["uni-load-more","uni-scss"],
|
||||
"encrypt": [],
|
||||
"platforms": {
|
||||
"cloud": {
|
||||
"tcb": "y",
|
||||
"aliyun": "y",
|
||||
"alipay": "n"
|
||||
},
|
||||
"client": {
|
||||
"App": {
|
||||
"app-vue": "y",
|
||||
"app-nvue": "y",
|
||||
"app-harmony": "u",
|
||||
"app-uvue": "u"
|
||||
},
|
||||
"H5-mobile": {
|
||||
"Safari": "y",
|
||||
"Android Browser": "y",
|
||||
"微信浏览器(Android)": "y",
|
||||
"QQ浏览器(Android)": "y"
|
||||
},
|
||||
"H5-pc": {
|
||||
"Chrome": "y",
|
||||
"IE": "y",
|
||||
"Edge": "y",
|
||||
"Firefox": "y",
|
||||
"Safari": "y"
|
||||
},
|
||||
"小程序": {
|
||||
"微信": "y",
|
||||
"阿里": "y",
|
||||
"百度": "y",
|
||||
"字节跳动": "y",
|
||||
"QQ": "y"
|
||||
},
|
||||
"快应用": {
|
||||
"华为": "u",
|
||||
"联盟": "u"
|
||||
},
|
||||
"Vue": {
|
||||
"vue2": "y",
|
||||
"vue3": "y"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
18
uni_modules/uni-data-checkbox/readme.md
Normal file
18
uni_modules/uni-data-checkbox/readme.md
Normal file
@@ -0,0 +1,18 @@
|
||||
|
||||
|
||||
## DataCheckbox 数据驱动的单选复选框
|
||||
> **组件名:uni-data-checkbox**
|
||||
> 代码块: `uDataCheckbox`
|
||||
|
||||
|
||||
本组件是基于uni-app基础组件checkbox的封装。本组件要解决问题包括:
|
||||
|
||||
1. 数据绑定型组件:给本组件绑定一个data,会自动渲染一组候选内容。再以往,开发者需要编写不少代码实现类似功能
|
||||
2. 自动的表单校验:组件绑定了data,且符合[uni-forms](https://ext.dcloud.net.cn/plugin?id=2773)组件的表单校验规范,搭配使用会自动实现表单校验
|
||||
3. 本组件合并了单选多选
|
||||
4. 本组件有若干风格选择,如普通的单选多选框、并列button风格、tag风格。开发者可以快速选择需要的风格。但作为一个封装组件,样式代码虽然不用自己写了,却会牺牲一定的样式自定义性
|
||||
|
||||
在uniCloud开发中,`DB Schema`中配置了enum枚举等类型后,在web控制台的[自动生成表单](https://uniapp.dcloud.io/uniCloud/schema?id=autocode)功能中,会自动生成``uni-data-checkbox``组件并绑定好data
|
||||
|
||||
### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-data-checkbox)
|
||||
#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839
|
||||
45
utils/media.js
Normal file
45
utils/media.js
Normal file
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* 封装 uni.chooseImage,返回 Promise,便于 async/await 使用
|
||||
* @param {Object} options - 透传给 uni.chooseImage 的配置
|
||||
* @returns {Promise<Array<string>>} 返回选中的临时图片路径数组
|
||||
*/
|
||||
export function chooseImage(options = {}) {
|
||||
// 默认配置
|
||||
const defaultOptions = {
|
||||
count: 1,
|
||||
sourceType: ['album', 'camera'],
|
||||
...options
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
uni.chooseImage({
|
||||
...defaultOptions,
|
||||
success(res) {
|
||||
if (res.tempFilePaths && res.tempFilePaths.length > 0) {
|
||||
resolve(res.tempFilePaths)
|
||||
} else {
|
||||
reject(new Error('未选择任何图片'))
|
||||
}
|
||||
},
|
||||
fail(err) {
|
||||
// 统一错误处理(如权限被拒、用户取消等)
|
||||
let msg = '选择图片失败'
|
||||
if (err.errMsg.includes('cancel')) {
|
||||
msg = '用户取消选择'
|
||||
} else if (
|
||||
err.errMsg.includes('auth deny') ||
|
||||
err.errMsg.includes('permission')
|
||||
) {
|
||||
msg = '请在设置中开启相册或相机权限'
|
||||
}
|
||||
// 可选:自动弹出 toast 提示
|
||||
uni.showToast({
|
||||
title: msg,
|
||||
icon: 'none',
|
||||
duration: 2000
|
||||
})
|
||||
reject(new Error(msg))
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -132,3 +132,4 @@ export const uploadMultipleFiles = (filePaths, config = {}) => {
|
||||
)
|
||||
return Promise.all(uploadPromises)
|
||||
}
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ const hideLoading = () => {
|
||||
* @param {string} type - 'success' | 'error' | 'warning' | 'none'
|
||||
* @param {number} duration - 持续时间(毫秒)
|
||||
*/
|
||||
const showToast = (message, type = 'none', duration = 2000) => {
|
||||
const showToast = (message, type = 'none', duration = 1800) => {
|
||||
let icon = 'none'
|
||||
if (type === 'success') icon = 'success'
|
||||
if (type === 'error') icon = 'error'
|
||||
|
||||
15
vue.config.js
Normal file
15
vue.config.js
Normal file
@@ -0,0 +1,15 @@
|
||||
const ScriptSetup = require('unplugin-vue2-script-setup/webpack').default;
|
||||
module.exports = {
|
||||
parallel: false,
|
||||
configureWebpack: {
|
||||
plugins: [
|
||||
ScriptSetup({
|
||||
/* options */
|
||||
}),
|
||||
],
|
||||
},
|
||||
chainWebpack(config) {
|
||||
// disable type check and let `vue-tsc` handles it
|
||||
config.plugins.delete('fork-ts-checker');
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user