This commit is contained in:
abu 2025-04-21 17:35:54 +08:00
parent 885503a767
commit 18bdb4d4d5
52 changed files with 9284 additions and 4469 deletions

531
App.vue
View File

@ -1,273 +1,270 @@
<script>
/**
* vuex管理登录状态具体可以参考官方登录模板示例
*/
import { mapMutations } from "vuex";
import APPUpdate from "@/plugins/APPUpdate";
import { getClipboardData } from "@/js_sdk/h5-copy/h5-copy.js";
import config from "@/config/config";
// code
import provinceList from "./json/area_province.js";
import cityList from "./json/area_city.js";
import districtList from "./json/area_district.js";
import storage from "@/utils/storage.js"; //
export default {
data() {
return {
config,
};
},
/**
* 监听返回
*/
onBackPress(e) {
if (e.from == "backbutton") {
let routes = getCurrentPages();
let curRoute = routes[routes.length - 1].options;
routes.forEach((item) => {
if (
item.route == "pages/tabbar/cart/cartList" ||
item.route.indexOf("pages/product/goods") != -1
) {
uni.redirectTo({
url: item.route,
});
}
});
if (curRoute.addId) {
uni.reLaunch({
url: "/pages/tabbar/cart/cartList",
});
} else {
uni.navigateBack();
}
return true; //
}
},
methods: {
...mapMutations(["login"]),
},
onLaunch: function () {
// #ifdef APP-PLUS
this.checkArguments(); //
APPUpdate();
this.hanleTabCenter();
//
plus.globalEvent.addEventListener("newintent", (e) => {
this.checkArguments(); //
});
// #endif
// #ifdef MP-WEIXIN
this.applyUpdateWeChat();
// #endif
},
onShow() {
// #ifndef H5
this.getClipboard();
// #endif
},
methods: {
//
hanleTabCenter() {
//
uni.onTabBarMidButtonTap(() => {
console.log("center");
//
let myUserInfo = storage.getVlogUserInfo() || null;
if (myUserInfo == null) {
uni.navigateTo({
// url: "../loginRegist/loginRegist",
url: "/pages/passport/login",
animationType: "slide-in-bottom",
success() {
this.loginWords = "请登录";
},
});
return;
}
uni.chooseVideo({
sourceType: ["album"],
compressed: false,
success(e) {
console.log(JSON.stringify(e));
uni.navigateTo({
url:
"/pages/publish/publish?fileObjectEvent=" + JSON.stringify(e),
});
},
});
});
},
/**
* 微信小程序版本提交更新版本 解决缓存问题
*/
applyUpdateWeChat() {
const updateManager = uni.getUpdateManager();
updateManager.onCheckForUpdate(function (res) {
//
});
updateManager.onUpdateReady(function (res) {
uni.showModal({
title: "更新提示",
content: "发现新版本,是否重启应用?",
success(res) {
if (res.confirm) {
// applyUpdate
updateManager.applyUpdate();
}
},
});
});
updateManager.onUpdateFailed(function (res) {
//
});
},
// TODO 广
launch() {
try {
// launchFlag 广
const value = uni.getStorageSync("launchFlag");
if (!value) {
// this.$u.route("/pages/index/agreement");
} else {
//app广
var w = plus.webview.open(
"/hybrid/html/advertise/advertise.html",
"本地地址",
{
top: 0,
bottom: 0,
zindex: 999,
},
"fade-in",
500
);
//4s广
setTimeout(function () {
plus.webview.close(w);
APPUpdate();
}, 3000);
}
} catch (e) {
// error
uni.setStorage({
key: "launchFlag",
data: true,
success: function () {
console.log("error时存储launchFlag");
},
});
}
},
/**
* 获取粘贴板数据
*/
async getClipboard() {
let res = await getClipboardData();
/**
* 解析粘贴板数据
*/
if (res.indexOf(config.shareLink) != -1) {
uni.showModal({
title: "提示",
content: "检测到一个分享链接是否跳转?",
confirmText: "跳转",
success: function (callback) {
if (callback.confirm) {
const path = res.split(config.shareLink)[1];
if (path.indexOf("tabbar") != -1) {
uni.switchTab({
url: path,
});
} else {
uni.navigateTo({
url: path,
});
}
}
},
});
}
},
/**
* h5中打开app获取跳转app的链接并跳转
*/
checkArguments() {
// #ifdef APP-PLUS
setTimeout(() => {
const args = plus.runtime.arguments;
if (args) {
const argsStr = decodeURIComponent(args);
const path = argsStr.split("//")[1];
if (path.indexOf("tabbar") != -1) {
uni.switchTab({
url: `/${path}`,
});
} else {
uni.navigateTo({
url: `/${path}`,
});
}
}
});
// #endif
},
},
};
</script>
<script>
/**
* vuex管理登录状态具体可以参考官方登录模板示例
*/
import { mapMutations } from 'vuex';
import APPUpdate from '@/plugins/APPUpdate';
import { getClipboardData } from '@/js_sdk/h5-copy/h5-copy.js';
import config from '@/config/config';
// code
import provinceList from './json/area_province.js';
import cityList from './json/area_city.js';
import districtList from './json/area_district.js';
import storage from '@/utils/storage.js'; //
export default {
data() {
return {
config
};
},
/**
* 监听返回
*/
onBackPress(e) {
if (e.from == 'backbutton') {
let routes = getCurrentPages();
let curRoute = routes[routes.length - 1].options;
routes.forEach((item) => {
if (item.route == 'pages/tabbar/cart/cartList' || item.route.indexOf('pages/product/goods') != -1) {
uni.redirectTo({
url: item.route
});
}
});
if (curRoute.addId) {
uni.reLaunch({
url: '/pages/tabbar/cart/cartList'
});
} else {
uni.navigateBack();
}
return true; //
}
},
methods: {
...mapMutations(['login'])
},
onLaunch: function () {
// #ifdef APP-PLUS
this.checkArguments(); //
APPUpdate();
this.hanleTabCenter();
//
plus.globalEvent.addEventListener('newintent', (e) => {
this.checkArguments(); //
});
// #endif
// #ifdef MP-WEIXIN
this.applyUpdateWeChat();
// #endif
},
onShow() {
// #ifndef H5
this.getClipboard();
// #endif
},
methods: {
//
hanleTabCenter() {
//
uni.onTabBarMidButtonTap(() => {
console.log('center');
//
let myUserInfo = storage.getVlogUserInfo();
if (myUserInfo == null) {
uni.navigateTo({
// url: "../loginRegist/loginRegist",
url: '/pages/passport/login',
animationType: 'slide-in-bottom',
success() {
this.loginWords = '请登录';
}
});
return;
}
uni.chooseVideo({
sourceType: ['album'],
compressed: false,
success(e) {
console.log(JSON.stringify(e));
uni.navigateTo({
url: '/pages/publish/publish?fileObjectEvent=' + JSON.stringify(e)
});
}
});
});
},
/**
* 微信小程序版本提交更新版本 解决缓存问题
*/
applyUpdateWeChat() {
const updateManager = uni.getUpdateManager();
updateManager.onCheckForUpdate(function (res) {
//
});
updateManager.onUpdateReady(function (res) {
uni.showModal({
title: '更新提示',
content: '发现新版本,是否重启应用?',
success(res) {
if (res.confirm) {
// applyUpdate
updateManager.applyUpdate();
}
}
});
});
updateManager.onUpdateFailed(function (res) {
//
});
},
// TODO 广
launch() {
try {
// launchFlag 广
const value = uni.getStorageSync('launchFlag');
if (!value) {
// this.$u.route("/pages/index/agreement");
} else {
//app广
var w = plus.webview.open(
'/hybrid/html/advertise/advertise.html',
'本地地址',
{
top: 0,
bottom: 0,
zindex: 999
},
'fade-in',
500
);
//4s广
setTimeout(function () {
plus.webview.close(w);
APPUpdate();
}, 3000);
}
} catch (e) {
// error
uni.setStorage({
key: 'launchFlag',
data: true,
success: function () {
console.log('error时存储launchFlag');
}
});
}
},
/**
* 获取粘贴板数据
*/
async getClipboard() {
let res = await getClipboardData();
/**
* 解析粘贴板数据
*/
if (res.indexOf(config.shareLink) != -1) {
uni.showModal({
title: '提示',
content: '检测到一个分享链接是否跳转?',
confirmText: '跳转',
success: function (callback) {
if (callback.confirm) {
const path = res.split(config.shareLink)[1];
if (path.indexOf('tabbar') != -1) {
uni.switchTab({
url: path
});
} else {
uni.navigateTo({
url: path
});
}
}
}
});
}
},
/**
* h5中打开app获取跳转app的链接并跳转
*/
checkArguments() {
// #ifdef APP-PLUS
setTimeout(() => {
const args = plus.runtime.arguments;
if (args) {
const argsStr = decodeURIComponent(args);
const path = argsStr.split('//')[1];
if (path.indexOf('tabbar') != -1) {
uni.switchTab({
url: `/${path}`
});
} else {
uni.navigateTo({
url: `/${path}`
});
}
}
});
// #endif
}
}
};
</script>
<style lang="scss">
/* #ifndef APP-NVUE */
@import "uview-ui/index.scss";
/* #endif */
// ------- x
// #ifdef MP-WEIXIN
.mp-iphonex-bottom {
padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
box-sizing: content-box;
height: auto !important;
padding-top: 10rpx;
}
// #endif
@import 'uview-ui/index.scss';
/* #endif */
// ------- x
// #ifdef MP-WEIXIN
.mp-iphonex-bottom {
padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
box-sizing: content-box;
height: auto !important;
padding-top: 10rpx;
}
// #endif
/* #ifndef APP-NVUE */
body {
background-color: $bg-color;
}
body {
background-color: $bg-color;
}
/* #endif */
/************************ */
.w200 {
width: 200rpx !important;
}
.flex1 {
flex: 1; //flex
}
.activate-line {
background-color: #ffffff;
transition-duration: 300ms;
}
// uni-page-body,
// html,
// body,
// page {
// width: 100% ;
// height: 100% ;
// overflow: hidden;
// }
</style>
/************************ */
.w200 {
width: 200rpx !important;
}
.flex1 {
flex: 1; //flex
}
.activate-line {
background-color: #ffffff;
transition-duration: 300ms;
}
// uni-page-body,
// html,
// body,
// page {
// width: 100% ;
// height: 100% ;
// overflow: hidden;
// }
</style>

View File

@ -1,60 +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);
}
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

@ -1,324 +1,325 @@
<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,onNavigationBarButtonTap } 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();
onNavigationBarButtonTap((e) => {
if (isGroup.value) {
handleGroup();
}
});
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 });
uni.navigateTo({
url: `/TUIKit/components/TUIGroup/index`
});
};
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>
<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,onNavigationBarButtonTap } from '@dcloudio/uni-app';
import { initChat, logout } from './entry-chat-only.ts';
onLoad((options) => {
console.dir(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();
onNavigationBarButtonTap((e) => {
if (isGroup.value) {
handleGroup();
}
});
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 });
uni.navigateTo({
url: `/TUIKit/components/TUIGroup/index`
});
};
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

@ -1,243 +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>
<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

@ -1,285 +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>
<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

@ -1,430 +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>
<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

@ -1,138 +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>
<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

@ -1,141 +1,141 @@
<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 @cancel="cancel" />
<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 cancel=(item)=>{
emits('switchConversation', item);
}
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>
<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 @cancel="cancel" />
<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 cancel=(item)=>{
emits('switchConversation', item);
}
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

