Compare commits

...

3 Commits

Author SHA1 Message Date
49fbeead9a 'imByCh' 2025-03-28 18:09:23 +08:00
cefc3d67dc localdev 2025-03-28 16:05:11 +08:00
57daf0f0f8 本地添加达人 2025-03-24 10:53:42 +08:00
29 changed files with 3557 additions and 991 deletions

2
.gitignore vendored
View File

@ -7,4 +7,4 @@
/idea/
.vscode/
/unpackage/
.hbuilderx/launch.json
.hbuilderx/launch.json

View File

@ -266,8 +266,8 @@ body {
// html,
// body,
// page {
// width: 100% !important;
// height: 100% !important;
// width: 100% ;
// height: 100% ;
// overflow: hidden;
// }
// }
</style>

View File

@ -0,0 +1,60 @@
body, div, ul, ol, dt, dd, li, dl, h1, h2, h3, h4, p {
margin:0;
padding:0;
font-style:normal;
/* font:12px/22px"\5B8B\4F53",Arial,Helvetica,sans-serif; */
}
ol, ul, li {
list-style:none;
}
img {
border:0;
vertical-align:middle;
pointer-events:none;
}
body{
height: 100% important;
color:#000;
background:#FFF;
}
.clear {
clear:both;
height:1px;
width:100%;
overflow:hidden;
margin-top:-1px;
}
a {
color:#000;
text-decoration:none;
cursor: pointer;
}
a:hover {
text-decoration:none;
}
input, textarea {
user-select: auto;
}
input:focus, input:active, textarea:focus, textarea:active {
outline: none;
}
.chat-aside {
position: absolute;
top: 50px;
right: 0;
box-sizing: border-box;
width: 360px !important;
border-radius: 8px 0 0 8px;
z-index: 9999;
max-height: calc(100% - 50px);
}

View File

@ -0,0 +1,314 @@
<template>
<div class="chat" :style="{height:fn+'px'}">
<div :style="{height:'100%'}" :class="['tui-chat', !isPC && 'tui-chat-h5']">
<div
v-if="!currentConversationID"
:class="['tui-chat-default', !isPC && 'tui-chat-h5-default']"
>
<slot />
</div>
<div
v-if="currentConversationID"
:class="['tui-chat', !isPC && 'tui-chat-h5']"
>
<ChatHeader
:class="[
'tui-chat-header',
!isPC && 'tui-chat-H5-header',
isUniFrameWork && 'tui-chat-uniapp-header',
]"
:isGroup="isGroup"
:headerExtensionList="headerExtensionList"
@closeChat="closeChat"
@openGroupManagement="handleGroup"
/>
<Forward @toggleMultipleSelectMode="toggleMultipleSelectMode" />
<MessageList
ref="messageListRef"
:class="['tui-chat-message-list', !isPC && 'tui-chat-h5-message-list']"
:isGroup="isGroup"
:groupID="groupID"
:isNotInGroup="isNotInGroup"
:isMultipleSelectMode="isMultipleSelectMode"
@handleEditor="handleEditor"
@closeInputToolBar="() => changeToolbarDisplayType('none')"
@toggleMultipleSelectMode="toggleMultipleSelectMode"
/>
<div
v-if="isNotInGroup"
:class="{
'tui-chat-leave-group': true,
'tui-chat-leave-group-mobile': isMobile,
}"
>
{{ leaveGroupReasonText }}
</div>
<MultipleSelectPanel
v-else-if="isMultipleSelectMode"
@oneByOneForwardMessage="oneByOneForwardMessage"
@mergeForwardMessage="mergeForwardMessage"
@toggleMultipleSelectMode="toggleMultipleSelectMode"
/>
<template v-else>
<MessageInputToolbar
v-if="isInputToolbarShow"
:class="[
'tui-chat-message-input-toolbar',
!isPC && 'tui-chat-h5-message-input-toolbar',
isUniFrameWork && 'tui-chat-uni-message-input-toolbar'
]"
:displayType="inputToolbarDisplayType"
@insertEmoji="insertEmoji"
@changeToolbarDisplayType="changeToolbarDisplayType"
@scrollToLatestMessage="scrollToLatestMessage"
/>
<MessageInput
ref="messageInputRef"
:class="[
'tui-chat-message-input',
!isPC && 'tui-chat-h5-message-input',
isUniFrameWork && 'tui-chat-uni-message-input',
isWeChat && 'tui-chat-wx-message-input',
]"
:enableAt="featureConfig.InputMention"
:isMuted="false"
:muteText="TUITranslateService.t('TUIChat.您已被管理员禁言')"
:placeholder="TUITranslateService.t('TUIChat.请输入消息')"
:inputToolbarDisplayType="inputToolbarDisplayType"
@changeToolbarDisplayType="changeToolbarDisplayType"
/>
</template>
</div>
<!-- Group Management -->
<div
v-if="!isNotInGroup && !isApp && isUniFrameWork && isGroup && headerExtensionList.length > 0"
class="group-profile"
@click="handleGroup"
>
{{ headerExtensionList[0].text }}
</div>
</div>
</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';
import TUICore, { TUIConstants, ExtensionInfo } from '@tencentcloud/tui-core';
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';
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
const emits = defineEmits(['closeChat']);
const fn = uni.getSystemInfoSync().windowHeight
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 ht = uni.getSystemInfoSync().windowHeight
onMounted(() => {
TUIStore.watch(StoreName.CONV, {
currentConversation: onCurrentConversationUpdate,
});
});
onUnmounted(() => {
TUIStore.unwatch(StoreName.CONV, {
currentConversation: onCurrentConversationUpdate,
});
reset();
});
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;
}
return text;
});
const reset = () => {
TUIConversationService.switchConversation('');
};
const closeChat = (conversationID: string) => {
emits('closeChat', conversationID);
reset();
};
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 handleGroup = () => {
headerExtensionList.value[0].listener.onClicked({ groupID: groupID.value });
};
function changeToolbarDisplayType(type: ToolbarDisplayType) {
inputToolbarDisplayType.value = inputToolbarDisplayType.value === type ? 'none' : type;
if (inputToolbarDisplayType.value !== 'none' && isUniFrameWork) {
uni.$emit('scroll-to-bottom');
}
}
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;
}
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 roomkitor 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;
}
</script>
<style scoped lang="scss" src="./style/index.scss">
.chat{
width: 100% !important;
height: 100vh !important;
overflow: hidden;
}
</style>

View File

