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

View File

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

View File

@ -1,324 +1,325 @@
<template> <template>
<div class="chat" :style="{height:fn+'px'}"> <div class="chat" :style="{height:fn+'px'}">
<div :style="{height:'100%'}" :class="['tui-chat', !isPC && 'tui-chat-h5']"> <div :style="{height:'100%'}" :class="['tui-chat', !isPC && 'tui-chat-h5']">
<div <div
v-if="!currentConversationID" v-if="!currentConversationID"
:class="['tui-chat-default', !isPC && 'tui-chat-h5-default']" :class="['tui-chat-default', !isPC && 'tui-chat-h5-default']"
> >
<slot /> <slot />
</div> </div>
<div <div
v-if="currentConversationID" v-if="currentConversationID"
:class="['tui-chat', !isPC && 'tui-chat-h5']" :class="['tui-chat', !isPC && 'tui-chat-h5']"
> >
<ChatHeader <ChatHeader
:class="[ :class="[
'tui-chat-header', 'tui-chat-header',
!isPC && 'tui-chat-H5-header', !isPC && 'tui-chat-H5-header',
isUniFrameWork && 'tui-chat-uniapp-header', isUniFrameWork && 'tui-chat-uniapp-header',
]" ]"
:isGroup="isGroup" :isGroup="isGroup"
:headerExtensionList="headerExtensionList" :headerExtensionList="headerExtensionList"
@closeChat="closeChat" @closeChat="closeChat"
@openGroupManagement="handleGroup" @openGroupManagement="handleGroup"
/> />
<Forward @toggleMultipleSelectMode="toggleMultipleSelectMode" /> <Forward @toggleMultipleSelectMode="toggleMultipleSelectMode" />
<MessageList <MessageList
ref="messageListRef" ref="messageListRef"
:class="['tui-chat-message-list', !isPC && 'tui-chat-h5-message-list']" :class="['tui-chat-message-list', !isPC && 'tui-chat-h5-message-list']"
:isGroup="isGroup" :isGroup="isGroup"
:groupID="groupID" :groupID="groupID"
:isNotInGroup="isNotInGroup" :isNotInGroup="isNotInGroup"
:isMultipleSelectMode="isMultipleSelectMode" :isMultipleSelectMode="isMultipleSelectMode"
@handleEditor="handleEditor" @handleEditor="handleEditor"
@closeInputToolBar="() => changeToolbarDisplayType('none')" @closeInputToolBar="() => changeToolbarDisplayType('none')"
@toggleMultipleSelectMode="toggleMultipleSelectMode" @toggleMultipleSelectMode="toggleMultipleSelectMode"
/> />
<div <div
v-if="isNotInGroup" v-if="isNotInGroup"
:class="{ :class="{
'tui-chat-leave-group': true, 'tui-chat-leave-group': true,
'tui-chat-leave-group-mobile': isMobile, 'tui-chat-leave-group-mobile': isMobile,
}" }"
> >
{{ leaveGroupReasonText }} {{ leaveGroupReasonText }}
</div> </div>
<MultipleSelectPanel <MultipleSelectPanel
v-else-if="isMultipleSelectMode" v-else-if="isMultipleSelectMode"
@oneByOneForwardMessage="oneByOneForwardMessage" @oneByOneForwardMessage="oneByOneForwardMessage"
@mergeForwardMessage="mergeForwardMessage" @mergeForwardMessage="mergeForwardMessage"
@toggleMultipleSelectMode="toggleMultipleSelectMode" @toggleMultipleSelectMode="toggleMultipleSelectMode"
/> />
<template v-else> <template v-else>
<MessageInputToolbar <MessageInputToolbar
v-if="isInputToolbarShow" v-if="isInputToolbarShow"
:class="[ :class="[
'tui-chat-message-input-toolbar', 'tui-chat-message-input-toolbar',
!isPC && 'tui-chat-h5-message-input-toolbar', !isPC && 'tui-chat-h5-message-input-toolbar',
isUniFrameWork && 'tui-chat-uni-message-input-toolbar' isUniFrameWork && 'tui-chat-uni-message-input-toolbar'
]" ]"
:displayType="inputToolbarDisplayType" :displayType="inputToolbarDisplayType"
@insertEmoji="insertEmoji" @insertEmoji="insertEmoji"
@changeToolbarDisplayType="changeToolbarDisplayType" @changeToolbarDisplayType="changeToolbarDisplayType"
@scrollToLatestMessage="scrollToLatestMessage" @scrollToLatestMessage="scrollToLatestMessage"
/> />
<MessageInput <MessageInput
ref="messageInputRef" ref="messageInputRef"
:class="[ :class="[
'tui-chat-message-input', 'tui-chat-message-input',
!isPC && 'tui-chat-h5-message-input', !isPC && 'tui-chat-h5-message-input',
isUniFrameWork && 'tui-chat-uni-message-input', isUniFrameWork && 'tui-chat-uni-message-input',
isWeChat && 'tui-chat-wx-message-input', isWeChat && 'tui-chat-wx-message-input',
]" ]"
:enableAt="featureConfig.InputMention" :enableAt="featureConfig.InputMention"
:isMuted="false" :isMuted="false"
:muteText="TUITranslateService.t('TUIChat.您已被管理员禁言')" :muteText="TUITranslateService.t('TUIChat.您已被管理员禁言')"
:placeholder="TUITranslateService.t('TUIChat.请输入消息')" :placeholder="TUITranslateService.t('TUIChat.请输入消息')"
:inputToolbarDisplayType="inputToolbarDisplayType" :inputToolbarDisplayType="inputToolbarDisplayType"
@changeToolbarDisplayType="changeToolbarDisplayType" @changeToolbarDisplayType="changeToolbarDisplayType"
/> />
</template> </template>
</div> </div>
<!-- Group Management --> <!-- Group Management -->
<div <div
v-if="!isNotInGroup && !isApp && isUniFrameWork && isGroup && headerExtensionList.length > 0" v-if="!isNotInGroup && !isApp && isUniFrameWork && isGroup && headerExtensionList.length > 0"
class="group-profile" class="group-profile"
@click="handleGroup" @click="handleGroup"
> >
{{ headerExtensionList[0].text }} {{ headerExtensionList[0].text }}
</div> </div>
</div> </div>
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref, onMounted, onUnmounted, computed } from '../../adapter-vue'; import { ref, onMounted, onUnmounted, computed } from '../../adapter-vue';
import TUIChatEngine, { import TUIChatEngine, {
TUITranslateService, TUITranslateService,
TUIConversationService, TUIConversationService,
TUIStore, TUIStore,
StoreName, StoreName,
IMessageModel, IMessageModel,
IConversationModel, IConversationModel,
} from '@tencentcloud/chat-uikit-engine'; } from '@tencentcloud/chat-uikit-engine';
import TUICore, { TUIConstants, ExtensionInfo } from '@tencentcloud/tui-core'; import TUICore, { TUIConstants, ExtensionInfo } from '@tencentcloud/tui-core';
import ChatHeader from './chat-header/index.vue'; import ChatHeader from './chat-header/index.vue';
import MessageList from './message-list/index.vue'; import MessageList from './message-list/index.vue';
import MessageInput from './message-input/index.vue'; import MessageInput from './message-input/index.vue';
import MultipleSelectPanel from './mulitple-select-panel/index.vue'; import MultipleSelectPanel from './mulitple-select-panel/index.vue';
import Forward from './forward/index.vue'; import Forward from './forward/index.vue';
import MessageInputToolbar from './message-input-toolbar/index.vue'; import MessageInputToolbar from './message-input-toolbar/index.vue';
import { isPC, isWeChat, isUniFrameWork, isMobile, isApp } from '../../utils/env'; import { isPC, isWeChat, isUniFrameWork, isMobile, isApp } from '../../utils/env';
import { ToolbarDisplayType } from '../../interface'; import { ToolbarDisplayType } from '../../interface';
import TUIChatConfig from './config'; import TUIChatConfig from './config';
// @Start uniapp use Chat only // @Start uniapp use Chat only
import { onLoad, onUnload,onNavigationBarButtonTap } from '@dcloudio/uni-app'; import { onLoad, onUnload,onNavigationBarButtonTap } from '@dcloudio/uni-app';
import { initChat, logout } from './entry-chat-only.ts'; import { initChat, logout } from './entry-chat-only.ts';
onLoad((options) => { onLoad((options) => {
initChat(options); console.dir(options)
}); initChat(options);
});
onUnload(() => {
// Whether logout is decided by yourself when the page is unloaded. The default is false. onUnload(() => {
logout(false).then(() => { // Whether logout is decided by yourself when the page is unloaded. The default is false.
// Handle success result from promise.then when you set true. logout(false).then(() => {
}).catch(() => { // Handle success result from promise.then when you set true.
// handle error }).catch(() => {
}); // handle error
}); });
// @End uniapp use Chat only });
// @End uniapp use Chat only
const emits = defineEmits(['closeChat']);
const fn = uni.getSystemInfoSync().windowHeight const emits = defineEmits(['closeChat']);
const groupID = ref(undefined); const fn = uni.getSystemInfoSync().windowHeight
const isGroup = ref(false); const groupID = ref(undefined);
const isNotInGroup = ref(false); const isGroup = ref(false);
const notInGroupReason = ref<number>(); const isNotInGroup = ref(false);
const currentConversationID = ref(); const notInGroupReason = ref<number>();
const isMultipleSelectMode = ref(false); const currentConversationID = ref("");
const inputToolbarDisplayType = ref<ToolbarDisplayType>('none'); const isMultipleSelectMode = ref(false);
const messageInputRef = ref(); const inputToolbarDisplayType = ref<ToolbarDisplayType>('none');
const messageListRef = ref<InstanceType<typeof MessageList>>(); const messageInputRef = ref();
const headerExtensionList = ref<ExtensionInfo[]>([]); const messageListRef = ref<InstanceType<typeof MessageList>>();
const featureConfig = TUIChatConfig.getFeatureConfig(); const headerExtensionList = ref<ExtensionInfo[]>([]);
const featureConfig = TUIChatConfig.getFeatureConfig();
onNavigationBarButtonTap((e) => {
if (isGroup.value) { onNavigationBarButtonTap((e) => {
handleGroup(); if (isGroup.value) {
} handleGroup();
}); }
});
onMounted(() => {
TUIStore.watch(StoreName.CONV, { onMounted(() => {
currentConversation: onCurrentConversationUpdate, TUIStore.watch(StoreName.CONV, {
}); currentConversation: onCurrentConversationUpdate,
}); });
});
onUnmounted(() => {
TUIStore.unwatch(StoreName.CONV, { onUnmounted(() => {
currentConversation: onCurrentConversationUpdate, TUIStore.unwatch(StoreName.CONV, {
}); currentConversation: onCurrentConversationUpdate,
reset(); });
}); reset();
});
const isInputToolbarShow = computed<boolean>(() => {
return isUniFrameWork ? inputToolbarDisplayType.value !== 'none' : true; const isInputToolbarShow = computed<boolean>(() => {
}); return isUniFrameWork ? inputToolbarDisplayType.value !== 'none' : true;
});
const leaveGroupReasonText = computed<string>(() => {
let text = ''; const leaveGroupReasonText = computed<string>(() => {
switch (notInGroupReason.value) { let text = '';
case 4: switch (notInGroupReason.value) {
text = TUITranslateService.t('TUIChat.您已被管理员移出群聊'); case 4:
break; text = TUITranslateService.t('TUIChat.您已被管理员移出群聊');
case 5: break;
text = TUITranslateService.t('TUIChat.该群聊已被解散'); case 5:
break; text = TUITranslateService.t('TUIChat.该群聊已被解散');
case 8: break;
text = TUITranslateService.t('TUIChat.您已退出该群聊'); case 8:
break; text = TUITranslateService.t('TUIChat.您已退出该群聊');
default: break;
text = TUITranslateService.t('TUIChat.您已退出该群聊'); default:
break; text = TUITranslateService.t('TUIChat.您已退出该群聊');
} break;
return text; }
}); return text;
});
const reset = () => {
TUIConversationService.switchConversation(''); const reset = () => {
}; TUIConversationService.switchConversation('');
};
const closeChat = (conversationID: string) => {
emits('closeChat', conversationID); const closeChat = (conversationID: string) => {
reset(); emits('closeChat', conversationID);
}; reset();
};
const insertEmoji = (emojiObj: object) => {
messageInputRef.value?.insertEmoji(emojiObj); const insertEmoji = (emojiObj: object) => {
}; messageInputRef.value?.insertEmoji(emojiObj);
};
const handleEditor = (message: IMessageModel, type: string) => {
if (!message || !type) return; const handleEditor = (message: IMessageModel, type: string) => {
switch (type) { if (!message || !type) return;
case 'reference': switch (type) {
// todo case 'reference':
break; // todo
case 'reply': break;
// todo case 'reply':
break; // todo
case 'reedit': break;
if (message?.payload?.text) { case 'reedit':
messageInputRef?.value?.reEdit(message?.payload?.text); if (message?.payload?.text) {
} messageInputRef?.value?.reEdit(message?.payload?.text);
break; }
default: break;
break; default:
} break;
}; }
};
const handleGroup = () => {
headerExtensionList.value[0].listener.onClicked({ groupID: groupID.value }); const handleGroup = () => {
uni.navigateTo({ headerExtensionList.value[0].listener.onClicked({ groupID: groupID.value });
url: `/TUIKit/components/TUIGroup/index` uni.navigateTo({
}); url: `/TUIKit/components/TUIGroup/index`
}; });
};
function changeToolbarDisplayType(type: ToolbarDisplayType) {
inputToolbarDisplayType.value = inputToolbarDisplayType.value === type ? 'none' : type; function changeToolbarDisplayType(type: ToolbarDisplayType) {
if (inputToolbarDisplayType.value !== 'none' && isUniFrameWork) { inputToolbarDisplayType.value = inputToolbarDisplayType.value === type ? 'none' : type;
uni.$emit('scroll-to-bottom'); if (inputToolbarDisplayType.value !== 'none' && isUniFrameWork) {
} uni.$emit('scroll-to-bottom');
} }
}
function scrollToLatestMessage() {
messageListRef.value?.scrollToLatestMessage(); function scrollToLatestMessage() {
} messageListRef.value?.scrollToLatestMessage();
}
function toggleMultipleSelectMode(visible?: boolean) {
isMultipleSelectMode.value = visible === undefined ? !isMultipleSelectMode.value : visible; function toggleMultipleSelectMode(visible?: boolean) {
} isMultipleSelectMode.value = visible === undefined ? !isMultipleSelectMode.value : visible;
}
function mergeForwardMessage() {
messageListRef.value?.mergeForwardMessage(); function mergeForwardMessage() {
} messageListRef.value?.mergeForwardMessage();
}
function oneByOneForwardMessage() {
messageListRef.value?.oneByOneForwardMessage(); function oneByOneForwardMessage() {
} messageListRef.value?.oneByOneForwardMessage();
}
function updateUIUserNotInGroup(conversation: IConversationModel) {
if (conversation?.operationType > 0) { function updateUIUserNotInGroup(conversation: IConversationModel) {
headerExtensionList.value = []; if (conversation?.operationType > 0) {
isNotInGroup.value = true; headerExtensionList.value = [];
/** isNotInGroup.value = true;
* 4 - be removed from the group /**
* 5 - group is dismissed * 4 - be removed from the group
* 8 - quit group * 5 - group is dismissed
*/ * 8 - quit group
notInGroupReason.value = conversation?.operationType; */
} else { notInGroupReason.value = conversation?.operationType;
isNotInGroup.value = false; } else {
notInGroupReason.value = undefined; isNotInGroup.value = false;
} notInGroupReason.value = undefined;
} }
}
function onCurrentConversationUpdate(conversation: IConversationModel) {
updateUIUserNotInGroup(conversation); function onCurrentConversationUpdate(conversation: IConversationModel) {
// return when currentConversation is null updateUIUserNotInGroup(conversation);
if (!conversation) { // return when currentConversation is null
return; if (!conversation) {
} return;
// return when currentConversationID.value is the same as conversation.conversationID. }
if (currentConversationID.value === conversation?.conversationID) { // return when currentConversationID.value is the same as conversation.conversationID.
return; if (currentConversationID.value === conversation?.conversationID) {
} return;
}
isGroup.value = false;
let conversationType = TUIChatEngine.TYPES.CONV_C2C; isGroup.value = false;
const conversationID = conversation.conversationID; let conversationType = TUIChatEngine.TYPES.CONV_C2C;
if (conversationID.startsWith(TUIChatEngine.TYPES.CONV_GROUP)) { const conversationID = conversation.conversationID;
conversationType = TUIChatEngine.TYPES.CONV_GROUP; if (conversationID.startsWith(TUIChatEngine.TYPES.CONV_GROUP)) {
isGroup.value = true; conversationType = TUIChatEngine.TYPES.CONV_GROUP;
groupID.value = conversationID.replace(TUIChatEngine.TYPES.CONV_GROUP, ''); isGroup.value = true;
} groupID.value = conversationID.replace(TUIChatEngine.TYPES.CONV_GROUP, '');
}
headerExtensionList.value = [];
isMultipleSelectMode.value = false; headerExtensionList.value = [];
// Initialize chatType isMultipleSelectMode.value = false;
TUIChatConfig.setChatType(conversationType); // Initialize chatType
// While converstaion change success, notify callkit and roomkitor other components. TUIChatConfig.setChatType(conversationType);
TUICore.notifyEvent(TUIConstants.TUIChat.EVENT.CHAT_STATE_CHANGED, TUIConstants.TUIChat.EVENT_SUB_KEY.CHAT_OPENED, { groupID: groupID.value }); // While converstaion change success, notify callkit and roomkitor other components.
// The TUICustomerServicePlugin plugin determines if the current conversation is a customer service conversation, then sets chatType and activates the conversation. TUICore.notifyEvent(TUIConstants.TUIChat.EVENT.CHAT_STATE_CHANGED, TUIConstants.TUIChat.EVENT_SUB_KEY.CHAT_OPENED, { groupID: groupID.value });
TUICore.callService({ // The TUICustomerServicePlugin plugin determines if the current conversation is a customer service conversation, then sets chatType and activates the conversation.
serviceName: TUIConstants.TUICustomerServicePlugin.SERVICE.NAME, TUICore.callService({
method: TUIConstants.TUICustomerServicePlugin.SERVICE.METHOD.ACTIVE_CONVERSATION, serviceName: TUIConstants.TUICustomerServicePlugin.SERVICE.NAME,
params: { conversationID: conversationID }, 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) { // When open chat in room, close main chat ui and reset theme.
if (TUIChatConfig.getFeatureConfig(TUIConstants.TUIChat.FEATURE.InputVoice) === true) { if (TUIChatConfig.getChatType() === TUIConstants.TUIChat.TYPE.ROOM) {
TUIChatConfig.setTheme('light'); if (TUIChatConfig.getFeatureConfig(TUIConstants.TUIChat.FEATURE.InputVoice) === true) {
currentConversationID.value = ''; TUIChatConfig.setTheme('light');
return; currentConversationID.value = '';
} return;
} }
// Get chat header extensions }
if (TUIChatConfig.getChatType() === TUIConstants.TUIChat.TYPE.GROUP) { // Get chat header extensions
headerExtensionList.value = TUICore.getExtensionList(TUIConstants.TUIChat.EXTENSION.CHAT_HEADER.EXT_ID); if (TUIChatConfig.getChatType() === TUIConstants.TUIChat.TYPE.GROUP) {
} headerExtensionList.value = TUICore.getExtensionList(TUIConstants.TUIChat.EXTENSION.CHAT_HEADER.EXT_ID);
TUIStore.update(StoreName.CUSTOM, 'activeConversation', conversationID); }
currentConversationID.value = conversationID; TUIStore.update(StoreName.CUSTOM, 'activeConversation', conversationID);
} currentConversationID.value = conversationID;
</script> }
</script>
<style scoped lang="scss" src="./style/index.scss">
.chat{ <style scoped lang="scss" src="./style/index.scss">
width: 100% !important; .chat{
height: 100vh !important; width: 100% !important;
overflow: hidden; height: 100vh !important;
} overflow: hidden;
}
</style>
</style>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,353 +1,477 @@
/** /**
* 短视频相关API * 短视频相关API
*/ */
import { http, Method } from "@/utils/request.js"; import {
http,
import api from "@/config/api.js"; Method
} from "@/utils/request.js";
/** import api from "@/config/api.js";
* 短视频关注列表
*/
export function vlogDetail(vlogId,userId) { /**
let data = { * 根据昵称搜索用户
vlogId, */
userId export function vlogSearchUser({
} id,
return http.request({ nickname,
url: api.vlog + "/vlog/detail", page,
method: Method.GET, pageSize
needToken: true, }) {
params: data, return http.request({
}); url: api.vlog + "/userInfo/searchByNickname",
} method: Method.GET,
needToken: true,
/** params: {
* 短视频关注列表 id,
*/ nickname,
export function vlogFollowList(page, pageSize,myId) { page,
let data = { pageSize
page, },
pageSize, });
myId }
}
return http.request({
url: api.vlog + "/vlog/followList", /**
method: Method.GET, * 短视频详情
needToken: true, */
params: data, export function vlogDetail(vlogId, userId) {
}); let data = {
} vlogId,
userId
/** }
* 短视频列表-true return http.request({
*/ url: api.vlog + "/vlog/detail",
export function vlogList(page, pageSize,userId='',cityCode='',search='') { method: Method.GET,
let data = { needToken: true,
page, params: data,
pageSize, });
userId, }
cityCode,
search /**
} * 短视频关注列表
return http.request({ */
url: api.vlog + "/vlog/indexList", export function vlogFollowList(page, pageSize, myId) {
method: Method.GET, let data = {
needToken: false, page,
params: data, pageSize,
}); myId
} }
return http.request({
/** url: api.vlog + "/vlog/followList",
* 短视频喜欢 method: Method.GET,
*/ needToken: true,
export function vlogLike({userId,vlogId,vlogerId}) { params: data,
return http.request({ });
url: api.vlog + "/vlog/like", }
method: Method.POST,
needToken: true, /**
params: {userId,vlogId,vlogerId} * 短视频列表-true
}); */
} export function vlogList(page, pageSize, userId = '', cityCode = '', search = '') {
let data = {
/** page,
* 短视频不喜欢 pageSize,
*/ userId,
export function vlogUnLike({userId,vlogId,vlogerId}) { cityCode,
return http.request({ search
url: api.vlog + "/vlog/unlike", }
method: Method.POST, return http.request({
needToken: true, url: api.vlog + "/vlog/indexList",
params: {userId,vlogId,vlogerId} method: Method.GET,
}); needToken: false,
} params: data,
});
/** }
* 短视频评论数量统计
*/ /**
export function vlogComment(vlogId) { * 短视频喜欢
return http.request({ */
url: api.vlog + "/comment/counts", export function vlogLike({
method: Method.GET, userId,
needToken: true, vlogId,
params: { vlogId }, vlogerId
}); }) {
} return http.request({
url: api.vlog + "/vlog/like",
method: Method.POST,
needToken: true,
params: {
userId,
/** vlogId,
* 短视频关注博主 vlogerId
*/ }
export function vlogFollow(myId,vlogerId) { });
return http.request({ }
url: api.vlog + "/fans/follow",
method: Method.POST, /**
needToken: true, * 短视频不喜欢
params: {myId,vlogerId} */
}); export function vlogUnLike({
} userId,
vlogId,
/** vlogerId
* 短视频查询当前点赞数 }) {
*/ return http.request({
export function vlogTotalLikedCounts(vlogId) { url: api.vlog + "/vlog/unlike",
return http.request({ method: Method.POST,
url: api.vlog + "/vlog/totalLikedCounts", needToken: true,
method: Method.POST, params: {
needToken: true, userId,
params: {vlogId} vlogId,
}); vlogerId
} }
});
/** }
* 查询用户信息
*/ /**
export function vlogUserInfo(userId) { * 短视频评论数量统计
return http.request({ */
url: api.vlog + "/userInfo/query", export function vlogComment(vlogId) {
method: Method.GET, return http.request({
needToken: true, url: api.vlog + "/comment/counts",
params: { userId }, method: Method.GET,
}); needToken: true,
} params: {
vlogId
/** },
* 查询我的公开视频 });
*/ }
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 vlogFollow(myId, vlogerId) {
/** return http.request({
* 查询我的私密视频 url: api.vlog + "/fans/follow",
*/ method: Method.POST,
export function vlogMyPrivateList(page,pageSize,userId) { needToken: true,
return http.request({ params: {
url: api.vlog + "/vlog/myPrivateList", myId,
method: Method.GET, vlogerId
needToken: true, }
params: { page,pageSize,userId }, });
}); }
}
/**
/** * 短视频查询当前点赞数
* 查询我喜欢的视频 */
*/ export function vlogTotalLikedCounts(vlogId) {
export function vlogMyLikedList(page,pageSize,userId) { return http.request({
return http.request({ url: api.vlog + "/vlog/totalLikedCounts",
url: api.vlog + "/vlog/myLikedList", method: Method.POST,
method: Method.GET, needToken: true,
needToken: true, params: {
params: { page,pageSize,userId }, vlogId
}); }
} });
}
/**
* 查询我喜欢的视频 /**
*/ * 查询用户信息
export function vlogMeTag(path,page,pageSize,userId) { */
return http.request({ export function vlogUserInfo(userId) {
url: api.vlog + "/vlog/"+path, return http.request({
method: Method.GET, url: api.vlog + "/userInfo/query",
needToken: true, method: Method.GET,
params: { page,pageSize,userId }, needToken: true,
}); params: {
} userId
},
/** });
* 视频评论数量 }
*/
export function vlogCommentCounts(vlogId) { /**
return http.request({ * 查询我的公开视频
url: api.vlog + "/comment/counts", */
method: Method.GET, export function vlogMyPublicList(page, pageSize, userId) {
needToken: true, return http.request({
params: { vlogId }, url: api.vlog + "/vlog/myPublicList",
}); method: Method.GET,
} needToken: true,
params: {
/** page,
* 视频评论不喜欢 pageSize,
*/ userId
export function vlogCommentUnLike(userId,commentId) { },
return http.request({ });
url: api.vlog + "/comment/unlike", }
method: Method.POST,
needToken: true, /**
params: {userId,commentId} * 查询我的私密视频
}); */
} export function vlogMyPrivateList(page, pageSize, userId) {
return http.request({
/** url: api.vlog + "/vlog/myPrivateList",
* 视频评论喜欢 method: Method.GET,
*/ needToken: true,
export function vlogCommentLike(userId,commentId) { params: {
return http.request({ page,
url: api.vlog + "/comment/like", pageSize,
method: Method.POST, userId
needToken: true, },
params: {userId,commentId} });
}); }
}
/**
/** * 查询我喜欢的视频
* 视频评论删除 */
*/ export function vlogMyLikedList(page, pageSize, userId) {
export function vlogCommentDelete(vlogId,commentUserId,commentId) { return http.request({
return http.request({ url: api.vlog + "/vlog/myLikedList",
url: api.vlog + "/comment/delete", method: Method.GET,
method: Method.DELETE, needToken: true,
needToken: true, params: {
params: {vlogId,commentUserId,commentId} page,
}); pageSize,
} userId
},
});
/** }
* 查询视频评论内容
*/ /**
export function vlogCommentList(page,pageSize,vlogId,userId) { * 查询我喜欢的视频
return http.request({ */
url: api.vlog + "/comment/list", export function vlogMeTag(path, page, pageSize, userId) {
method: Method.GET, return http.request({
needToken: true, url: api.vlog + "/vlog/" + path,
params: { page,pageSize,vlogId,userId }, method: Method.GET,
}); needToken: true,
} params: {
page,
/** pageSize,
* 发表评论 userId
*/ },
export function vlogCommentCreate(data) { });
return http.request({ }
url: api.vlog + "/comment/create",
method: Method.POST, /**
needToken: true, * 视频评论数量
data */
}); export function vlogCommentCounts(vlogId) {
} return http.request({
url: api.vlog + "/comment/counts",
/** method: Method.GET,
* 将视频转为公开 needToken: true,
*/ params: {
export function vlogChangeToPublic(userId,vlogId) { vlogId
return http.request({ },
url: api.vlog + "/vlog/changeToPublic", });
method: Method.POST, }
needToken: true,
params:{userId,vlogId} /**
}); * 视频评论不喜欢
} */
export function vlogCommentUnLike(userId, commentId) {
/** return http.request({
* 将视频转为私密 url: api.vlog + "/comment/unlike",
*/ method: Method.POST,
export function vlogChangeToPrivate(userId,vlogId) { needToken: true,
return http.request({ params: {
url: api.vlog + "/vlog/changeToPrivate", userId,
method: Method.POST, commentId
needToken: true, }
params:{userId,vlogId} });
}); }
}
/**
/** * 视频评论喜欢
* 修改信息 */
*/ export function vlogCommentLike(userId, commentId) {
export function vlogModifyUserInfo(data,type) { return http.request({
return http.request({ url: api.vlog + "/comment/like",
url: api.vlog + "/userInfo/modifyUserInfo?type="+type, method: Method.POST,
method: Method.POST, needToken: true,
needToken: true, params: {
data userId,
}); commentId
} }
});
}
/**
* 取关 /**
*/ * 视频评论删除
export function vlogFansCancel({myId,vlogerId}) { */
return http.request({ export function vlogCommentDelete(vlogId, commentUserId, commentId) {
url: api.vlog + "/fans/cancel", return http.request({
method: Method.POST, url: api.vlog + "/comment/delete",
needToken: true, method: Method.DELETE,
params:{myId,vlogerId} needToken: true,
}); params: {
} vlogId,
/** commentUserId,
* 关注 commentId
*/ }
export function vlogFansFollow({myId,vlogerId}) { });
return http.request({ }
url: api.vlog + "/fans/follow",
method: Method.POST,
needToken: true, /**
params:{myId,vlogerId} * 查询视频评论内容
}); */
} export function vlogCommentList(page, pageSize, vlogId, userId) {
return http.request({
/** url: api.vlog + "/comment/list",
* 我的粉丝 method: Method.GET,
*/ needToken: true,
export function vlogQueryMyFans({myId,page,pageSize}) { params: {
return http.request({ page,
url: api.vlog + "/fans/queryMyFans", pageSize,
method: Method.GET, vlogId,
needToken: true, userId
params:{myId,page,pageSize} },
}); });
} }
/** /**
* 我的关注 * 发表评论
*/ */
export function vlogQueryMyFollows({myId,page,pageSize}) { export function vlogCommentCreate(data) {
return http.request({ return http.request({
url: api.vlog + "/fans/queryMyFollows", url: api.vlog + "/comment/create",
method: Method.GET, method: Method.POST,
needToken: true, needToken: true,
params:{myId,page,pageSize} data
}); });
} }
/** /**
* 我的关注 * 将视频转为公开
*/ */
export function vlogQueryDoIFollowVloger({myId,vlogerId}) { export function vlogChangeToPublic(userId, vlogId) {
return http.request({ return http.request({
url: api.vlog + "/fans/queryDoIFollowVloger", url: api.vlog + "/vlog/changeToPublic",
method: Method.GET, method: Method.POST,
needToken: true, needToken: true,
params:{myId,vlogerId} 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> <text class="muisc-words">{{ item.vlogerName }}的原声创作</text>
</view> </view>
</view> </view>
<view <!-- 右下角 -->
<!-- <view
class="" class=""
style="flex-direction: row" style="flex-direction: row"
> >
@ -120,17 +121,7 @@
src="/static/images/cd-play-4.gif" src="/static/images/cd-play-4.gif"
style="width: 150rpx; height: 150rpx; opacity: 0.8" style="width: 150rpx; height: 150rpx; opacity: 0.8"
></image> ></image>
<image </view> -->
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 class="operation-box">
@ -323,7 +314,6 @@ export default {
playStatus(val) { playStatus(val) {
var me = this; var me = this;
console.log(val);
if (!val) { if (!val) {
me.videoContext.pause(); me.videoContext.pause();
} else { } else {
@ -839,7 +829,6 @@ export default {
height: 2px; height: 2px;
background-color: #ccc; background-color: #ccc;
z-index: 3; z-index: 3;
pointer-events: none;
} }
.progress-foreground { .progress-foreground {
@ -849,7 +838,6 @@ export default {
position: absolute; position: absolute;
bottom: 0; bottom: 0;
left: 0; left: 0;
pointer-events: none;
} }
.anm { .anm {
transition: width 0.25s linear; transition: width 0.25s linear;

View File

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

View File

@ -122,24 +122,13 @@
<text class="muisc-words">{{ item.vlogerName }}的原声创作</text> <text class="muisc-words">{{ item.vlogerName }}的原声创作</text>
</view> </view>
</view> </view>
<view <!-- <view
class="" class=""
style="flex-direction: row" style="flex-direction: row"
> >
<!-- <image src="/static/images/cd-play-4.gif" <image src="/static/images/cd-play-4.gif"
style="width: 150rpx;height: 150rpx;opacity: 0.8;"></image> --> style="width: 150rpx;height: 150rpx;opacity: 0.8;"></image>
<image </view> -->
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"> <view class="operation-box">
@ -334,7 +323,6 @@ export default {
}, },
onTimeUpdate(e) { onTimeUpdate(e) {
console.log(e.detail.currentTime); console.log(e.detail.currentTime);
console.log(this.progressFlag);
if (e.detail.currentTime > 0.2) { if (e.detail.currentTime > 0.2) {
this.doplay(e.detail.currentTime); this.doplay(e.detail.currentTime);
} }
@ -346,7 +334,6 @@ export default {
updateProgress() { updateProgress() {
const percent = this.currentTime / this.duration; const percent = this.currentTime / this.duration;
this.progressWidth = Math.floor(system.screenWidth * percent); this.progressWidth = Math.floor(system.screenWidth * percent);
console.log(this.progressWidth);
}, },
startDrag(e) { startDrag(e) {
console.log('触发开始拖动'); console.log('触发开始拖动');
@ -690,6 +677,7 @@ export default {
} else { } else {
// return; // return;
} }
console.log(myUserInfo);
var result = await vlogFollowList(page, 10, userId); var result = await vlogFollowList(page, 10, userId);
console.log(result); console.log(result);
if (result.data.status == 200) { if (result.data.status == 200) {

View File

@ -121,7 +121,7 @@
<text class="muisc-words">{{ item.vlogerName }}的原声创作</text> <text class="muisc-words">{{ item.vlogerName }}的原声创作</text>
</view> </view>
</view> </view>
<view <!-- <view
class="" class=""
style="flex-direction: row" style="flex-direction: row"
> >
@ -129,17 +129,7 @@
src="/static/images/cd-play-4.gif" src="/static/images/cd-play-4.gif"
style="width: 150rpx; height: 150rpx; opacity: 0.8" style="width: 150rpx; height: 150rpx; opacity: 0.8"
></image> ></image>
<image </view> -->
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 class="operation-box">

View File

@ -7,19 +7,19 @@ const dev = {
// common: "https://common-api.pickmall.cn", // common: "https://common-api.pickmall.cn",
// buyer: "https://buyer-api.pickmall.cn", // buyer: "https://buyer-api.pickmall.cn",
common: "http://192.168.1.200:8890", common: "http://192.168.1.113:8890",
buyer: "http://192.168.1.200:8888", buyer: "http://192.168.1.113:8888",
vlog: "http://192.168.1.200:8099", vlog: "http://192.168.1.86:8099",
web: "http://192.168.1.200:8099", web: "http://192.168.1.113:8099",
}; };
// 生产环境 // 生产环境
const prod = { const prod = {
// common: "https://common-api.pickmall.cn", // common: "https://common-api.pickmall.cn",
// buyer: "https://buyer-api.pickmall.cn", // buyer: "https://buyer-api.pickmall.cn",
common: "http://192.168.1.200:8890", common: "http://192.168.1.113:8890",
buyer: "http://192.168.1.200:8888", buyer: "http://192.168.1.113:8888",
vlog: "http://192.168.1.200:8099", 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": { "dependencies": {
"@dcloudio/uni-app": "^2.0.2-4050720250324001", "@dcloudio/uni-app": "^2.0.2-4050720250324001",
"@tencentcloud/chat-uikit-uniapp": "^2.4.3", "@tencentcloud/chat-uikit-uniapp": "^2.4.3",
"@vue/runtime-core": "^3.5.13", "@vue/runtime-core": "^3.5.13",
"unplugin-vue2-script-setup": "^0.11.4", "unplugin-vue2-script-setup": "^0.11.4",
"vue": "^3.2.0", "vue": "^3.2.0",
"ws": "^8.18.1" "ws": "^8.18.1"
} }
} }

View File

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

View File

@ -229,7 +229,7 @@ export default {
}, 300); }, 300);
}, },
onTabItemTap: function (e) { onTabItemTap: function (e) {
console.log(e); // console.log(e);
// let tabIndex = e.index; // let tabIndex = e.index;
// this.playStatus = tabIndex === 0 ? true : false; // this.playStatus = tabIndex === 0 ? true : false;
// 切换视频要做暂停或播放的判断 // 切换视频要做暂停或播放的判断
@ -343,7 +343,8 @@ export default {
} }
}); });
}, },
fail: () => { fail: (err) => {
console.log(err);
uni.showToast({ uni.showToast({
icon: 'none', icon: 'none',
title: '获取位置信息失败' title: '获取位置信息失败'
@ -373,15 +374,7 @@ export default {
let index = e.target.dataset.current || e.currentTarget.dataset.current; let index = e.target.dataset.current || e.currentTarget.dataset.current;
this.isTap = true; this.isTap = true;
var currentSize = this.tabListSize[index]; 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.updateIndicator(currentSize.left, currentSize.width);
this._touchTabIndex = index; this._touchTabIndex = index;
this.switchTab(index); this.switchTab(index);
@ -433,7 +426,6 @@ export default {
this.isDraw_gz = false; this.isDraw_gz = false;
this.isDraw_lo = false; this.isDraw_lo = false;
this.isDraw_tj = false; this.isDraw_tj = false;
console.log(this.playStatus);
// if (this.playStatus == true) { // if (this.playStatus == true) {
// this.playStatus = this._lastTabIndex == 2 ? false : 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(""); storage.setRefreshToken("");
console.log("清空token"); console.log("清空token");
storage.setUuid(""); storage.setUuid("");
storage.setUserInfo({}); storage.setUserInfo({});
// 清理vlog信息 // 清理vlog信息
storage.setVlogToken("") storage.setVlogToken("")
storage.setVlogUserInfo({}) storage.setVlogUserInfo(null)
// 清除初始化数据内容 // 清除初始化数据内容
storage.setRefreshVlogIndex('0') //不需要刷新 storage.setRefreshVlogIndex('0') //不需要刷新
// 防抖处理跳转 // 防抖处理跳转
// #ifdef MP-WEIXIN // #ifdef MP-WEIXIN
@ -96,23 +96,23 @@ http.interceptors.request.use(
config.header.accessToken = accessToken; config.header.accessToken = accessToken;
} }
// 配置vlog所需参数 // 配置vlog所需参数
let vlogToken = storage.getVlogToken(); let vlogToken = storage.getVlogToken();
let vlogId = storage.getVlogUserInfo(); let vlogId = storage.getVlogUserInfo();
// console.log(vlogId) // console.log(vlogId)
// console.log(vlogToken) // console.log(vlogToken)
if(vlogToken){ if (vlogToken) {
config.header.headerUserToken = vlogToken; config.header.headerUserToken = vlogToken;
config.header.headerUserId = vlogId.id; config.header.headerUserId = vlogId.id;
} }
config.header = { config.header = {
...config.header, ...config.header,
uuid: storage.getUuid() || uuid.v1(), uuid: storage.getUuid() || uuid.v1(),
}; };
// console.log(config) // console.log(config)
return config; return config;
}, },
(config) => { (config) => {
return Promise.reject(config); return Promise.reject(config);
} }
); );
@ -124,8 +124,8 @@ let isRefreshing = false;
let requests = []; let requests = [];
// 必须使用异步函数,注意 // 必须使用异步函数,注意
http.interceptors.response.use( http.interceptors.response.use(
async (response) => { async (response) => {
// console.log(isRefreshing) // console.log(isRefreshing)
// console.log(response) // console.log(response)
/* 请求之后拦截器。可以使用async await 做异步操作 */ /* 请求之后拦截器。可以使用async await 做异步操作 */
// token存在并且token过期 // token存在并且token过期
@ -194,9 +194,8 @@ http.interceptors.response.use(
duration: 1500, duration: 1500,
}); });
} }
} } else if (response.data.code == 502) {
else if (response.data.code==502){ cleanStorage();
cleanStorage();
} }
return response; return response;
}, },

View File

@ -95,16 +95,28 @@ const theNextDayTime = () => {
}; };
const graceNumber = (number) => { 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) { if (number == 0) {
return "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, getDateBeforeNow,
isStrEmpty, isStrEmpty,
getAstro, getAstro,
getAnimal, getAnimal,
dateFormat dateFormat
}; };

View File

@ -1,35 +1,35 @@
// const ScriptSetup = require('unplugin-vue2-script-setup/webpack').default; const ScriptSetup = require('unplugin-vue2-script-setup/webpack').default;
module.exports = { module.exports = {
parallel: false, parallel: false,
configureWebpack: { configureWebpack: {
plugins: [ plugins: [
// ScriptSetup({ ScriptSetup({
// /* options */ /* options */
// }), }),
], ],
}, },
chainWebpack(config) { chainWebpack(config) {
// disable type check and let `vue-tsc` handles it // disable type check and let `vue-tsc` handles it
config.plugins.delete('fork-ts-checker'); config.plugins.delete('fork-ts-checker');
}, },
}; };
// module.exports = { // module.exports = {
// /** // /**
// * 此处为发行h5,微信小程序app中删除console // * 此处为发行h5,微信小程序app中删除console
// * 如需显示console 需要注释此处重新运行 // * 如需显示console 需要注释此处重新运行
// */ // */
// chainWebpack: (config) => { // chainWebpack: (config) => {
// // 发行或运行时启用了压缩时会生效 // // 发行或运行时启用了压缩时会生效
// config.optimization.minimizer('terser').tap((args) => { // config.optimization.minimizer('terser').tap((args) => {
// const compress = args[0].terserOptions.compress // const compress = args[0].terserOptions.compress
// // 非 App 平台移除 console 代码(包含所有 console 方法,如 log,debug,info...) // // 非 App 平台移除 console 代码(包含所有 console 方法,如 log,debug,info...)
// compress.drop_console = true // compress.drop_console = true
// compress.pure_funcs = [ // compress.pure_funcs = [
// '__f__', // App 平台 vue 移除日志代码 // '__f__', // App 平台 vue 移除日志代码
// // 'console.debug' // 可移除指定的 console 方法 // // 'console.debug' // 可移除指定的 console 方法
// ] // ]
// return args // return args
// }) // })
// } // }
// } // }