@ -1,353 +1,477 @@
/**
* 短视频相关API
*/
import { http, Method } from "@/utils/request.js";
import api from "@/config/api.js";
/**
* 短视频关注列表
*/
export function vlogDetail(vlogId,userId) {
let data = {
vlogId,
userId
}
return http.request({
url: api.vlog + "/vlog/detail",
method: Method.GET,
needToken: true,
params: data,
});
}
/**
* 短视频关注列表
*/
export function vlogFollowList(page, pageSize,myId) {
let data = {
page,
pageSize,
myId
}
return http.request({
url: api.vlog + "/vlog/followList",
method: Method.GET,
needToken: true,
params: data,
});
}
/**
* 短视频列表-true
*/
export function vlogList(page, pageSize,userId='',cityCode='',search='') {
let data = {
page,
pageSize,
userId,
cityCode,
search
}
return http.request({
url: api.vlog + "/vlog/indexList",
method: Method.GET,
needToken: false,
params: data,
});
}
/**
* 短视频喜欢
*/
export function vlogLike({userId,vlogId,vlogerId}) {
return http.request({
url: api.vlog + "/vlog/like",
method: Method.POST,
needToken: true,
params: {userId,vlogId,vlogerId}
});
}
/**
* 短视频不喜欢
*/
export function vlogUnLike({userId,vlogId,vlogerId}) {
return http.request({
url: api.vlog + "/vlog/unlike",
method: Method.POST,
needToken: true,
params: {userId,vlogId,vlogerId}
});
}
/**
* 短视频评论数量统计
*/
export function vlogComment(vlogId) {
return http.request({
url: api.vlog + "/comment/counts",
method: Method.GET,
needToken: true,
params: { vlogId },
});
}
/**
* 短视频关注博主
*/
export function vlogFollow(myId,vlogerId) {
return http.request({
url: api.vlog + "/fans/follow",
method: Method.POST,
needToken: true,
params: {myId,vlogerId}
});
}
/**
* 短视频查询当前点赞数
*/
export function vlogTotalLikedCounts(vlogId) {
return http.request({
url: api.vlog + "/vlog/totalLikedCounts",
method: Method.POST,
needToken: true,
params: {vlogId}
});
}
/**
* 查询用户信息
*/
export function vlogUserInfo(userId) {
return http.request({
url: api.vlog + "/userInfo/query",
method: Method.GET,
needToken: true,
params: { userId },
});
}
/**
* 查询我的公开视频
*/
export function vlogMyPublicList(page,pageSize,userId) {
return http.request({
url: api.vlog + "/vlog/myPublicList",
method: Method.GET,
needToken: true,
params: { page,pageSize,userId },
});
}
/**
* 查询我的私密视频
*/
export function vlogMyPrivateList(page,pageSize,userId) {
return http.request({
url: api.vlog + "/vlog/myPrivateList",
method: Method.GET,
needToken: true,
params: { page,pageSize,userId },
});
}
/**
* 查询我喜欢的视频
*/
export function vlogMyLikedList(page,pageSize,userId) {
return http.request({
url: api.vlog + "/vlog/myLikedList",
method: Method.GET,
needToken: true,
params: { page,pageSize,userId },
});
}
/**
* 查询我喜欢的视频
*/
export function vlogMeTag(path,page,pageSize,userId) {
return http.request({
url: api.vlog + "/vlog/"+path,
method: Method.GET,
needToken: true,
params: { page,pageSize,userId },
});
}
/**
* 视频评论数量
*/
export function vlogCommentCounts(vlogId) {
return http.request({
url: api.vlog + "/comment/counts",
method: Method.GET,
needToken: true,
params: { vlogId },
});
}
/**
* 视频评论不喜欢
*/
export function vlogCommentUnLike(userId,commentId) {
return http.request({
url: api.vlog + "/comment/unlike",
method: Method.POST,
needToken: true,
params: {userId,commentId}
});
}
/**
* 视频评论喜欢
*/
export function vlogCommentLike(userId,commentId) {
return http.request({
url: api.vlog + "/comment/like",
method: Method.POST,
needToken: true,
params: {userId,commentId}
});
}
/**
* 视频评论删除
*/
export function vlogCommentDelete(vlogId,commentUserId,commentId) {
return http.request({
url: api.vlog + "/comment/delete",
method: Method.DELETE,
needToken: true,
params: {vlogId,commentUserId,commentId}
});
}
/**
* 查询视频评论内容
*/
export function vlogCommentList(page,pageSize,vlogId,userId) {
return http.request({
url: api.vlog + "/comment/list",
method: Method.GET,
needToken: true,
params: { page,pageSize,vlogId,userId },
});
}
/**
* 发表评论
*/
export function vlogCommentCreate(data) {
return http.request({
url: api.vlog + "/comment/create",
method: Method.POST,
needToken: true,
data
});
}
/**
* 将视频转为公开
*/
export function vlogChangeToPublic(userId,vlogId) {
return http.request({
url: api.vlog + "/vlog/changeToPublic",
method: Method.POST,
needToken: true,
params:{userId,vlogId}
});
}
/**
* 将视频转为私密
*/
export function vlogChangeToPrivate(userId,vlogId) {
return http.request({
url: api.vlog + "/vlog/changeToPrivate",
method: Method.POST,
needToken: true,
params:{userId,vlogId}
});
}
/**
* 修改信息
*/
export function vlogModifyUserInfo(data,type) {
return http.request({
url: api.vlog + "/userInfo/modifyUserInfo?type="+type,
method: Method.POST,
needToken: true,
data
});
}
/**
* 取关
*/
export function vlogFansCancel({myId,vlogerId}) {
return http.request({
url: api.vlog + "/fans/cancel",
method: Method.POST,
needToken: true,
params:{myId,vlogerId}
});
}
/**
* 关注
*/
export function vlogFansFollow({myId,vlogerId}) {
return http.request({
url: api.vlog + "/fans/follow",
method: Method.POST,
needToken: true,
params:{myId,vlogerId}
});
}
/**
* 我的粉丝
*/
export function vlogQueryMyFans({myId,page,pageSize}) {
return http.request({
url: api.vlog + "/fans/queryMyFans",
method: Method.GET,
needToken: true,
params:{myId,page,pageSize}
});
}
/**
* 我的关注
*/
export function vlogQueryMyFollows({myId,page,pageSize}) {
return http.request({
url: api.vlog + "/fans/queryMyFollows",
method: Method.GET,
needToken: true,
params:{myId,page,pageSize}
});
}
/**
* 我的关注
*/
export function vlogQueryDoIFollowVloger({myId,vlogerId}) {
return http.request({
url: api.vlog + "/fans/queryDoIFollowVloger",
method: Method.GET,
needToken: true,
params:{myId,vlogerId}
});
/**
* 短视频相关API
*/
import {
http,
Method
} from "@/utils/request.js";
import api from "@/config/api.js";
/**
* 根据昵称搜索用户
*/
export function vlogSearchUser({
id,
nickname,
page,
pageSize
}) {
return http.request({
url: api.vlog + "/userInfo/searchByNickname",
method: Method.GET,
needToken: true,
params: {
id,
nickname,
page,
pageSize
},
});
}
/**
* 短视频详情
*/
export function vlogDetail(vlogId, userId) {
let data = {
vlogId,
userId
}
return http.request({
url: api.vlog + "/vlog/detail",
method: Method.GET,
needToken: true,
params: data,
});
}
/**
* 短视频关注列表
*/
export function vlogFollowList(page, pageSize, myId) {
let data = {
page,
pageSize,
myId
}
return http.request({
url: api.vlog + "/vlog/followList",
method: Method.GET,
needToken: true,
params: data,
});
}
/**
* 短视频列表-true
*/
export function vlogList(page, pageSize, userId = '', cityCode = '', search = '') {
let data = {
page,
pageSize,
userId,
cityCode,
search
}
return http.request({
url: api.vlog + "/vlog/indexList",
method: Method.GET,
needToken: false,
params: data,
});
}
/**
* 短视频喜欢
*/
export function vlogLike({
userId,
vlogId,
vlogerId
}) {
return http.request({
url: api.vlog + "/vlog/like",
method: Method.POST,
needToken: true,
params: {
userId,
vlogId,
vlogerId
}
});
}
/**
* 短视频不喜欢
*/
export function vlogUnLike({
userId,
vlogId,
vlogerId
}) {
return http.request({
url: api.vlog + "/vlog/unlike",
method: Method.POST,
needToken: true,
params: {
userId,
vlogId,
vlogerId
}
});
}
/**
* 短视频评论数量统计
*/
export function vlogComment(vlogId) {
return http.request({
url: api.vlog + "/comment/counts",
method: Method.GET,
needToken: true,
params: {
vlogId
},
});
}
/**
* 短视频关注博主
*/
export function vlogFollow(myId, vlogerId) {
return http.request({
url: api.vlog + "/fans/follow",
method: Method.POST,
needToken: true,
params: {
myId,
vlogerId
}
});
}
/**
* 短视频查询当前点赞数
*/
export function vlogTotalLikedCounts(vlogId) {
return http.request({
url: api.vlog + "/vlog/totalLikedCounts",
method: Method.POST,
needToken: true,
params: {
vlogId
}
});
}
/**
* 查询用户信息
*/
export function vlogUserInfo(userId) {
return http.request({
url: api.vlog + "/userInfo/query",
method: Method.GET,
needToken: true,
params: {
userId
},
});
}
/**
* 查询我的公开视频
*/
export function vlogMyPublicList(page, pageSize, userId) {
return http.request({
url: api.vlog + "/vlog/myPublicList",
method: Method.GET,
needToken: true,
params: {
page,
pageSize,
userId
},
});
}
/**
* 查询我的私密视频
*/
export function vlogMyPrivateList(page, pageSize, userId) {
return http.request({
url: api.vlog + "/vlog/myPrivateList",
method: Method.GET,
needToken: true,
params: {
page,
pageSize,
userId
},
});
}
/**
* 查询我喜欢的视频
*/
export function vlogMyLikedList(page, pageSize, userId) {
return http.request({
url: api.vlog + "/vlog/myLikedList",
method: Method.GET,
needToken: true,
params: {
page,
pageSize,
userId
},
});
}
/**
* 查询我喜欢的视频
*/
export function vlogMeTag(path, page, pageSize, userId) {
return http.request({
url: api.vlog + "/vlog/" + path,
method: Method.GET,
needToken: true,
params: {
page,
pageSize,
userId
},
});
}
/**
* 视频评论数量
*/
export function vlogCommentCounts(vlogId) {
return http.request({
url: api.vlog + "/comment/counts",
method: Method.GET,
needToken: true,
params: {
vlogId
},
});
}
/**
* 视频评论不喜欢
*/
export function vlogCommentUnLike(userId, commentId) {
return http.request({
url: api.vlog + "/comment/unlike",
method: Method.POST,
needToken: true,
params: {
userId,
commentId
}
});
}
/**
* 视频评论喜欢
*/
export function vlogCommentLike(userId, commentId) {
return http.request({
url: api.vlog + "/comment/like",
method: Method.POST,
needToken: true,
params: {
userId,
commentId
}
});
}
/**
* 视频评论删除
*/
export function vlogCommentDelete(vlogId, commentUserId, commentId) {
return http.request({
url: api.vlog + "/comment/delete",
method: Method.DELETE,
needToken: true,
params: {
vlogId,
commentUserId,
commentId
}
});
}
/**
* 查询视频评论内容
*/
export function vlogCommentList(page, pageSize, vlogId, userId) {
return http.request({
url: api.vlog + "/comment/list",
method: Method.GET,
needToken: true,
params: {
page,
pageSize,
vlogId,
userId
},
});
}
/**
* 发表评论
*/
export function vlogCommentCreate(data) {
return http.request({
url: api.vlog + "/comment/create",
method: Method.POST,
needToken: true,
data
});
}
/**
* 将视频转为公开
*/
export function vlogChangeToPublic(userId, vlogId) {
return http.request({
url: api.vlog + "/vlog/changeToPublic",
method: Method.POST,
needToken: true,
params: {
userId,
vlogId
}
});
}
/**
* 将视频转为私密
*/
export function vlogChangeToPrivate(userId, vlogId) {
return http.request({
url: api.vlog + "/vlog/changeToPrivate",
method: Method.POST,
needToken: true,
params: {
userId,
vlogId
}
});
}
/**
* 修改信息
*/
export function vlogModifyUserInfo(data, type) {
return http.request({
url: api.vlog + "/userInfo/modifyUserInfo?type=" + type,
method: Method.POST,
needToken: true,
data
});
}
/**
* 取关
*/
export function vlogFansCancel({
myId,
vlogerId
}) {
return http.request({
url: api.vlog + "/fans/cancel",
method: Method.POST,
needToken: true,
params: {
myId,
vlogerId
}
});
}
/**
* 关注
*/
export function vlogFansFollow({
myId,
vlogerId
}) {
return http.request({
url: api.vlog + "/fans/follow",
method: Method.POST,
needToken: true,
params: {
myId,
vlogerId
}
});
}
/**
* 我的粉丝
*/
export function vlogQueryMyFans({
myId,
page,
pageSize
}) {
return http.request({
url: api.vlog + "/fans/queryMyFans",
method: Method.GET,
needToken: true,
params: {
myId,
page,
pageSize
}
});
}
/**
* 我的关注
*/
export function vlogQueryMyFollows({
myId,
page,
pageSize
}) {
return http.request({
url: api.vlog + "/fans/queryMyFollows",
method: Method.GET,
needToken: true,
params: {
myId,
page,
pageSize
}
});
}
/**
* 我的关注
*/
export function vlogQueryDoIFollowVloger({
myId,
vlogerId
}) {
return http.request({
url: api.vlog + "/fans/queryDoIFollowVloger",
method: Method.GET,
needToken: true,
params: {
myId,
vlogerId
}
});
}

View File

@ -112,7 +112,8 @@
<text class="muisc-words">{{ item.vlogerName }}的原声创作</text>
</view>
</view>
<view
<!-- 右下角 -->
<!-- <view
class=""
style="flex-direction: row"
>
@ -120,17 +121,7 @@
src="/static/images/cd-play-4.gif"
style="width: 150rpx; height: 150rpx; opacity: 0.8"
></image>
<image
v-if="!isIOS"
src="/static/images/icon-cd.png"
class="play-cd"
></image>
<image
v-if="isIOS"
:src="'https://imooc-news.oss-cn-shanghai.aliyuncs.com/image/cd-play-4.gif?time=' + times"
class="play-cd"
></image>
</view>
</view> -->
</view>
<!-- 视频展示右侧的操作按钮头像 - 点赞 - 评论 - 转发 -->
<view class="operation-box">
@ -323,7 +314,6 @@ export default {
playStatus(val) {
var me = this;
console.log(val);
if (!val) {
me.videoContext.pause();
} else {
@ -839,7 +829,6 @@ export default {
height: 2px;
background-color: #ccc;
z-index: 3;
pointer-events: none;
}
.progress-foreground {
@ -849,7 +838,6 @@ export default {
position: absolute;
bottom: 0;
left: 0;
pointer-events: none;
}
.anm {
transition: width 0.25s linear;

View File

@ -30,7 +30,7 @@
@click="playOrPause"
@play="onplay"
@error="onerror"
@timeupdate="timeupdate"
@timeupdate="onTimeUpdate"
></video>
<image
:lazy-load="true"
@ -75,7 +75,7 @@
v-if="isDragging"
:style="{ left: floatLeft + 'px' }"
>
{{ formatTime(floatTime) }}
<text style="color: #fff">{{ formatTime(floatTime) }}</text>
</view>
<view class="publish-info-box">
@ -90,7 +90,7 @@
<text class="muisc-words">{{ item.vlogerName }}的原声创作</text>
</view>
</view>
<view
<!-- <view
class=""
style="flex-direction: row"
>
@ -98,18 +98,7 @@
src="/static/images/cd-play-4.gif"
style="width: 150rpx; height: 150rpx; opacity: 0.8"
></image>
<image
v-if="!isIOS"
src="/static/images/icon-cd.png"
class="play-cd"
style="width: 120rpx; height: 120rpx"
></image>
<image
v-if="isIOS"
:src="'https://imooc-news.oss-cn-shanghai.aliyuncs.com/image/cd-play-4.gif?time=' + times"
class="play-cd"
></image>
</view>
</view> -->
</view>
<!-- 视频展示右侧的操作按钮, 头像 - 点赞 - 评论 - 转发 -->
<view class="operation-box">
@ -659,7 +648,6 @@ export default {
height: 2px;
background-color: #ccc;
z-index: 3;
pointer-events: none;
}
.progress-foreground {
@ -669,15 +657,14 @@ export default {
position: absolute;
bottom: 0;
left: 0;
pointer-events: none;
}
.anm {
transition: width 0.25s linear;
}
.float-time {
position: absolute;
bottom: 56px;
position: fixed;
bottom: 2px;
width: 60px;
height: 30px;
background-color: rgba(0, 0, 0, 0.3);
@ -688,7 +675,7 @@ export default {
line-height: 30px;
font-size: 12px;
border-radius: 4px;
z-index: 1;
z-index: 5;
}
.abs-box {
@ -702,7 +689,6 @@ export default {
position: absolute;
width: 120rpx;
height: 120rpx;
pointer-events: none;
}
.icon {
width: 80rpx;
@ -728,7 +714,7 @@ export default {
}
.publish-info-box {
position: absolute;
bottom: 200rpx;
bottom: 120rpx;
left: 0;
right: 0;
padding-left: 20rpx;

View File

@ -122,24 +122,13 @@
<text class="muisc-words">{{ item.vlogerName }}的原声创作</text>
</view>
</view>
<view
<!-- <view
class=""
style="flex-direction: row"
>
<!-- <image src="/static/images/cd-play-4.gif"
style="width: 150rpx;height: 150rpx;opacity: 0.8;"></image> -->
<image
v-if="!isIOS"
src="/static/images/icon-cd.png"
class="play-cd"
style="width: 120rpx; height: 120rpx"
></image>
<image
v-if="isIOS"
:src="'https://imooc-news.oss-cn-shanghai.aliyuncs.com/image/cd-play-4.gif?time=' + times"
class="play-cd"
></image>
</view>
<image src="/static/images/cd-play-4.gif"
style="width: 150rpx;height: 150rpx;opacity: 0.8;"></image>
</view> -->
</view>
<!-- 视频展示右侧的操作按钮头像 - 点赞 - 评论 - 转发 -->
<view class="operation-box">
@ -334,7 +323,6 @@ export default {
},
onTimeUpdate(e) {
console.log(e.detail.currentTime);
console.log(this.progressFlag);
if (e.detail.currentTime > 0.2) {
this.doplay(e.detail.currentTime);
}
@ -346,7 +334,6 @@ export default {
updateProgress() {
const percent = this.currentTime / this.duration;
this.progressWidth = Math.floor(system.screenWidth * percent);
console.log(this.progressWidth);
},
startDrag(e) {
console.log('触发开始拖动');
@ -690,6 +677,7 @@ export default {
} else {
// return;
}
console.log(myUserInfo);
var result = await vlogFollowList(page, 10, userId);
console.log(result);
if (result.data.status == 200) {

View File

@ -121,7 +121,7 @@
<text class="muisc-words">{{ item.vlogerName }}的原声创作</text>
</view>
</view>
<view
<!-- <view
class=""
style="flex-direction: row"
>
@ -129,17 +129,7 @@
src="/static/images/cd-play-4.gif"
style="width: 150rpx; height: 150rpx; opacity: 0.8"
></image>
<image
v-if="!isIOS"
src="/static/images/icon-cd.png"
class="play-cd"
></image>
<image
v-if="isIOS"
:src="'https://imooc-news.oss-cn-shanghai.aliyuncs.com/image/cd-play-4.gif?time=' + times"
class="play-cd"
></image>
</view>
</view> -->
</view>
<!-- 视频展示右侧的操作按钮头像 - 点赞 - 评论 - 转发 -->
<view class="operation-box">

View File

@ -7,19 +7,19 @@ const dev = {
// common: "https://common-api.pickmall.cn",
// buyer: "https://buyer-api.pickmall.cn",
common: "http://192.168.1.200:8890",
buyer: "http://192.168.1.200:8888",
vlog: "http://192.168.1.200:8099",
web: "http://192.168.1.200:8099",
common: "http://192.168.1.113:8890",
buyer: "http://192.168.1.113:8888",
vlog: "http://192.168.1.86:8099",
web: "http://192.168.1.113:8099",
};
// 生产环境
const prod = {
// common: "https://common-api.pickmall.cn",
// buyer: "https://buyer-api.pickmall.cn",
common: "http://192.168.1.200:8890",
buyer: "http://192.168.1.200:8888",
vlog: "http://192.168.1.200:8099",
common: "http://192.168.1.113:8890",
buyer: "http://192.168.1.113:8888",
vlog: "http://192.168.1.86:8099",
};
//默认生产环境

View File

@ -0,0 +1 @@
{"agcgw":{"url":"connect-drcn.dbankcloud.cn","backurl":"connect-drcn.hispace.hicloud.com","websocketurl":"connect-ws-drcn.hispace.dbankcloud.cn","websocketbackurl":"connect-ws-drcn.hispace.dbankcloud.com"},"agcgw_all":{"SG":"connect-dra.dbankcloud.cn","SG_back":"connect-dra.hispace.hicloud.com","CN":"connect-drcn.dbankcloud.cn","CN_back":"connect-drcn.hispace.hicloud.com","RU":"connect-drru.hispace.dbankcloud.ru","RU_back":"connect-drru.hispace.dbankcloud.cn","DE":"connect-dre.dbankcloud.cn","DE_back":"connect-dre.hispace.hicloud.com"},"websocketgw_all":{"SG":"connect-ws-dra.hispace.dbankcloud.cn","SG_back":"connect-ws-dra.hispace.dbankcloud.com","CN":"connect-ws-drcn.hispace.dbankcloud.cn","CN_back":"connect-ws-drcn.hispace.dbankcloud.com","RU":"connect-ws-drru.hispace.dbankcloud.ru","RU_back":"connect-ws-drru.hispace.dbankcloud.cn","DE":"connect-ws-dre.hispace.dbankcloud.cn","DE_back":"connect-ws-dre.hispace.dbankcloud.com"},"client":{"cp_id":"30086000741655511","product_id":"388421841221950710","client_id":"1346412471476315840","client_secret":"F4B8602C5E2D5642872ED211C7478DB199EA3882FF02AD64113FCFD85CAE0E39","project_id":"388421841221950710","app_id":"110231111","api_key":"DQEDAKxxD0khNfIg6UkOoL08FvJ7BHkKnRi926D1isiHr1coC0avMtrtUxZSJOgu8nUoEWou+q/cZEY+O+5p40IarYKa/PD/JPoYMQ==","package_name":"cn.net.wzj.mall"},"oauth_client":{"client_id":"110231111","client_type":1},"app_info":{"app_id":"110231111","package_name":"cn.net.wzj.mall"},"service":{"analytics":{"collector_url":"datacollector-drcn.dt.hicloud.com,datacollector-drcn.dt.dbankcloud.cn","collector_url_cn":"datacollector-drcn.dt.hicloud.com,datacollector-drcn.dt.dbankcloud.cn","collector_url_de":"datacollector-dre.dt.hicloud.com,datacollector-dre.dt.dbankcloud.cn","collector_url_ru":"datacollector-drru.dt.dbankcloud.ru,datacollector-drru.dt.hicloud.com","collector_url_sg":"datacollector-dra.dt.hicloud.com,datacollector-dra.dt.dbankcloud.cn","resource_id":"p1","channel_id":""},"ml":{"mlservice_url":"ml-api-drcn.ai.dbankcloud.com,ml-api-drcn.ai.dbankcloud.cn"},"cloudstorage":{"storage_url":"https://agc-storage-drcn.platform.dbankcloud.cn","storage_url_ru":"https://agc-storage-drru.cloud.huawei.ru","storage_url_sg":"https://ops-dra.agcstorage.link","storage_url_de":"https://ops-dre.agcstorage.link","storage_url_cn":"https://agc-storage-drcn.platform.dbankcloud.cn","storage_url_ru_back":"https://agc-storage-drru.cloud.huawei.ru","storage_url_sg_back":"https://agc-storage-dra.cloud.huawei.asia","storage_url_de_back":"https://agc-storage-dre.cloud.huawei.eu","storage_url_cn_back":"https://agc-storage-drcn.cloud.huawei.com.cn"},"search":{"url":"https://search-drcn.cloud.huawei.com"},"edukit":{"edu_url":"edukit.cloud.huawei.com.cn","dh_url":"edukit.cloud.huawei.com.cn"}},"region":"CN","configuration_version":"3.0","appInfos":[{"package_name":"cn.net.wzj.mall","client":{"app_id":"110231111"},"app_info":{"package_name":"cn.net.wzj.mall","app_id":"110231111"},"oauth_client":{"client_type":1,"client_id":"110231111"}}]}

View File

@ -0,0 +1 @@
{"version":"1.0.1","cn.net.wzj.mall":{"manifestPlaceholders":{"VIVO_APPKEY":"ebe0d2ef18e69264e8ddfb48472cb3ec","VIVO_APPID":"105722088","HONOR_APPID":"104443878"},"xiaomiPushBussinessId":"41169","xiaomiPushAppId":"2882303761520283584","xiaomiPushAppKey":"5242028361584","oppoPushBussinessId":"41170","oppoPushAppKey":"d64af1f3a4c54c0dae37d556fde086af","oppoPushAppSecret":"f6458a874f9b432aaa7a4e4a7b782fd0","huaweiPushBussinessId":"41171","huaweiBadgeClassName":"","meizuPushBussinessId":"41176","meizuPushAppId":"153237","meizuPushAppKey":"e6cdd35fcd6b47679ccd9f5bba117578","vivoPushBussinessId":"41177","honorPushBussinessId":"41178"}}

View File

@ -0,0 +1,5 @@
{
"VIVO_APPKEY": "ebe0d2ef18e69264e8ddfb48472cb3ec",
"VIVO_APPID": "105722088",
"HONOR_APPID": "104443878"
}

View File

@ -0,0 +1,6 @@
{
"developer_id":"109999867274",
"app_id":"104443878",
"package_name":"cn.net.wzj.mall",
"version":"1.0"
}

View File

@ -0,0 +1,3 @@
{
"businessID": "45148"
}

4096
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,10 +1,10 @@
{
"dependencies": {
"@dcloudio/uni-app": "^2.0.2-4050720250324001",
"@tencentcloud/chat-uikit-uniapp": "^2.4.3",
"@vue/runtime-core": "^3.5.13",
"unplugin-vue2-script-setup": "^0.11.4",
"vue": "^3.2.0",
"ws": "^8.18.1"
}
}
{
"dependencies": {
"@dcloudio/uni-app": "^2.0.2-4050720250324001",
"@tencentcloud/chat-uikit-uniapp": "^2.4.3",
"@vue/runtime-core": "^3.5.13",
"unplugin-vue2-script-setup": "^0.11.4",
"vue": "^3.2.0",
"ws": "^8.18.1"
}
}

View File

@ -1,375 +1,418 @@
<template>
<scroll-view class="prpage" scroll-y="true">
<view class="line"></view>
<!-- 进度条 -->
<view class="progress" v-if="percentCompleted != 100">
<progress :percent="percentCompleted" stroke-width="3" activeColor="#ef274d" backgroundColor="#F1F1F1" />
<text class="progress-text">视频上传中,请耐心等待~~</text>
<image class="progress-img" mode="aspectFit" src="/static/images/loading-4.gif" />
</view>
<!-- 发布主体内容 -->
<view class="main-body" v-if="percentCompleted == 100">
<image class="main-body-img-m" v-if="tempCover" :src="tempCover" mode="widthFix" />
<image class="main-body-img-m" v-if="!tempCover" src='/static/images/loading-4.gif' mode="aspectFit" />
<view class="main-body-content">
<view class="preplay-wrapper" @click="preview" @touchstart="touchstartPreplay"
@touchend="touchendPreplay">
<image class="preplay-icon" src="/static/images/btn-play.png" />
<text class="preplay-text">预览视频</text>
</view>
<view class="choose-cover" @click="chooseCover">
<text class="choose-cover-text">选择封面</text>
</view>
</view>
<textarea class="vlog-content" placeholder-style="color: #9798a0;" placeholder="添加合适的描述内容~" :value="title"
:model="title" maxlength="60" @input="typingContent" confirm-type="done"></textarea>
<view class="mbtn" :class="{
'btn-publish': !publishTouched,
'btn-publish-touched': publishTouched,
}" @touchstart="touchstartPublish" @touchend="touchendPublish" @click="doPublich">
<text class="btn-text">发布视频</text>
<template>
<scroll-view
class="prpage"
scroll-y="true"
>
<view class="line"></view>
<!-- 进度条 -->
<view
class="progress"
v-if="percentCompleted != 100"
>
<progress
:percent="percentCompleted"
stroke-width="3"
activeColor="#ef274d"
backgroundColor="#F1F1F1"
/>
<text class="progress-text">视频上传中,请耐心等待~~</text>
<image
class="progress-img"
mode="aspectFit"
src="/static/images/loading-4.gif"
/>
</view>
<!-- 发布主体内容 -->
<view
class="main-body"
v-if="percentCompleted == 100"
>
<image
class="main-body-img-m"
v-if="tempCover"
:src="tempCover"
mode="widthFix"
/>
<image
class="main-body-img-m"
v-if="!tempCover"
src="/static/images/loading-4.gif"
mode="aspectFit"
/>
<view class="main-body-content">
<view
class="preplay-wrapper"
@click="preview"
@touchstart="touchstartPreplay"
@touchend="touchendPreplay"
>
<image
class="preplay-icon"
src="/static/images/btn-play.png"
/>
<text class="preplay-text">预览视频</text>
</view>
<view
class="choose-cover"
@click="chooseCover"
>
<text class="choose-cover-text">选择封面</text>
</view>
</view>
</view>
</scroll-view>
</template>
<script>
import storage from "@/utils/storage.js"; //缓存
// import {
// graceNumber
// } from '@/utils/tools.js'
import api from "@/config/api.js";
export default {
data() {
return {
publishTouched: false,
preplayTouched: false,
tempFilePath: "",
videoUrl: "",
tempCover: "", // 视频封面
title: "",
width: 0,
height: 0,
percentCompleted: 0, // 进度
};
},
onLoad(params) {
let me = this;
let vlogInfo = storage.getVlogUserInfo()
// 上个页面传过来的文件事件对象, 其中包含了相册中选择的视频内容
let fileObjectEvent = JSON.parse(params.fileObjectEvent);
let times = new Date().getTime();
var userId = vlogInfo.id;
let nickname = vlogInfo.nickname;
let serverUrl = api.vlog;
const uploadTask = uni.uploadFile({
filePath: fileObjectEvent.tempFilePath,
url: serverUrl + "/upload",
name: 'file',
formData: {
filetype: 'video'
},
header: {
headerUserId: userId,
headerUserToken: storage.getVlogToken()
},
success: (f) => {
console.log(f)
let jsondata = f.data
let data = JSON.parse(jsondata)
let videoUrl = data.data;
me.videoUrl = videoUrl;
me.width = fileObjectEvent.width;
me.height = fileObjectEvent.height;
}
})
uploadTask.onProgressUpdate((res) => {
console.log('上传进度' + res.progress);
// console.log('已经上传的数据长度' + res.totalBytesSent);
// console.log('预期需要上传的数据总长度' + res.totalBytesExpectedToSend);
// 显示进度
// let percentCompleted = Math.round(
// (res.progress * 100) / res.total
// );
me.percentCompleted = res.progress
})
},
methods: {
typingContent(e) {
let event = e;
this.title = e.detail.value;
},
doPublich() {
if (this.title.length < 5) {
uni.showToast({
title: "请输入5个字以上的标题",
icon: "none",
});
return;
}
let me = this;
let vlogInfo = storage.getVlogUserInfo()
let userId = vlogInfo.id;
let vlog = {
vlogerId: userId,
url: me.videoUrl,
cover: me.tempCover || '',
title: me.title,
width: me.width,
height: me.height,
};
// 发布视频
let serverUrl = api.vlog
uni.request({
method: "POST",
header: {
headerUserId: userId,
headerUserToken: storage.getVlogToken(),
},
url: serverUrl + "/vlog/publish",
data: vlog,
success(result) {
if (result.data.status == 200) {
uni.showToast({
title: result.data.msg,
icon: "none",
duration: 2000,
});
setTimeout(() => {
uni.switchTab({
url: "/pages/tabbar/user/my",
});
}, 2000);
} else {
uni.showToast({
title: result.data.msg,
icon: "none",
duration: 3000,
});
}
},
});
},
preview() {
uni.navigateTo({
url: "/pages/publish/preview?videoUrl=" +
this.videoUrl +
"&width=" +
this.width +
"&height=" +
this.height,
animationType: "slide-in-bottom",
animationDuration: 500,
});
},
touchstartPreplay() {
this.preplayTouched = true;
},
touchendPreplay() {
this.preplayTouched = false;
},
touchstartPublish() {
this.publishTouched = true;
},
touchendPublish() {
this.publishTouched = false;
},
chooseCover() {
let me = this;
let vlogInfo = storage.getVlogUserInfo()
let userId = vlogInfo.id;
uni.chooseImage({
count: 1,
sizeType: "original",
sourceType: ["album"],
success(e) {
me.tempCover = e.tempFilePaths[0]; //先在本地回显
// 上传封面
let serverUrl = api.vlog;
uni.uploadFile({
filePath: e.tempFilePaths[0],
url: serverUrl + "/upload",
formData: {
filetype: 'video'
},
header: {
headerUserId: userId,
headerUserToken: storage.getVlogToken(),
},
name: "file",
success(result) {
let res = JSON.parse(result.data);
console.log(res)
if (res.status == 200) {
let imageUrl = res.data;
me.tempCover = imageUrl;
uni.showToast({
title: res.msg,
duration: 2000,
});
} else {
uni.showToast({
title: res.msg,
icon: "none",
duration: 3000,
});
}
},
});
},
});
},
},
};
</script>
<style scoped>
.prpage {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
background-color: #181b27;
}
.main-body-img-m {
min-height: 400rpx;
/* width: 750rpx; */
border: 2rpx solid #545456;
border-radius: 20rpx;
align-items: center;
}
.choose-cover-text {
color: #ffffff;
font-size: 28rpx;
align-items: center;
}
.choose-cover {
display: flex;
flex-direction: column;
justify-content: center;
margin-left: 100rpx;
margin-top: 10px;
width: 200rpx;
height: 100rpx;
position: relative;
}
.preplay-icon {
width: 22rpx;
height: 22rpx;
align-items: center;
}
.preplay-text {
color: #e6e6e6;
font-size: 28rpx;
align-items: center;
margin-left: 15rpx;
}
.preplay-wrapper {
display: flex;
flex-direction: row;
justify-content: center;
padding: 6rpx 16rpx;
width: 200rpx;
}
.main-body-content {
/* display: flex; */
flex-direction: row;
justify-content: flex-start;
}
.vlog-content {
margin-top: 30rpx;
height: 200rpx;
color: #000000;
font-size: 16px;
background-color: #ffffff;
padding-left: 20rpx;
padding-top: 20rpx;
padding-right: 20rpx;
padding-bottom: 20rpx;
border-radius: 20rpx;
}
.btn-text {
color: #e6e6e6;
font-size: 36rpx;
align-items: center;
font-weight: 500;
}
.mbtn {
margin-top: 30rpx;
height: 90rpx;
/* display: flex; */
justify-content: center;
align-items: center;
border-radius: 40rpx;
border: transparent;
}
.btn-publish {
background-color: #ef274d;
overflow: hidden;
}
.btn-publish-touched {
background-color: #de6981;
overflow: hidden;
}
.main-body {
margin-top: 20rpx;
padding: 0 20rpx;
}
.line {
height: 1rpx;
background-color: #393a41;
width: 750rpx;
}
.progress {
margin-top: 60rpx;
display: flex;
flex-direction: column;
justify-content: center;
width: 750rpx;
}
.progress-text {
color: #f1f1f1;
font-size: 32rpx;
text-align: center;
margin-top: 40rpx;
}
.progress-img {
width: 600rpx;
height: 600rpx;
align-items: center;
}
</style>
<textarea
class="vlog-content"
placeholder-style="color: #9798a0;"
placeholder="添加合适的描述内容~"
:value="title"
:model="title"
maxlength="60"
@input="typingContent"
confirm-type="done"
></textarea>
<view
class="mbtn"
:class="{
'btn-publish': !publishTouched,
'btn-publish-touched': publishTouched
}"
@touchstart="touchstartPublish"
@touchend="touchendPublish"
@click="doPublich"
>
<text class="btn-text">发布视频</text>
</view>
</view>
</scroll-view>
</template>
<script>
import storage from '@/utils/storage.js'; //缓存
// import {
// graceNumber
// } from '@/utils/tools.js'
import api from '@/config/api.js';
export default {
data() {
return {
publishTouched: false,
preplayTouched: false,
tempFilePath: '',
videoUrl: '',
tempCover: '', // 视频封面
title: '',
width: 0,
height: 0,
percentCompleted: 0 // 进度
};
},
onLoad(params) {
let me = this;
let vlogInfo = storage.getVlogUserInfo();
// 上个页面传过来的文件事件对象, 其中包含了相册中选择的视频内容
let fileObjectEvent = JSON.parse(params.fileObjectEvent);
let times = new Date().getTime();
var userId = vlogInfo.id;
let nickname = vlogInfo.nickname;
let serverUrl = api.vlog;
const uploadTask = uni.uploadFile({
filePath: fileObjectEvent.tempFilePath,
url: serverUrl + '/upload',
name: 'file',
formData: {
filetype: 'video'
},
header: {
headerUserId: userId,
headerUserToken: storage.getVlogToken()
},
success: (f) => {
console.log(f);
let jsondata = f.data;
let data = JSON.parse(jsondata);
let videoUrl = data.data;
me.videoUrl = videoUrl;
me.width = fileObjectEvent.width;
me.height = fileObjectEvent.height;
}
});
uploadTask.onProgressUpdate((res) => {
console.log('上传进度' + res.progress);
// console.log('已经上传的数据长度' + res.totalBytesSent);
// console.log('预期需要上传的数据总长度' + res.totalBytesExpectedToSend);
// 显示进度
// let percentCompleted = Math.round(
// (res.progress * 100) / res.total
// );
me.percentCompleted = res.progress;
});
},
methods: {
typingContent(e) {
let event = e;
this.title = e.detail.value;
},
doPublich() {
if (this.title.length < 5) {
uni.showToast({
title: '请输入5个字以上的标题',
icon: 'none'
});
return;
}
let me = this;
let vlogInfo = storage.getVlogUserInfo();
let userId = vlogInfo.id;
let vlog = {
vlogerId: userId,
url: me.videoUrl,
cover: me.tempCover || '',
title: me.title,
width: me.width,
height: me.height,
cityCode: storage.getCityCode()
};
// 发布视频
let serverUrl = api.vlog;
uni.request({
method: 'POST',
header: {
headerUserId: userId,
headerUserToken: storage.getVlogToken()
},
url: serverUrl + '/vlog/publish',
data: vlog,
success(result) {
if (result.data.status == 200) {
uni.showToast({
title: result.data.msg,
icon: 'none',
duration: 2000
});
setTimeout(() => {
uni.switchTab({
url: '/pages/me/me'
});
}, 2000);
} else {
uni.showToast({
title: result.data.msg,
icon: 'none',
duration: 3000
});
}
}
});
},
preview() {
uni.navigateTo({
url: '/pages/publish/preview?videoUrl=' + this.videoUrl + '&width=' + this.width + '&height=' + this.height,
animationType: 'slide-in-bottom',
animationDuration: 500
});
},
touchstartPreplay() {
this.preplayTouched = true;
},
touchendPreplay() {
this.preplayTouched = false;
},
touchstartPublish() {
this.publishTouched = true;
},
touchendPublish() {
this.publishTouched = false;
},
chooseCover() {
let me = this;
let vlogInfo = storage.getVlogUserInfo();
let userId = vlogInfo.id;
uni.chooseImage({
count: 1,
sizeType: 'original',
sourceType: ['album'],
success(e) {
me.tempCover = e.tempFilePaths[0]; //先在本地回显
// 上传封面
let serverUrl = api.vlog;
uni.uploadFile({
filePath: e.tempFilePaths[0],
url: serverUrl + '/upload',
formData: {
filetype: 'video'
},
header: {
headerUserId: userId,
headerUserToken: storage.getVlogToken()
},
name: 'file',
success(result) {
let res = JSON.parse(result.data);
console.log(res);
if (res.status == 200) {
let imageUrl = res.data;
me.tempCover = imageUrl;
uni.showToast({
title: res.msg,
duration: 2000
});
} else {
uni.showToast({
title: res.msg,
icon: 'none',
duration: 3000
});
}
}
});
}
});
}
}
};
</script>
<style scoped>
.prpage {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
background-color: #181b27;
}
.main-body-img-m {
min-height: 400rpx;
/* width: 750rpx; */
border: 2rpx solid #545456;
border-radius: 20rpx;
align-items: center;
}
.choose-cover-text {
color: #ffffff;
font-size: 28rpx;
align-items: center;
}
.choose-cover {
display: flex;
flex-direction: column;
justify-content: center;
margin-left: 100rpx;
margin-top: 10px;
width: 200rpx;
height: 100rpx;
position: relative;
}
.preplay-icon {
width: 22rpx;
height: 22rpx;
align-items: center;
}
.preplay-text {
color: #e6e6e6;
font-size: 28rpx;
align-items: center;
margin-left: 15rpx;
}
.preplay-wrapper {
display: flex;
flex-direction: row;
justify-content: center;
padding: 6rpx 16rpx;
width: 200rpx;
}
.main-body-content {
/* display: flex; */
flex-direction: row;
justify-content: flex-start;
}
.vlog-content {
margin-top: 30rpx;
height: 200rpx;
color: #000000;
font-size: 16px;
background-color: #ffffff;
padding-left: 20rpx;
padding-top: 20rpx;
padding-right: 20rpx;
padding-bottom: 20rpx;
border-radius: 20rpx;
}
.btn-text {
color: #e6e6e6;
font-size: 36rpx;
align-items: center;
font-weight: 500;
}
.mbtn {
margin-top: 30rpx;
height: 90rpx;
/* display: flex; */
justify-content: center;
align-items: center;
border-radius: 40rpx;
border: transparent;
}
.btn-publish {
background-color: #ef274d;
overflow: hidden;
}
.btn-publish-touched {
background-color: #de6981;
overflow: hidden;
}
.main-body {
margin-top: 20rpx;
padding: 0 20rpx;
}
.line {
height: 1rpx;
background-color: #393a41;
width: 750rpx;
}
.progress {
margin-top: 60rpx;
display: flex;
flex-direction: column;
justify-content: center;
width: 750rpx;
}
.progress-text {
color: #f1f1f1;
font-size: 32rpx;
text-align: center;
margin-top: 40rpx;
}
.progress-img {
width: 600rpx;
height: 600rpx;
align-items: center;
}
</style>

View File

@ -1,274 +0,0 @@
<template>
<view class="page">
<!-- 这里是状态栏, 每个页面都需要有, 目的不让页面覆盖状态栏 -->
<view :style="{ height: statusBarHeight + 'px' }"></view>
<view class="big-search-wrapper">
<image
class="header-right-search icon-search"
src="/static/images/icon-back.png"
@click="back" />
<view class="search-box">
<view class="search-box-left">
<image
class="header-right-search search-image"
src="/static/images/icon-search.png" />
</view>
<input
type="text"
:model="searchContent"
:value="searchContent"
@input="typingContent"
placeholder="请输入内容~"
maxlength="10"
class="search-input" />
<view class="search-box-right">
<image
class="scan-image"
src="/static/images/icon-scan-qrcode.png"
@click="scan" />
</view>
</view>
<view class="btn" @click="doSearch">
<text class="search-btn">搜索</text>
</view>
</view>
<view class="history">
<view
v-for="(h, index) in historyList"
:key="index"
class="history-item-wrapper">
<view class="time-and-text" @click="searchByHistory(h)">
<image class="time-image" src="/static/images/icon-time.png" />
<text class="history-text">{{ h }}</text>
</view>
<image
class="delete-image"
src="/static/images/icon-delete.png"
@click="removeHistoryItem(index)" />
</view>
<view
v-if="historyList.length == 0"
class="clear-all-wrapper"
@click="removeAllHistory">
<text class="clear-all"></text>
</view>
<view v-else class="clear-all-wrapper" @click="removeAllHistory">
<text class="clear-all">清除所有搜索记录</text>
</view>
</view>
</view>
</template>
<script>
let system = uni.getSystemInfoSync();
export default {
data() {
return {
searchContent: "",
historyList: [],
};
},
onLoad() {
this.statusBarHeight = system.statusBarHeight;
// 从本地缓存获得搜索的历史记录
let historyListJSON = uni.getStorageSync("historyList");
if (historyListJSON != null && historyListJSON != undefined) {
this.historyList = JSON.parse(historyListJSON);
}
},
methods: {
back() {
uni.navigateBack({
delta: 1,
});
},
scan() {
uni.scanCode({
success: (e) => {
let result = e.result;
let vlogId = JSON.parse(result).content;
uni.navigateTo({
url: "../vlog/vlog?vlogId=" + vlogId,
});
},
});
},
typingContent(e) {
this.searchContent = e.detail.value;
},
searchByHistory(searchContent) {
this.searchContent = searchContent;
this.doSearch();
},
doSearch() {
let me = this;
let searchContent = this.searchContent;
if (getApp().isStrEmpty(searchContent)) {
uni.showToast({
title: "搜索关键字为空!",
icon: "none",
duration: 2000,
});
this.searchContent = "";
return;
}
let tempList = this.historyList;
// 判断搜索内容是否已经存在, 如果存在, 则移除
for (let i = 0; i < tempList.length; i++) {
let old = tempList[i];
if (searchContent === old) {
tempList.splice(i, 1);
break;
}
}
tempList.unshift(searchContent);
// 如果超过10个, 则删除最后一项
if (tempList.length > 10) {
tempList.splice(10, 1);
this.historyList = tempList;
}
// 保存到本地缓存
uni.setStorageSync("historyList", JSON.stringify(this.historyList));
// 跳转页面, 把搜索条件携带过去
uni.navigateTo({
url: "searchList?search=" + searchContent,
});
},
removeHistoryItem(index) {
this.historyList.splice(index, 1);
uni.setStorageSync("historyList", JSON.stringify(this.historyList));
},
removeAllHistory() {
this.historyList = [];
uni.setStorageSync("historyList", JSON.stringify(this.historyList));
},
},
};
</script>
<style lang="scss">
.page {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
background-color: #181b27;
.big-search-wrapper {
padding: 30rpx;
display: flex;
flex-direction: row;
justify-content: space-between;
.header-right-search {
height: 100rpx;
}
.icon-search {
width: 40rpx;
height: 40rpx;
opacity: 0.8;
align-self: center;
}
.search-box {
display: flex;
flex-direction: row;
.search-box-left {
padding: 0 10rpx;
display: flex;
flex-direction: row;
background-color: #55565e;
border-top-left-radius: 6rpx;
border-bottom-left-radius: 6rpx;
.search-image {
width: 50rpx;
height: 50rpx;
opacity: 0.8;
align-self: center;
}
}
}
.search-input {
width: 360rpx;
background-color: #55565e;
height: 60rpx;
font-size: 28rpx;
color: #ffffff;
}
.search-box-right {
padding: 0 16rpx;
display: flex;
flex-direction: row;
background-color: #55565e;
border-top-right-radius: 6rpx;
border-bottom-right-radius: 6rpx;
.scan-image {
width: 50rpx;
height: 50rpx;
opacity: 0.8;
align-self: center;
}
}
.btn {
align-self: center;
.search-btn {
color: #ffffff;
font-size: 32rpx;
align-self: center;
}
}
}
.history {
.history-item-wrapper {
padding: 16rpx 26rpx;
display: flex;
flex-direction: row;
justify-content: space-between;
.time-and-text {
display: flex;
flex-direction: row;
width: 500rpx;
.time-image {
width: 40rpx;
height: 40rpx;
align-self: center;
}
.history-text {
color: #ffffff;
font-size: 30rpx;
align-self: center;
margin-left: 20rpx;
}
}
.delete-image {
width: 30rpx;
height: 30rpx;
opacity: 0.9;
align-self: center;
}
}
.clear-all-wrapper {
display: flex;
flex-direction: row;
justify-content: center;
margin-top: 40rpx;
.clear-all {
color: #f1f1f1;
font-size: 28rpx;
align-self: center;
}
}
}
}
</style>

296
pages/search/search.vue Executable file
View File

@ -0,0 +1,296 @@
<template>
<view class="page">
<!-- 这里是状态栏, 每个页面都需要有, 目的不让页面覆盖状态栏 -->
<view :style="{ height: statusBarHeight + 'px' }"></view>
<view class="big-search-wrapper">
<image
class="header-right-search icon-search"
src="/static/images/icon-back.png"
@click="back"
/>
<view class="search-box">
<view class="search-box-left">
<image
class="header-right-search search-image"
src="/static/images/icon-search.png"
/>
</view>
<input
type="text"
:model="searchContent"
:value="searchContent"
@input="typingContent"
@confirm=""
placeholder="请输入内容~"
maxlength="10"
class="search-input"
/>
<!-- <view class="search-box-right">
<image
class="scan-image"
src="/static/images/icon-scan-qrcode.png"
@click="scan"
/>
</view> -->
</view>
<view
class="sbtn"
@click="doSearch"
>
搜索
</view>
</view>
<view class="history">
<view
v-for="(h, index) in historyList"
:key="index"
class="history-item-wrapper"
>
<view
class="time-and-text"
@click="searchByHistory(h)"
>
<image
class="time-image"
src="/static/images/icon-time.png"
/>
<text class="history-text">{{ h }}</text>
</view>
<image
class="delete-image"
src="/static/images/icon-delete.png"
@click="removeHistoryItem(index)"
/>
</view>
<view
v-if="historyList.length == 0"
class="clear-all-wrapper"
@click="removeAllHistory"
>
<text class="clear-all"></text>
</view>
<view
v-else
class="clear-all-wrapper"
@click="removeAllHistory"
>
<text class="clear-all">清除所有搜索记录</text>
</view>
</view>
</view>
</template>
<script>
let system = uni.getSystemInfoSync();
import storage from '@/utils/storage.js'; //
import { isStrEmpty } from '@/utils/tools.js';
export default {
data() {
return {
searchContent: '',
historyList: []
};
},
onLoad() {
this.statusBarHeight = system.statusBarHeight;
//
let historyListJSON = uni.getStorageSync('historyList') || null;
console.log(historyListJSON);
if (historyListJSON != null && historyListJSON != undefined) {
this.historyList = JSON.parse(historyListJSON);
} else {
this.historyList = [];
}
},
methods: {
back() {
uni.navigateBack({
delta: 1
});
},
scan() {
//
// uni.scanCode({
// success: (e) => {
// let result = e.result;
// let vlogId = JSON.parse(result).content;
// uni.navigateTo({
// url: '../vlog/vlog?vlogId=' + vlogId
// });
// }
// });
},
typingContent(e) {
this.searchContent = e.detail.value;
},
searchByHistory(searchContent) {
console.log(searchContent);
this.searchContent = searchContent;
this.doSearch();
},
doSearch() {
var searchContent = this.searchContent;
if (isStrEmpty(this.searchContent)) {
uni.showToast({
title: '搜索关键字为空!',
icon: 'none',
duration: 2000
});
this.searchContent = '';
return;
}
let tempList = this.historyList;
// , ,
for (let i = 0; i < tempList.length; i++) {
let old = tempList[i];
if (this.searchContent === old) {
tempList.splice(i, 1);
break;
}
}
tempList.unshift(searchContent);
// 10,
if (tempList.length > 10) {
tempList.splice(0, 1);
this.historyList = tempList;
}
//
uni.setStorageSync('historyList', JSON.stringify(this.historyList));
// ,
uni.navigateTo({
url: 'searchList?search=' + searchContent
});
},
removeHistoryItem(index) {
this.historyList.splice(index, 1);
uni.setStorageSync('historyList', JSON.stringify(this.historyList));
},
removeAllHistory() {
this.historyList = [];
uni.setStorageSync('historyList', JSON.stringify(this.historyList));
}
}
};
</script>
<style lang="scss" scope>
.page {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
background-color: #181b27;
.big-search-wrapper {
padding: 30rpx;
display: flex;
align-items: flex-start;
flex-direction: row;
justify-content: space-between;
.header-right-search {
height: 100rpx;
}
.icon-search {
width: 50rpx;
height: 50rpx;
opacity: 0.8;
}
.search-box {
display: flex;
flex-direction: row;
.search-box-left {
padding: 0 10rpx;
display: flex;
flex-direction: row;
background-color: #55565e;
border-top-left-radius: 6rpx;
border-bottom-left-radius: 6rpx;
.search-image {
width: 50rpx;
height: 50rpx;
opacity: 0.8;
}
}
}
.search-input {
width: 410rpx;
height: 50rpx;
background-color: #55565e;
font-size: 28rpx;
color: #ffffff;
border-top-right-radius: 6rpx;
border-bottom-right-radius: 6rpx;
}
.search-box-right {
padding: 0 16rpx;
display: flex;
flex-direction: row;
background-color: #55565e;
border-top-right-radius: 6rpx;
border-bottom-right-radius: 6rpx;
.scan-image {
width: 50rpx;
height: 50rpx;
opacity: 0.8;
}
}
.sbtn {
height: 50rpx;
line-height: 50rpx;
color: #ffffff;
font-size: 28rpx;
}
}
.history {
.history-item-wrapper {
padding: 16rpx 26rpx;
display: flex;
flex-direction: row;
justify-content: space-between;
.time-and-text {
display: flex;
flex-direction: row;
width: 500rpx;
.time-image {
width: 40rpx;
height: 40rpx;
}
.history-text {
color: #ffffff;
font-size: 30rpx;
margin-left: 20rpx;
}
}
.delete-image {
width: 30rpx;
height: 30rpx;
opacity: 0.9;
}
}
.clear-all-wrapper {
display: flex;
flex-direction: row;
justify-content: center;
margin-top: 40rpx;
.clear-all {
color: #f1f1f1;
font-size: 28rpx;
}
}
}
}
</style>

View File

@ -1,260 +0,0 @@
<template>
<view class="page">
<view :style="{ height: statusBarHeight + 'px' }">
<!-- 这里是状态栏, 每个页面都需要有, 目的不让页面覆盖状态栏 -->
</view>
<view class="big-search-wrapper">
<image
class="header-right-search icon-search"
src="/static/images/icon-back.png"
@click="back" />
<view class="search-box">
<view class="search-box-left">
<image
class="header-right-search search-image"
src="/static/images/icon-search.png" />
</view>
<input
type="text"
:model="searchContent"
:value="searchContent"
@input="typingContent"
placeholder="请输入内容~"
maxlength="10"
class="search-input" />
</view>
<view class="btn" @click="doSearch">
<text class="search-btn">搜索</text>
</view>
</view>
<view class="waterfall-wrapper" :style="{ height: screenHeight + 'px' }">
<waterfall
:style="{ height: screenHeight + 'px' }"
column-count="2"
column-width="auto"
column-gap="2rpx"
left-gap="4rpx"
right-gap="4rpx">
<cell v-for="(vlog, index) in waterList" :key="index">
<view class="every-single-video" @appear="appearVlog(index)">
<image
:src="vlog.cover"
mode="aspectFill"
class="half-cover"
@click="goToVlog(vlog.vlogId)" />
</view>
</cell>
</waterfall>
</view>
</view>
</template>
<script>
let system = uni.getSystemInfoSync();
let app = getApp();
export default {
data() {
return {
screenHeight: 0,
statusBarHeight: 0,
waterList: [],
page: 0,
totalPage: 0,
search: "",
searchContent: "",
};
},
onLoad(params) {
uni.showLoading({
title: "正在获取!",
});
this.statusBarHeight = system.statusBarHeight;
let screenHeight = system.safeArea.bottom;
this.screenHeight = screenHeight;
this.searchContent = params.search
// 搜索的关键字
let search = params.search;
this.search = search;
this.fetchList(0);
},
onShow() {},
methods: {
doSearch() {
let me = this;
let searchContent = this.searchContent;
if (getApp().isStrEmpty(searchContent)) {
uni.showToast({
title: "搜索为空!",
icon: "none",
duration: 2000,
});
this.searchContent = "";
return;
}
uni.navigateTo({
url: "searchList?search=" + searchContent,
});
},
typingContent(e) {
this.searchContent = e.detail.value;
},
back() {
uni.navigateBack({
delta: 1,
});
},
loadMore() {
if (this.page >= this.totalPage) {
return;
} else {
this.fetchList(this.page);
}
},
fetchList(page) {
let me = this;
page = page + 1;
let search = me.search;
let userInfo = getApp().getUserInfoSession();
let userId = "";
if (!app.isStrEmpty(userInfo)) {
userId = userInfo.id;
}
let serverUrl = app.globalData.serverUrl;
uni.request({
method: "GET",
url:
serverUrl +
"/vlog/indexList?userId=" +
userId +
"&search=" +
search +
"&page=" +
page +
"&pageSize=10",
success(result) {
if (result.data.status == 206) {
uni.hideLoading();
let waterList = result.data.data.rows;
let totalPage = result.data.data.total;
me.waterList = waterList;
me.page = page;
me.totalPage = totalPage;
if (
waterList == null ||
waterList == undefined ||
waterList.length == 0
) {
uni.showToast({
title: "没有结果~",
icon: "none",
duration: 2000,
});
setTimeout(() => {
uni.navigateBack({
delta: 1,
});
}, 1000);
}
}
},
});
},
goToVlog(vlogId) {
uni.navigateTo({
url: "../vlog/vlog?vlogId=" + vlogId,
});
},
// 每个vlog出现都会触发
appearVlog(index, e) {
let me = this;
// 如果最后一个vlog出现, 则加载更多
if (index == me.waterList.length - 1) {
me.loadMore();
}
},
},
};
</script>
<style lang="scss">
.page {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
background-color: #181b27;
.big-search-wrapper {
padding: 30rpx;
display: flex;
flex-direction: row;
justify-content: space-between;
.header-right-search {
height: 100rpx;
}
.icon-search {
width: 40rpx;
height: 40rpx;
opacity: 0.8;
align-self: center;
}
.search-box {
display: flex;
flex-direction: row;
.search-box-left {
padding: 0 10rpx;
display: flex;
flex-direction: row;
background-color: #55565e;
border-top-left-radius: 6rpx;
border-bottom-left-radius: 6rpx;
.search-image {
width: 50rpx;
height: 50rpx;
opacity: 0.8;
align-self: center;
}
}
}
.search-input {
width: 440rpx;
background-color: #55565e;
height: 60rpx;
font-size: 28rpx;
color: #ffffff;
border-top-right-radius: 6rpx;
border-bottom-right-radius: 6rpx;
}
.btn {
align-self: center;
.search-btn {
color: #ffffff;
font-size: 32rpx;
align-self: center;
}
}
}
.waterfall-wrapper {
background-color: #181b27;
.every-single-video {
display: flex;
flex-direction: column;
margin-top: 10rpx;
.half-cover {
background-color: #000000;
height: 600rpx;
width: 365rpx;
border-top-left-radius: 10rpx;
border-top-right-radius: 10rpx;
}
}
}
}
</style>

399
pages/search/searchList.vue Executable file
View File

@ -0,0 +1,399 @@
<template>
<view class="page">
<view :style="{ height: statusBarHeight + 'px' }">
<!-- 这里是状态栏, 每个页面都需要有, 目的不让页面覆盖状态栏 -->
</view>
<view class="big-search-wrapper">
<image
class="header-right-search icon-search"
src="/static/images/icon-back.png"
@click="back"
/>
<view class="search-box">
<view class="search-box-left">
<image
class="header-right-search search-image"
src="/static/images/icon-search.png"
/>
</view>
<input
type="text"
:model="searchContent"
:value="searchContent"
@input="typingContent"
placeholder="请输入内容~"
maxlength="10"
class="search-input"
/>
</view>
<view
class="sbtn"
@click="doSearch"
>
搜索
</view>
</view>
<view class="mainCont">
<view
class="wrap"
:style="{ height: screenHeight - statusBarHeight - 50 + 'px' }"
>
<view class="u-tabs-box">
<u-tabs-swiper
bgColor=" #F5F5F5"
activeColor="#FF3229"
ref="tabs"
:list="list"
:current="current"
@change="change"
:is-scroll="false"
swiperWidth="750"
font-size="30rpx"
barWidth="24"
barHeight="8"
></u-tabs-swiper>
</view>
<swiper
class="swiper-box"
:current="swiperCurrent"
@transition="transition"
@animationfinish="animationfinish"
>
<swiper-item class="swiper-item">
<scroll-view
scroll-y
class="no-scrollbar"
style="height: 100%; width: 100%"
@scrolltolower="reachBottom"
>
<view class="page-box">
<!--视频-->
<search-vd
ref="vd"
:keywords="searchContent"
></search-vd>
</view>
</scroll-view>
</swiper-item>
<swiper-item class="swiper-item">
<scroll-view
scroll-y
class="no-scrollbar"
style="height: 100%; width: 100%"
@scrolltolower="reachBottom"
>
<view class="page-box">
<!--商品-->
<shop
ref="shop"
:keywords="searchContent"
></shop>
</view>
</scroll-view>
</swiper-item>
<swiper-item class="swiper-item">
<scroll-view
scroll-y
class="no-scrollbar"
style="height: 100%; width: 100%"
@scrolltolower="reachBottom"
>
<view class="page-box">
<!--用户-->
<user
ref="user"
:keywords="searchContent"
></user>
</view>
</scroll-view>
</swiper-item>
<swiper-item class="swiper-item">
<scroll-view
scroll-y
class="no-scrollbar"
style="height: 100%; width: 100%"
@scrolltolower="reachBottom"
>
<view class="page-box">
<!--团购-->
<tuangou
ref="tuangou"
:keywords="searchContent"
></tuangou>
</view>
</scroll-view>
</swiper-item>
</swiper>
<!-- <view class="page-footer">
<view class="contract-button">
自定义底部栏
</view>
</view> -->
</view>
</view>
</view>
</template>
<script>
let system = uni.getSystemInfoSync();
console.log(system);
import { isStrEmpty } from '@/utils/tools.js';
import storage from '@/utils/storage.js'; //
import searchVd from './searchVd.vue';
import shop from './shop';
import user from './user';
import tuangou from './tuangou';
export default {
components: {
searchVd,
shop,
user,
tuangou
},
data() {
return {
screenHeight: 0,
statusBarHeight: 0,
search: '',
searchContent: '',
// tabs
list: [
{
name: '视频'
},
{
name: '商品'
},
{
name: '用户'
},
{
name: '团购'
}
],
listValue: ['vd', 'shop', 'user', 'tuangou'],
current: 0,
swiperCurrent: 0,
dx: 0
// tabs-end
};
},
onLoad(params) {
this.statusBarHeight = system.statusBarHeight;
let screenHeight = system.safeArea.bottom;
this.screenHeight = screenHeight;
//
let search = params.search || ''.trim();
this.searchContent = params.search;
},
watch: {
current(n) {
this.initCurrentData();
}
},
methods: {
//
initCurrentData() {
console.log(this.current);
var prop = this.listValue[this.current];
var dom = this.$refs[prop];
var child = dom.search; //
console.log('子关键字:' + child);
var flag = dom.flag; //
var parent = this.searchContent;
console.log('fu关键字' + parent);
if (parent != child && this.swiperCurrent == flag && this.searchContent != '') {
dom.initData();
}
},
// tab
change(index) {
this.swiperCurrent = index;
},
transition({ detail: { dx } }) {
this.$refs.tabs.setDx(dx);
},
animationfinish({ detail: { current } }) {
this.$refs.tabs.setFinishCurrent(current);
this.swiperCurrent = current;
this.current = current;
},
reachBottom() {
console.log('触底' + this.current);
var prop = this.listValue[this.current];
var dom = this.$refs[prop];
dom.getData();
},
doSearch() {
let me = this;
let searchContent = this.searchContent;
if (isStrEmpty(searchContent)) {
uni.showToast({
title: '搜索为空!',
icon: 'none',
duration: 2000
});
this.searchContent = '';
return;
} else {
this.initCurrentData();
}
},
typingContent(e) {
this.searchContent = e.detail.value || ''.trim();
},
back() {
uni.navigateBack({
delta: 1
});
}
}
};
</script>
<style lang="scss">
.page {
// position: absolute;
// left: 0;
// right: 0;
// top: 0;
// bottom: 0;
background-color: #181b27;
.big-search-wrapper {
padding: 14px;
height: 50px;
display: flex;
align-items: flex-start;
flex-direction: row;
justify-content: space-between;
.header-right-search {
height: 50px;
}
.icon-search {
width: 25px;
height: 25px;
opacity: 0.8;
}
.search-box {
display: flex;
flex-direction: row;
.search-box-left {
padding: 0 10rpx;
display: flex;
flex-direction: row;
background-color: #55565e;
border-top-left-radius: 6rpx;
border-bottom-left-radius: 6rpx;
.search-image {
width: 25px;
height: 25px;
opacity: 0.8;
}
}
}
.search-input {
width: 410rpx;
height: 25px;
background-color: #55565e;
font-size: 28rpx;
color: #ffffff;
border-top-right-radius: 6rpx;
border-bottom-right-radius: 6rpx;
}
.search-box-right {
padding: 0 16rpx;
display: flex;
flex-direction: row;
background-color: #55565e;
border-top-right-radius: 6rpx;
border-bottom-right-radius: 6rpx;
.scan-image {
width: 25px;
height: 25px;
opacity: 0.8;
}
}
.sbtn {
height: 25px;
line-height: 25px;
color: #ffffff;
font-size: 28rpx;
}
}
// ios
::-webkit-scrollbar {
display: none;
width: 0 !important;
height: 0 !important;
-webkit-appearance: none;
background: transparent;
}
//
.mainCont {
background: #f5f5f5;
}
.u-tabs-box {
height: 40px;
}
.page-box {
width: 710rpx;
margin: 0 auto;
}
.wrap {
display: flex;
flex-direction: column;
width: 100%;
}
.swiper-box {
flex: 1;
}
.swiper-item {
height: 100%;
}
// footer config
// .page-footer {
// width: 750rpx;
// height: 128rpx;
// background: #ffffff;
// box-shadow: 0rpx -4rpx 16rpx rgba(219, 208, 208, 0.61);
// opacity: 1;
// border-radius: 0rpx;
// .contract-button {
// width: 686rpx;
// height: 80rpx;
// margin-top: 24rpx;
// margin-left: 32rpx;
// font-size: 34rpx;
// font-family: PingFang SC;
// font-weight: 500;
// text-align: center;
// line-height: 80rpx;
// color: #ffffff;
// letter-spacing: 5rpx;
// background: linear-gradient(137deg, #ff3229 0%, #ff7b59 100%);
// opacity: 1;
// border-radius: 40rpx;
// }
// }
}
</style>

243
pages/search/searchVd.vue Normal file
View File

@ -0,0 +1,243 @@
<template>
<view class="wrap">
<view v-if="flowList.length">
<u-waterfall
v-model="flowList"
ref="uWaterfall"
idKey="vlogId"
>
<template v-slot:left="{ leftList }">
<view
class="demo-warter"
v-for="(item, index) in leftList"
:key="index"
@click="goToVlog(item.vlogId)"
>
<u-lazy-load
border-radius="10"
:image="item.cover || item.firstFrameImg"
:index="index"
></u-lazy-load>
<view class="content">
{{ item.content }}
</view>
<view class="flxbox">
<view class="bottom-info">
<u-image
width="30rpx"
height="30rpx"
:src="item.vlogerFace"
loading-icon="/static/missing-face.png"
error-icon="/static/missing-face.png"
shape="circle"
style="display: flex; align-items: center"
></u-image>
<view class="showOne ml">{{ item.vlogerName }}</view>
</view>
<view class="bottom-info">
<image
style="width: 20rpx; height: 20rpx"
src="/static/images/icon-comment-unlike.png"
></image>
<view class="ml">{{ getGraceNumber(item.likeCounts) }}</view>
</view>
</view>
</view>
</template>
<template v-slot:right="{ rightList }">
<view
class="demo-warter"
v-for="(item, index) in rightList"
:key="index"
@click="goToVlog(item.vlogId)"
>
<u-lazy-load
border-radius="10"
:image="item.cover || item.firstFrameImg"
:index="index"
></u-lazy-load>
<view class="content">
{{ item.content }}
</view>
<view class="flxbox">
<view class="bottom-info">
<u-image
width="30rpx"
height="30rpx"
loading-icon="/static/missing-face.png"
error-icon="/static/missing-face.png"
:src="item.vlogerFace"
shape="circle"
style="display: flex; align-items: center"
></u-image>
<view class="showOne ml">{{ item.vlogerName }}</view>
</view>
<view class="bottom-info">
<image
style="width: 20rpx; height: 20rpx"
src="/static/images/icon-comment-unlike.png"
></image>
<view class="ml">{{ getGraceNumber(item.likeCounts) }}</view>
</view>
</view>
</view>
</template>
</u-waterfall>
<u-loadmore
style="padding: 10px 0"
bg-color="#f8f8f8"
:status="loadStatus"
@loadmore="getData"
></u-loadmore>
</view>
<u-empty
class="mt20"
v-else
text="暂无数据"
mode="data"
></u-empty>
</view>
</template>
<script>
import { vlogList } from '@/api/vlog';
import { graceNumber, isStrEmpty } from '@/utils/tools.js';
import storage from '@/utils/storage.js'; //
export default {
props: {
keywords: {
default: ''
}
},
data() {
return {
flag: 0, // tabs
loadStatus: 'loadmore',
flowList: [],
page: 0,
totalPage: 0,
search: ''
};
},
created() {
this.initData();
},
methods: {
initData() {
this.clear();
this.search = this.keywords;
this.page = 0;
this.totalPage = 0;
this.flowList = [];
this.loadStatus = 'loadmore';
this.getData();
},
async getData() {
try {
if (this.loadStatus !== 'loadmore') return;
let me = this;
me.loadStatus = 'loading';
let page = me.page + 1;
let keywords = me.search;
let userInfo = storage.getVlogUserInfo();
let userId = '';
if (userInfo != null) {
userId = userInfo.id;
}
var result = await vlogList(page, 10, userId, '', keywords);
console.log(result);
if (result.data.status == 200) {
let flowList = result.data.data.rows;
let totalPage = result.data.data.total;
me.flowList = me.flowList.concat(flowList);
me.page = page;
me.totalPage = totalPage;
if (page >= totalPage) {
me.loadStatus = 'nomore';
} else {
me.loadStatus = 'loadmore';
}
}
} catch (e) {
console.log(e);
this.loadStatus = 'nomore';
}
},
// 1000100001.3k/6.8w
getGraceNumber(num) {
return graceNumber(num);
},
goToVlog(vlogId) {
uni.navigateTo({
url: '/pages/me/vlog?vlogId=' + vlogId
});
},
remove(id) {
this.$refs.uWaterfall.remove(id);
},
clear() {
var dom = this.$refs.uWaterfall;
if (dom) {
clear();
}
}
}
};
</script>
<style lang="scss" scoped>
.mt20 {
margin-top: 20% !important;
}
.demo-warter {
border-radius: 8px;
margin: 5px;
background-color: #ffffff;
padding: 8px;
position: relative;
max-width: 329rpx;
width: 100%;
}
.u-close {
position: absolute;
top: 32rpx;
right: 32rpx;
}
.showOne {
width: 200rpx;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.content {
// width: 329rpx;
font-size: 28rpx;
color: #000;
margin-top: 5px;
display: -webkit-box;
-webkit-line-clamp: 3; /* 最多显示三行 */
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
}
.flxbox {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 5px;
}
.bottom-info {
display: flex;
align-items: center;
font-size: 22rpx;
color: $u-tips-color;
}
.ml {
display: block;
margin-left: 5px;
}
</style>

818
pages/search/shop.vue Normal file
View File

@ -0,0 +1,818 @@
<template>
<view class="wrap">
<view v-if="flowList.length">
<view class="goods-list">
<view
v-for="(item, index) in flowList"
:key="index"
class="goods-item"
>
<view
class="image-wrapper"
@click="navigateToDetailPage(item)"
>
<image
:src="removeOssStyle(item.content.thumbnail)"
mode="aspectFill"
></image>
</view>
<view class="goods-detail">
<div
class="title"
@click="navigateToDetailPage(item)"
>
{{ item.content.goodsName }}
</div>
<view
class="price-box"
@click="navigateToDetailPage(item)"
>
<div
class="price"
v-if="item.content.price != undefined"
>
¥
<span>{{ formatPrice(item.content.price)[0] }}</span>
.{{ formatPrice(item.content.price)[1] }}
</div>
</view>
<div
class="promotion"
@click="navigateToDetailPage(item)"
>
<div
v-for="(promotionItem, promotionIndex) in getPromotion(item)"
:key="promotionIndex"
>
<span v-if="promotionItem.indexOf('COUPON') != -1"></span>
<span v-if="promotionItem.indexOf('FULL_DISCOUNT') != -1">满减</span>
<span v-if="promotionItem.indexOf('SECKILL') != -1">秒杀</span>
</div>
</div>
<div
class="count-config"
@click="navigateToDetailPage(item)"
>
<span>已售 {{ item.content.buyCount || '0' }}</span>
<span>{{ item.content.commentNum || '0' }}条评论</span>
</div>
<div
class="store-seller-name"
@click="navigateToStoreDetailPage(item)"
>
<div class="text-hidden">
<u-tag
style="margin-right: 10rpx"
size="mini"
mode="dark"
v-if="item.selfOperated"
text="自营"
type="error"
/>
<span>{{ item.content.storeName || '暂无' }}</span>
</div>
<span>
<u-icon name="arrow-right"></u-icon>
</span>
</div>
</view>
</view>
</view>
<u-loadmore
style="padding: 10px 0"
bg-color="#f8f8f8"
:status="loadStatus"
@loadmore="getData"
></u-loadmore>
</view>
<u-empty
v-else
class="mt20"
style="margin-top: 20%"
text="暂无数据"
mode="data"
></u-empty>
</view>
</template>
<script>
import { getGoodsList } from '@/api/goods.js';
export default {
props: {
keywords: {
default: ''
}
},
data() {
return {
flag: 1, // tabs
loadStatus: 'loadmore',
flowList: [],
page: 0,
totalPage: 0,
search: ''
//
// params: {
// pageNumber: 1,
// pageSize: 10,
// keyword: "",
// }
};
},
created() {
this.initData();
},
methods: {
async getData() {
if (this.loadStatus != 'loadmore') return;
console.log('加载商品数据');
this.page += 1;
var params = {
pageNumber: this.page,
pageSize: 10,
keyword: this.search
};
var res = await getGoodsList(params);
console.log(res);
if (res.data.result.content.length < 10) {
this.loadStatus = 'nomore';
} else {
this.loadStatus = 'loadmore';
}
this.flowList.push(...res.data.result.content);
},
initData() {
this.search = this.keywords;
this.page = 0;
this.totalPage = 0;
this.flowList = [];
this.loadStatus = 'loadmore';
console.log('初始化商品数据');
this.getData();
},
formatPrice(val) {
if (typeof val == 'undefined') {
return val;
}
return val.toFixed(2).split('.');
},
//
getPromotion(item) {
if (item.promotionMap) {
let array = [];
Object.keys(item.promotionMap).forEach((child) => {
if (!array.includes(child.split('-')[0])) {
array.push(child.split('-')[0]);
}
});
return array;
}
},
removeOssStyle(url) {
return url.split('?')[0];
},
//
navigateToDetailPage(item) {
uni.navigateTo({
url: `/pages/product/goods?id=${item.content.id}&goodsId=${item.content.goodsId}`
});
},
//
navigateToStoreDetailPage(item) {
uni.navigateTo({
url: `/pages/product/shopPage?id=${item.content.storeId}`
});
}
}
};
</script>
<style lang="scss" scoped>
.mt20 {
margin-top: 20% !important;
}
.sort-active {
border: 1px solid $light-color;
color: $light-color;
background: $price-light-color !important;
}
.oldKeyList {
display: flex;
flex-wrap: wrap;
padding: 20rpx 3%;
> .oldKeyItem {
padding: 4rpx 24rpx;
background: #f0f2f5;
margin-right: 20rpx;
max-width: 200rpx;
border-radius: 100px;
font-size: 24rpx;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
margin-bottom: 20rpx;
}
}
.promotion {
margin-top: 4rpx;
display: flex;
div {
span {
font-size: 24rpx;
color: $light-color;
margin-right: 10rpx;
padding: 0 4rpx;
border-radius: 2rpx;
}
}
}
.status_bar {
height: var(--status-bar-height);
background: #fff !important;
width: 100%;
}
page {
background-color: #fff !important;
}
.sort-box {
width: 100%;
height: 100%;
position: relative;
background: #f7f7f7;
.sort-list {
margin: 20rpx 0;
background: #fff;
border-radius: 20rpx;
padding: 30rpx;
> .sort-item {
> .sort-title {
margin: 20rpx 0;
font-size: 26rpx;
font-weight: bold;
}
}
}
}
.null-view {
height: 140rpx;
}
.sort-btn {
display: flex;
position: fixed;
bottom: 0;
border-top: 1px solid #f7f7f7;
height: 100rpx;
left: 0;
width: 100%;
background: #fff;
align-items: center;
> view {
width: 50%;
height: 80rpx;
line-height: 80rpx;
text-align: center;
margin: 0 20rpx;
border-radius: 1000px;
}
> .sort-btn-repick {
border: 1px solid #ededed;
}
> .sort-btn-confim {
color: #fff;
background-image: linear-gradient(90deg, #ff6b35, #ff9f28, #ffcc03);
}
}
.uinput {
width: 50% !important;
> .sort-radius {
height: 70rpx;
line-height: 70rpx;
font-size: 24rpx;
}
/deep/ .uni-input-input {
font-size: 24rpx;
}
}
.sort-radius {
margin: 0 12rpx;
background: #f7f7f7;
height: 65rpx;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
border-radius: 1000px;
font-size: 24rpx;
}
.flex {
flex-wrap: wrap;
align-items: center;
> .sort-brand-item {
width: 33%;
text-align: center;
margin-bottom: 20rpx;
}
}
.scoll-page {
overflow: auto;
}
.content {
background-color: $bg-color;
height: 100vh;
overflow: hidden;
}
.index-nav-arrow:last-child {
margin-top: -22rpx;
}
.line1-store-name {
font-size: 24rpx;
color: #999;
}
.to-store {
font-size: 24rpx;
color: #333;
margin-left: 10rpx;
}
.img {
width: 26rpx;
height: 26rpx;
}
.goods-row {
background: #fff;
padding: 16rpx;
> .goods-col {
display: flex;
> .goods-img {
flex: 4;
}
> .goods-detail {
flex: 7;
}
}
}
.add1 {
background: #fff;
padding: 30rpx 0;
}
.oldKeyRow {
background: #fff;
padding: 34rpx 3%;
border-bottom: 1px solid #eeeeee;
}
.clamp3 {
margin-bottom: 10rpx;
font-size: 28rpx;
color: #333333;
font-weight: 400;
display: -webkit-box;
height: 80rpx;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2 !important;
overflow: hidden;
> span {
line-height: 1.5;
}
}
view {
display: block;
}
.store-seller-name {
color: #666;
overflow: hidden;
> div {
float: left;
}
> span {
float: right;
}
}
.count-config {
padding: 10rpx 0;
color: #666;
display: flex;
font-size: 24rpx;
justify-content: space-between;
}
.search-box {
z-index: 99;
width: 100%;
background: $light-color;
padding: 20rpx 2.5%;
display: flex;
justify-content: space-between;
position: sticky;
top: 0;
}
.search-box .mSearch-input-box {
width: 100%;
}
.search-box .input-box {
width: 85%;
flex-shrink: 1;
display: flex;
justify-content: center;
align-items: center;
}
.search-box .search-btn {
width: 15%;
margin: 0 0 0 2%;
display: flex;
justify-content: center;
align-items: center;
flex-shrink: 0;
font-size: 28rpx;
color: #fff;
background: linear-gradient(to right, #ff9801, #ff570a);
border-radius: 60rpx;
}
.search-box .input-box > input {
width: 100%;
height: 60rpx;
font-size: 32rpx;
border: 0;
border-radius: 60rpx;
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
padding: 0 3%;
margin: 0;
background-color: #ffffff;
}
.uni-scroll-view-content {
background: #ededed !important;
}
.placeholder-class {
color: #9e9e9e;
}
.search-keyword {
width: 100%;
background-color: #ededed;
}
.keyword-list-box {
height: calc(100vh - 110rpx);
padding-top: 10rpx;
border-radius: 20rpx 20rpx 0 0;
background-color: #fff;
}
.keyword-entry-tap {
background-color: #eee;
}
.keyword-entry {
width: 94%;
height: 80rpx;
margin: 0 3%;
font-size: 30rpx;
color: #333;
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: solid 1rpx #e7e7e7;
}
.keyword-entry image {
width: 60rpx;
height: 60rpx;
}
.keyword-entry .keyword-text,
.keyword-entry .keyword-img {
height: 80rpx;
display: flex;
align-items: center;
}
.keyword-entry .keyword-text {
width: 90%;
}
.keyword-entry .keyword-img {
width: 10%;
justify-content: center;
}
.keyword-box {
background-color: #fff;
}
.keyword-box .keyword-block {
padding: 10rpx 0;
}
.keyword-box .keyword-block .keyword-list-header {
width: 100%;
padding: 20rpx 3%;
font-size: 27rpx;
color: #333;
display: flex;
justify-content: space-between;
}
.keyword-box .keyword-block .keyword-list-header image {
width: 40rpx;
height: 40rpx;
}
.keyword-box .keyword-block .keyword > view {
width: 50%;
line-height: 1.75;
overflow: hidden;
padding: 0 3% 0 3% !important;
}
.u-tips {
font-size: 30rpx;
font-weight: 700;
color: #333;
}
.keyword {
display: flex;
flex-wrap: wrap;
}
.keyword-box {
position: relative;
}
.clear {
color: #666666;
position: fixed;
bottom: 0;
width: 100%;
text-align: center;
height: 100rpx;
line-height: 100rpx;
font-size: 28rpx;
background: #f7f7f7;
}
.keyword-box .keyword-block .hide-hot-tis {
display: flex;
justify-content: center;
font-size: 28rpx;
color: #6b6b6b;
}
.navbar {
display: flex;
width: 100%;
height: 80rpx;
background: #fff;
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.06);
z-index: 10;
// position: fixed;
// left: 0;
// top: var(--status-bar-height);
.nav-item {
flex: 1;
display: flex;
justify-content: center;
align-items: center;
height: 100%;
font-size: 30rpx;
color: $font-color-dark;
position: relative;
}
.current {
color: $light-color;
position: relative;
&:after {
content: '';
position: absolute;
left: 50%;
bottom: 0;
transform: translateX(-50%);
width: 40rpx;
height: 0;
border-bottom: 4rpx solid $light-color;
}
}
.p-box {
display: flex;
flex-direction: column;
.yticon {
display: flex;
align-items: center;
justify-content: center;
width: 30rpx;
height: 14rpx;
line-height: 1;
margin-left: 4rpx;
font-size: 26rpx;
color: #888;
}
.xia {
transform: scaleY(-1);
}
}
.cate-item {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
width: 80rpx;
position: relative;
font-size: 44rpx;
&:after {
content: '';
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
border-left: 1px solid #ddd;
width: 0;
height: 36rpx;
}
}
}
/* 分类 */
.cate-mask {
position: fixed;
left: 0;
top: var(--window-top);
bottom: 0;
width: 100%;
background: rgba(0, 0, 0, 0);
z-index: 95;
transition: 0.3s;
.cate-content {
width: 630rpx;
height: 100%;
background: #fff;
float: right;
transform: translateX(100%);
transition: 0.3s;
}
&.none {
display: none;
}
&.show {
background: rgba(0, 0, 0, 0.4);
.cate-content {
transform: translateX(0);
}
}
}
.cate-list {
display: flex;
flex-direction: column;
height: 100%;
.cate-item {
display: flex;
align-items: center;
height: 90rpx;
padding-left: 30rpx;
font-size: 28rpx;
color: #555;
position: relative;
}
.two {
height: 64rpx;
color: #303133;
font-size: 30rpx;
background: #f8f8f8;
}
}
.price-box {
margin-top: 10rpx;
display: flex;
align-items: center;
justify-content: space-between;
padding-right: 10rpx;
font-size: 24rpx;
color: $font-color-light;
}
.price {
font-size: 26rpx;
line-height: 1;
color: $main-color;
font-weight: bold;
/deep/ span:nth-of-type(1) {
font-size: 38rpx;
}
}
/* 商品列表 */
.goods-list {
display: flex;
flex-wrap: wrap;
margin: 10rpx 20rpx 0;
// background: #fff;
width: 100%;
.goods-item {
background-color: #ffffff;
display: flex;
border-radius: 16rpx;
flex-direction: column;
width: calc(50% - 30rpx);
margin-bottom: 20rpx;
padding-bottom: 20rpx;
&:nth-child(2n + 1) {
margin-right: 20rpx;
}
.goods-detail {
margin: 0 20rpx;
}
}
.image-wrapper {
width: 100%;
height: 330rpx;
border-radius: 16rpx 16rpx 0 0;
overflow: hidden;
padding: 0;
image {
width: 100%;
height: 100%;
opacity: 1;
border-radius: 16rpx 16rpx 0 0;
}
}
.title {
font-size: $font-base;
color: $font-color-dark;
line-height: 1.5;
height: 84rpx;
padding: 10rpx 0 0;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
}
.count-config,
.store-seller-name {
font-size: $font-sm;
}
.text-hidden {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
.status_bar {
height: var(--status-bar-height);
width: 100%;
background: $light-color;
}
.empty {
padding-top: 300rpx;
color: #999999;
text-align: center;
/deep/ .u-image {
width: 346rpx;
height: 304rpx;
}
}
</style>

288
pages/search/tuangou.vue Normal file
View File

@ -0,0 +1,288 @@
<template>
<view class="wrapper">
<!-- 商品栏 -->
<div class="swiper">
<div v-if="groupBuy.length != 0">
<view
class="view-item"
v-for="(groupItem, groupIndex) in groupBuy"
:key="groupIndex"
>
<view class="view-left">
<u-image
border-radius="10"
shape="square"
:src="groupItem.thumbnail"
width="186rpx"
height="186rpx"
>
<view
slot="error"
style="font-size: 24rpx"
>
加载失败
</view>
</u-image>
</view>
<view class="view-content">
<view class="view-content-name">
{{ groupItem.goodsName }}
</view>
<view class="view-content-bottom">
<view>
<view class="view-content-price">
<!-- {{groupItem.sales_price | unitPrice }} <span v-if="groupItem.point">+{{groupItem.point}}积分</span> -->
{{ groupItem.price | unitPrice }}
</view>
<view class="view-content-original_price">{{ groupItem.originalPrice | unitPrice }}</view>
</view>
<view>
<view
class="btn-group"
@click="toHref(groupItem)"
>
去拼团
</view>
<view class="buy-content">已售{{ groupItem.num || 0 }}</view>
</view>
</view>
</view>
</view>
<u-loadmore
style="padding: 10px 0"
bg-color="#f8f8f8"
:status="status"
/>
</div>
<u-empty
v-else
style="margin-top: 20%"
text="暂无数据"
mode="data"
></u-empty>
</div>
</view>
</template>
<script>
import * as API_Promotions from '@/api/promotions';
import * as API_Goods from '@/api/goods';
export default {
props: {
keywords: {
default: ''
}
},
data() {
return {
flag: 3, //tabs
status: 'loadmore',
is_empty: false,
search: false,
title: '拼团活动',
background: {
backgroundColor: '#fff'
},
empty: false,
params: {
pageNumber: 0,
pageSize: 10,
categoryPath: '',
goodsName: ''
},
groupBuy: [],
search: ''
};
},
created() {
this.initData();
},
methods: {
initData() {
this.search = this.keywords;
this.status = 'loadmore';
this.params = {
pageNumber: 0,
pageSize: 10,
categoryPath: '',
goodsName: this.search
};
this.groupBuy = [];
console.log('初始化团购数据');
this.getData();
},
toHref(goods) {
uni.navigateTo({
url: `/pages/product/goods?id=${goods.skuId}&goodsId=${goods.goodsId}`
});
},
//
getData() {
if (this.status !== 'loadmore') return;
this.params.pageNumber++;
this.status = 'loading';
this.params.goodsName = this.search;
const params = JSON.parse(JSON.stringify(this.params));
if (params.category_id === 0) delete params.category_id;
API_Promotions.getAssembleList(params)
.then((response) => {
const data = response.data.result.records;
if (!data || !data.length) {
this.is_empty = true;
this.status = 'nomore';
} else {
if (data.length < this.params.pageSize) {
this.status = 'nomore';
} else {
this.status = 'loadmore';
}
this.is_empty = false;
this.groupBuy.push(...(data || []));
}
})
.catch(() => {});
}
}
};
</script>
<style lang="scss" scoped>
.view-item {
background: #fff;
border-radius: 0.4em;
margin: 20rpx 30rpx;
padding: 20rpx 0;
}
.nodata {
text-align: center;
margin: 40rpx 0 20rpx 0;
}
.container-wrap {
width: 100%;
}
.white_class {
color: #fff;
font-size: 28rpx;
}
.popupTips {
font-size: 22rpx;
font-family: PingFang SC, PingFang SC-Regular;
font-weight: 400;
text-align: left;
color: #999999;
margin: 0 20rpx;
/deep/ view {
line-height: 1.75;
}
}
.search {
margin: 30rpx 20rpx !important;
}
.view-left,
.view-content,
.view-right,
.view-item {
display: flex;
}
.wrapper {
width: 100%;
overflow: hidden;
}
.view-left {
width: 226rpx;
height: 100%;
overflow: hidden;
display: flex;
justify-content: center;
}
.view-content {
width: calc((100% - 240rpx));
padding-left: 20rpx;
flex-direction: column;
justify-content: center;
text-align: center;
}
.buy-content {
font-size: 22rpx;
font-family: PingFang SC, PingFang SC-Regular;
font-weight: 400;
margin-top: 15rpx;
text-align: center;
color: #999999;
}
.view-content-bottom {
width: 100%;
display: flex;
justify-content: space-between;
align-items: center;
}
.group-wrapper {
padding: 16rpx 32rpx;
}
.view-content-name {
font-family: PingFang SC, PingFang SC-Regular;
font-weight: 400;
text-align: left;
color: #333333;
font-size: 28rpx;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
}
.view-content-price {
margin: 10rpx 0;
letter-spacing: 0px;
overflow: hidden;
font-size: 28rpx;
font-family: PingFang SC, PingFang SC-Regular;
font-weight: 400;
text-align: left;
color: #ff5a10;
text-overflow: ellipsis;
white-space: nowrap;
}
.view-content-original_price {
font-size: 22rpx;
font-family: PingFang SC, PingFang SC-Regular;
font-weight: 400;
text-decoration: line-through;
text-align: left;
color: #999999;
}
.btn-group {
background: $aider-light-color;
border-radius: 10rpx;
font-size: 24rpx;
font-family: PingFang SC, PingFang SC-Regular;
font-weight: 400;
color: #fff;
text-align: center;
padding: 6rpx 16rpx;
}
/deep/ .empty {
position: relative;
padding-top: 20%;
> .empty-content {
position: relative;
padding-top: 20%;
}
}
</style>

220
pages/search/user.vue Normal file
View File

@ -0,0 +1,220 @@
<template>
<view class="wrap">
<view v-if="flowList.length">
<view
class="flex-box"
v-for="i in flowList"
v-if="i.id != id"
:key="i.id"
>
<u-image
:src="i.face"
class="flxleft"
width="120"
height="120"
shape="circle"
loading-icon="/static/missing-face.png"
error-icon="/static/missing-face.png"
style="display: flex; align-items: center"
></u-image>
<view class="flxcenter">
<view class="nkname">{{ i.nickname }}</view>
<!-- <view class="fans">粉丝125.2</view> -->
</view>
<view class="flxright">
<u-button
type="error"
@click="follow(i)"
size="mini"
v-if="i.followStatus == '未关注'"
>
关注
</u-button>
<u-button
v-else
type="plain"
@click="cancelFollow(i)"
size="mini"
>
{{ i.followStatus }}
</u-button>
</view>
</view>
<u-loadmore
style="padding: 10px 0"
bg-color="#f8f8f8"
:status="loadStatus"
@loadmore="getData"
></u-loadmore>
</view>
<u-empty
v-else
style="margin-top: 20%"
text="暂无数据"
mode="data"
></u-empty>
</view>
</template>
<script>
import storage from '@/utils/storage.js'; //
import { vlogSearchUser, vlogFansFollow, vlogFansCancel } from '@/api/vlog';
export default {
props: {
keywords: {
default: ''
}
},
data() {
return {
flag: 2, // tabs
loadStatus: 'loadmore',
flowList: [],
page: 0,
totalPage: 0,
search: '',
id: ''
};
},
created() {
this.initData();
},
methods: {
async getData() {
if (this.loadStatus !== 'loadmore') return;
var info = storage.getVlogUserInfo();
var id = '';
if (info != null) {
id = info.id;
}
this.id = id;
this.loadStatus = 'loading';
this.page += 1;
console.log('加载用户数据');
var params = {
id: id,
nickname: this.search,
page: this.page,
pageSize: 10
};
console.log(params);
var result = await vlogSearchUser(params);
console.log(result);
if (result.data.status == 200) {
var data = result.data.data || [];
if (!data.length) {
this.loadStatus = 'nomore';
} else {
if (data.length < 10) {
this.loadStatus = 'nomore';
} else {
this.loadStatus = 'loadmore';
}
data.forEach((i) => {
i.doIflow = false;
this.flowList.push(i);
});
// this.flowList.push(...data);
}
}
},
initData() {
this.search = this.keywords;
this.page = 0;
this.totalPage = 0;
this.flowList = [];
this.loadStatus = 'loadmore';
console.log('初始化用户数据');
this.getData();
},
async follow(item) {
let userInfo = storage.getVlogUserInfo();
if (userInfo == null) {
uni.navigateTo({
url: '/pages/passport/login',
animationType: 'slide-in-bottom'
});
return;
}
let data = {
myId: userInfo.id,
vlogerId: item.id
};
var result = await vlogFansFollow(data);
console.log(result);
if (result.data.status == 200) {
item.followStatus = '已关注';
} else {
uni.showToast({
title: result.data.msg,
icon: 'none',
duration: 3000
});
}
},
async cancelFollow(item) {
let userInfo = storage.getVlogUserInfo();
if (userInfo == null) {
uni.navigateTo({
url: '/pages/passport/login',
animationType: 'slide-in-bottom'
});
return;
}
let data = {
myId: userInfo.id,
vlogerId: item.id
};
var result = await vlogFansCancel(data);
console.log(result);
if (result.data.status == 200) {
item.doIflow = false;
} else {
uni.showToast({
title: result.data.msg,
icon: 'none',
duration: 3000
});
}
}
}
};
</script>
<style lang="scss" scoped>
.flex-box {
display: flex;
align-items: center;
margin-bottom: 10rpx;
padding: 10rpx 20rpx;
}
.flxleft {
width: 120rpx;
height: 120rpx;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin-right: 5px;
}
.flxcenter {
flex: 1;
}
.nkname {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 1;
overflow: hidden;
font-weight: 500;
font-size: 26rpx;
}
.fans {
font-size: 24rpx;
color: #999;
}
.flxright {
margin-left: 10px;
}
</style>

View File

@ -117,6 +117,11 @@ import SelectFriendqlioa from '@/TUIKit/components/TUIGroup/index.vue';
import TUICore, { ExtensionInfo, TUIConstants } from '@tencentcloud/tui-core';
import storage from '@/utils/storage.js';
import { getUserimInfo, getMember, getMemberstate, getMemberdelete } from '@/api/members';
// push
import { TUIConversationService } from '@tencentcloud/chat-uikit-engine';
import * as Push from '@/uni_modules/TencentCloud-Push';
TUIChatKit.init();
let vueVersion = 2;
// vueVersion = 3;
@ -161,8 +166,53 @@ export default {
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
useUploadPlugin: true,
framework: `vue${vueVersion}`
}).then(() => {
Push.setRegistrationID(par.userID, () => {
console.log('设置id设置id设置id设置id设置id设置id设置id设置id设置id设置id', par.userID);
Push.registerPush(
par.sdkAppId,
'vkFpe55aYqfV7Sk5uGaoxhEstJ3tcI9dquk7JwG1GloDSLD2HeMWeQweWWXgNlhC',
(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) => {
console.log('notification clicked', res);
//
try {
const data = JSON.parse(res.data);
const conv_type = data?.entity?.chatType === 1 ? 'C2C' : 'GROUP';
// conversationID
const conversationID = `${conv_type}${data.entity.sender}`;
//
TUIConversationService.switchConversation(conversationID);
const chatPath = '/TUIKit/components/TUIChat/index';
uni.navigateTo({ url: chatPath });
} catch (error) {
console.log('error', error);
}
});
// 线
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);
});
});
} else {
// 200

View File

@ -229,7 +229,7 @@ export default {
}, 300);
},
onTabItemTap: function (e) {
console.log(e);
// console.log(e);
// let tabIndex = e.index;
// this.playStatus = tabIndex === 0 ? true : false;
// 切换视频要做暂停或播放的判断
@ -343,7 +343,8 @@ export default {
}
});
},
fail: () => {
fail: (err) => {
console.log(err);
uni.showToast({
icon: 'none',
title: '获取位置信息失败'
@ -373,15 +374,7 @@ export default {
let index = e.target.dataset.current || e.currentTarget.dataset.current;
this.isTap = true;
var currentSize = this.tabListSize[index];
// if (obj.playerList.length === 0) {
// this.isDraw_gz = false;
// this.isDraw_lo = false;
// this.isDraw_tj = false;
// } else {
// this.isDraw_gz = preloadIndex == 1 ? true : false;
// this.isDraw_lo = preloadIndex == 0 ? true : false;
// this.isDraw_tj = preloadIndex == 2 ? true : false;
// }
this.updateIndicator(currentSize.left, currentSize.width);
this._touchTabIndex = index;
this.switchTab(index);
@ -433,7 +426,6 @@ export default {
this.isDraw_gz = false;
this.isDraw_lo = false;
this.isDraw_tj = false;
console.log(this.playStatus);
// if (this.playStatus == true) {
// this.playStatus = this._lastTabIndex == 2 ? false : true;
// }

View File

@ -0,0 +1,29 @@
## 1.2.02025-03-31
- 适配出海手机支持 FCM 推送。
## 1.1.02024-12-11
- 大幅减小插件包体积,优化产品体验。
- 兼容 HBuilderX 4.36 的 Breaking changes。如果您需要 vivo/荣耀 的厂商推送,请参考 [文档](https://cloud.tencent.com/document/product/269/103522),正确配置 `manifestPlaceholders.json``mcn-services.json`
## 1.0.02024-11-29
- 优化和 [TencentCloud-TUICallKit 插件](https://ext.dcloud.net.cn/plugin?id=9035) 融合时的产品体验。
- 新增点击通知栏事件 NOTIFICATION_CLICKED支持获取推送扩展信息。
- 在线通道支持自定义铃音功能。
## 0.5.12024-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.02024-10-17
- 支持与 [TencentCloud-TUICallKit 插件](https://ext.dcloud.net.cn/plugin?id=9035) 融合打包。
## 0.3.02024-10-12
- 新增接口 addPushListener/removePushListener支持获取在线推送消息支持推送消息撤回通知。
## 0.2.02024-09-18
- 支持 FCM
- 支持 hihonor
## 0.1.02024-09-10
- 使用 uts 开发基于腾讯云推送服务Push支持 iOS 和 Android 推送,同时适配各大厂商推送。

View File

View 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"
}
}
}
}
}

View 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) ,单击创建应用,填写应用名称,选择数据中心,单击确定,完成应用创建。
![](https://qcloudimg.tencent-cloud.cn/image/document/e2761226f7d2bbdfb0a301192316c7d3.png)
### 步骤2开通推送服务 Push
进入 [推送服务 Push](https://console.cloud.tencent.com/im/push-plugin-push-identifier),单击立即购买或免费试用 。每个应用可免费试用一次有效期7天
![](https://qcloudimg.tencent-cloud.cn/image/document/a7e1f3847c91a807ec9be3a586f1290f.png)
### 步骤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 - 接入设置页面 获取的应用的信息。如图所示:
![](https://sdk-web-1252463788.cos.ap-hongkong.myqcloud.com/im/assets/push/push.png)
```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://qcloudimg.tencent-cloud.cn/image/document/742b7c05364e8ff9a16d5d5601aa038b.png)
自定义调试基座打好后,安装到手机运行。
[登录](https://console.cloud.tencent.com/im/push-plugin-push-check) 控制台,使用测试工具进行在线推送测试。
![](https://sdk-web-1252463788.cos.ap-hongkong.myqcloud.com/im/assets/push/test-online-push.png)
## 厂商推送配置
> - 请注意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|是|接口调用成功的回调函数|

View 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) ,单击创建应用,填写应用名称,选择数据中心,单击确定,完成应用创建。
![](https://qcloudimg.tencent-cloud.cn/image/document/e2761226f7d2bbdfb0a301192316c7d3.png)
### 步骤2开通推送服务 Push
进入 [推送服务 Push](https://console.cloud.tencent.com/im/push-plugin-push-identifier),单击立即购买或免费试用 。每个应用可免费试用一次有效期7天
![](https://qcloudimg.tencent-cloud.cn/image/document/a7e1f3847c91a807ec9be3a586f1290f.png)
### 步骤3将 [uni-app 腾讯云推送服务Push](https://ext.dcloud.net.cn/plugin?id=20169)插件导入 HbuilderX 中的工程。如图所示:
![](https://qcloudimg.tencent-cloud.cn/image/document/ab8061fea2bf6659f571c2c11aa0d8f4.png)
![](https://qcloudimg.tencent-cloud.cn/image/document/13a3e33564e6ab79d3e609b36e8ba0d5.png)
![](https://qcloudimg.tencent-cloud.cn/image/document/3c7060f9db637c826009926c5f34a1b8.png)
### 步骤4在 App.vue 中引入并注册腾讯云推送服务Push
将 SDKAppID 和 appKey 替换为您在IM 控制台 - 推送服务 Push - 接入设置页面 获取的应用的信息。如图所示:
![](https://sdk-web-1252463788.cos.ap-hongkong.myqcloud.com/im/assets/push/push.png)
```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://qcloudimg.tencent-cloud.cn/image/document/742b7c05364e8ff9a16d5d5601aa038b.png)
自定义调试基座打好后,安装到手机运行。
[登录](https://console.cloud.tencent.com/im/push-plugin-push-check) 控制台,使用测试工具进行在线推送测试。
![](https://sdk-web-1252463788.cos.ap-hongkong.myqcloud.com/im/assets/push/test-online-push.png)
## 厂商推送配置
> - 请注意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|是|接口调用成功的回调函数|

View File

@ -0,0 +1,29 @@
{
"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",
"com.tencent.timpush:honor:8.5.6864",
"com.tencent.timpush:vivo: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"
]
}
}

View 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);
}
}

View File

@ -0,0 +1,5 @@
export type PushCallbackOptions = {
apiName: string
success: (res?: any) => void
fail: (errCode: number, errMsg: string) => void
}

View File

@ -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);
}
}

View File

@ -0,0 +1,3 @@
export type PushListenerOptions = {
listener: (eventType: string, data: any) => void
}

View File

@ -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 });
}
}

View File

@ -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>

View 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"
}]
}

View 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);
}
}

View File

@ -0,0 +1,3 @@
export type PushListenerOptions = {
listener: (eventType: string, data: any) => void
}

View File

@ -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 });
}
}