@ -0,0 +1,243 @@
<template>
<div :class="['message-input', !isPC && 'message-input-h5']">
<div class="audio-main-content-line">
<MessageInputAudio
v-if="(isWeChat || isApp) && isRenderVoice"
:class="{
'message-input-wx-audio-open': displayType === 'audio',
}"
:isEnableAudio="displayType === 'audio'"
@changeDisplayType="changeDisplayType"
/>
<MessageInputEditor
v-show="displayType === 'editor'"
ref="editor"
class="message-input-editor"
:placeholder="props.placeholder"
:isMuted="props.isMuted"
:muteText="props.muteText"
:enableInput="props.enableInput"
:enableAt="props.enableAt"
:enableTyping="props.enableTyping"
:isGroup="isGroup"
@onTyping="onTyping"
@onAt="onAt"
@onFocus="onFocus"
/>
<MessageInputAt
v-if="props.enableAt"
ref="messageInputAtRef"
@insertAt="insertAt"
@onAtListOpen="onAtListOpen"
/>
<Icon
v-if="isRenderEmojiPicker"
class="icon icon-face"
:file="faceIcon"
:size="'23px'"
:hotAreaSize="'3px'"
@onClick="changeToolbarDisplayType('emojiPicker')"
/>
<Icon
v-if="isRenderMore"
class="icon icon-more"
:file="moreIcon"
:size="'23px'"
:hotAreaSize="'3px'"
@onClick="changeToolbarDisplayType('tools')"
/>
</div>
<div>
<MessageQuote
:style="{minWidth: 0}"
:displayType="displayType"
/>
</div>
</div>
</template>
<script setup lang="ts">
import TUIChatEngine, {
TUIStore,
StoreName,
IMessageModel,
IConversationModel,
} from '@tencentcloud/chat-uikit-engine';
import { ref, watch, onMounted, onUnmounted } from '../../../adapter-vue';
import MessageInputEditor from './message-input-editor.vue';
import MessageInputAt from './message-input-at/index.vue';
import MessageInputAudio from './message-input-audio.vue';
import MessageQuote from './message-input-quote/index.vue';
import Icon from '../../common/Icon.vue';
import faceIcon from '../../../assets/icon/face-uni.png';
import moreIcon from '../../../assets/icon/more-uni.png';
import { isPC, isH5, isWeChat, isApp } from '../../../utils/env';
import { sendTyping } from '../utils/sendMessage';
import { ToolbarDisplayType, InputDisplayType } from '../../../interface';
import TUIChatConfig from '../config';
interface IProps {
placeholder: string;
isMuted?: boolean;
muteText?: string;
enableInput?: boolean;
enableAt?: boolean;
enableTyping?: boolean;
replyOrReference?: Record<string, any>;
inputToolbarDisplayType: ToolbarDisplayType;
}
interface IEmits {
(e: 'changeToolbarDisplayType', displayType: ToolbarDisplayType): void;
}
const emits = defineEmits<IEmits>();
const props = withDefaults(defineProps<IProps>(), {
placeholder: 'this is placeholder',
replyOrReference: () => ({}),
isMuted: true,
muteText: '',
enableInput: true,
enableAt: true,
enableTyping: true,
inputToolbarDisplayType: 'none',
});
const editor = ref();
const messageInputAtRef = ref();
const currentConversation = ref<IConversationModel>();
const isGroup = ref<boolean>(false);
const displayType = ref<InputDisplayType>('editor');
const featureConfig = TUIChatConfig.getFeatureConfig();
const isRenderVoice = ref<boolean>(featureConfig.InputVoice);
const isRenderEmojiPicker = ref<boolean>(featureConfig.InputEmoji || featureConfig.InputStickers);
const isRenderMore = ref<boolean>(featureConfig.InputImage || featureConfig.InputVideo || featureConfig.InputEvaluation || featureConfig.InputQuickReplies);
onMounted(() => {
TUIStore.watch(StoreName.CONV, {
currentConversation: onCurrentConversationUpdated,
});
TUIStore.watch(StoreName.CHAT, {
quoteMessage: onQuoteMessageUpdated,
});
});
onUnmounted(() => {
TUIStore.unwatch(StoreName.CONV, {
currentConversation: onCurrentConversationUpdated,
});
TUIStore.unwatch(StoreName.CHAT, {
quoteMessage: onQuoteMessageUpdated,
});
});
watch(() => props.inputToolbarDisplayType, (newVal: ToolbarDisplayType) => {
if (newVal !== 'none') {
changeDisplayType('editor');
}
});
function changeDisplayType(display: InputDisplayType) {
displayType.value = display;
if (display === 'audio') {
emits('changeToolbarDisplayType', 'none');
}
}
function changeToolbarDisplayType(displayType: ToolbarDisplayType) {
emits('changeToolbarDisplayType', displayType);
}
const onTyping = (inputContentEmpty: boolean, inputBlur: boolean) => {
sendTyping(inputContentEmpty, inputBlur);
};
const onAt = (show: boolean) => {
messageInputAtRef?.value?.toggleAtList(show);
};
const onFocus = () => {
// if (isH5) {
emits('changeToolbarDisplayType', 'none');
// }
//
// emits('changeToolbarDisplayType', 'none');
};
const insertEmoji = (emoji: any) => {
editor?.value?.addEmoji && editor?.value?.addEmoji(emoji);
};
const insertAt = (atInfo: any) => {
editor?.value?.insertAt && editor?.value?.insertAt(atInfo);
};
const onAtListOpen = () => {
editor?.value?.blur && editor?.value?.blur();
};
const reEdit = (content: any) => {
editor?.value?.resetEditor();
editor?.value?.setEditorContent(content);
};
function onCurrentConversationUpdated(conversation: IConversationModel) {
currentConversation.value = conversation;
isGroup.value = currentConversation.value?.type === TUIChatEngine.TYPES.CONV_GROUP;
}
function onQuoteMessageUpdated(options?: { message: IMessageModel; type: string }) {
// switch text input mode when there is a quote message
if (options?.message && options?.type === 'quote') {
changeDisplayType('editor');
}
}
defineExpose({
insertEmoji,
reEdit,
});
</script>
<style scoped lang="scss">
@import "../../../assets/styles/common";
:not(not) {
display: flex;
flex-direction: column;
min-width: 0;
box-sizing: border-box;
}
.message-input {
position: relative;
display: flex;
flex-direction: column;
border: none;
overflow: hidden;
background: #ebf0f6;
&-h5 {
padding: 10px 10px 15px;
}
&-editor {
flex: 1;
display: flex;
}
.icon {
margin-left: 3px;
}
&-wx-audio-open {
flex: 1;
}
}
.audio-main-content-line {
display: flex;
flex-direction: row;
align-items: center;
}
</style>

View File

