IM相关模块本地测试

This commit is contained in:
cuiyouliang 2025-05-30 17:56:05 +08:00
parent 2dc094c1db
commit e1f771cbcc
43 changed files with 3880 additions and 8 deletions

View File

@ -22,6 +22,8 @@
"dependencies": { "dependencies": {
"@element-plus/icons-vue": "2.3.1", "@element-plus/icons-vue": "2.3.1",
"@highlightjs/vue-plugin": "2.1.0", "@highlightjs/vue-plugin": "2.1.0",
"@tencentcloud/chat": "^3.5.5",
"@tencentcloud/chat-uikit-vue": "^2.4.3",
"@vueup/vue-quill": "1.2.0", "@vueup/vue-quill": "1.2.0",
"@vueuse/core": "13.1.0", "@vueuse/core": "13.1.0",
"animate.css": "4.1.1", "animate.css": "4.1.1",

View File

@ -8,7 +8,9 @@ import 'element-plus/theme-chalk/dark/css-vars.css';
import App from './App.vue'; import App from './App.vue';
import store from './store'; import store from './store';
import router from './router'; import router from './router';
import { TUIComponents, TUIChatKit } from './TUIKit';
import { TUIStore, StoreName, TUITranslateService } from '@tencentcloud/chat-uikit-engine';
import TUINotification from './TUIKit/components/TUINotification/index';
// 自定义指令 // 自定义指令
import directive from './directive'; import directive from './directive';
@ -55,3 +57,32 @@ app.use(plugins);
directive(app); directive(app);
app.mount('#app'); app.mount('#app');
const SDKAppID = 1600089570; // Your SDKAppID
const secretKey = 'b84abcf1d5c41a702b1c63fe50adaa1bc77cc51233d0073f044e25c6f21fcb58';
const userSig =
'eJyrVgrxCdYrSy1SslIy0jNQ0gHzM1NS80oy0zLBwpX5pUCUk5mYl16ckZgPVVKckp1YUJCZomRlaGZgYGBhaWpuAJFJrSjILEoFipuamhoBpSCiJZm5IDFzEwtTSwsTY2OoKZnpQBtcPYq8XMvLkiKjTC1yDPLcM53zI7yS3POLLfyyDSNzK0ucjMOT-Q1Lgn09bZVqAf37Nfg_'; // Your secretKey
TUIChatKit.components(TUIComponents, app);
TUIChatKit.init();
/**
* Init TUINotification configuration.
*/
TUINotification.setNotificationConfiguration({
showPreviews: true,
allowNotifications: true,
notificationTitle: 'Tencent Cloud Chat',
notificationIcon: 'https://web.sdk.qcloud.com/im/demo/latest/faviconnew.png'
});
/**
* Listen for new messages and use notification components.
* This capability is only available in the web environment.
*/
TUIStore.watch(StoreName.CHAT, {
newMessageList: (newMessageList: unknown) => {
if (Array.isArray(newMessageList)) {
newMessageList.forEach((message) => TUINotification.notify(message));
}
}
});
export { SDKAppID, secretKey, userSig };

View File

@ -19,9 +19,10 @@ const isWhiteList = (path: string) => {
router.beforeEach(async (to, from, next) => { router.beforeEach(async (to, from, next) => {
NProgress.start(); NProgress.start();
if (getToken()) { next();
/* if (getToken()) {
to.meta.title && useSettingsStore().setTitle(to.meta.title as string); to.meta.title && useSettingsStore().setTitle(to.meta.title as string);
/* has token*/ /!* has token*!/
if (to.path === '/login') { if (to.path === '/login') {
next({ path: '/' }); next({ path: '/' });
NProgress.done(); NProgress.done();
@ -62,7 +63,7 @@ router.beforeEach(async (to, from, next) => {
next(`/login?redirect=${redirect}`); // 否则全部重定向到登录页 next(`/login?redirect=${redirect}`); // 否则全部重定向到登录页
NProgress.done(); NProgress.done();
} }
} }*/
}); });
router.afterEach(() => { router.afterEach(() => {

View File

@ -37,6 +37,11 @@ export const constantRoutes: RouteRecordRaw[] = [
} }
] ]
}, },
{
path: '/im-test',
hidden: true,
component: () => import('@/views/im/index.vue')
},
{ {
path: '/social-callback', path: '/social-callback',
hidden: true, hidden: true,

140
src/utils/link.ts Normal file
View File

@ -0,0 +1,140 @@
const Link = {
adv: {
label: '首购低至1折, 复购7.5折起! 立即选购',
url: 'https://cloud.tencent.com/act/pro/imnew?from=16262',
},
demo: {
label: '体验更多Demo',
url: 'https://cloud.tencent.com/document/product/269/36852',
},
im: {
label: '访问官网',
url: 'https://cloud.tencent.com/product/im',
},
privacy: {
label: '隐私条例',
url: 'https://web.sdk.qcloud.com/document/Tencent-IM-Privacy-Protection-Guidelines.html',
},
agreement: {
label: '用户协议',
url: 'https://web.sdk.qcloud.com/document/Tencent-IM-User-Agreement.html',
},
product: {
label: '产品文档',
url: 'https://cloud.tencent.com/document/product/269/1499#.E7.BE.A4.E7.BB.84.E5.8A.9F.E8.83.BD',
},
customMessage: {
label: '自定义消息',
url: 'https://web.sdk.qcloud.com/im/doc/zh-cn/SDK.html#createCustomMessage',
},
contact: {
label: '联系我们',
url: 'https://cloud.tencent.com/document/product/269/59590',
},
intl: {
label: '国际站',
url: 'https://web.sdk.qcloud.com/im/demo/en/index.html#/home',
},
stepList: [
{
label: '创建项目',
url: 'https://cloud.tencent.com/document/product/269/68493#.E6.AD.A5.E9.AA.A41.EF.BC.9A.E5.88.9B.E5.BB.BA.E9.A1.B9.E7.9B.AE',
},
{
label: '下载TUIKit组件',
url: 'https://cloud.tencent.com/document/product/269/68493#.E6.AD.A5.E9.AA.A42.EF.BC.9A.E4.B8.8B.E8.BD.BD-tuikit-.E7.BB.84.E4.BB.B6',
},
{
label: '引入TUIKit组件',
url: 'https://cloud.tencent.com/document/product/269/68493#.E6.AD.A5.E9.AA.A43.EF.BC.9A.E5.BC.95.E5.85.A5-tuikit-.E7.BB.84.E4.BB.B6',
},
{
label: '获取SDKAppID',
url: 'https://cloud.tencent.com/document/product/269/68493#.E6.AD.A5.E9.AA.A44.EF.BC.9A-.E8.8E.B7.E5.8F.96-sdkappid-.E3.80.81.E5.AF.86.E9.92.A5.E4.B8.8E-userid',
},
{
label: '调用TUIKit组件',
url: 'https://cloud.tencent.com/document/product/269/68493#.E6.AD.A5.E9.AA.A45.EF.BC.9A.E8.B0.83.E7.94.A8-tuikit-.E7.BB.84.E4.BB.B6',
},
{
label: '启动项目',
url: 'https://cloud.tencent.com/document/product/269/68493#.E6.AD.A5.E9.AA.A46.EF.BC.9A.E5.90.AF.E5.8A.A8.E9.A1.B9.E7.9B.AE',
},
{
label: '发送您的第一条消息',
url: 'https://cloud.tencent.com/document/product/269/68493#.E6.AD.A5.E9.AA.A47.EF.BC.9A.E5.8F.91.E9.80.81.E6.82.A8.E7.9A.84.E7.AC.AC.E4.B8.80.E6.9D.A1.E6.B6.88.E6.81.AF',
},
],
advList: [
{
label: 'IM首购低至1折',
subLabel: '续费9折起',
btnText: '立即选购',
url: 'https://cloud.tencent.com/act/pro/imnew?from=16262',
},
],
apkQRCodeList: [
{
type: 'android',
label: '扫码体验 Chat (React Native)',
url: 'https://web.sdk.qcloud.com/component/TUIKit/chat_react-native_demo_apk.png',
},
{
type: 'android',
label: '扫码体验 Chat&Push (uni-app)',
url: 'https://qcloudimg.tencent-cloud.cn/raw/c1fed062d91cd95fdfb57059edcd5890.png',
},
],
};
const qrList = [
{
icon: 'https://web.sdk.qcloud.com/im/assets/images/react_native.svg',
name: 'React Native',
link: 'https://web.sdk.qcloud.com/component/TUIKit/chat_react-native_demo_apk.png',
detail: '扫描二维码下载',
},
{
icon: 'https://web.sdk.qcloud.com/im/assets/images/uniapp.svg',
name: 'Uniapp',
link: 'https://qcloudimg.tencent-cloud.cn/raw/c1fed062d91cd95fdfb57059edcd5890.png',
detail: '扫描二维码下载',
},
{
icon: 'https://web.sdk.qcloud.com/im/assets/images/Android.svg',
name: 'Android',
link: 'https://web.sdk.qcloud.com/im/assets/images/android.png',
detail: '扫描二维码下载',
},
{
icon: 'https://web.sdk.qcloud.com/im/assets/images/iOS.svg',
name: 'iOS',
link: 'https://web.sdk.qcloud.com/im/assets/images/ios.png',
detail: '扫描二维码下载',
},
{
icon: 'https://web.sdk.qcloud.com/im/assets/images/Miniprogram.svg',
name: '小程序',
link: 'https://web.sdk.qcloud.com/im/assets/images/mini.png',
detail: '微信扫码进入',
},
];
const mobileList = [
{
type: 'android',
link: 'https://web.sdk.qcloud.com/im/assets/images/android-mobile.svg',
url: 'https://comm.qq.com/im_demo_download/index.html#/mobile-demo',
},
{
type: 'iphone',
link: 'https://web.sdk.qcloud.com/im/assets/images/iphone-mobile.svg',
url: 'https://apps.apple.com/cn/app/%E8%85%BE%E8%AE%AF%E4%BA%91im/id1112479040',
},
{
type: 'miniprogram',
link: 'https://web.sdk.qcloud.com/im/assets/images/mini-mobile.svg',
},
];
export { Link, qrList, mobileList };

View File

@ -124,7 +124,8 @@ service.interceptors.response.use(
if (res.request.responseType === 'blob' || res.request.responseType === 'arraybuffer') { if (res.request.responseType === 'blob' || res.request.responseType === 'arraybuffer') {
return res.data; return res.data;
} }
if (code === 401) { return Promise.resolve(res.data);
/* if (code === 401) {
// prettier-ignore // prettier-ignore
if (!isRelogin.show) { if (!isRelogin.show) {
isRelogin.show = true; isRelogin.show = true;
@ -158,7 +159,7 @@ service.interceptors.response.use(
return Promise.reject('error'); return Promise.reject('error');
} else { } else {
return Promise.resolve(res.data); return Promise.resolve(res.data);
} }*/
}, },
(error: any) => { (error: any) => {
let { message } = error; let { message } = error;

View File

@ -0,0 +1,430 @@
<template>
<div
:class="['about-container', 'container', isH5 && 'container-h5']"
@click="closeAboutBox"
@mousedown.stop
>
<div
:class="['about-box', 'box', isH5 && 'box-h5']"
@click.stop
>
<header
v-if="isH5"
class="title"
>
<div
class="title-back"
@click="closeAboutBox"
>
<Icon :file="backSVG" />
</div>
<div class="title-name">
{{ TUITranslateService.t("Home.关于腾讯云·通信") }}
</div>
</header>
<main class="main">
<div class="main-name">
<img
class="logo"
src="../assets/image/logo.svg"
alt=""
>
<div class="name">
{{ TUITranslateService.t("即时通信") }}
</div>
</div>
<div class="main-version">
{{ TUITranslateService.t("Home.SDK版本") }}: {{ SDKVersion }}
</div>
</main>
<footer class="footer">
<ul class="list">
<li class="line">
<a
class="link"
:href="Link.privacy.url"
target="_blank"
>
{{ TUITranslateService.t(`Login.${Link.privacy.label}`) }}
</a>
<a
class="link"
:href="Link.agreement.url"
target="_blank"
>
{{ TUITranslateService.t(`Login.${Link.agreement.label}`) }}
</a>
<a
class="link"
@click="toggleDisclaimer"
>
{{ TUITranslateService.t("Home.免责声明") }}
<div
v-if="isDisclaimerBoxShow"
:class="['disclaimer-container', 'container', isH5 && 'container-h5']"
@click="toggleDisclaimer"
>
<div
:class="['disclaimer-box', 'box', isH5 && 'box-h5']"
@click.stop
>
<header
v-if="isH5"
class="title"
>
<div
class="title-back"
@click="toggleDisclaimer"
>
<Icon :file="backSVG" />
</div>
<div class="title-name">
{{ TUITranslateService.t("Home.关于腾讯云·通信") }}
</div>
</header>
<main class="main">
<header class="main-title">
{{ TUITranslateService.t(`Home.IM-免责声明`) }}
</header>
<p class="main-info">
{{
TUITranslateService.t(
`Home.IM“本产品”是由腾讯云提供的一款测试产品腾讯云享有本产品的著作权和所有权。本产品仅用于功能体验不得用于任何商业用途。依据相关部门监管要求严禁在使用中有任何色情、辱骂、暴恐、涉政等违法内容传播。`
)
}}
</p>
</main>
<footer class="footer">
<button
class="btn btn-default"
@click.stop="submitDisclaimer"
>
{{ TUITranslateService.t("Home.同意") }}
</button>
</footer>
</div>
</div>
</a>
</li>
<li class="line">
<a
class="link"
:href="Link.contact.url"
target="_blank"
>
{{ TUITranslateService.t(`Home.${Link.contact.label}`) }}
</a>
<a
class="link"
@click="toggleCancellation"
>
{{ TUITranslateService.t("Home.注销账户") }}
<div
v-if="isCancellationBoxShow"
:class="['cancellation-container', 'container', isH5 && 'container-h5']"
@click="toggleCancellation"
>
<div
:class="['cancellation-box', 'box', isH5 && 'box-h5']"
@click.stop
>
<header
v-if="isH5"
class="title"
>
<div
class="title-back"
@click="toggleCancellation"
>
<Icon :file="backSVG" />
</div>
<div class="title-name">
{{ TUITranslateService.t("Home.关于腾讯云·通信") }}
</div>
</header>
<main class="main">
<img
src="../assets/image/warn.svg"
width="60"
height="60"
>
<p class="main-text">
{{
TUITranslateService.t(
"Home.注销后,您将无法使用当前账号,相关数据也将删除无法找回"
)
}}
</p>
<p>
<span>{{ TUITranslateService.t("Home.当前账号") + ": " }}</span>
<span class="main-id">{{ props.userProfile.userID }}</span>
</p>
</main>
<footer class="footer">
<button
class="btn btn-error"
@click.stop="submitCancellation"
>
{{ TUITranslateService.t("Home.注销") }}
</button>
<button
class="btn btn-default"
@click.stop="toggleCancellation"
>
{{ TUITranslateService.t("Home.取消") }}
</button>
</footer>
</div>
</div></a>
</li>
<li class="line">
<p class="copyright">
Copyright © 2013-2023 Tencent Cloud. All Rights Reserved.
</p>
</li>
</ul>
</footer>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, withDefaults, defineProps, defineEmits } from '@/TUIKit/adapter-vue';
import { TUITranslateService, TUIStore, StoreName } from '@tencentcloud/chat-uikit-engine';
import { TUILogin } from '@tencentcloud/tui-core';
import Icon from '../TUIKit/components/common/Icon.vue';
import backSVG from '../TUIKit/assets/icon/back.svg';
import { isH5 } from '../TUIKit/utils/env';
import { Link } from '../utils/link';
import { cancellation } from '../api';
import { IUserProfile } from '../TUIKit/interface';
import router from '../router';
const props = withDefaults(
defineProps<{
userProfile: IUserProfile;
}>(),
{
userProfile: () => ({} as IUserProfile),
},
);
const emits = defineEmits(['closeAboutBox']);
const isAboutBoxShow = ref<boolean>(false);
const isDisclaimerBoxShow = ref<boolean>(false);
const isCancellationBoxShow = ref<boolean>(false);
const SDKVersion = TUIStore.getData(StoreName.APP, 'SDKVersion');
function closeAboutBox() {
isAboutBoxShow.value = false;
emits('closeAboutBox');
}
// Disclaimer dialog
function toggleDisclaimer() {
isDisclaimerBoxShow.value = !isDisclaimerBoxShow.value;
}
function submitDisclaimer() {
isDisclaimerBoxShow.value = false;
}
// Cancellation dialog
function toggleCancellation() {
isCancellationBoxShow.value = !isCancellationBoxShow.value;
}
function submitCancellation() {
isCancellationBoxShow.value = false;
const deleteInfo: any = localStorage.getItem('TUIKit-userInfo');
const deleteInfoList = JSON.parse(deleteInfo);
const options: any = {
userId: deleteInfoList.userId,
token: deleteInfoList.token,
phone: deleteInfoList.phone,
};
TUILogin.logout().then(() => {
localStorage.removeItem('TUIKit-userInfo');
cancellation(options);
router.push({ path: '/' });
});
}
</script>
<style scoped lang="scss">
@import "../styles/common";
.main {
flex: 1;
@include flex;
}
.btn {
flex: 1;
margin-top: 14px;
margin-right: 10px;
&:last-child {
margin-right: 0;
}
}
.btn-default {
@include btn-default;
}
.btn-error {
@include btn-error;
}
.about-container {
.about-box {
padding: 100px 180px;
padding-bottom: 0;
.main {
padding-bottom: 126px;
.main-name {
@include flex;
padding-bottom: 28px;
.logo {
width: 110px;
}
.name {
font-size: 40px;
font-family: PingFangSC-Medium;
font-weight: 500;
}
}
.main-version {
color: #999;
}
}
.footer {
.list {
@include flex;
list-style: none;
.line {
@include flex(row, center, center);
padding-top: 10px;
.link {
font-size: 16px;
padding: 0 10px;
color: #006eff;
border-right: 1px solid #eee;
}
.link:last-child {
border-right: 0 solid #000;
}
.copyright {
font-size: 14px;
padding: 25px 0;
color: #999;
}
}
}
}
}
.disclaimer-box {
@include flex(column, center, stretch);
padding: 10px 10px 0;
.main {
align-items: flex-start;
width: 360px;
font-size: 1rem;
padding: 3.5rem 4rem;
color: #000;
.main-title {
font-size: 18px;
padding-bottom: 5px;
}
.main-info {
text-wrap: wrap;
}
}
.footer {
display: flex;
padding: 4rem;
}
}
.cancellation-box {
@include flex;
padding: 55px 42px;
max-width: 566px;
.main {
@include flex;
padding-bottom: 30px;
.main-text {
margin-top: 20px;
text-wrap: wrap;
text-align: center;
}
.main-info {
text-wrap: wrap;
}
.main-id {
color: #006eff;
}
}
.footer {
display: flex;
flex-direction: row;
width: 100%;
.btn {
flex: 1;
}
}
}
}
.about-box.box-h5 {
padding: 0;
}
.disclaimer-box.box-h5,
.cancellation-box.box-h5 {
padding: 0;
width: 100%;
height: 100%;
justify-content: flex-start;
.main {
flex: none;
box-sizing: border-box;
padding: 30px;
width: 100%;
}
.footer {
box-sizing: border-box;
padding: 10px 30px;
.btn {
height: 50px;
}
}
}
</style>

View File

@ -0,0 +1,31 @@
<template>
<ul class="adv-list">
<li v-for="(item, index) of list" :key="index" :item="item">
<slot name="item" :data="item" />
</li>
</ul>
</template>
<script setup lang="ts">
import { defineProps } from "../TUIKit/adapter-vue";
const props = defineProps({
list: {
type: Array,
default: () => []
}
});
</script>
<style scoped lang="scss">
.adv-list {
flex: 1;
display: flex;
flex-direction: row;
justify-content: space-around;
align-items: center;
li {
width: 150px;
}
}
</style>

View File

@ -0,0 +1,105 @@
<template>
<li class="adv-list-item">
<div class="show-box" @click="showEvent">
<label class="img-box">
<img :src="item?.icon" alt="" />
</label>
<span class="name">{{ item?.name && TUITranslateService.t(`Login.${item?.name}`) }}</span>
</div>
<div class="show-box hover-box" @click="hoverEvent">
<label class="img-box">
<img :src="item?.link" alt="" />
</label>
<span class="name">{{ item?.detail && TUITranslateService.t(`Login.${item?.name}`) }}</span>
<span class="name">{{ item?.detail && TUITranslateService.t(`Login.${item?.detail}`) }}</span>
</div>
</li>
</template>
<script lang="ts" setup>
import { defineProps, defineEmits, withDefaults } from "../TUIKit/adapter-vue";
import { TUITranslateService } from "@tencentcloud/chat-uikit-engine";
type IAdvListItem = {
name?: string;
link?: string;
detail?: string;
};
const props = withDefaults(
defineProps<{
item: IAdvListItem;
}>(),
{
item: () => ({} as IAdvListItem),
}
);
const emits = defineEmits(["showEvent", "hoverEvent"]);
const showEvent = () => {
emits("showEvent", props.item);
};
const hoverEvent = () => {
emits("hoverEvent", props.item);
};
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped lang="scss">
.adv-list-item {
display: flex;
flex-direction: column;
align-items: center;
&:hover {
.show-box {
display: none;
}
.hover-box {
display: flex;
}
}
.show-box {
display: flex;
flex-direction: column;
align-items: center;
.img-box {
width: 82px;
height: 82px;
border-radius: 8px;
background: rgba(255, 255, 255, 1);
box-shadow: 0 8px 12px 0 rgba(223, 235, 253, 0.5);
display: flex;
justify-content: center;
align-items: center;
img {
max-width: 100%;
max-height: 100%;
}
}
.name {
color: rgba(0, 0, 0, 1);
font-size: 12px;
font-weight: 400;
font-family: "PingFang SC";
text-align: left;
line-height: 16px;
height: 16px;
padding-top: 8px;
}
}
.hover-box {
display: none;
position: relative;
top: -15px;
}
}
</style>

View File

@ -0,0 +1,59 @@
<template>
<div class="welcome">
<div class="welcome-title">
{{ TUITranslateService.t("Home.欢迎使用") }}
<img class="logo" src="../assets/image/logo.svg" alt="" />
{{ TUITranslateService.t("即时通信") }}
</div>
<div v-if="isOfficial" class="welcome-content">
{{
TUITranslateService.t(
"Home.我们为您默认提供了一位“示例好友”和一个“示例客服群”您不用额外添加好友和群聊就可完整体验腾讯云 IM 单聊、群聊的所有功能。"
)
}}
<br />
{{ TUITranslateService.t("Home.随时随地") }}
</div>
</div>
</template>
<script setup lang="ts">
import { TUITranslateService, TUIStore, StoreName } from "@tencentcloud/chat-uikit-engine";
const isOfficial = TUIStore.getData(StoreName.APP, "isOfficial");
</script>
<style lang="scss" scoped>
.welcome {
width: 100%;
height: 100%;
box-sizing: border-box;
padding-left: 40px;
padding-top: 100px;
display: flex;
flex-direction: column;
background: url("../assets/image/login-background.png") no-repeat;
background-size: cover;
background-position-x: -17px;
background-position-y: 173px;
.welcome-title {
font-size: 1.75rem;
display: flex;
align-items: center;
font-family: PingFangSC-Medium;
font-weight: 500;
color: #000;
.logo {
width: 40px;
padding-left: 0.98rem;
padding-right: 0.98rem;
}
}
.welcome-content {
padding-top: 1.88rem;
max-width: 393px;
font-size: 16px;
line-height: 24px;
font-family: PingFangSC-Regular;
font-weight: 400;
color: #666;
}
}
</style>

View File

@ -0,0 +1,492 @@
<template>
<div
class="edit-profile-container container dialog"
:class="[isH5 ? 'edit-profile-container-h5' : 'edit-profile-container-pc']"
@click="closeEditProfileBox"
@mousedown.stop
>
<div :class="['edit-profile-box']" @click.stop>
<header class="title">
<div v-if="isH5" class="title-back" @click="closeEditProfileBox">
<Icon :file="backSVG"></Icon>
</div>
<div class="title-name">{{ TUITranslateService.t("Profile.编辑资料") }}</div>
</header>
<div class="edit-form">
<div v-if="isH5" class="edit-form-space"></div>
<div class="edit-form-item">
<div class="form-label">{{ TUITranslateService.t("Profile.头像") }}</div>
<div v-if="isH5" class="form-info" @click="showBottomPopup('avatar')">
<Avatar useSkeletonAnimation :url="userProfile.avatar" size="60px" />
<Icon class="form-info-arrow" :file="rightArrowIcon" size="14px"></Icon>
</div>
<EditProfilePopup
class="form-item"
:show="isPC || currentBottomPopupShow === 'avatar'"
:title="TUITranslateService.t('Profile.选择头像')"
@onClose="closeBottomPopup"
@onSubmit="submitEditProfileBox"
>
<ul class="avatar-list">
<li
:class="[
'avatar-list-item',
currentEditProfile.avatar === avatar && 'avatar-list-item-selected',
]"
v-for="avatar in avatarList"
:key="avatar"
@click="changeCurrentEditProfile('avatar', avatar)"
>
<Avatar useSkeletonAnimation :url="avatar" :size="isPC ? '36px' : '50px'" />
</li>
</ul>
</EditProfilePopup>
</div>
<div v-if="isH5" class="edit-form-space"></div>
<div class="edit-form-item">
<div class="form-label">{{ TUITranslateService.t("Profile.昵称") }}</div>
<div v-if="isH5" class="form-info" @click="showBottomPopup('nick')">
<div class="form-info-content">{{ userProfile.nick }}</div>
<Icon class="form-info-arrow" :file="rightArrowIcon" size="14px"></Icon>
</div>
<EditProfilePopup
class="form-item"
:show="isPC || currentBottomPopupShow === 'nick'"
:title="TUITranslateService.t('Profile.设置昵称')"
@onClose="closeBottomPopup"
@onSubmit="submitEditProfileBox"
>
<input class="form-item-input" type="text" v-model="currentEditProfile.nick" />
</EditProfilePopup>
</div>
<div class="edit-form-item">
<div class="form-label">{{ TUITranslateService.t("Profile.账号") }}</div>
<div class="form-info">{{ userProfile.userID }}</div>
</div>
<div v-if="isH5" class="edit-form-space"></div>
<div class="edit-form-item">
<div class="form-label">{{ TUITranslateService.t("Profile.个性签名") }}</div>
<div v-if="isH5" class="form-info" @click="showBottomPopup('selfSignature')">
<div class="form-info-content">{{ userProfile.selfSignature }}</div>
<Icon class="form-info-arrow" :file="rightArrowIcon" size="14px"></Icon>
</div>
<EditProfilePopup
class="form-item"
:show="isPC || currentBottomPopupShow === 'selfSignature'"
:title="TUITranslateService.t('Profile.个性签名')"
@onClose="closeBottomPopup"
@onSubmit="submitEditProfileBox"
>
<input class="form-item-input" type="text" v-model="currentEditProfile.selfSignature" />
</EditProfilePopup>
</div>
<div class="edit-form-item">
<div class="form-label">{{ TUITranslateService.t("Profile.性别") }}</div>
<div v-if="isH5" class="form-info" @click="showBottomPopup('gender')">
<div>
{{
(userProfile.gender &&
TUITranslateService.t(`Profile.${genderLabelList[userProfile.gender]}`)) ||
""
}}
</div>
<Icon class="form-info-arrow" :file="rightArrowIcon" size="14px"></Icon>
</div>
<EditProfilePopup
class="form-item"
:show="isPC || currentBottomPopupShow === 'gender'"
:title="TUITranslateService.t('Profile.性别选择')"
@onClose="closeBottomPopup"
@onSubmit="submitEditProfileBox"
>
<ul class="gender-list">
<li
class="gender-list-li"
v-for="(value, key) in genderLabelList"
:key="key"
@click="changeCurrentEditProfile('gender', key)"
>
<div
:class="[
'gender-list-item',
currentEditProfile.gender === key && 'gender-list-item-selected',
]"
@click="changeCurrentEditProfile('gender', key)"
>
<input
v-if="isPC"
class="gender-list-radio"
type="radio"
name="gender"
:value="key"
:checked="currentEditProfile.gender === key"
/>
{{ TUITranslateService.t(`Profile.${value}`) }}
</div>
</li>
</ul>
</EditProfilePopup>
</div>
<div class="edit-form-item">
<div class="form-label">{{ TUITranslateService.t("Profile.出生年月") }}</div>
<div v-if="isH5" class="form-info" @click="showBottomPopup('birthday')">
<div class="form-info-content">{{ birthdayObj.format }}</div>
<Icon class="form-info-arrow" :file="rightArrowIcon" size="14px"></Icon>
</div>
<EditProfilePopup
class="form-item"
:show="isPC || currentBottomPopupShow === 'birthday'"
:title="TUITranslateService.t('Profile.请选择出生日期')"
@onClose="closeBottomPopup"
@onSubmit="submitEditProfileBox"
>
<div class="birthday-container">
<DatePicker
class="birthday-date-picker"
type="single"
rangeTableType="one"
:startPlaceholder="TUITranslateService.t('Profile.请选择出生日期')"
popupPosition="top"
:defaultSingleDate="birthdayObj.obj"
@pick="pickBirthday"
>
<template #end-icon>
<Icon :file="calendarSVG" size="16px"></Icon>
</template>
</DatePicker>
</div>
</EditProfilePopup>
</div>
</div>
<footer v-if="!isH5" class="edit-footer">
<button class="btn-close" @click="closeEditProfileBox">
{{ TUITranslateService.t("Profile.取消") }}
</button>
<button class="btn-save" @click="submitEditProfileBox">
{{ TUITranslateService.t("Profile.保存") }}
</button>
</footer>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, defineEmits } from "../TUIKit/adapter-vue";
import TUIChatEngine, {
TUITranslateService,
TUIUserService,
TUIStore,
StoreName,
} from "@tencentcloud/chat-uikit-engine";
import dayjs, { Dayjs } from "dayjs";
import { Toast, TOAST_TYPE } from "../TUIKit/components/common/Toast/index";
import EditProfilePopup from "./EditProfilePopup.vue";
import DatePicker from "../TUIKit/components/common/DatePicker";
import Avatar from "../TUIKit/components/common/Avatar/index.vue";
import Icon from "../TUIKit/components/common/Icon.vue";
import backSVG from "../TUIKit/assets/icon/back.svg";
import rightArrowIcon from "../TUIKit/assets/icon/right-icon.svg";
import calendarSVG from "../assets/icon/calendar.svg";
import { IUserProfile } from "../TUIKit/interface";
import { isH5, isPC } from "../TUIKit/utils/env";
import { enableSampleTaskStatus } from "../TUIKit/utils/enableSampleTaskStatus";
const emits = defineEmits(["closeEditProfileBox"]);
// config
const avatarListBaseUrl = "https://im.sdk.qcloud.com/download/tuikit-resource/avatar/avatar_";
const avatarList = ["1.png", "2.png", "3.png", "4.png", "5.png", "6.png"].map(
(url: string) => avatarListBaseUrl + url
);
const genderLabelList: {
[propsName: string]: string;
} = {
[TUIChatEngine.TYPES.GENDER_MALE]: "男",
[TUIChatEngine.TYPES.GENDER_FEMALE]: "女",
[TUIChatEngine.TYPES.GENDER_UNKNOWN]: "不显示",
};
const userProfile = ref<IUserProfile>({});
// current edit value
const currentEditProfile = ref<IUserProfile>({
avatar: userProfile.value?.avatar,
nick: userProfile.value?.nick,
selfSignature: userProfile.value?.selfSignature,
gender: userProfile.value?.gender,
birthday: userProfile.value?.birthday,
});
const birthdayObj = ref<{ obj: typeof Dayjs; format: string; value: number }>({});
const currentBottomPopupShow = ref<string>("");
TUIStore.watch(StoreName.USER, {
userProfile: (userProfileData: IUserProfile) => {
userProfile.value = userProfileData;
const { avatar, nick, selfSignature, gender, birthday } = userProfileData;
currentEditProfile.value = { avatar, nick, selfSignature, gender, birthday };
birthdayObj.value = generateBirthdayObj(userProfileData.birthday);
},
});
function generateBirthdayObj(YYYYMMDD: any) {
let birthdayDayjsObj: typeof Dayjs = null;
let birthdayFormat = "";
if (YYYYMMDD && typeof YYYYMMDD === "number") {
birthdayDayjsObj = dayjs(YYYYMMDD.toString(), "YYYYMMDD");
birthdayFormat = birthdayDayjsObj.format("YYYY/MM/DD");
}
return {
obj: birthdayDayjsObj,
format: birthdayFormat,
value: userProfile.value?.birthday || 0,
};
}
function showBottomPopup(key: string) {
currentBottomPopupShow.value = key;
}
function closeBottomPopup() {
currentBottomPopupShow.value = "";
}
function pickBirthday(date: typeof Dayjs) {
currentEditProfile.value.birthday = parseInt(date.format("YYYYMMDD"), 10);
}
function changeCurrentEditProfile(key: keyof IUserProfile, value: any) {
if (Object.prototype.hasOwnProperty.call(currentEditProfile.value, key)) {
currentEditProfile.value[key] = value;
}
}
function closeEditProfileBox() {
emits("closeEditProfileBox");
}
function submitEditProfileBox() {
let isNickModified = currentEditProfile.value.nick !== userProfile.value.nick;
const profileOptions = Object.fromEntries(
Object.entries(currentEditProfile.value).filter(([key, value]) => {
return value !== null && value !== undefined && value !== "";
})
);
TUIUserService.updateMyProfile(profileOptions)
.then(() => {
isNickModified && enableSampleTaskStatus("modifyNickName");
Toast({
message: TUITranslateService.t("Profile.修改个人资料成功"),
type: TOAST_TYPE.SUCCESS,
});
isPC && closeEditProfileBox();
})
.catch((error: Error) => {
Toast({
message: TUITranslateService.t("Profile.修改个人资料失败") + error?.message,
type: TOAST_TYPE.ERROR,
});
isPC && closeEditProfileBox();
});
}
</script>
<style lang="scss" scoped>
@import "../styles/common.scss";
.edit-profile-container {
@extend .container;
font-size: 14px;
.edit-profile-box {
@extend .box;
align-items: stretch;
.edit-form {
flex: 1;
@include flex(column, flex-start, stretch);
.edit-form-item {
@include flex(row, flex-start, center);
min-height: 54px;
padding: 10px;
.form-label {
box-sizing: border-box;
width: 70px;
margin-right: 20px;
color: #333;
}
.form-item {
@include flex(row, flex-start, stretch);
.avatar-list {
@include flex(row, space-between, stretch);
.avatar-list-item {
margin: 10px;
border: 1px solid transparent;
}
.avatar-list-item:first-child {
margin-left: 0px;
}
.avatar-list-item-selected {
border: 1px solid #006eff;
color: #006eff;
border-radius: 5px;
}
}
.form-item-input {
flex: 1;
padding: 6px 10px;
border: 1px solid rgba(131, 137, 153, 0.4);
border-radius: 2px;
line-height: 20px;
color: #596174;
}
.gender-list {
@include flex(row, space-between, center);
.gender-list-li {
@include flex(row);
margin-right: 20px;
.gender-list-item {
@include flex(row);
flex: 1;
font-size: 14px;
.gender-list-item-radio {
margin: 0px 2px;
}
}
}
}
.birthday-container {
@include flex(row, space-between, stretch);
flex: 1;
padding: 6px 10px;
border: 1px solid rgba(131, 137, 153, 0.4);
.birthday-date-picker {
@include flex(row);
flex: 1;
height: 20px;
:deep(.tui-date-picker-input) {
@include flex(row, flex-start);
flex: 1;
}
:deep(.tui-date-picker-input-start) {
padding: 2px 0px;
text-align: start;
font-size: 14px;
&::placeholder {
text-align: start;
}
}
:deep(.tui-date-picker-dialog-container-one) {
left: -220px;
}
}
}
}
}
}
.edit-footer {
@include flex(row, flex-end);
.btn-close {
@include btn-normal;
margin-right: 10px;
}
.btn-save {
@include btn-default;
}
.btn-close,
.btn-save {
width: 90px;
height: 30px;
padding: 2px;
}
}
}
}
.edit-profile-container-pc {
.edit-profile-box {
width: 495px;
height: 472px;
border-radius: 10px;
padding: 20px;
.edit-form {
.edit-form-item {
.form-item {
flex: 1;
}
}
}
}
.title {
justify-content: flex-start;
padding: 0px;
}
}
.edit-profile-container-h5 {
@extend .edit-profile-container;
.edit-profile-box {
@extend .box-h5;
width: 100%;
height: 100%;
background-color: #efefef;
.title {
background-color: #ffffff;
}
.edit-form {
.edit-form-item {
background-color: #ffffff;
.form-label {
font-size: 16px;
color: #000000;
width: 80px;
}
.form-info {
@include flex(row, flex-end, stretch);
.form-info-content {
@include single-line-ellipsis(auto);
}
flex: 1;
overflow: hidden;
font-size: 16px;
color: rgba(151, 151, 151, 1);
.form-info-arrow {
margin-left: 2px;
}
}
.form-item {
flex: none;
.avatar-list,
.form-item-input,
.gender-list,
.birthday-container {
margin: 0 20px;
}
.avatar-list {
.avatar-list-item {
margin: 10px 0px;
}
}
.gender-list {
.gender-list-li {
margin: 0px;
.gender-list-item {
border: 1px solid rgba(221, 221, 221, 1);
font-size: 16px;
border-radius: 5px;
width: 120px;
height: 40px;
&-selected {
border: 1px solid #006eff;
color: #006eff;
border-radius: 5px;
}
}
}
}
.form-item-input {
padding: 14px 10px;
font-size: 16px;
border: 0px;
background-color: rgba(248, 248, 248, 1);
}
}
}
.edit-form-space {
box-sizing: border-box;
height: 10px;
padding: 0px;
background-color: transparent;
}
}
}
}
</style>

View File

@ -0,0 +1,38 @@
<template>
<BottomPopup
class="form-item-bottom-popup"
:show="props.show"
:title="props.title"
:showHeaderCloseButton="true"
:showFooterSubmitButton="true"
:submitButtonContent="TUITranslateService.t('确认')"
borderRadius="20px"
@onClose="onClose"
@onSubmit="onSubmit"
>
<slot></slot>
</BottomPopup>
</template>
<script setup lang="ts">
import { withDefaults, defineProps, defineEmits } from "../TUIKit/adapter-vue";
import { TUITranslateService } from "@tencentcloud/chat-uikit-engine";
import BottomPopup from "../TUIKit/components/common/BottomPopup";
const props = withDefaults(
defineProps<{
show: boolean;
title: string;
}>(),
{
show: false,
title: "",
}
);
const emits = defineEmits(["onClose", "onSubmit"]);
function onClose() {
emits("onClose");
}
function onSubmit() {
emits("onSubmit");
}
</script>
<style scoped lang="scss"></style>

View File

@ -0,0 +1,147 @@
<template>
<div class="header">
<div class="left">
<div v-if="showType === 'menu'" class="menu" @click="toggleMenu">
<Icon :file="menuPNG" />
<div class="menu-guide">
{{ TUITranslateService.t('使用指引') }}
</div>
</div>
<div v-else-if="showType === 'logo'" class="logo">
<a :href="Link.im.url" target="_blank" class="logo">
<img class="logo-img" src="../assets/image/txc-logo.svg" alt="" />
<div class="logo-name label-tencent">{{ TUITranslateService.t('腾讯云') }}</div>
<div class="logo-name label-im">{{ TUITranslateService.t('即时通信IM') }}</div>
</a>
</div>
</div>
<div class="right">
<el-dropdown @command="changeLanguage">
<div class="dropdown">
<Icon :file="globalPNG" />
<div class="dropdown-label">
{{ localeLabelMap[locale] }}
</div>
<Icon :file="arrowDownPNG" width="10px" height="5px" />
</div>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item command="zh" class="language-item"> 简体中文 </el-dropdown-item>
<el-dropdown-item command="en" class="language-item"> English </el-dropdown-item>
<el-dropdown-item command="zh_tw" class="language-item"> 繁體中文 </el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</div>
</template>
<script lang="ts" setup>
import { withDefaults, defineProps, defineEmits, ref } from '@/TUIKit/adapter-vue';
import { TUITranslateService } from '@tencentcloud/chat-uikit-engine';
import { TUICore, TUIConstants } from '@tencentcloud/tui-core';
import Icon from '@/TUIKit/components/common/Icon.vue';
import menuPNG from '@/views/im/icon/menu.png';
import globalPNG from '@/views/im/icon/global.png';
import arrowDownPNG from '@/views/im/icon/arrow-down.png';
import { Link } from '@/utils/link';
const props = withDefaults(
defineProps<{
showType: 'menu' | 'logo';
defaultLanguage: string;
}>(),
{
showType: 'logo',
defaultLanguage: 'zh'
}
);
const localeLabelMap: { [propsName: string]: string } = {
zh: '简体中文',
en: 'English',
zh_tw: '繁體中文'
};
const emits = defineEmits(['toggleMenu', 'closeMenu', 'changeLanguage']);
const locale = ref<string>(props.defaultLanguage);
function toggleMenu() {
emits('toggleMenu');
}
function changeLanguage(value: string) {
TUITranslateService.changeLanguage(value).then(() => {
locale.value = value;
emits('changeLanguage', locale.value);
TUICore.notifyEvent(TUIConstants.TUITranslate.EVENT.LANGUAGE_CHANGED, TUIConstants.TUITranslate.EVENT_SUB_KEY.CHANGE_SUCCESS, {
language: locale.value
});
});
}
</script>
<style scoped lang="scss">
.header {
display: flex;
justify-content: space-between;
align-items: center;
background: #fff;
box-sizing: border-box;
padding: 20px 34px;
user-select: none;
.left {
.menu,
.logo {
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
font-size: 14px;
font-weight: 500;
line-height: 20px;
}
.menu {
.menu-guide {
margin-left: 15px;
}
}
.logo {
.logo-img {
width: 30px;
margin-right: 7px;
}
.logo-name {
font-family: PingFangSC-Regular;
font-size: 18px;
font-weight: 500;
display: flex;
}
.label-tencent {
padding-right: 15px;
border-right: 1px solid #ddd;
}
.label-im {
padding-left: 15px;
}
}
}
.right {
.dropdown {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
cursor: pointer;
.dropdown-label {
padding: 0 8px;
}
}
}
}
</style>

View File

@ -0,0 +1,427 @@
<template>
<div :class="['menu', isH5 && 'menu-h5']">
<div
v-if="isPC"
class="header header-border"
>
<div class="header-tencent-cloud">
<img
class="header-icon"
src="../assets/image/txc-logo.svg"
alt=""
>
<span class="header-name">{{ TUITranslateService.t("腾讯云") }}</span>
</div>
<div class="header-im header-name">
{{ TUITranslateService.t("即时通信IM") }}
</div>
</div>
<div
v-if="isH5"
class="header header-guide"
>
<div class="header-name">
{{ TUITranslateService.t("使用指引") }}
</div>
<div
class="header-close"
@click="closeMenu"
>
{{ TUITranslateService.t("关闭") }}
</div>
</div>
<div class="main">
<div
v-for="item in apkQRCodeList"
:key="item.url"
class="task"
>
<div class="task-title">
{{ TUITranslateService.t(`Home.${item.label}`) }}
</div>
<div class="task-list qr-box">
<img
class="qr-code"
:src="item.url"
>
</div>
</div>
<div class="task">
<div class="task-title">
{{ TUITranslateService.t("Home.建议体验功能") }}
</div>
<div class="task-list">
<div
v-for="(taskLabel, taskKey) in taskLabelMap"
:key="taskKey"
:class="['task-list-item', tasks[taskKey] && 'task-list-item-done']"
>
<div class="task-list-item-label">
{{ TUITranslateService.t(`Home.${taskLabel}`) }}
</div>
<div class="task-list-item-status">
{{ TUITranslateService.t(tasks[taskKey] ? "Home.已完成" : "Home.待体验") }}
</div>
</div>
</div>
</div>
<div class="step">
<div class="step-title">
{{ TUITranslateService.t("Home.用UI组件快速集成") }}
</div>
<div class="step-list">
<div
v-for="(step, index) in stepList"
:key="step.label"
class="step-list-item"
>
<div class="step-list-item-index">
{{ index + 1 }}
</div>
<a
class="step-list-item-label"
:href="step.url"
target="_blank"
>{{
TUITranslateService.t(`Home.${step.label}`)
}}</a>
</div>
</div>
</div>
</div>
<div class="footer">
<div class="footer-card-list">
<div
v-for="advItem in advList"
:key="advItem.label"
class="footer-card"
@click="openLink(advItem.url)"
>
<div class="footer-card-content">
<div>{{ TUITranslateService.t(`Home.${advItem.label}`) }}</div>
<div>{{ TUITranslateService.t(`Home.${advItem.subLabel}`) }}</div>
</div>
<div class="footer-card-button">
{{ TUITranslateService.t(`Home.${advItem.btnText}`) }}
</div>
</div>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { defineEmits, onMounted, ref, onUnmounted } from '@/TUIKit/adapter-vue';
import { TUITranslateService, TUIStore, StoreName } from '@tencentcloud/chat-uikit-engine';
import { Link } from '@/utils/link';
import { isPC, isH5 } from '@/TUIKit/utils/env';
import { deepCopy } from '@/TUIKit/components/TUIChat/utils/utils';
interface ITasks {
[propsName: string]: boolean;
}
const emits = defineEmits(['closeMenu']);
const stepList = Link.stepList;
const advList = Link.advList;
const apkQRCodeList = Link.apkQRCodeList;
const tasks = ref<ITasks>({
sendMessage: false,
revokeMessage: false,
modifyNickName: false,
groupChat: false,
muteGroup: false,
dismissGroup: false,
call: false,
searchCloudMessage: false,
customerService: false,
});
const taskLabelMap = {
sendMessage: '发送一条消息',
revokeMessage: '撤回一条消息',
modifyNickName: '修改一次我的昵称',
groupChat: '发起一个群聊',
muteGroup: '开启一次群禁言',
dismissGroup: '解散一个群聊',
call: '发起一次通话',
searchCloudMessage: '搜索一次消息',
customerService: '进行一次客服会话',
};
onMounted(() => {
TUIStore.watch(StoreName.APP, {
tasks: setTasksValue,
});
});
onUnmounted(() => {
TUIStore.unwatch(StoreName.APP, {
tasks: setTasksValue,
});
});
function setTasksValue(tasksValue: ITasks) {
if (JSON.stringify(tasksValue) === '{}') {
return;
}
tasks.value = deepCopy(tasksValue);
}
function closeMenu() {
emits('closeMenu');
}
function openLink(url: string) {
window.open(url);
}
</script>
<style scoped lang="scss">
.menu,
.header,
.main,
.footer {
box-sizing: border-box;
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-start;
}
.menu {
width: 300px;
z-index: 100;
background: rgb(255, 255, 255);
box-shadow: 10px 20px 30px 0 rgba(56, 73, 90, 0.09);
padding: 0 30px;
user-select: none;
overflow: auto;
.header {
width: 100%;
flex-direction: row;
padding: 20px 0;
.header-tencent-cloud,
.header-im {
box-sizing: border-box;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
}
.header-tencent-cloud {
padding-right: 20px;
}
.header-im {
padding-left: 20px;
border-left: 1px solid rgb(221, 221, 221);
}
.header-name {
font-size: 16px;
font-weight: 500;
color: rgb(0, 0, 0);
font-style: normal;
font-family: PingFangSC-Regular;
}
.header-icon {
width: 30px;
margin-right: 7px;
}
}
.header-border {
border-bottom: 1px solid rgb(221, 221, 221);
}
.header-guide {
padding-bottom: 0;
justify-content: space-between;
.header-name {
font-family: PingFangSC-Medium;
font-weight: 500;
font-size: 20px;
color: #000;
line-height: 28px;
}
.header-close {
font-family: PingFangSC-Regular;
font-weight: 400;
color: #006eff;
font-size: 18px;
}
}
.main {
width: 100%;
padding-bottom: 16px;
font-size: 14px;
font-family: PingFangSC-Regular;
font-weight: 400;
.task,
.step {
width: 100%;
}
.task-title,
.step-title {
padding: 20px 0;
font-size: 16px;
font-family: PingFangSC-Medium;
font-weight: 500;
}
.task-list-item,
.step-list-item {
box-sizing: border-box;
display: flex;
flex-direction: row;
padding-bottom: 16px;
}
.task {
padding-bottom: 14px;
border-bottom: 1px solid rgb(221, 221, 221);
.task-list-item {
justify-content: space-between;
.task-list-item-label {
color: rgb(181, 181, 181);
}
.task-list-item-status {
background-color: rgba(207, 215, 224);
border-radius: 2px;
padding: 0 5px;
color: rgb(255, 255, 255);
text-wrap: nowrap;
height: fit-content;
align-self: center;
}
}
.task-list-item-done {
.task-list-item-label {
color: rgb(51, 51, 51);
}
.task-list-item-status {
background-color: rgb(20, 122, 255);
}
}
}
.step-list-item {
justify-content: flex-start;
.step-list-item-index {
background: rgba(81, 94, 136, 0.04);
border: 1px solid #d2d6dc;
color: rgb(210, 214, 220);
display: inline-flex;
justify-content: center;
align-items: center;
width: 20px;
height: 20px;
border-radius: 50%;
font-size: 11.67px;
margin-right: 10px;
}
.step-list-item-label {
line-height: 22px;
color: #147aff;
}
}
}
.footer {
width: 100%;
margin-top: auto;
margin-bottom: 30px;
.footer-card-list {
box-sizing: border-box;
display: flex;
width: 100%;
.footer-card {
box-sizing: border-box;
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
width: 100%;
border: 1px solid #96c3ff;
border-radius: 4px;
background-image: url("../assets/image/adv-background.svg");
background-size: cover;
.footer-card-content {
padding: 10px;
font-size: 14px;
font-weight: 500;
line-height: 20px;
}
.footer-card-button {
margin-right: 10px;
padding: 1px 7px;
background: #147aff;
border-radius: 0.88rem;
box-shadow: 0 0.19rem 0.25rem 0 rgba(255, 255, 255, 0.7),
0 0.13rem 0.38rem 0 rgba(20, 122, 255, 0.55);
color: #fff;
}
}
}
}
.qr-box {
display: flex;
justify-content: center;
align-items: center;
.qr-code {
max-width: 150px;
max-height: 150px;
}
}
}
.menu-h5 {
flex: 1;
border-radius: 12px 12px 0 0;
margin-top: 200px;
height: calc(100% - 200px);
display: flex;
flex-direction: column;
overflow: hidden;
padding: 0;
.header-guide {
position: sticky;
padding: 15px 30px;
}
.main {
flex: 1;
overflow: auto;
padding: 0 30px;
}
.footer {
margin: 10px 0;
padding: 0 30px;
}
}
</style>

View File

@ -0,0 +1,256 @@
<template>
<div :class="['navbar', isH5 && 'navbar-h5']">
<div v-if="isPC" class="header">
<div class="user-info">
<Avatar useSkeletonAnimation :url="avatar" />
<div class="user-info-main">
<slot name="profile"></slot>
</div>
</div>
</div>
<div class="navbar-list">
<div
v-for="item in navBarListForShow"
:key="item.name"
:class="['navbar-list-item', currentSelectedNav === item.name && 'navbar-list-item-selected']"
@click="switchNavBar(item.name)"
>
<Icon
:file="(currentSelectedNav === item.name && item.selectedIcon) || (isPC && item.icon) || (!isPC && item.h5Icon)"
:width="isH5 ? '20px' : '24px'"
:height="isH5 ? '20px' : '24px'"
></Icon>
<div v-if="isH5" class="navbar-list-item-name">
{{ TUITranslateService.t(`Home.${item.label}`) }}
</div>
</div>
</div>
<div v-if="isPC" class="footer">
<div class="setting">
<Icon :file="settingPNG" @click="toggleSetting" @mousedown.stop></Icon>
<div v-if="isSettingShow" class="setting-more">
<slot name="setting"></slot>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed, defineEmits, watch, withDefaults, defineProps } from '@/TUIKit/adapter-vue';
import { TUIStore, StoreName, TUITranslateService } from '@tencentcloud/chat-uikit-engine';
import Icon from '@/TUIKit/components/common/Icon.vue';
import Avatar from '@/TUIKit/components/common/Avatar/index.vue';
import messageWebSVG from '@/views/im/icon/message.svg';
import messageH5SVG from '@/views/im/icon/message-real.svg';
import messageSelectedSVG from '@/views/im/icon/message-selected.svg';
import relationWebSVG from '@/views/im/icon/relation.svg';
import relationH5SVG from '@/views/im/icon/relation-real.svg';
import relationSelectedSVG from '@/views/im/icon/relation-selected.svg';
import profileH5SVG from '@/views/im/icon/profile.svg';
import profileSelectedSVG from '@/views/im/icon/profile-selected.svg';
import settingPNG from '@/views/im/icon/setting.png';
import { IUserProfile } from '@/TUIKit/interface';
import { isPC, isH5 } from '@/TUIKit/utils/env';
export interface INavBarItem {
icon: any;
h5Icon: any;
selectedIcon: any;
name: string;
label: string;
}
const props = withDefaults(
defineProps<{
currentNavBar: string;
isSettingShow: boolean;
}>(),
{
currentNavBar: () => 'message',
isSettingShow: false
}
);
const emits = defineEmits(['update:currentNavBar', 'update:isSettingShow']);
const currentSettingStatus = ref<boolean>(false);
const currentSelectedNav = ref(props.currentNavBar);
const navBarListForShow = computed<Array<INavBarItem>>(() => {
return isPC ? navBarList.filter((item: INavBarItem) => item.name !== 'profile') : navBarList;
});
const avatar = ref<string>('');
TUIStore.watch(StoreName.USER, {
userProfile: (userProfileData: IUserProfile) => {
avatar.value = userProfileData?.avatar || '';
}
});
const navBarList: Array<INavBarItem> = [
{
icon: messageWebSVG,
h5Icon: messageH5SVG,
selectedIcon: messageSelectedSVG,
name: 'message',
label: '消息'
},
{
icon: relationWebSVG,
h5Icon: relationH5SVG,
selectedIcon: relationSelectedSVG,
name: 'relation',
label: '通讯录'
},
{
icon: profileH5SVG,
h5Icon: profileH5SVG,
selectedIcon: profileSelectedSVG,
name: 'profile',
label: '个人中心'
}
];
function toggleSetting() {
currentSettingStatus.value = !currentSettingStatus.value;
emits('update:isSettingShow', currentSettingStatus.value);
}
function switchNavBar(name: string) {
currentSelectedNav.value = name;
emits('update:currentNavBar', name);
}
watch(
() => props.currentNavBar,
() => {
currentSelectedNav.value = props.currentNavBar;
},
{
immediate: true
}
);
watch(
() => props.isSettingShow,
() => {
currentSettingStatus.value = props.isSettingShow;
},
{
immediate: true
}
);
</script>
<style scoped lang="scss">
.navbar {
box-sizing: border-box;
width: 60px;
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: center;
padding: 56px 0 17px;
background: #e8e8e9;
user-select: none;
.navbar-list {
box-sizing: border-box;
display: flex;
flex: 1;
flex-direction: column;
width: 100%;
.navbar-list-item {
padding: 13px;
cursor: pointer;
}
.navbar-list-item-selected {
background: #ddd;
}
}
.header,
.footer {
display: flex;
width: 100%;
flex-direction: row;
justify-content: center;
align-items: center;
}
.header {
.user-info {
padding: 12px;
position: relative;
.avatar {
width: 36px;
height: 36px;
border-radius: 5px;
}
&:hover {
.user-info-main {
display: block;
}
}
.user-info-main {
width: 100%;
display: none;
position: absolute;
min-width: 165px;
max-width: 200px;
top: 0;
left: 100%;
z-index: 5;
padding: 14px 20px;
border-radius: 0 4px 4px 0;
background: #fff;
box-shadow: 0 1px 10px 0 rgba(2, 16, 43, 0.15);
}
}
}
.footer {
.setting {
cursor: pointer;
padding: 12px;
position: relative;
flex: 1;
.setting-more {
background: #fff;
box-shadow: 0 1px 10px 0 rgba(2, 16, 43, 0.15);
min-width: 11.25rem;
border-radius: 0 4px 4px 0;
position: absolute;
bottom: 10px;
left: 100%;
z-index: 9999;
white-space: nowrap;
}
}
}
}
.navbar-h5 {
padding: 0px;
flex-direction: row;
flex: 1;
box-shadow: 0 0 18px 0 #f0f6ff;
border-radius: 9px;
overflow: hidden;
.navbar-list {
flex-direction: row;
background: #ebf0f6;
justify-content: space-around;
.navbar-list-item {
flex: 1;
padding: 10px;
.navbar-list-item-name {
text-align: center;
padding-top: 4px;
font-size: 12px;
color: #a0a3a6;
}
}
.navbar-list-item-selected {
background-color: inherit;
.navbar-list-item-name {
color: #006eff;
}
}
}
}
</style>

View File

@ -0,0 +1,423 @@
<template>
<div :class="['TUI-profile', !isPC && 'TUI-profile-h5']">
<div v-if="displayType !== 'setting'" :class="['TUI-profile-basic', !isPC && 'TUI-profile-h5-basic']">
<img
:class="['TUI-profile-basic-avatar', !isPC && 'TUI-profile-h5-basic-avatar']"
:src="userProfile.avatar || 'https://web.sdk.qcloud.com/component/TUIKit/assets/avatar_21.png'"
/>
<div :class="['TUI-profile-basic-info', !isPC && 'TUI-profile-h5-basic-info']">
<div :class="['TUI-profile-basic-info-nick', !isPC && 'TUI-profile-h5-basic-info-nick']">
{{ userProfile.nick || '-' }}
</div>
<div :class="['TUI-profile-basic-info-id', !isPC && 'TUI-profile-h5-basic-info-id']">
<label :class="['TUI-profile-basic-info-id-label', !isPC && 'TUI-profile-h5-basic-info-id-label']"
>{{ TUITranslateService.t('Profile.用户ID') }}:</label
>
<div :class="['TUI-profile-basic-info-id-value', !isPC && 'TUI-profile-h5-basic-info-id-value']">
{{ userProfile.userID }}
</div>
</div>
</div>
</div>
<div
v-if="displayType !== 'profile' && (!isPC || (showSetting && !hideSetting))"
ref="settingDomRef"
:class="['TUI-profile-setting', !isPC && 'TUI-profile-h5-setting']"
@click.stop
>
<div
v-for="item in settingList"
:key="item.value"
:class="['TUI-profile-setting-item', !isPC && 'TUI-profile-h5-setting-item', item.value === 'exit' && 'TUI-profile-h5-setting-item-exit']"
>
<div :class="['TUI-profile-setting-item-label', !isPC && 'TUI-profile-h5-setting-item-label']" @click="handleSettingListItemOnClick(item)">
<div :class="['label-left']">
<div :class="['label-title']">
{{ TUITranslateService.t(`Profile.${item.label}`) }}
</div>
<div v-if="item.children && !isPC && item.childrenShowType === 'switch'" :class="['label-desc']">
{{ item.value }}
</div>
</div>
<div :class="['label-right']">
<div
v-if="!isPC && item.children && item.selectedChild && item.childrenShowType === 'bottomPopup'"
:class="['TUI-profile-setting-item-label-value', !isPC && 'TUI-profile-h5-setting-item-label-value']"
>
{{ TUITranslateService.t(`Profile.${item?.children[item.selectedChild]?.label}`) }}
</div>
<Icon v-if="item.children" :file="rightArrowIcon" width="14px" height="14px" />
</div>
</div>
<div v-if="item.children && isPC" :class="['TUI-profile-setting-item-children', !isPC && 'TUI-profile-h5-setting-item-children']">
<div
v-for="child in item.children"
:key="child.value"
:class="['TUI-profile-setting-item-children-item', !isPC && 'TUI-profile-h5-setting-item-children-item']"
@click="handleSettingListItemOnClick(child)"
>
<div :class="['TUI-profile-setting-item-children-item-label', !isPC && 'TUI-profile-h5-setting-item-children-item-label']">
{{ TUITranslateService.t(`Profile.${child.label}`) }}
</div>
<Icon v-if="item.selectedChild === child.value" :file="selectedIcon" width="14px" height="14px" />
</div>
</div>
<!-- <BottomPopup
v-if="item.children && !isPC && item.childrenShowType === 'bottomPopup'"
:show="item.showChildren"
@onClose="item.showChildren = false"
>
<div
v-for="child in item.children"
:key="child.value"
:class="['TUI-profile-setting-item-bottom-popup', !isPC && 'TUI-profile-h5-setting-item-bottom-popup']"
@click="handleSettingListItemOnClick(child)"
>
{{ TUITranslateService.t(`Profile.${child.label}`) }}
</div>
</BottomPopup>-->
</div>
</div>
<!-- <AboutDialog v-if="isAboutBoxShow" :userProfile="userProfile" @closeAboutBox="closeAboutBox" />
<EditProfileDialog v-if="isEditProfileBoxShow" @closeEditProfileBox="closeEditProfileBox" />-->
</div>
</template>
<script lang="ts" setup>
import { ref, watch, nextTick, onMounted } from '@/TUIKit/adapter-vue';
import TUIChatEngine, { TUITranslateService, TUIUserService, TUIStore, StoreName, TUIChatService } from '@tencentcloud/chat-uikit-engine';
import { TUILogin } from '@tencentcloud/tui-core';
import { Toast, TOAST_TYPE } from '@/TUIKit/components/common/Toast/index';
import BottomPopup from '@/TUIKit/components/common/BottomPopup/index.vue';
import Icon from '@/TUIKit/components/common/Icon.vue';
/*import AboutDialog from '../components/About.vue';
import EditProfileDialog from '../components/EditProfile.vue';*/
import rightArrowIcon from '@/TUIKit/assets/icon/right-icon.svg';
import selectedIcon from '@/TUIKit/assets/icon/selected.svg';
import { IUserProfile } from '@/TUIKit/interface';
import { isPC } from '@/TUIKit/utils/env';
import { deepCopy } from '@/TUIKit/components/TUIChat/utils/utils';
import { translator } from '@/TUIKit/components/TUIChat/utils/translation';
const props = defineProps({
displayType: {
type: String,
default: 'profile' // "profile"/"setting"/"all"
},
showSetting: {
type: Boolean,
default: false
}
});
const router = useRouter();
const emits = defineEmits(['update:showSetting']);
const settingDomRef = ref();
const userProfile = ref<IUserProfile>({});
const isAboutBoxShow = ref<boolean>(false);
const isEditProfileBoxShow = ref<boolean>(false);
const hideSetting = ref<boolean>(false);
const settingList = ref<{
[propsName: string]: {
value: string;
label: string;
onClick?: any;
// children related
selectedChild?: string;
childrenShowType?: string; // "bottomPopup"/"switch"
showChildren?: boolean;
children?: {
[propsName: string]: {
value: string;
label: string;
onClick?: any;
};
};
};
}>({
editProfile: {
value: 'editProfile',
label: '编辑资料',
onClick: () => {
hideSetting.value = true;
isEditProfileBoxShow.value = true;
}
},
allowType: {
value: 'allowType',
label: '加我为好友时',
selectedChild: '',
childrenShowType: 'bottomPopup',
showChildren: false,
onClick: (item: any) => {
if (!isPC) {
item.showChildren = true;
}
},
children: {
[TUIChatEngine.TYPES.ALLOW_TYPE_ALLOW_ANY]: {
value: TUIChatEngine.TYPES.ALLOW_TYPE_ALLOW_ANY,
label: '同意任何用户加好友',
onClick: (item: any) => {
switchAllowType(item.value);
}
},
[TUIChatEngine.TYPES.ALLOW_TYPE_NEED_CONFIRM]: {
value: TUIChatEngine.TYPES.ALLOW_TYPE_NEED_CONFIRM,
label: '需要验证',
onClick: (item: any) => {
switchAllowType(item.value);
}
},
[TUIChatEngine.TYPES.ALLOW_TYPE_DENY_ANY]: {
value: TUIChatEngine.TYPES.ALLOW_TYPE_DENY_ANY,
label: '拒绝任何人加好友',
onClick: (item: any) => {
switchAllowType(item.value);
}
}
}
},
displayMessageReadReceipt: {
value: 'displayMessageReadReceipt',
label: '消息阅读状态',
selectedChild: 'userLevelReadReceiptOpen',
childrenShowType: 'bottomPopup',
showChildren: false,
onClick(item: any) {
if (!isPC) {
item.showChildren = true;
}
},
children: {
userLevelReadReceiptOpen: {
value: 'userLevelReadReceiptOpen',
label: '开启',
onClick() {
switchEnableUserLevelReadReceipt(true);
}
},
userLevelReadReceiptClose: {
value: 'userLevelReadReceiptClose',
label: '关闭',
onClick() {
switchEnableUserLevelReadReceipt(false);
}
}
}
},
displayOnlineStatus: {
value: 'displayOnlineStatus',
label: '显示在线状态',
selectedChild: 'userLevelOnlineStatusOpen',
childrenShowType: 'bottomPopup',
showChildren: false,
onClick(item: any) {
if (!isPC) {
item.showChildren = true;
}
},
children: {
userLevelOnlineStatusOpen: {
value: 'userLevelOnlineStatusOpen',
label: '开启',
onClick() {
switchUserLevelOnlineStatus(true);
}
},
userLevelOnlineStatusClose: {
value: 'userLevelOnlineStatusClose',
label: '关闭',
onClick() {
switchUserLevelOnlineStatus(false);
}
}
}
},
translateLanguage: {
value: 'translateLanguage',
label: '翻译语言',
selectedChild: 'zh',
childrenShowType: 'bottomPopup',
showChildren: false,
onClick(item: any) {
if (!isPC) {
item.showChildren = true;
}
},
children: {
zh: {
value: 'zh',
label: 'zh',
onClick() {
switchTranslationTargetLanguage('zh');
}
},
en: {
value: 'en',
label: 'en',
onClick() {
switchTranslationTargetLanguage('en');
}
},
jp: {
value: 'jp',
label: 'jp',
onClick() {
switchTranslationTargetLanguage('jp');
}
},
kr: {
value: 'kr',
label: 'kr',
onClick() {
switchTranslationTargetLanguage('kr');
}
}
}
},
about: {
value: 'about',
label: '关于腾讯云IM',
selectedChild: '',
childrenShowType: 'bottomPopup',
showChildren: false,
children: {},
onClick: () => {
hideSetting.value = true;
isAboutBoxShow.value = true;
}
},
exit: {
value: 'exit',
label: '退出登录',
onClick: () => {
TUILogin.logout().then(() => {
router.push({ path: '/' });
});
}
}
});
const handleSettingListItemOnClick = (item: any) => {
if (item?.onClick && typeof item?.onClick === 'function') {
item.onClick(item);
}
};
const closeAboutBox = () => {
isAboutBoxShow.value = false;
emits('update:showSetting', false);
};
const closeEditProfileBox = () => {
isEditProfileBoxShow.value = false;
emits('update:showSetting', false);
};
const updateMyProfile = (props: object) => {
TUIUserService.updateMyProfile(props)
.then(() => {
Toast({
message: '更新用户资料成功',
type: TOAST_TYPE.SUCCESS
});
})
.catch((err: any) => {
console.warn('更新用户资料失败', err);
Toast({
message: '更新用户资料失败',
type: TOAST_TYPE.ERROR
});
});
};
TUIStore.watch(StoreName.USER, {
userProfile: (userProfileData: IUserProfile) => {
userProfile.value = deepCopy(userProfileData);
if (userProfile?.value?.allowType) {
settingList.value.allowType.selectedChild = userProfile?.value?.allowType;
}
},
displayMessageReadReceipt(isDisplay: boolean) {
settingList.value.displayMessageReadReceipt.selectedChild = isDisplay ? 'userLevelReadReceiptOpen' : 'userLevelReadReceiptClose';
},
displayOnlineStatus(isOnlineStatusDisplay: boolean) {
settingList.value.displayOnlineStatus.selectedChild = isOnlineStatusDisplay ? 'userLevelOnlineStatusOpen' : 'userLevelOnlineStatusClose';
}
});
// pc click outside
let clickOutside = false;
let clickInner = false;
const onClickOutside = (component: any) => {
document.addEventListener('mousedown', onClickDocument);
component?.addEventListener && component?.addEventListener('mousedown', onClickTarget);
};
const onClickDocument = () => {
clickOutside = true;
if (!clickInner && clickOutside) {
emits('update:showSetting', false);
removeClickListener(settingDomRef.value);
}
clickOutside = false;
clickInner = false;
};
const onClickTarget = () => {
clickInner = true;
};
const removeClickListener = (component: any) => {
document.removeEventListener('mousedown', onClickDocument);
component?.removeEventListener && component?.removeEventListener('mousedown', onClickTarget);
};
watch(
() => props.showSetting,
(newVal: any, oldVal: any) => {
if (isPC && newVal && !oldVal) {
nextTick(() => {
onClickOutside(settingDomRef.value);
});
}
},
{
immediate: true
}
);
onMounted(() => {
// get user profile
TUIUserService.getUserProfile().then((res: any) => {
userProfile.value = res.data;
});
// get current target language when component mounted
const lang = TUIStore.getData(StoreName.USER, 'targetLanguage');
if (lang) {
settingList.value.translateLanguage.selectedChild = lang;
}
});
function switchAllowType(value: string) {
updateMyProfile({ allowType: value });
settingList.value.allowType.showChildren = false;
}
function switchEnableUserLevelReadReceipt(status: boolean) {
TUIStore.update(StoreName.USER, 'displayMessageReadReceipt', status);
settingList.value.displayMessageReadReceipt.showChildren = false;
}
function switchUserLevelOnlineStatus(status: boolean) {
TUIUserService.switchUserStatus({ displayOnlineStatus: status });
settingList.value.displayOnlineStatus.showChildren = false;
}
function switchTranslationTargetLanguage(lang: string) {
translator.clear();
TUIChatService.setTranslationLanguage(lang);
settingList.value.translateLanguage.selectedChild = lang;
settingList.value.translateLanguage.showChildren = false;
}
</script>
<style lang="scss" scoped>
@import '../styles/profile';
</style>

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="21px" height="21px" viewBox="0 0 21 21" version="1.1">
<title>编组 5</title>
<g id="web" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="登录" transform="translate(-627.000000, -516.000000)" stroke="#FFFFFF" stroke-width="1.2">
<g id="编组-3" transform="translate(266.400000, 332.400000)">
<g id="编组-6" transform="translate(3.600000, 172.800000)">
<g id="编组-5" transform="translate(357.600000, 10.800000)">
<rect id="矩形" x="6.6" y="10.2" width="6" height="1" rx="0.5"></rect>
<circle id="椭圆形" cx="10.2" cy="10.2" r="9.6"></circle>
<polyline id="路径-2" stroke-linecap="round" stroke-linejoin="round" points="9.6 13.4662027 13.2 10.2 9.6 7.30762441"></polyline>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 969 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 400 B

View File

@ -0,0 +1,8 @@
<svg width="16" height="14" viewBox="0 0 16 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path id="&#229;&#189;&#162;&#231;&#138;&#182;&#231;&#187;&#147;&#229;&#144;&#136;"
fill-rule="evenodd" clip-rule="evenodd"
d="M3.73333 0H5.86667V1.55556H10.1333V0H12.2667V1.55556H16V14H0V1.55556H3.73333V0ZM10.1333 3.11111V4.14815H12.2667V3.11111H14.4V12.4444H1.6V3.11111H3.73333V4.14815H5.86667V3.11111H10.1333ZM12.2667 7.25926H9.06667V10.3704H12.2667V7.25926Z"
fill="#596174"
style="fill:#596174;fill:color(display-p3 0.3490 0.3804 0.4549);fill-opacity:1;" />
</svg>

After

Width:  |  Height:  |  Size: 605 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

BIN
src/views/im/icon/menu.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 691 B

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="48px" height="48px" viewBox="0 0 48 48" version="1.1">
<title>编组 8</title>
<g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="通讯页" transform="translate(-123.000000, -1406.000000)">
<g id="编组-15" transform="translate(0.000000, 1378.000000)">
<g id="编组-12备份-5" transform="translate(32.000000, 12.000000)">
<g id="编组-8" transform="translate(91.000000, 16.000000)">
<rect id="矩形" x="0" y="0" width="48" height="48"></rect>
<path d="M24,5 C34.4934102,5 43,13.5065898 43,24 C43,34.1653543 35.0169798,42.4661744 24.9781429,42.9752568 L25.004234,43 L7,43 C5.8954305,43 5,42.1045695 5,41 L5,23 L5.02459166,23.0248508 C5.53217539,12.9846073 13.8336352,5 24,5 Z M24,25 L14,25 C13.4477153,25 13,25.4477153 13,26 L13,26 L13,28 C13,28.5522847 13.4477153,29 14,29 L14,29 L24,29 C24.5522847,29 25,28.5522847 25,28 L25,28 L25,26 C25,25.4477153 24.5522847,25 24,25 L24,25 Z M34,17 L14,17 C13.4477153,17 13,17.4477153 13,18 L13,18 L13,20 C13,20.5522847 13.4477153,21 14,21 L14,21 L34,21 C34.5522847,21 35,20.5522847 35,20 L35,20 L35,18 C35,17.4477153 34.5522847,17 34,17 L34,17 Z" id="形状结合" fill="#A0A3A6"></path>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24px" height="24px" viewBox="0 0 24 24" version="1.1">
<title>编组 8</title>
<g id="页面-2备份" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="更多" transform="translate(-498.000000, -339.000000)">
<g id="左侧边栏" transform="translate(480.000000, 220.000000)">
<g id="编组-8" transform="translate(18.000000, 119.000000)">
<rect id="矩形" x="0" y="0" width="24" height="24"></rect>
<g id="编组-18" transform="translate(3.000000, 3.000000)" fill="#147AFF">
<path d="M1.33333333,18 C0.596953667,18 -7.9799792e-16,17.4030463 0,16.6666667 L0,8.52631579 L0.0116474743,8.53811106 C0.252069935,3.78219505 4.18434547,0 9,0 C13.9705627,0 18,4.02943725 18,9 C18,13.815317 14.2183351,17.7473789 9.46288884,17.9883019 L9.47568977,18 L1.33333333,18 Z" id="形状结合"></path>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24px" height="24px" viewBox="0 0 24 24" version="1.1">
<title>编组 8</title>
<g id="new" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="ICON" transform="translate(-58.000000, -20.000000)">
<g id="编组-8" transform="translate(58.000000, 20.000000)">
<g id="编组-18" transform="translate(3.000000, 3.000000)" stroke="#999999">
<path d="M9,0.5 C11.3472102,0.5 13.4722102,1.45139491 15.0104076,2.98959236 C16.5486051,4.52778981 17.5,6.65278981 17.5,9 C17.5,11.2679988 16.6117526,13.3285462 15.1641141,14.8526931 C13.7116662,16.3819037 11.6961744,17.3713309 9.44969786,17.4883204 L9.44969786,17.4883204 L1.33333333,17.5 C1.10321469,17.5 0.894881354,17.406726 0.744077682,17.2559223 C0.59327401,17.1051186 0.5,16.8967853 0.5,16.6666667 L0.5,16.6666667 L0.5,8.53977783 C0.62256394,6.29760907 1.61581474,4.28394186 3.14775549,2.83262957 C4.67315258,1.38751653 6.73292542,0.5 9,0.5 Z" id="形状结合"></path>
</g>
<rect id="矩形" x="0" y="0" width="24" height="24"></rect>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="48px" height="48px" viewBox="0 0 48 48" version="1.1">
<title>编组 8</title>
<defs>
<path d="M16,0 L670,0 C678.836556,-1.623249e-15 686,7.163444 686,16 L686,94 C686,102.836556 678.836556,110 670,110 L16,110 C7.163444,110 1.082166e-15,102.836556 0,94 L0,16 C-1.082166e-15,7.163444 7.163444,1.623249e-15 16,0 Z" id="path-1"></path>
<filter x="-7.0%" y="-43.6%" width="114.0%" height="187.3%" filterUnits="objectBoundingBox" id="filter-2">
<feOffset dx="0" dy="0" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
<feGaussianBlur stdDeviation="16" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
<feColorMatrix values="0 0 0 0 0.941176471 0 0 0 0 0.964705882 0 0 0 0 1 0 0 0 1 0" type="matrix" in="shadowBlurOuter1"></feColorMatrix>
</filter>
</defs>
<g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="个人信息" transform="translate(-580.000000, -1405.000000)">
<rect fill="#F7F8FA" x="0" y="0" width="750" height="1624"></rect>
<g id="编组-17" transform="translate(0.000000, 1378.000000)">
<polygon id="矩形" fill="#FFFFFF" points="0 -1.15130128e-13 750 -1.15130128e-13 750 122 0 122"></polygon>
<g id="编组-12备份-5" transform="translate(32.000000, 12.000000)">
<g id="矩形备份-22">
<use fill="black" fill-opacity="1" filter="url(#filter-2)" xlink:href="#path-1"></use>
<use fill="#EBF0F6" fill-rule="evenodd" xlink:href="#path-1"></use>
</g>
<g id="编组-6" transform="translate(457.000000, 0.000000)">
<polygon id="矩形" points="0 3.55965257e-14 229 3.55965257e-14 229 110 0 110"></polygon>
<g id="编组-8" transform="translate(91.000000, 15.000000)" fill="#006EFF">
<path d="M31,26 C35.418278,26 39,29.581722 39,34 L39,40 C39,41.1045695 38.1045695,42 37,42 L11,42 C9.8954305,42 9,41.1045695 9,40 L9,34 C9,29.581722 12.581722,26 17,26 L31,26 Z M24.0020419,6 C28.9737572,6 33.0040839,10.0294125 33.0040839,15 C33.0040839,19.9705875 28.9737572,24 24.0020419,24 C19.0303267,24 15,19.9705875 15,15 C15,10.0294125 19.0303267,6 24.0020419,6 Z" id="形状结合"></path>
</g>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="48px" height="48px" viewBox="0 0 48 48" version="1.1">
<title>编组 8</title>
<g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="消息页" transform="translate(-580.000000, -1405.000000)" fill="#B0B4B8">
<g id="编组-15" transform="translate(0.000000, 1378.000000)">
<g id="编组-12备份-5" transform="translate(32.000000, 12.000000)">
<g id="编组-6" transform="translate(457.000000, 0.000000)">
<g id="编组-8" transform="translate(91.000000, 15.000000)">
<path d="M31,26 C35.418278,26 39,29.581722 39,34 L39,40 C39,41.1045695 38.1045695,42 37,42 L11,42 C9.8954305,42 9,41.1045695 9,40 L9,34 C9,29.581722 12.581722,26 17,26 L31,26 Z M24.0020419,6 C28.9737572,6 33.0040839,10.0294125 33.0040839,15 C33.0040839,19.9705875 28.9737572,24 24.0020419,24 C19.0303267,24 15,19.9705875 15,15 C15,10.0294125 19.0303267,6 24.0020419,6 Z" id="形状结合"></path>
</g>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="48px" height="48px" viewBox="0 0 48 48" version="1.1">
<title>编组 8</title>
<g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="消息页" transform="translate(-351.000000, -1405.000000)" fill="#B0B4B8">
<g id="编组-15" transform="translate(0.000000, 1378.000000)">
<g id="编组-12备份-5" transform="translate(32.000000, 12.000000)">
<g id="编组-6" transform="translate(229.000000, 0.000000)">
<g id="编组-2" transform="translate(84.000000, 15.000000)">
<g id="编组-8" transform="translate(6.000000, 0.000000)">
<path d="M25,26 C29.418278,26 33,29.581722 33,34 L33,40 C33,41.1045695 32.1045695,42 31,42 L5,42 C3.8954305,42 3,41.1045695 3,40 L3,34 C3,29.581722 6.581722,26 11,26 L25,26 Z M43.9994591,26 C44.5517438,26 44.9994591,26.4477153 44.9994591,27 L44.9994591,29 C44.9994591,29.5522847 44.5517438,30 43.9994591,30 L36.9994591,30 C36.4471743,30 35.9994591,29.5522847 35.9994591,29 L35.9994591,27 C35.9994591,26.4477153 36.4471743,26 36.9994591,26 L43.9994591,26 Z M18.0020419,6 C22.9737572,6 27.0040839,10.0294125 27.0040839,15 C27.0040839,19.9705875 22.9737572,24 18.0020419,24 C13.0303267,24 9,19.9705875 9,15 C9,10.0294125 13.0303267,6 18.0020419,6 Z M43.9994591,18 C44.5517438,18 44.9994591,18.4477153 44.9994591,19 L44.9994591,21 C44.9994591,21.5522847 44.5517438,22 43.9994591,22 L31.9994591,22 C31.4471743,22 30.9994591,21.5522847 30.9994591,21 L30.9994591,19 C30.9994591,18.4477153 31.4471743,18 31.9994591,18 L43.9994591,18 Z" id="形状结合"></path>
</g>
</g>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24px" height="24px" viewBox="0 0 24 24" version="1.1">
<title>编组 8</title>
<g id="new" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="ICON" transform="translate(-19.000000, -60.000000)">
<g id="编组-8" transform="translate(19.000000, 60.000000)">
<rect id="矩形" x="0" y="0" width="24" height="24"></rect>
<rect id="矩形" stroke="#006EFF" transform="translate(19.000000, 8.500000) scale(1, -1) translate(-19.000000, -8.500000) " x="16.5" y="8.5" width="5" height="1" rx="0.5"></rect>
<rect id="矩形备份-2" stroke="#006EFF" x="16.5" y="11.5" width="5" height="1" rx="0.5"></rect>
<rect id="矩形备份-3" stroke="#006EFF" x="18.5" y="14.5" width="3" height="1" rx="0.5"></rect>
<path d="M9,12 C11.209139,12 13,9.35025579 13,7.09090909 C13,4.83156239 11.209139,3 9,3 C6.790861,3 5,4.83156239 5,7.09090909 C5,9.35025579 6.790861,12 9,12 Z" id="椭圆形" fill="#006EFF"></path>
<path d="M6,13 L12,13 C14.209139,13 16,14.790861 16,17 L16,20 C16,20.5522847 15.5522847,21 15,21 L3,21 C2.44771525,21 2,20.5522847 2,20 L2,17 C2,14.790861 3.790861,13 6,13 Z" id="矩形" fill="#006EFF"></path>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24px" height="24px" viewBox="0 0 24 24" version="1.1">
<title>编组 8</title>
<g id="页面-2备份" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="更多" transform="translate(-498.000000, -389.000000)">
<g id="左侧边栏" transform="translate(480.000000, 220.000000)">
<g id="编组-8" transform="translate(18.000000, 169.000000)">
<rect id="矩形" x="0" y="0" width="24" height="24"></rect>
<rect id="矩形" stroke="#999999" transform="translate(19.000000, 8.500000) scale(1, -1) translate(-19.000000, -8.500000) " x="16.5" y="8.5" width="5" height="1" rx="0.5"></rect>
<rect id="矩形备份-2" stroke="#999999" x="16.5" y="11.5" width="5" height="1" rx="0.5"></rect>
<rect id="矩形备份-3" stroke="#999999" x="18.5" y="14.5" width="3" height="1" rx="0.5"></rect>
<path d="M9,3.5 C9.9652364,3.5 10.8385592,3.90102188 11.4709238,4.54775835 C12.1073095,5.19860738 12.5,6.09799921 12.5,7.09090909 C12.5,8.1638596 12.0512293,9.33155704 11.3410312,10.2031638 C10.7279048,10.9556371 9.91231344,11.5 9,11.5 C8.08768656,11.5 7.27209516,10.9556371 6.65896878,10.2031638 C5.94877068,9.33155704 5.5,8.1638596 5.5,7.09090909 C5.5,6.09799921 5.89269049,5.19860738 6.52907621,4.54775835 C7.16144075,3.90102188 8.0347636,3.5 9,3.5 L9,3.5 Z" id="椭圆形" stroke="#999999"></path>
<path d="M12,13.5 C12.9664983,13.5 13.8414983,13.8917508 14.4748737,14.5251263 C15.1082492,15.1585017 15.5,16.0335017 15.5,17 L15.5,17 L15.5,20 L3,20.5 L2.5,17 C2.5,16.0335017 2.89175084,15.1585017 3.52512627,14.5251263 C4.15850169,13.8917508 5.03350169,13.5 6,13.5 L6,13.5 Z" id="矩形" stroke="#999999"></path>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 457 B

189
src/views/im/index.vue Normal file
View File

@ -0,0 +1,189 @@
<!-- eslint-disable vue/multi-word-component-names -->
<template>
<div :id="isPC ? 'preloadedImages' : ''" :class="['home', isH5 && 'home-h5']">
<!-- <div v-show="isMenuShow" class="home-menu">
<Menu @closeMenu="toggleMenu(false)" />
</div>-->
<div :class="['home-container', isMenuShow && 'menu-expand']">
<!-- <div v-if="isPC" class="home-header">
<Header
:class="[isMenuShow && 'header-menu-show']"
showType="menu"
:defaultLanguage="locale"
@toggleMenu="toggleMenu(!isMenuShow)"
@changeLanguage="changeLanguage"
/>
</div>-->
<div class="home-main">
<div class="home-TUIKit">
<div v-if="isPC || !currentConversationID" class="home-TUIKit-navbar">
<NavBar v-model:currentNavBar="currentNavBar" v-model:isSettingShow="isSettingShow">
<template #profile>
<Profile display-type="profile" />
</template>
<template #setting>
<Profile v-model:showSetting="isSettingShow" display-type="setting" />
</template>
</NavBar>
</div>
<div v-if="isPC" class="home-TUIKit-main">
<div v-show="currentNavBar === 'message'" class="home-TUIKit-main">
<div class="home-conversation">
<TUISearch searchType="global" />
<TUIConversation />
</div>
<div class="home-chat">
<TUIChat>
<ChatDefaultContent />
</TUIChat>
<TUIGroup class="chat-aside" />
<TUISearch class="chat-aside" searchType="conversation" />
</div>
<TUIContact display-type="selectFriend" />
</div>
<div v-show="currentNavBar === 'relation'" class="home-TUIKit-main">
<TUIContact display-type="contactList" @switchConversation="currentNavBar = 'message'" />
</div>
</div>
<div v-else-if="isH5" class="home-TUIKit-main">
<div v-if="!currentConversationID" class="home-TUIKit-main">
<div v-show="currentNavBar === 'message'" class="home-TUIKit-main">
<TUISearch searchType="global" />
<TUIConversation />
<TUIContact display-type="selectFriend" />
</div>
<div v-show="currentNavBar === 'relation'" class="home-TUIKit-main">
<TUIContact display-type="contactList" @switchConversation="currentNavBar = 'message'" />
</div>
<div v-show="currentNavBar === 'profile'" class="home-TUIKit-main">
<Profile display-type="all" />
</div>
</div>
<TUIChat v-else />
<TUIGroup class="chat-popup" />
<TUISearch class="chat-popup" searchType="conversation" />
</div>
<Drag ref="dragRef" :show="isCalling" domClassName="callkit-drag-container">
<TUICallKit
:class="['callkit-drag-container', `callkit-drag-container-${isMinimized ? 'mini' : isH5 ? 'h5' : 'pc'}`]"
:allowedMinimized="true"
:allowedFullScreen="false"
:beforeCalling="beforeCalling"
:afterCalling="afterCalling"
:onMinimized="onMinimized"
/>
</Drag>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from '@/TUIKit/adapter-vue';
import { SDKAppID, secretKey, userSig } from '@/main';
import { TUILogin } from '@tencentcloud/tui-core';
import { TUIStore, StoreName, TUIUserService } from '@tencentcloud/chat-uikit-engine';
import { TUICallKit } from '@tencentcloud/call-uikit-vue';
import { TUIChat, TUIConversation, TUIContact, TUIGroup, TUISearch, genTestUserSig } from '@/TUIKit';
import Header from '@/views/im/components/Header.vue';
import Menu from '@/views/im/components/Menu.vue';
import NavBar from '@/views/im/components/NavBar.vue';
import Profile from '@/views/im/components/Profile.vue';
import ChatDefaultContent from '@/views/im/components/ChatDefaultContent.vue';
import Drag from '@/TUIKit/components/common/Drag';
import { isPC, isH5 } from '@/TUIKit/utils/env';
import { enableSampleTaskStatus } from '@/TUIKit/utils/enableSampleTaskStatus';
const props = withDefaults(
defineProps<{
language: string;
}>(),
{
language: 'zh'
}
);
const emits = defineEmits(['changeLanguage']);
const locale = ref<string>(props.language);
const isMenuShow = ref<boolean>(true);
const currentNavBar = ref<string>('message');
const currentConversationID = ref<string>('');
const isCalling = ref<boolean>(false);
const isMinimized = ref<boolean>(false);
const dragRef = ref<typeof Drag>();
const isSettingShow = ref<boolean>(false);
function changeLanguage(language: string) {
emits('changeLanguage', language);
}
TUIStore.watch(StoreName.CONV, {
currentConversationID: (id: string) => {
currentConversationID.value = id;
}
});
function toggleMenu(value: boolean) {
isMenuShow.value = value;
}
function beforeCalling() {
isCalling.value = true;
isMinimized.value = false;
enableSampleTaskStatus('call');
}
function afterCalling() {
isCalling.value = false;
isMinimized.value = false;
}
function onMinimized(oldMinimizedStatus: boolean, newMinimizedStatus: boolean) {
isMinimized.value = newMinimizedStatus;
dragRef?.value?.positionReset();
}
function genUser() {
const options = genTestUserSig({
SDKAppID,
secretKey,
userID: 'youyouliangshao'
});
const loginInfo: any = {
SDKAppID,
// userID: ruleForm.value.userID,
userID: 'youyouliangshao',
userSig: userSig,
useUploadPlugin: true,
framework: `vue3`
};
imLogin(loginInfo);
}
function imLogin(loginInfo) {
TUILogin.login(loginInfo)
.then((res: any) => {
// router.push({ path: 'home' });
// TUIUserService.switchUserStatus({ displayOnlineStatus: true });
})
.catch((error: any) => {
console.info('111111111111111111111111', error);
ElMessage({
message: '登录失败',
grouping: true,
type: 'error'
});
});
}
onMounted(() => {
/* TUILogin.logout().then(() => {
});*/
genUser();
});
</script>
<style lang="scss">
@import './styles/home';
</style>

View File

@ -0,0 +1,115 @@
@import "@/TUIKit/assets/styles/common.scss";
// button
@mixin btn {
border-radius: 5px;
padding: 13px 0;
cursor: pointer;
}
@mixin btn-default {
@include btn;
background: #006eff;
border: 1px solid #006eff;
color: #fff;
}
@mixin btn-error {
@include btn;
border: 1px solid #e54545;
color: #e54545;
background: #fff;
}
@mixin btn-normal {
@include btn;
background: #fff;
border: 1px solid #ddd;
color: #000;
}
// flex
// flex布局 默认 纵向垂直居中水平居中
@mixin flex($direction: column, $js: center, $al: center) {
box-sizing: border-box;
display: -webkit-box;
display: -moz-box;
display: -webkit-flex;
display: -moz-flex;
display: -ms-flexbox;
display: flex;
flex-direction: $direction;
justify-content: $js;
align-items: $al;
border: 0px solid black;
margin: 0;
padding: 0;
min-width: 0;
}
// 文本超出隐藏 ...隐藏文本
@mixin single-line-ellipsis($width: 100%) {
width: $width;
overflow: hidden;
-ms-text-overflow: ellipsis;
text-overflow: ellipsis;
white-space: nowrap;
}
// 文本最多(n)超出部分用...表示
@mixin line($num) {
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: $num;
-webkit-box-orient: vertical;
}
// position居中
@mixin positionCenter {
position: absolute;
top: 50%;
left: 50%;
-webkit-transform: translate(-50%, -50%);
-moz-transform: translate(-50%, -50%);
-ms-transform: translate(-50%, -50%);
-o-transform: translate(-50%, -50%);
transform: translate(-50%, -50%);
}
.container {
@include flex;
position: fixed;
width: 100vw;
height: 100vh;
top: 0;
left: 0;
z-index: 3;
background: rgba(0, 0, 0, 0.3);
overflow: hidden;
.box {
@include flex;
border-radius: 0.5rem;
background: #fff;
overflow: hidden;
color: #000;
}
.box-h5 {
width: 100%;
height: 100%;
padding: 0px;
border-radius: 0px;
.title {
@include flex;
box-sizing: border-box;
width: 100%;
padding: 15px 18px;
position: relative;
.title-back {
position: absolute;
left: 18px;
}
.title-name {
font-size: 18px;
font-weight: 500;
font-family: PingFangSC-Medium;
}
}
}
}

View File

@ -0,0 +1,63 @@
/* stylelint-disable */
.home-h5 {
flex-direction: column;
min-width: 100%;
min-height: 100%;
background: #fff;
.home-menu {
position: fixed;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
z-index: 10;
align-items: flex-end;
}
.home-container {
.home-main {
padding: 0;
min-width: 0;
.home-TUIKit {
flex-direction: column-reverse;
border-radius: 0;
max-width: 100%;
.home-TUIKit-navbar {
width: 100%;
padding: 12px 18px;
}
.home-TUIKit-main {
flex: 1;
display: flex;
flex-direction: column;
.chat-popup {
position: absolute;
}
}
.callkit-drag-container {
&-h5 {
width: 100%;
height: 100%;
left: 0;
top: 0;
border-radius: 0;
box-shadow: none;
}
&-mini {
width: 72px;
height: 72px;
left: calc(100% - 100px);
top: 20px;
background-color: transparent;
}
}
}
}
}
}

View File

@ -0,0 +1,154 @@
@import "../common.scss";
.login-h5 {
min-width: 100%;
max-width: 100%;
padding: 17px 0;
.login-header {
padding: 0 23px;
}
.login-main {
.login-main-content {
padding: 0 23px;
background: url("../assets/image/h5/login-bg.png") no-repeat;
background-size: 65%;
background-position-x: right;
align-items: flex-start;
.login-form {
flex: 1;
.login-title {
flex-direction: column;
padding: 60px 0 18px;
p {
padding-left: 0;
font-size: 27px;
line-height: 40px;
}
}
.login-form-item {
font-size: 18px;
.el-checkbox {
.checked-text {
font-size: 14px;
}
}
.login-form-item-disabled {
font-size: 18px;
padding: 20px;
}
}
.login-btn {
button {
height: auto !important;
font-size: 20px;
line-height: 27px;
padding: 13px 0;
}
}
}
}
}
.login-footer {
background: none;
padding: 10px 10px;
&-list {
flex: 1;
display: flex;
&-item {
flex: 1;
display: flex;
background: url("../assets/image/h5/adv-more.svg") no-repeat;
background-size: 100% 100%;
border: solid #96c3ff 1px;
&:last-child {
background: url("../assets/image/h5/adv-im.svg") no-repeat;
background-size: 100% 100%;
}
a {
flex: 1;
display: flex;
justify-content: space-around;
align-items: center;
box-sizing: border-box;
padding: 20px;
span {
padding: 5px 20px;
background: #147aff;
box-shadow: 0 4px 5px 0 rgba(255, 255, 255, 0.7), 0 3px 8px 0 rgba(20, 122, 255, 0.55);
border-radius: 30.5px;
display: flex;
align-items: center;
justify-content: center;
font-family: PingFangSC-Regular;
font-weight: 400;
font-size: 15px;
color: #ffffff;
letter-spacing: 0;
}
aside {
display: flex;
flex-direction: column;
h1 {
font-family: PingFangSC-Regular;
font-size: 16px;
color: #000000;
letter-spacing: 0;
}
.sub {
align-self: flex-end;
}
}
}
}
&-bottom {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
.text-header {
display: flex;
align-items: center;
span {
padding: 20px 20px;
width: 84px;
font-family: "PingFang SC";
font-style: normal;
font-weight: 400;
color: #bbbbbb;
}
}
i {
width: 120px;
height: 1px;
background: #dbdbdb;
}
&-image {
display: flex;
.platform {
width: 41px;
height: 41px;
padding: 0 20px;
img {
width: 100%;
height: 100%;
}
}
}
}
}
}
}

View File

@ -0,0 +1,94 @@
.TUI-profile-h5 {
background: #efefef;
flex: 1;
display: flex;
flex-direction: column;
width: 100%;
&-basic {
width: 100%;
box-sizing: border-box;
background: #ffffff;
padding: 14px 18px;
flex-direction: row;
align-items: flex-start;
margin-bottom: 10px;
&-avatar {
width: 78px;
height: 78px;
border-radius: 8px;
}
&-info {
&-nick {
font-size: 14px;
flex: 1;
word-break: keep-all;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
padding: 6px 0;
font-family: PingFangSC-Medium;
font-weight: 500;
color: #000;
letter-spacing: 0;
}
&-id {
font-family: PingFangSC-Regular;
font-weight: 400;
color: #999;
letter-spacing: 0;
font-size: 14px;
word-break: keep-all;
padding: 6px 0;
&-label {
}
&-value {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
}
}
&-setting {
width: 100%;
flex-direction: column;
&-item {
margin-bottom: 10px;
background: #ffffff;
padding: 10px;
&-exit {
.TUI-profile-h5-setting-item-label {
justify-content: center;
.label-left {
.label-title {
color: red;
}
}
}
}
&-label {
.label-left {
display: flex;
flex-direction: column;
.label-title {
color: #444444;
font-size: 14px;
}
}
.label-right {
color: #000000;
font-size: 14px;
display: flex;
flex-direction: row;
}
}
&-bottom-popup {
font-size: 16px;
color: #147aff;
padding: 10px;
border-bottom: 1px solid #DBDBDB;
}
}
}
}

View File

@ -0,0 +1,3 @@
@import './web/home.scss';
@import './h5/home.scss';
@import "./common.scss";

View File

@ -0,0 +1,3 @@
@import "./web/login.scss";
@import "./h5/login.scss";
@import "./common.scss";

View File

@ -0,0 +1,3 @@
@import "./web/profile.scss";
@import "./h5/profile.scss";
@import "./common.scss";

View File

@ -0,0 +1,121 @@
#preloadedImages {
background: linear-gradient(135deg, #f1f4f7 0%, #edf5ff 100%) no-repeat;
background-size: cover;
background-image: url("https://web.sdk.qcloud.com/im/assets/images/background-zip.png");
}
.home {
box-sizing: border-box;
flex: 1;
display: flex;
flex-direction: row;
min-width: 1024px;
background-size: contain;
width: 100%;
height: 100%;
overflow: hidden;
.home-menu {
box-sizing: border-box;
display: flex;
}
.home-container {
box-sizing: border-box;
display: flex;
flex: 1;
flex-direction: column;
overflow: hidden;
align-items: stretch;
.home-header {
box-sizing: border-box;
overflow: hidden;
.header-menu-show {
padding-left: 0px;
}
}
.home-main {
box-sizing: border-box;
flex: 1;
display: flex;
justify-content: center;
overflow: hidden;
padding: 50px;
min-width: 968px;
.home-TUIKit {
box-sizing: border-box;
display: flex;
flex: 1;
width: 100%;
height: 100%;
max-width: 1400px;
overflow: hidden;
min-height: 640px;
border-radius: 12px;
background-color: #ffffff;
box-shadow: 0 11px 20px 0 rgba(0, 0, 0, 0.3);
.home-TUIKit-navbar {
box-sizing: border-box;
display: flex;
}
.home-TUIKit-main {
box-sizing: border-box;
flex: 1;
display: flex;
overflow: hidden;
flex-direction: row;
border: 0 solid black;
.home-conversation,
.home-relation {
min-width: 285px;
box-sizing: border-box;
flex: 0 0 24%;
display: flex;
flex-direction: column;
border-right: 1px solid #f4f5f9;
}
.home-chat {
box-sizing: border-box;
min-width: 0;
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
position: relative;
}
}
.callkit-drag-container {
position: fixed;
z-index: 100;
background-color: #ffffff;
user-select: none;
&-pc {
left: calc(50% - 25rem);
top: calc(50% - 18rem);
width: 50rem;
height: 36rem;
border-radius: 16px;
box-shadow: rgba(0, 0, 0, 0.16) 0px 3px 6px, rgba(0, 0, 0, 0.23) 0px 3px 6px;
}
&-mini {
width: 168px;
height: 56px;
right: 10px;
top: 70px;
background-color: transparent;
border-radius: 0px;
box-shadow: none;
}
}
}
}
}
.home-container.menu-expand {
.dialog,
.container {
width: calc(100% - 300px) !important;
left: 300px !important;
}
.home-main .home-TUIKit .callkit-drag-container.callkit-drag-container-pc {
left: calc(50% - 25rem + 150px);
}
}
}

View File

@ -0,0 +1,237 @@
@import "../common.scss";
.login {
flex: 1;
@include flex(column, center, stretch);
min-width: 980px;
width: 100%;
height: 100%;
background: #ffffff;
.login-main {
flex: 1;
@include flex(column, center, stretch);
padding-bottom: 5vw;
.login-main-content {
flex: 1;
@include flex(row, space-between, center);
padding: 0 6vh;
width: 100%;
max-width: 100rem;
align-self: center;
background: url("../assets/image/login-background.png") no-repeat;
background-position: center left;
.login-main-adv {
@include flex(column, flex-start, flex-start);
.login-main-adv-introduce {
font-size: 3rem;
line-height: 4.2rem;
font-family: PingFangSC-Regular;
font-weight: 400;
color: #000000;
}
}
.login-sale {
margin-top: 40px;
padding: 8px 16px;
font-size: 1.4rem;
line-height: 2rem;
border-radius: 6px;
display: flex;
align-items: center;
cursor: pointer;
color: #ffffff;
background: url("../assets/image/adv-bg.svg") no-repeat;
background-size: cover;
background-position: center;
.icon {
margin: 0 8px;
}
}
.small-txt {
width: 42rem;
font-size: 2.6rem;
line-height: 3.6rem;
}
.checked-text {
display: flex;
flex-wrap: wrap;
font-size: 0.88rem;
line-height: 1.73rem;
font-family: PingFangSC-Regular;
font-weight: 400;
color: #bbbbbb;
letter-spacing: 0;
a {
color: #006ef0;
}
}
.login-form {
box-sizing: border-box;
width: 22.41rem;
.login-title {
display: flex;
letter-spacing: 0;
font-family: PingFangSC-Medium;
font-weight: 500;
color: #000000;
img {
width: 4.61rem;
height: 3.23rem;
}
p {
padding-left: 10.5px;
font-size: 1.8rem;
line-height: 2.7rem;
align-items: center;
display: flex;
}
}
.login-form-item {
margin: 18px 0;
.el-select {
width: 100%;
font-size: 1rem;
padding-top: 28px;
line-height: 1.73rem;
font-weight: 400;
color: #000000;
}
.el-input__inner {
width: 356.4px;
height: 54px;
margin-top: 12px;
border-radius: 4.8px;
background: #ffffff;
border: 1.2px solid #dddddd;
}
.input-with-select {
margin-top: 14px;
input {
height: 40px;
}
}
.el-input-group__append {
.code-box {
color: #006eff;
}
}
.login-form-item-disabled {
cursor: pointer;
background: #f4f5f9;
border: 1.2px solid #dddddd;
color: #111111;
letter-spacing: 0;
border-radius: 4.8px;
width: 100%;
margin-top: 28px;
padding: 14px 11px;
box-sizing: border-box;
font-size: 1rem;
line-height: 1.2rem;
label {
cursor: pointer;
color: #999999;
padding-right: 16px;
}
}
}
.login-form-footer {
display: flex;
flex-direction: row;
justify-content: space-between;
text-decoration: none;
a {
color: #006ef0;
}
}
.login-btn {
flex: 1;
@include flex(column, center, stretch);
.btn {
width: 100%;
flex: 1;
height: 3rem;
font-size: 1.25rem;
font-weight: 400;
letter-spacing: 0;
border: 1px solid #006eff;
border-radius: 5px;
color: #006eff;
margin-bottom: 10px;
background-color: transparent;
cursor: pointer;
&:disabled {
opacity: 0.3;
}
&-primary {
background: #006eff;
color: #ffffff;
}
}
}
}
}
.login-main-middle {
height: 130px;
width: 100%;
max-width: 100rem;
align-self: center;
padding: 0 1.6rem 20px;
box-sizing: border-box;
display: flex;
.login-main-middle-box {
flex: 1;
display: flex;
}
}
.login-main-footer {
box-sizing: border-box;
padding: 0 1.6rem;
width: 100%;
max-width: 100rem;
display: flex;
align-items: center;
align-self: center;
background: rgba(231, 242, 255, 0.4);
.mask {
flex: 0 0 25%;
padding: 1.6rem 0;
display: flex;
flex-direction: column;
align-items: center;
.mask-top {
word-break: break-all;
white-space: nowrap;
font-size: 3rem;
height: 4.19rem;
font-family: PingFangSC-Regular;
font-weight: 400;
color: #006eff;
letter-spacing: 0;
}
.mask-under {
opacity: 0.49;
font-weight: 400;
font-size: 1.2rem;
font-family: PingFangSC-Regular;
color: #000;
letter-spacing: 0;
height: 62px;
text-align: center;
}
}
}
}
}

View File

@ -0,0 +1,124 @@
/* stylelint-disable-next-line selector-class-pattern */
.TUI-profile {
background: #fff;
display: flex;
flex-direction: column;
box-sizing: border-box;
overflow: hidden;
&-basic {
display: flex;
flex-direction: row;
box-sizing: border-box;
overflow: hidden;
&-avatar {
width: 30px;
height: 30px;
border-radius: 5px;
margin-right: 10px;
}
&-info {
flex: 1;
display: flex;
flex-direction: column;
box-sizing: border-box;
overflow: hidden;
font-size: 14px;
&-nick {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
&-id {
display: flex;
flex-direction: row;
overflow: hidden;
&-label {
font-weight: 400;
color: #999;
}
&-value {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
}
}
&-setting {
&-item {
height: 40px;
padding-left: 14px;
padding-right: 8px;
display: flex;
line-height: 40px;
text-align: center;
align-items: center;
justify-content: space-between;
cursor: pointer;
&:hover {
background-color: rgba(0, 110, 255, 0.1);
/* stylelint-disable-next-line selector-class-pattern */
.TUI-profile-setting-item-children {
display: block;
}
}
&-label {
font-size: 14px;
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
flex: 1;
}
/* stylelint-disable-next-line no-descending-specificity */
&-children {
display: none;
position: absolute;
left: 100%;
min-width: 167px;
border-radius: 0 4px 4px 0;
z-index: 2;
background: #fff;
box-shadow: 2px 1px 6px 0 rgba(2, 16, 43, 0.15);
&-item {
height: 40px;
padding-left: 14px;
padding-right: 8px;
display: flex;
line-height: 40px;
text-align: center;
align-items: center;
justify-content: space-between;
cursor: pointer;
&:hover {
background-color: rgba(0, 110, 255, 0.1);
}
&-label {
font-size: 14px;
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
flex: 1;
padding-right: 5px;
}
}
}
}
}
}

View File

@ -24,10 +24,15 @@ export default defineConfig(({ mode, command }) => {
open: true, open: true,
proxy: { proxy: {
[env.VITE_APP_BASE_API]: { [env.VITE_APP_BASE_API]: {
target: 'http://localhost:8080', target: 'http://192.168.1.250:8080',
changeOrigin: true, changeOrigin: true,
ws: true, ws: true,
rewrite: (path) => path.replace(new RegExp('^' + env.VITE_APP_BASE_API), '') rewrite: (path) => path.replace(new RegExp('^' + env.VITE_APP_BASE_API), ''),
bypass(req, res, options) {
const proxyURL = options.target + options.rewrite(req.url);
console.log('请求的真实api地址===>', proxyURL);
res.setHeader('x-req-proxyURL', proxyURL); // 设置真实请求地址
}
} }
} }
}, },