View 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
}

View File

@ -26,13 +26,13 @@ function cleanStorage() {
storage.setRefreshToken("");
console.log("清空token");
storage.setUuid("");
storage.setUserInfo({});
// 清理vlog信息
storage.setVlogToken("")
storage.setVlogUserInfo({})
// 清除初始化数据内容
storage.setUserInfo({});
// 清理vlog信息
storage.setVlogToken("")
storage.setVlogUserInfo(null)
// 清除初始化数据内容
storage.setRefreshVlogIndex('0') //不需要刷新
// 防抖处理跳转
// #ifdef MP-WEIXIN
@ -96,23 +96,23 @@ http.interceptors.request.use(
config.header.accessToken = accessToken;
}
// 配置vlog所需参数
let vlogToken = storage.getVlogToken();
let vlogId = storage.getVlogUserInfo();
// console.log(vlogId)
// console.log(vlogToken)
if(vlogToken){
config.header.headerUserToken = vlogToken;
config.header.headerUserId = vlogId.id;
// 配置vlog所需参数
let vlogToken = storage.getVlogToken();
let vlogId = storage.getVlogUserInfo();
// console.log(vlogId)
// console.log(vlogToken)
if (vlogToken) {
config.header.headerUserToken = vlogToken;
config.header.headerUserId = vlogId.id;
}
config.header = {
...config.header,
uuid: storage.getUuid() || uuid.v1(),
};
};
// console.log(config)
return config;
},
(config) => {
(config) => {
return Promise.reject(config);
}
);
@ -124,8 +124,8 @@ let isRefreshing = false;
let requests = [];
// 必须使用异步函数,注意
http.interceptors.response.use(
async (response) => {
// console.log(isRefreshing)
async (response) => {
// console.log(isRefreshing)
// console.log(response)
/* 请求之后拦截器。可以使用async await 做异步操作 */
// token存在并且token过期
@ -194,9 +194,8 @@ http.interceptors.response.use(
duration: 1500,
});
}
}
else if (response.data.code==502){
cleanStorage();
} else if (response.data.code == 502) {
cleanStorage();
}
return response;
},