@ -0,0 +1,285 @@
<template>
<div
:class="{
'message-input-container': true,
'message-input-container-h5': !isPC,
}"
>
<div
v-if="props.isMuted"
class="message-input-mute"
>
{{ props.muteText }}
</div>
<input
id="editor"
ref="inputRef"
v-model="inputText"
:adjust-position="true"
cursor-spacing="20"
confirm-type="send"
:confirm-hold="true"
maxlength="140"
type="text"
placeholder-class="input-placeholder"
class="message-input-area"
:placeholder="props.placeholder"
auto-blur
@confirm="handleSendMessage"
@input="onInput"
@blur="onBlur"
@focus="onFocus"
>
</div>
</template>
<script lang="ts" setup>
import { ref, watch, onMounted, onUnmounted } from '../../../adapter-vue';
import { TUIStore, StoreName, IConversationModel, IMessageModel } from '@tencentcloud/chat-uikit-engine';
import { TUIGlobal } from '@tencentcloud/universal-api';
import DraftManager from '../utils/conversationDraft';
import { transformTextWithEmojiNamesToKeys } from '../emoji-config';
import { isPC } from '../../../utils/env';
import { sendMessages } from '../utils/sendMessage';
import { ISendMessagePayload } from '../../../interface';
const props = defineProps({
placeholder: {
type: String,
default: 'this is placeholder',
},
replayOrReferenceMessage: {
type: Object,
default: () => ({}),
required: false,
},
isMuted: {
type: Boolean,
default: true,
},
muteText: {
type: String,
default: '',
},
enableInput: {
type: Boolean,
default: true,
},
enableAt: {
type: Boolean,
default: true,
},
enableTyping: {
type: Boolean,
default: true,
},
isGroup: {
type: Boolean,
default: false,
},
});
const emits = defineEmits(['onTyping', 'onFocus', 'onAt']);
const inputText = ref('');
const inputRef = ref();
const inputBlur = ref(true);
const inputContentEmpty = ref(true);
const allInsertedAtInfo = new Map();
const currentConversation = ref<IConversationModel>();
const currentConversationID = ref<string>('');
const currentQuoteMessage = ref<{ message: IMessageModel; type: string }>();
onMounted(() => {
TUIStore.watch(StoreName.CONV, {
currentConversation: onCurrentConversationUpdated,
});
TUIStore.watch(StoreName.CHAT, {
quoteMessage: onQuoteMessageUpdated,
});
uni.$on('insert-emoji', (data) => {
inputText.value += data?.emoji?.name;
});
uni.$on('send-message-in-emoji-picker', () => {
handleSendMessage();
});
});
onUnmounted(() => {
if (currentConversationID.value) {
DraftManager.setStore(currentConversationID.value, inputText.value, inputText.value, currentQuoteMessage.value);
}
uni.$off('insertEmoji');
uni.$off('send-message-in-emoji-picker');
TUIStore.unwatch(StoreName.CONV, {
currentConversation: onCurrentConversationUpdated,
});
TUIStore.unwatch(StoreName.CHAT, {
quoteMessage: onQuoteMessageUpdated,
});
reset();
});
const handleSendMessage = () => {
const messageList = getEditorContent();
resetEditor();
sendMessages(messageList as any, currentConversation.value!);
};
const insertAt = (atInfo: any) => {
if (!allInsertedAtInfo?.has(atInfo?.id)) {
allInsertedAtInfo?.set(atInfo?.id, atInfo?.label);
}
inputText.value += atInfo?.label;
};
const getEditorContent = () => {
let text = inputText.value;
text = transformTextWithEmojiNamesToKeys(text);
const atUserList: string[] = [];
allInsertedAtInfo?.forEach((value: string, key: string) => {
if (text?.includes('@' + value)) {
atUserList.push(key);
}
});
const payload: ISendMessagePayload = {
text,
};
if (atUserList?.length) {
payload.atUserList = atUserList;
}
return [
{
type: 'text',
payload,
},
];
};
const resetEditor = () => {
inputText.value = '';
inputContentEmpty.value = true;
allInsertedAtInfo?.clear();
};
const setEditorContent = (content: any) => {
inputText.value = content;
};
const onBlur = () => {
inputBlur.value = true;
};
const onFocus = (e: any) => {
inputBlur.value = false;
emits('onFocus', e?.detail?.height);
};
const isEditorContentEmpty = () => {
inputContentEmpty.value = inputText?.value?.length ? false : true;
};
const onInput = (e: any) => {
// uni-app recognizes mention messages
const text = e?.detail?.value;
isEditorContentEmpty();
if (props.isGroup && (text.endsWith('@') || text.endsWith('@\n'))) {
// TUIGlobal?.hideKeyboard();
emits('onAt', true);
}
};
watch(
() => [inputContentEmpty.value, inputBlur.value],
(newVal: any, oldVal: any) => {
if (newVal !== oldVal) {
emits('onTyping', inputContentEmpty.value, inputBlur.value);
}
},
{
immediate: true,
deep: true,
},
);
function onCurrentConversationUpdated(conversation: IConversationModel) {
const prevConversationID = currentConversationID.value;
currentConversation.value = conversation;
currentConversationID.value = conversation?.conversationID;
if (prevConversationID !== currentConversationID.value) {
if (prevConversationID) {
DraftManager.setStore(
prevConversationID,
inputText.value,
inputText.value,
currentQuoteMessage.value,
);
}
resetEditor();
if (currentConversationID.value) {
DraftManager.getStore(currentConversationID.value, setEditorContent);
}
}
}
function onQuoteMessageUpdated(options?: { message: IMessageModel; type: string }) {
currentQuoteMessage.value = options;
}
function reset() {
inputBlur.value = true;
currentConversation.value = null;
currentConversationID.value = '';
currentQuoteMessage.value = null;
resetEditor();
}
defineExpose({
insertAt,
resetEditor,
setEditorContent,
getEditorContent,
});
</script>
<style lang="scss" scoped>
@import "../../../assets/styles/common";
.message-input-container {
display: flex;
flex-direction: column;
flex: 1;
padding: 3px 10px 10px;
overflow: hidden;
&-h5 {
flex: 1;
height: auto;
background: #fff;
border-radius: 10px;
padding: 7px 0 7px 10px;
font-size: 16px !important;
max-height: 86px;
}
.message-input-mute {
flex: 1;
display: flex;
color: #999;
font-size: 14px;
justify-content: center;
align-items: center;
}
.message-input-area {
flex: 1;
overflow-y: scroll;
min-height: 25px;
}
}
</style>

View File