View File

@ -95,16 +95,28 @@ const theNextDayTime = () => {
};
const graceNumber = (number) => {
// if (number == 0) {
// return "0";
// } else if (number > 999 && number <= 9999) {
// return (number / 1000).toFixed(1) + "k";
// } else if (number > 9999 && number <= 99999) {
// return (number / 10000).toFixed(1) + "w";
// } else if (number > 99999) {
// return "10w+";
// }
// return number;
if (number == 0) {
return "0";
} else if (number > 999 && number <= 9999) {
return (number / 1000).toFixed(1) + "k";
} else if (number > 9999 && number <= 99999) {
return (number / 10000).toFixed(1) + "w";
} else if (number > 99999) {
return "10w+";
}
return number;
if (number < 1000) {
return number.toString();
} else if (number < 10000) {
return (number / 1000).toFixed(1).replace(/\.0$/, '') + "k";
} else if (number < 100000000) {
return (number / 10000).toFixed(1).replace(/\.0$/, '') + "w";
} else {
return (number / 100000000).toFixed(1).replace(/\.0$/, '') + "亿+";
}
}
// 时间格式化时间为: 多少分钟前、多少天前
@ -237,6 +249,6 @@ export {
getDateBeforeNow,
isStrEmpty,
getAstro,
getAnimal,
getAnimal,
dateFormat
};