@ -0,0 +1,430 @@
<template>
<div
:style="{height: '100%'}"
v-if="typeof contactInfoData === 'object' && Object.keys(contactInfoData).length"
:class="['tui-contact-info', !isPC && 'tui-contact-info-h5']"
>
<div
v-if="!isPC"
:class="[
'tui-contact-info-header',
!isPC && 'tui-contact-info-h5-header',
]"
>
<div
:class="[
'tui-contact-info-header-icon',
!isPC && 'tui-contact-info-h5-header-icon',
]"
@click="resetContactSearchingUIData"
>
<Icon :file="backSVG" />
</div>
<div
:class="[
'tui-contact-info-header-title',
!isPC && 'tui-contact-info-h5-header-title',
]"
>
{{ TUITranslateService.t("TUIContact.添加好友/群聊") }}
</div>
</div>
<div :class="['tui-contact-info-basic', !isPC && 'tui-contact-info-h5-basic']">
<div
:class="[
'tui-contact-info-basic-text',
!isPC && 'tui-contact-info-h5-basic-text',
]"
>
<div
:class="[
'tui-contact-info-basic-text-name',
!isPC && 'tui-contact-info-h5-basic-text-name',
]"
>
{{ generateContactInfoName(contactInfoData) }}
</div>
<div
v-for="item in contactInfoBasicList"
:key="item.label"
:class="[
'tui-contact-info-basic-text-other',
!isPC && 'tui-contact-info-h5-basic-text-other',
]"
>
{{
`${TUITranslateService.t(`TUIContact.${item.label}`)}:
${item.data}`
}}
</div>
</div>
<img
:class="[
'tui-contact-info-basic-avatar',
!isPC && 'tui-contact-info-h5-basic-avatar',
]"
:src="generateAvatar(contactInfoData)"
>
</div>
<div
v-if="contactInfoMoreList[0]"
:class="['tui-contact-info-more', !isPC && 'tui-contact-info-h5-more']"
>
<div
v-for="item in contactInfoMoreList"
:key="item.key"
:class="[
'tui-contact-info-more-item',
!isPC && 'tui-contact-info-h5-more-item',
item.labelPosition === CONTACT_INFO_LABEL_POSITION.TOP
? 'tui-contact-info-more-item-top'
: 'tui-contact-info-more-item-left',
]"
>
<div
:class="[
'tui-contact-info-more-item-label',
!isPC && 'tui-contact-info-h5-more-item-label',
]"
>
{{ `${TUITranslateService.t(`TUIContact.${item.label}`)}` }}
</div>
<div
:class="[
'tui-contact-info-more-item-content',
!isPC && 'tui-contact-info-h5-more-item-content',
]"
>
<div
v-if="!item.editing"
:class="[
'tui-contact-info-more-item-content-text',
!isPC && 'tui-contact-info-h5-more-item-content-text',
]"
>
<div
:class="[
'tui-contact-info-more-item-content-text-data',
!isPC && 'tui-contact-info-h5-more-item-content-text-data',
]"
>
{{ item.data }}
</div>
<div
v-if="item.editable"
:class="[
'tui-contact-info-more-item-content-text-icon',
!isPC && 'tui-contact-info-h5-more-item-content-text-icon',
]"
@click="setEditing(item)"
>
<Icon
:file="editSVG"
width="14px"
height="14px"
/>
</div>
</div>
<input
v-else-if="item.editType === CONTACT_INFO_MORE_EDIT_TYPE.INPUT"
v-model="item.data"
:class="[
'tui-contact-info-more-item-content-input',
!isPC && 'tui-contact-info-h5-more-item-content-input',
]"
type="text"
@confirm="onContactInfoEmitSubmit(item)"
@keyup.enter="onContactInfoEmitSubmit(item)"
>
<textarea
v-else-if="item.editType === CONTACT_INFO_MORE_EDIT_TYPE.TEXTAREA"
v-model="item.data"
:class="[
'tui-contact-info-more-item-content-textarea',
!isPC && 'tui-contact-info-h5-more-item-content-textarea',
]"
confirm-type="done"
/>
<div
v-else-if="item.editType === CONTACT_INFO_MORE_EDIT_TYPE.SWITCH"
@click="onContactInfoEmitSubmit(item)"
>
<SwitchBar :value="item.data" />
</div>
</div>
</div>
</div>
<div
:class="[
'tui-contact-info-button',
!isPC && 'tui-contact-info-h5-button',
]"
>
<button
v-for="item in contactInfoButtonList"
:key="item.key"
:class="[
'tui-contact-info-button-item',
!isPC && 'tui-contact-info-h5-button-item',
item.type === CONTACT_INFO_BUTTON_TYPE.CANCEL
? `tui-contact-info-button-item-cancel`
: `tui-contact-info-button-item-submit`,
]"
@click="onContactInfoButtonClicked(item)"
>
{{ TUITranslateService.t(`TUIContact.${item.label}`) }}
</button>
</div>
</div>
</template>
<script setup lang="ts">
import TUIChatEngine, {
TUIStore,
StoreName,
TUITranslateService,
IGroupModel,
Friend,
FriendApplication,
} from '@tencentcloud/chat-uikit-engine';
import { TUIGlobal } from '@tencentcloud/universal-api';
import { ref, computed, onMounted, onUnmounted } from '../../../adapter-vue';
import { isPC } from '../../../utils/env';
import {
generateAvatar,
generateContactInfoName,
generateContactInfoBasic,
isFriend,
isApplicationType,
} from '../utils/index';
import {
contactMoreInfoConfig,
contactButtonConfig,
} from './contact-info-config';
import Icon from '../../common/Icon.vue';
import editSVG from '../../../assets/icon/edit.svg';
import backSVG from '../../../assets/icon/back.svg';
import SwitchBar from '../../common/SwitchBar/index.vue';
import {
IBlackListUserItem,
IContactInfoMoreItem,
IContactInfoButton,
} from '../../../interface';
import {
CONTACT_INFO_LABEL_POSITION,
CONTACT_INFO_MORE_EDIT_TYPE,
CONTACT_INFO_BUTTON_TYPE,
} from '../../../constant';
import { deepCopy } from '../../TUIChat/utils/utils';
type IContactInfoType = IGroupModel | Friend | FriendApplication | IBlackListUserItem;
const emits = defineEmits(['switchConversation']);
const contactInfoData = ref<IContactInfoType>({} as IContactInfoType);
const contactInfoBasicList = ref<Array<{ label: string; data: string }>>([]);
const contactInfoMoreList = ref<IContactInfoMoreItem[]>([]);
const contactInfoButtonList = ref<IContactInfoButton[]>([]);
const setEditing = (item: any) => {
item.editing = true;
};
const isGroup = computed((): boolean =>
(contactInfoData.value as IGroupModel)?.groupID ? true : false,
);
const isApplication = computed((): boolean => {
return isApplicationType(contactInfoData?.value);
});
// is both friend, if is group type always false
const isBothFriend = ref<boolean>(false);
// is group member, including ordinary member, admin, group owner
const isGroupMember = computed((): boolean => {
return (contactInfoData.value as IGroupModel)?.selfInfo?.userID ? true : false;
});
// is in black list, if is group type always false
const isInBlackList = computed((): boolean => {
return (
!isGroup.value
&& blackList.value?.findIndex(
(item: IBlackListUserItem) =>
item?.userID === (contactInfoData.value as IBlackListUserItem)?.userID,
) >= 0
);
});
const blackList = ref<IBlackListUserItem[]>([]);
onMounted(() => {
TUIStore.watch(StoreName.CUSTOM, {
currentContactInfo: onCurrentContactInfoUpdated,
});
TUIStore.watch(StoreName.USER, {
userBlacklist: onUserBlacklistUpdated,
});
});
onUnmounted(() => {
TUIStore.unwatch(StoreName.CUSTOM, {
currentContactInfo: onCurrentContactInfoUpdated,
});
TUIStore.unwatch(StoreName.USER, {
userBlacklist: onUserBlacklistUpdated,
});
});
const resetContactInfoUIData = () => {
contactInfoData.value = {} as IContactInfoType;
contactInfoBasicList.value = [];
contactInfoMoreList.value = [];
contactInfoButtonList.value = [];
};
const resetContactSearchingUIData = () => {
TUIStore.update(StoreName.CUSTOM, 'currentContactInfo', {});
TUIStore.update(StoreName.CUSTOM, 'currentContactSearchingStatus', false);
TUIGlobal?.closeSearching && TUIGlobal?.closeSearching();
};
const onContactInfoEmitSubmit = (item: any) => {
item.editSubmitHandler
&& item.editSubmitHandler({
item,
contactInfoData: contactInfoData.value,
isBothFriend: isBothFriend.value,
isInBlackList: isInBlackList.value,
});
};
const onContactInfoButtonClicked = (item: any) => {
item.onClick
&& item.onClick({
contactInfoData: contactInfoData.value,
contactInfoMoreList: contactInfoMoreList.value,
});
if (
item.key === 'enterGroupConversation'
|| item.key === 'enterC2CConversation'
) {
emits('switchConversation', contactInfoData.value);
resetContactSearchingUIData();
}
};
const generateMoreInfo = async () => {
if (!isApplication.value) {
if (
(!isGroup.value && !isBothFriend.value && !isInBlackList.value)
|| (isGroup.value
&& !isGroupMember.value
&& (contactInfoData.value as IGroupModel)?.type !== TUIChatEngine?.TYPES?.GRP_AVCHATROOM)
) {
contactMoreInfoConfig.setWords.data = '';
contactInfoMoreList.value.push(contactMoreInfoConfig.setWords);
}
if (!isGroup.value && !isInBlackList.value) {
contactMoreInfoConfig.setRemark.data
= (contactInfoData.value as Friend)?.remark || '';
contactMoreInfoConfig.setRemark.editing = false;
contactInfoMoreList.value.push(contactMoreInfoConfig.setRemark);
}
if (!isGroup.value && (isBothFriend.value || isInBlackList.value)) {
contactMoreInfoConfig.blackList.data = isInBlackList.value || false;
contactInfoMoreList.value.push(contactMoreInfoConfig.blackList);
}
} else {
contactMoreInfoConfig.displayWords.data
= (contactInfoData.value as FriendApplication)?.wording || '';
contactInfoMoreList.value.push(contactMoreInfoConfig.displayWords);
}
};
const generateButton = () => {
if (isInBlackList.value) {
return;
}
if (isApplication.value) {
if (
(contactInfoData.value as FriendApplication)?.type
=== TUIChatEngine?.TYPES?.SNS_APPLICATION_SENT_TO_ME
) {
contactInfoButtonList?.value?.push(
contactButtonConfig.refuseFriendApplication,
);
contactInfoButtonList?.value?.push(
contactButtonConfig.acceptFriendApplication,
);
}
} else {
if (isGroup.value && isGroupMember.value) {
switch ((contactInfoData.value as IGroupModel)?.selfInfo?.role) {
case 'Owner':
contactInfoButtonList?.value?.push(contactButtonConfig.dismissGroup);
break;
default:
contactInfoButtonList?.value?.push(contactButtonConfig.quitGroup);
break;
}
contactInfoButtonList?.value?.push(
contactButtonConfig.enterGroupConversation,
);
} else if (!isGroup.value && isBothFriend.value) {
contactInfoButtonList?.value?.push(contactButtonConfig.deleteFriend);
contactInfoButtonList?.value?.push(
contactButtonConfig.enterC2CConversation,
);
} else {
if (isGroup.value) {
contactInfoButtonList?.value?.push(
(contactInfoData.value as IGroupModel)?.type === TUIChatEngine?.TYPES?.GRP_AVCHATROOM
? contactButtonConfig.joinAVChatGroup
: contactButtonConfig.joinGroup,
);
} else {
contactInfoButtonList?.value?.push(contactButtonConfig.addFriend);
}
}
}
};
function onUserBlacklistUpdated(userBlacklist: IBlackListUserItem[]) {
blackList.value = userBlacklist;
}
async function onCurrentContactInfoUpdated(contactInfo: IContactInfoType) {
if (
contactInfoData.value
&& contactInfo
&& JSON.stringify(contactInfoData.value) === JSON.stringify(contactInfo)
) {
return;
}
resetContactInfoUIData();
// deep clone
contactInfoData.value = deepCopy(contactInfo) || {};
if (!contactInfoData.value || Object.keys(contactInfoData.value)?.length === 0) {
return;
}
contactInfoBasicList.value = generateContactInfoBasic(
contactInfoData.value,
);
isBothFriend.value = await isFriend(contactInfoData.value);
generateMoreInfo();
generateButton();
if (contactInfo.infoKeyList) {
contactInfoMoreList.value = contactInfo.infoKeyList.map((key: string) => {
return (contactMoreInfoConfig as any)[key];
});
}
if (contactInfo.btnKeyList) {
contactInfoButtonList.value = contactInfo.btnKeyList.map((key: string) => {
return (contactButtonConfig as any)[key];
});
}
}
</script>
<style lang="scss" scoped src="./style/index.scss"></style>