View File

@ -1,35 +1,35 @@
// 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');
},
};
// module.exports = {
// /**
// * 此处为发行h5,微信小程序app中删除console
// * 如需显示console 需要注释此处重新运行
// */
// chainWebpack: (config) => {
// // 发行或运行时启用了压缩时会生效
// config.optimization.minimizer('terser').tap((args) => {
// const compress = args[0].terserOptions.compress
// // 非 App 平台移除 console 代码(包含所有 console 方法,如 log,debug,info...)
// compress.drop_console = true
// compress.pure_funcs = [
// '__f__', // App 平台 vue 移除日志代码
// // 'console.debug' // 可移除指定的 console 方法
// ]
// return args
// })
// }
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');
},
};
// module.exports = {
// /**
// * 此处为发行h5,微信小程序app中删除console
// * 如需显示console 需要注释此处重新运行
// */
// chainWebpack: (config) => {
// // 发行或运行时启用了压缩时会生效
// config.optimization.minimizer('terser').tap((args) => {
// const compress = args[0].terserOptions.compress
// // 非 App 平台移除 console 代码(包含所有 console 方法,如 log,debug,info...)
// compress.drop_console = true
// compress.pure_funcs = [
// '__f__', // App 平台 vue 移除日志代码
// // 'console.debug' // 可移除指定的 console 方法
// ]
// return args
// })
// }
// }