View File

@ -0,0 +1,138 @@
<template>
<SelectFriend v-if="isShowSelectFriend" />
<div
v-else-if="isShowContactList"
:class="['tui-contact', !isPC && 'tui-contact-h5']"
>
<div :class="['tui-contact-left', !isPC && 'tui-contact-h5-left']" :style="{height:ht+'px'}">
<!-- <ContactSearch /> -->
<ContactList :class="['tui-contact-left-list', !isPC && 'tui-contact-h5-left-list']" />
</div>
<div
v-if="isShowContactInfo"
:class="['tui-contact-right', !isPC && 'tui-contact-h5-right']"
:style="{height:ht+'px'}"
>
<ContactInfo @switchConversation="switchConversation" />
</div>
</div>
</template>
<script lang="ts" setup>
import { TUIStore, StoreName } from '@tencentcloud/chat-uikit-engine';
import { TUIGlobal } from '@tencentcloud/universal-api';
import { ref, watchEffect,defineProps } from '../../adapter-vue';
import { isPC, isUniFrameWork } from '../../utils/env';
const ht = uni.getSystemInfoSync().windowHeight
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';
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,
},
stu: {
type: Number, // String, Number, Object
require: true,
}
});
const displayTypeRef = ref<string>(props.displayType || 'contactList');
const isShowSelectFriend = ref(false);
const isShowContactList = ref(true);
const isShowContactInfo = ref(true);
const isstu=ref(props.stu);
watchEffect(() => {
isShowContactList.value = props?.displayType !== 'selectFriend';
});
TUIStore.watch(StoreName.CUSTOM, {
isShowSelectFriendComponent: (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 */ });
}
}
},
currentContactInfo: (contactInfo: any) => {
isShowContactInfo.value = isPC || (contactInfo && typeof contactInfo === 'object' && Object.keys(contactInfo)?.length > 0);
},
});
const switchConversation = (data: any) => {
isUniFrameWork
&& TUIGlobal?.navigateTo({
url: '/TUIKit/components/TUIChat/index',
});
emits('switchConversation', data);
};
</script>
<style lang="scss" scoped>
@import "../../assets/styles/common";
.tui-contact {
width: 100%;
height: 100%;
box-sizing: border-box;
display: flex;
overflow: hidden;
&-left {
min-width: 285px;
flex: 0 0 24%;
overflow: hidden;
display: flex;
flex-direction: column;
}
&-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>

View File

@ -0,0 +1,139 @@
<template>
<SelectFriend v-if="isShowSelectFriend" />
<div
v-else-if="isShowContactList"
:class="['tui-contact', !isPC && 'tui-contact-h5']"
>
<div :class="['tui-contact-left', !isPC && 'tui-contact-h5-left']" :style="{height:ht+'px'}" >
<ContactSearch />
<ContactList :class="['tui-contact-left-list', !isPC && 'tui-contact-h5-left-list']" />
</div>
<div
v-if="isShowContactInfo"
:class="['tui-contact-right', !isPC && 'tui-contact-h5-right']"
:style="{height:ht+'px'}"
>
<ContactInfo @switchConversation="switchConversation" />
</div>
</div>
</template>
<script lang="ts" setup>
import { TUIStore, StoreName } from '@tencentcloud/chat-uikit-engine';
import { TUIGlobal } from '@tencentcloud/universal-api';
import { ref, watchEffect,defineProps } from '../../adapter-vue';
import { isPC, isUniFrameWork } from '../../utils/env';
import SelectFriend from './select-friend/index.vue';
import ContactSearch from './contact-search/index.vue';
import ContactList from './contact-list/indexsea.vue';
import ContactInfo from './contact-info/index.vue';
const emits = defineEmits(['switchConversation']);
const ht = uni.getSystemInfoSync().windowHeight
const props = defineProps({
// web/h5 single page application display format, uniapp please ignore
displayType: {
type: String,
default: 'contactList', // "contactList" / "selectFriend"
require: false,
},
stu: {
type: Number, // String, Number, Object
require: true,
}
});
const displayTypeRef = ref<string>(props.displayType || 'contactList');
const isShowSelectFriend = ref(false);
const isShowContactList = ref(true);
const isShowContactInfo = ref(true);
const isstu=ref(props.stu);
watchEffect(() => {
isShowContactList.value = props?.displayType !== 'selectFriend';
});
TUIStore.watch(StoreName.CUSTOM, {
isShowSelectFriendComponent: (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 */ });
}
}
},
currentContactInfo: (contactInfo: any) => {
isShowContactInfo.value = isPC || (contactInfo && typeof contactInfo === 'object' && Object.keys(contactInfo)?.length > 0);
},
});
const switchConversation = (data: any) => {
isUniFrameWork
&& TUIGlobal?.navigateTo({
url: '/TUIKit/components/TUIChat/index',
});
emits('switchConversation', data);
};
</script>
<style lang="scss" scoped>
@import "../../assets/styles/common";
.tui-contact {
width: 100%;
height: 100%;
box-sizing: border-box;
display: flex;
overflow: hidden;
&-left {
min-width: 285px;
flex: 0 0 24%;
overflow: hidden;
display: flex;
flex-direction: column;
}
&-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>

View File

@ -12,6 +12,17 @@ export function getAdvertisement() {
});
}
/**
* 像后端传递经纬度
*/
export function postAdvertisementjw(data) {
return http.request({
url: "/member/address/location",
method: Method.POST,
header: { "content-type": "application/x-www-form-urlencoded" },
data
});
}
/**
@ -50,7 +61,17 @@ export function getFloorData() {
method: "get",
});
}
/**
* 获取本地生活楼层数据
* @param client_type
* @param page_type
*/
export function getFloorgetBendi() {
return http.request({
url: `/other/pageData/getBendi?clientType=H5`,
method: "get",
});
}
/**
* 获取获取首页分类数据
*/

View File

@ -13,7 +13,7 @@ import {http, Method} from '@/utils/request.js';
*/
export function getStoreList(params) {
return http.request({
url: '/store',
url: '/store/store',
method: Method.GET,
params,
});

View File

@ -284,9 +284,11 @@ export default {
});
return;
}
const storedSte = uni.getStorageSync('ste');
let data = {
skuId: this.goodsDetail.id,
num: this.num,
ste:storedSte
};
if (val == "cart") {

View File

@ -7,7 +7,7 @@ export default {
shareLink: "https://m-b2b2c.pickmall.cn", //分享地址也就是在h5中默认的复制地址
appid: "wx6f10f29075dc1b0b", //小程序唯一凭证,即 AppID可在「微信公众平台 - 设置 - 开发设置」页中获得。(需要已经成为开发者,且帐号没有异常状态)
appSecret: "6dfbe0c72380dce5d49d65b3c91059b1", //可在 manifest.json 查看
aMapKey: "d649892b3937a5ad20b76dacb2bcb5bd", //在高德中申请web端key
aMapKey: "AOHBZ-VCEL3-XX73N-O623U-FMTP6-ASBTD", //在腾讯的中申请web端key
scanAuthNavigation:['https://m-b2b2c.pickmall.cn/'], //扫码认证跳转域名配置 会根据此处配置的路由进行跳转
iosAppId:"id1564638363", //AppStore的应用地址id 具体在分享->拷贝链接中查看
logo:"https://lilishop-oss.oss-cn-beijing.aliyuncs.com/4c864e133c2944efad1f7282ac8a3b9e.png", //logo地址

View File

@ -1,7 +1,7 @@
{
"name" : "wzj4",
"appid" : "__UNI__6DB512D",
"description" : "",
"appid" : "__UNI__1F0975C",
"description" : "admin",
"versionName" : "4.0.0",
"versionCode" : 4000049,
"transformPx" : false,

2084
pages.json

File diff suppressed because it is too large Load Diff

View File

@ -541,13 +541,15 @@ export default {
// #ifdef APP-PLUS
client = "APP";
// #endif
const storedSte = uni.getStorageSync('ste');
let submit = {
client,
way: this.routerVal.way,
remark: this.remarkVal,
parentOrderSn: "",
ste:storedSte
};
console.log(submit,'===')
//
this.routerVal.parentOrder && this.routerVal.parentOrder.orderSn
? (submit.parentOrderSn = this.routerVal.parentOrder.orderSn)

View File

@ -788,7 +788,7 @@ export default {
id: this.productId,
};
storage.setCartBackbtn(obj);
uni.switchTab({
uni.navigateTo({
url: "/pages/tabbar/cart/cartList",
});
},

View File

@ -63,6 +63,7 @@ export default {
},
tostorePage(val) {
console.log(val);
uni.navigateTo({
url: "../product/shopPage?id=" + val.storeId,
});

View File

@ -7,21 +7,22 @@
<div class="wrapper" v-if="storeList.length!=0">
<div class="store-item" @click="handleClickStore(item)" v-for="(item,index) in storeList" :key="index">
<div>
<u-image shape="circle" width="100" height="100" :src="item.storeLogo">
<u-image shape="square" width="100" height="100" :src="item.storeLogo">
</u-image>
</div>
<div class="store-msg">
<div class="store-name">
{{item.storeName}}
<div> {{item.storeName}}</div>
<div class="typeof" >{{item.selfOperated?'自营':'非自营'}}</div>
</div>
<div class="goods-num">
商品 {{item.goodsNum}}
商品 {{item.goodsNum}} <span class="line">|</span> <span class="store-collection">收藏 {{item.collectionNum}}</span>
</div>
<div class="flex store-distance">
<div>
<span class="store-score">{{item.serviceScore | unitPrice}}</span>
<span class="line">|</span>
<span class="store-collection">收藏 {{item.collectionNum}}</span>
<template v-for="i in 5">
<u-icon :name="i <= item.serviceScore ? 'star-fill' : 'star'" color="#FF0000" size="30"></u-icon>
</template>
</div>
</div>
</div>
@ -113,11 +114,19 @@ export default {
padding: 24rpx;
}
.store-msg {
width: 100%;
margin-left: 20rpx;
}
.store-name {
font-weight: bold;
font-size: 30rpx;
font-size: 35rpx;
display: flex;
justify-content: space-between;
.typeof{
border: 1px solid red;
color: red;
font-size: 30rpx;
}
}
.goods-num,
.store-collection {

View File

@ -1,7 +1,23 @@
<template>
<div class="wrapper">
<!-- 楼层装修组件 -->
<tpl ref="tpl" />
<u-navbar class="navbar" :is-back="false" :is-fixed="false">
<div class="tab-container">
<div
v-for="(item, index) in list1"
:key="index"
:class="{ active: currentTabIndex === index }"
@click="click(item)"
>
{{ item.name }}
</div>
</div>
</u-navbar>
<!-- 商城 -->
<tpl ref="tpl" v-if="currentTabIndex == 1" :ste="1" />
<!-- 本地生活 -->
<tpl ref="tpl" v-if="currentTabIndex == 0" :ste="0" />
</div>
</template>
<script>
@ -13,21 +29,55 @@ export default {
background: {
backgroundColor: "#fff",
},
list1: [
{
name: "本地生活",
},
{
name: "商城",
},
],
currentTabIndex: 0,
};
},
onPullDownRefresh() {
this.$refs.tpl.init();
uni.stopPullDownRefresh();
},
components: {
tpl,
},
methods: {
click(item) {
this.currentTabIndex = this.list1.indexOf(item);
},
},
onPullDownRefresh() {
this.$refs.tpl.init();
uni.stopPullDownRefresh();
},
};
</script>
<style lang="scss" scoped>
.wrapper{
.tab-container {
width: 100%;
display: flex;
justify-content: space-evenly;
// background-color: #f0f0f0;
// padding: 0 16rpx 0 0;
font-size: 18px;
}
.tab-container div {
cursor: pointer;
padding: 5px 10px;
}
.tab-container div.active {
border-bottom: 2px solid #F50505;
color:#F50505;
background-color: transparent;
}
.wrapper {
// background: red;
}
.navbar{
height: 100%;
}
</style>

View File

@ -0,0 +1,103 @@
<template>
<div class="layout">
<div class="con_view">
<div class="-item-tilte">
<img class="image-mode" style="height: 150px" :src="model.imgs" />
<span> {{ model.name }}</span>
</div>
<div class="sea" @click="clickSwiper">更多></div>
</div>
<!-- <div>自己代码编辑</div> -->
<div class="menu-list" style="overflow-x: auto; white-space: nowrap; scrollbar-width: none; -ms-overflow-style: none;">
<div class="menu-item" v-for="(item, index) in res.list" :key="index">
<div>
<img class="menu-img" :src="item.img" alt="" @click="tostorePage(item)" />
</div>
<!-- <div class="menu-title">{{ item.title }}</div> -->
</div>
</div>
</div>
</template>
<script>
import { modelNavigateTo } from "./tpl";
export default {
title: "达人店",
props: ["res","model"],
watch: {
res: {
handler(newValue, oldValue) {
this.$set(this, "res", newValue);
},
deep: true,
},
},
methods: {
//
tostorePage(val) {
uni.navigateTo({
url: "/pages/product/shopPage?id=" + val.url.id,
});
},
//
clickSwiper(index) {
uni.navigateTo({
url: "/pages/product/shopList",
});
},
},
};
</script>
<style lang="scss" scoped>
@import "./tpl.scss";
.carousel,
.image-mode {
width: 30px;
height: 30px !important;
}
.layout{
background: url('@/static/tanback.png') no-repeat;
background-size: 100% 100%;
}
.con_view {
display: flex;
justify-content: space-between;
}
.-item-tilte {
display: flex;
& > span {
display: flex;
align-items: center;
padding: 10px;
font-weight: 800;
color: white;
}
}
.sea {
display: flex;
align-items: center;
padding: 10px;
color: white;
}
.menu-list {
display: flex;
align-items: center;
// flex-wrap: wrap;
> .menu-item {
text-align: center;
width: 200px;
margin: 10px 10px;
}
}
.menu-img {
width: 60px;
height: 60px;
}
.menu-title {
font-size: 12px;
}
.menu-list::-webkit-scrollbar {
display: none;
}
</style>

View File

@ -0,0 +1,20 @@
<template>
<div class="layout">
<img :src="res.list[0].img" alt="">
</div>
</template>
<script>
export default {
title: "活动",
props: ["res"],
};
</script>
<style lang="scss" scoped>
@import "./tpl.scss";
.layout{
width: 100%;
height: 100%;
}
</style>

View File

@ -1,38 +1,261 @@
<template>
<div class="layout">
<div class="search" @click="handleSearch">
<u-icon name="search"></u-icon>
{{ res.list[0].title }}
<div class="addres" @click="showPicker" style="white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">
{{ cityOrCounty == "" ? "请选择" : cityOrCounty }}
</div>
<div class="search">{{ res.list[0].title }}</div>
<div class="shop" @click="toshop"></div>
<m-city
:provinceData="list"
headTitle="区域选择"
ref="cityPicker"
@funcValue="getpickerParentValue"
pickerSize="3"
>
</m-city>
</div>
</template>
<script>
import config from "@/config/config";
import { postAdvertisementjw } from "@/api/home.js";
import gkcity from "@/components/m-city/m-city.vue";
import { Icon } from "uview-ui";
export default {
title:"搜索栏",
components: {
"m-city": gkcity,
Icon,
},
title: "搜索栏",
props: ["res"],
data() {
return {
location: null,
//
cityOrCounty: uni.getStorageSync("cityOrCounty") || "",
form: {},
list: [
{
id: "",
localName: "请选择",
children: [],
},
],
//
storedLat: uni.getStorageSync("latitude") || null,
storedLon: uni.getStorageSync("longitude") || null,
};
},
mounted() {
// 使
if (this.storedLat && this.storedLon) {
this.form.lat = this.storedLat;
this.form.lon = this.storedLon;
this.getAdvertisementByLocation();
}
//
this.cityOrCounty = uni.getStorageSync("cityOrCounty") || "";
// this.getLocation();
},
methods: {
toshop() {
uni.navigateTo({
url: "/pages/tabbar/cart/cartList",
});
},
//
async getpickerParentValue(e) {
//
this.form.consigneeAddressIdPath = [];
this.form.consigneeAddressPath = [];
let name = "";
e.forEach((item, index) => {
if (item.id) {
//
this.form.consigneeAddressIdPath.push(item.id);
this.form.consigneeAddressPath.push(item.localName);
name += item.localName;
this.form.___path = name;
}
if (index == e.length - 1) {
//
let _town = item.children.filter((_child) => {
return _child.id == item.id;
});
this.form.lat = _town[0].center.split(",")[1];
this.form.lon = _town[0].center.split(",")[0];
}
});
const jwobj = {
longitude: Number(this.form.lon),
latitude: Number(this.form.lat),
};
const { data } = await postAdvertisementjw(jwobj);
if (data.success) {
//
this.cityOrCounty = this.form.consigneeAddressPath[2];
uni.setStorageSync("cityOrCounty", this.cityOrCounty);
//
uni.setStorageSync("longitude", this.form.lon);
uni.setStorageSync("latitude", this.form.lat);
//
this.storedLat = this.form.lat;
this.storedLon = this.form.lon;
}
},
// 广
async getAdvertisementByLocation() {
const jwobj = {
longitude: Number(this.storedLon),
latitude: Number(this.storedLat),
};
const { data } = await postAdvertisementjw(jwobj);
if (data.success) {
//
}
},
showPicker() {
this.$refs.cityPicker.show();
},
handleSearch() {
uni.navigateTo({
url: "/pages/navigation/search/searchPage",
});
},
// async getLocation() {
// const permision = await import("@/js_sdk/wa-permission/permission.js");
// // #ifdef APP-PLUS
// if (plus.os.name == "iOS") {
// if (permision.judgeIosPermissionLocation()) {
// this.getLocationInfo();
// } else {
// this.refuseLocation();
// }
// } else {
// const result = await permision.requestAndroidPermission(
// "android.permission.ACCESS_FINE_LOCATION"
// );
// if (result == 1) {
// this.getLocationInfo();
// } else {
// this.refuseLocation();
// }
// }
// // #endif
// // #ifndef APP-PLUS
// this.getLocationInfo();
// // #endif
// },
// getLocationInfo() {
// uni.getLocation({
// type: "wgs84",
// success: async (resss) => {
// this.location = resss;
// console.log("", resss);
// const jwobj = {
// latitude: resss.latitude,
// longitude: resss.longitude,
// };
// const { data } = await postAdvertisementjw(jwobj);
// if (data.success) {
// uni.request({
// url: `https://apis.map.qq.com/ws/geocoder/v1/?location=${resss.latitude},${resss.longitude}&key=${config.aMapKey}`,
// success: (res) => {
// console.log("API", res);
// this.cityOrCounty = res.data.result.address_component.district || res.data.result.address_component.city;
// uni.setStorageSync('cityOrCounty', this.cityOrCounty);
// //
// uni.setStorageSync('longitude', resss.longitude);
// uni.setStorageSync('latitude', resss.latitude);
// //
// this.storedLat = resss.latitude;
// this.storedLon = resss.longitude;
// },
// fail: (error) => {
// uni.showToast({
// title: "222",
// icon: "none"
// });
// }
// });
// }
// },
// fail: (err) => {
// console.error("", err);
// },
// });
// },
// refuseLocation() {
// uni.showModal({
// title: "",
// content: ",",
// confirmText: "",
// success(res) {
// if (res.confirm) {
// //
// // #ifndef MP-WEIXIN
// uni.getSystemInfo({
// success(res) {
// if (res.platform == "ios") {
// //IOS
// plus.runtime.openURL("app-settings://");
// } else if (res.platform == "android") {
// //
// let main = plus.android.runtimeMainActivity();
// let Intent = plus.android.importClass(
// "android.content.Intent"
// );
// let mIntent = new Intent("android.settings.ACTION_SETTINGS");
// main.startActivity(mIntent);
// }
// },
// });
// // #endif
// }
// },
// });
// },
},
};
</script>
<style lang="scss" scoped>
@import "./tpl.scss";
.search {
height: 64rpx;
border-radius: 10rpx;
width: 68%;
height: 32px;
border-radius: 5px;
display: flex;
align-items: center;
justify-content: center;
background: #ededed;
}
.layout {
background: #fff;
padding: 0 16rpx;
// background: #fff;
display: flex;
justify-content: space-between;
background: transparent;
}
.addres {
// width: 15%;
height: 32px;
display: flex;
justify-content: center;
align-items: center;
}
.layout {
// background: #fff;
background: transparent;
display: flex;
justify-content: space-between;
}
.shop {
width: 10%;
height: 32px;
display: flex;
background: url("@/static/shop.png") no-repeat;
background-size: 100% 100%;
}
</style>

View File

@ -3,7 +3,7 @@
<!-- uni 中不能使用 vue component 所以用if判断每个组件 -->
<div v-for="(item,index) in pageData.list" :key="index">
<!-- 搜索栏如果在楼层装修顶部则会自动浮动否则不浮动 -->
<u-navbar class="navbar" v-if="item.type == 'search'" :is-back="false" :is-fixed="index ===1 ? false : true">
<div class="navbar" v-if="item.type == 'search'" :is-back="false" :is-fixed="false">
<search style="width:100%" :res="item.options" />
<!-- #ifndef H5 -->
<!-- 扫码功能 不兼容h5 详情文档: https://uniapp.dcloud.io/api/system/barcode?id=scancode -->
@ -11,21 +11,24 @@
<u-icon name="scan" @click="scan()" color="#666" size="50"></u-icon>
</div>
<!-- #endif -->
</u-navbar>
</div>
<carousel v-if="item.type == 'carousel'" :res="item.options" />
<titleLayout v-if="item.type == 'title'" :res="item.options" />
<leftOneRightTwo v-if="item.type == 'leftOneRightTwo'" :res="item.options" />
<leftTwoRightOne v-if="item.type == 'leftTwoRightOne'" :res="item.options" />
<topOneBottomTwo v-if="item.type == 'topOneBottomTwo'" :res="item.options" />
<topTwoBottomOne v-if="item.type == 'topTwoBottomOne'" :res="item.options" />
<flexThree v-if="item.type == 'flexThree'" :res="item.options" />
<flexThree v-if="item.type == 'flexThree'" :res="item.options" />
<flexFive v-if="item.type == 'flexFive'" :res="item.options" />
<flexFour v-if="item.type == 'flexFour'" :res="item.options" />
<flexTwo v-if="item.type == 'flexTwo'" :res="item.options" />
<textPicture v-if="item.type == 'textPicture'" :res="item.options" />
<menuLayout v-if="item.type == 'menu'" :res="item.options" />
<flexOne v-if="item.type == 'flexOne'" :res="item.options" />
<activity v-if="item.type == 'activity'" :res="item.options" />
<Talent v-if="item.type == 'talent'" :res="item.options" :model="item" />
<goods v-if="item.type == 'goods'" :res="item.options" />
<group v-if="item.type == 'group'" :res="item.options" />
@ -56,8 +59,10 @@ import tpl_menu from "@/pages/tabbar/home/template/tpl_menu"; //五列菜单模
import tpl_search from "@/pages/tabbar/home/template/tpl_search"; //
import tpl_group from "@/pages/tabbar/home/template/tpl_group"; //
import tpl_goods from "@/pages/tabbar/home/template/tpl_goods"; //
import Talent from "@/pages/tabbar/home/template/Talent"; //
import tpl_activity from "@/pages/tabbar/home/template/tpl_activity"; //
//
import { getFloorData } from "@/api/home"; //
import { getFloorData ,getFloorgetBendi} from "@/api/home"; //
import permision from "@/js_sdk/wa-permission/permission.js"; //
import config from "@/config/config";
// TODO
@ -90,11 +95,23 @@ export default {
flexOne: tpl_flex_one,
goods: tpl_goods,
group: tpl_group,
Talent,
activity: tpl_activity,
// spike: tpl_spike,
// joinGroup: tpl_join_group,
// integral: tpl_integral,
},
props: {
ste: {
type: Number,
default: 0,
},
},
watch: {
ste(val) {
this.init();
},
},
mounted() {
this.init();
// #ifdef MP-WEIXIN
@ -102,18 +119,32 @@ export default {
uni.showShareMenu({ withShareTicket: true });
// #endif
},
// provide() {
// return {
// ste: this.ste,
// };
// },
methods: {
/**
* 实例化首页数据楼层
*/
init() {
uni.setStorageSync('ste', this.ste); // ste
this.pageData = "";
getFloorData().then((res) => {
if (this.ste == 1) {
getFloorData().then((res) => {
if (res.data.success) {
this.pageData = JSON.parse(res.data.result.pageData);
}
});
}else{
getFloorgetBendi().then((res) => {
if (res.data.success) {
this.pageData = JSON.parse(res.data.result.pageData);
}
});
}
},
/**
@ -128,7 +159,6 @@ export default {
uni.scanCode({
success: function (res) {
let path = encodeURIComponent(res.result);
// WX_CODE
if (res.scanType == "WX_CODE") {
console.log(res)
@ -208,16 +238,26 @@ export default {
this.seacnCode();
// #endif
},
},
};
</script>
<style scoped lang="scss">
.wrapper{
height: auto;
overflow-y: auto; /* 当内容超出时显示垂直滚动条 */
height: 100%;
}
.navbar{
display: flex;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
background:white;
padding-right: 10px;
}
.navbar-right {
padding: 0 16rpx 0 0;
// padding: 0 16rpx 0 0;
}
</style>

View File

@ -1,3 +1,289 @@
<template>
<div>im</div>
</template>
<div style="height: 100%">
<div class="jolkp">
<div class="fan" :style="{ marginRight: statue == 0 ? '56%' : '70%' }">
<span @click="hui(0)">消息</span>
</div>
<!-- <TUIContact v-else-if="statue==1" :stu="2" /> -->
<div @click="hui(1)" class="jolkp_h" v-if="statue == 0"></div>
<div @click="fnkiopl" class="jolkp_l"></div>
<!-- 弹出框 -->
<div v-if="isPopupVisible" class="popup">
<ul>
<li @click="handleAddFriend">添加好友/群聊</li>
<li @click="handleMenu()">发起群聊</li>
<li>扫一扫</li>
</ul>
</div>
</div>
<div class="three">
<div class="con">
<div class="three_div1"></div>
<div class="title">点赞</div>
</div>
<div class="con">
<div class="three_div2"></div>
<div class="title">关注</div>
</div>
<div class="con">
<div class="three_div3"></div>
<div class="title">评论</div>
</div>
</div>
<!-- 搜索添加 -->
<TUIContactsea v-if="statue == 2" />
<!-- 联系人 -->
<TUIContact v-else-if="statue == 1" style="height: 100%;" />
<SelectFriend v-else-if="statue == 3" @con="confn" />
<SelectFriendqlioa v-else-if="statue == 4" @concen="concen" />
<!-- 会话 -->
<TUIConversation style="height: 100%;" v-else />
</div>
</template>
<script>
//
import TUIConversation from "@/TUIKit/components/TUIConversation/index";
import TUIContact from "@/TUIKit/components/TUIContact/index";
import TUIContactsea from "@/TUIKit/components/TUIContact/indexsea";
import { TUILogin } from "@tencentcloud/tui-core";
import ContactSearch from "@/TUIKit/components/TUIContact/contact-search/index.vue";
import { TUIChatKit } from "@/TUIKit";
import SelectFriend from "@/TUIKit/components/TUIContact/select-friend/index.vue";
import SelectFriendqlioa from "@/TUIKit/components/TUIGroup/index.vue";
import TUICore, { ExtensionInfo, TUIConstants } from "@tencentcloud/tui-core";
import storage from "@/utils/storage.js";
import { getUserimInfo } from "@/api/members";
TUIChatKit.init();
let vueVersion = 2;
// vueVersion = 3;
export default {
//
components: {
TUIConversation,
TUIContact,
TUIContactsea,
ContactSearch,
SelectFriend,
SelectFriendqlioa,
TUIChatKit,
},
data() {
return {
statue: 0, //1 2/ 0 3 4
isPopupVisible: false, //
isq: false,
};
},
created() {},
onShow() {
//
//
getUserimInfo()
.then(({ data }) => {
if (data.code == 200) {
const par = data.result;
TUILogin.login({
SDKAppID: par.sdkAppId,
userID: par.userID,
userSig: par.userSig,
useUploadPlugin: true, // If you need to send rich media messages, please set to true.
framework: `vue${vueVersion}`, // framework used vue2 / vue3
});
} else {
// 200
uni.navigateTo({
url: "/pages/passport/login",
});
}
})
.catch(() => {
//
uni.navigateTo({
url: "/pages/passport/login",
});
});
},
mounted() {},
methods: {
//
confn() {
this.statue = 4;
},
concen() {
this.statue = 0;
this.isPopupVisible = false;
},
hui(index) {
this.statue = index;
},
fnkiopl() {
this.isPopupVisible = !this.isPopupVisible;
},
handleMenu() {
const fn = [
...TUICore.getExtensionList(
TUIConstants.TUISearch.EXTENSION.SEARCH_MORE.EXT_ID
),
];
const item = fn[1];
const {
listener = {
onClicked: () => {},
},
} = item;
listener?.onClicked?.(item);
this.statue = 3;
},
// /
handleAddFriend() {
this.statue = 2;
this.isPopupVisible = false;
},
// //
// openConversationList() {
// uni.navigateTo({
// url: '/TUIKit/components/TUIConversation/index'
// });
// },
// //
// openContact() {
// uni.navigateTo({
// url: '/TUIKit/components/TUIContact/index'
// });
// },
},
};
</script>
<style scoped>
uni-page-body,
html,
body,
page {
width: 100% !important;
height: 100% !important;
overflow: hidden;
}
.jolkp {
height: 66px;
display: flex;
font-size: 15px;
justify-content: end;
/* 设置背景图 */
background-image: url("@/static/im/Rectangle.png");
/* 让背景图覆盖整个元素 */
background-size: cover;
/* 背景图不重复 */
background-repeat: no-repeat;
}
.jolkp_l {
width: 2rem;
height: 2rem;
background-image: url("@/static/im/Frame.png");
/* 让背景图覆盖整个元素 */
background-size: cover;
/* 背景图不重复 */
background-repeat: no-repeat;
margin-top: 35px;
margin-right: 20px;
}
.jolkp_h {
width: 2rem;
height: 2rem;
background-image: url("@/static/im/user.png");
/* 让背景图覆盖整个元素 */
background-size: cover;
/* 背景图不重复 */
background-repeat: no-repeat;
margin-top: 35px;
margin-right: 20px;
}
.popup {
position: absolute;
top: 65px;
/* 调整弹出框的位置 */
right: 5px;
background-color: #fff;
border: 1px solid #ccc;
border-radius: 4px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
z-index: 3;
}
.popup ul {
list-style-type: none;
padding: 0;
margin: 0;
}
.popup li {
padding: 10px 20px;
cursor: pointer;
}
.popup li:hover {
background-color: #f0f0f0;
}
.fan {
width: 10%;
height: "100%";
font-size: 18px;
display: flex;
align-items: end;
padding: 5px;
}
.three {
width: 100%;
height: 10%;
display: flex;
justify-content: space-around;
align-items: center;
background: rgb(255, 255, 255);
}
.con{
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.three_div1 {
height: 3rem;
width: 40%;
background-image: url("@/static/im/kf.png");
background-size: 100% 100%;
background-repeat: no-repeat;
display: flex;
align-items: center;
justify-content: end;
}
.three_div2 {
width: 40%;
height: 3rem;
background-image: url("@/static/im/hy.png");
background-size: 100% 100%;
background-repeat: no-repeat;
width: 40%;
}
.three_div3 {
height: 3rem;
background-image: url("@/static/im/da.png");
background-size: 100% 100%;
background-repeat: no-repeat;
width: 40%;
}
</style>

BIN
static/shop.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
static/tabbar/bei.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 496 KiB

BIN
static/tabbar/shop.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
static/tanback.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB