UPDATE
This commit is contained in:
@@ -7,14 +7,5 @@
|
|||||||
"modules": false
|
"modules": false
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
],
|
|
||||||
"plugins": [
|
|
||||||
[
|
|
||||||
"component",
|
|
||||||
{
|
|
||||||
"libraryName": "element-ui",
|
|
||||||
"styleLibraryName": "theme-chalk"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
+528
-23
@@ -1,7 +1,31 @@
|
|||||||
<template>
|
<template>
|
||||||
<div id="app">
|
<div id="app">
|
||||||
<lemon-im v-bind="IMData"
|
<lemon-imui
|
||||||
@pull-friends-message="pullFriendsMessage"></lemon-im>
|
:user="user"
|
||||||
|
class="imui-center"
|
||||||
|
ref="IMUI"
|
||||||
|
@change-menu="handleChangeMenu"
|
||||||
|
@change-contact="handleChangeContact"
|
||||||
|
@pull-messages="handlePullMessages"
|
||||||
|
@send="handleSend"
|
||||||
|
>
|
||||||
|
<template #cover>
|
||||||
|
<h1 style="text-indent:20px">自定义封面内容</h1>
|
||||||
|
</template>
|
||||||
|
<!-- <template #contact-info="contact">
|
||||||
|
<span style="color:blue">contact-info {{ contact }}</span>
|
||||||
|
</template> -->
|
||||||
|
<!--
|
||||||
|
<template #drawer="contact">
|
||||||
|
<h1>自定义抽屉内容</h1>
|
||||||
|
<p>{{ contact }}</p>
|
||||||
|
</template>
|
||||||
|
-->
|
||||||
|
<template #contact-title="contact">
|
||||||
|
<span>{{ contact.displayName }}</span>
|
||||||
|
<small class="more" @click="changeDrawer(contact)">…</small>
|
||||||
|
</template>
|
||||||
|
</lemon-imui>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -9,36 +33,517 @@
|
|||||||
export default {
|
export default {
|
||||||
name: "app",
|
name: "app",
|
||||||
data() {
|
data() {
|
||||||
this.friendData = {
|
|
||||||
"id": 1,
|
|
||||||
"diaplayName": "贤心",
|
|
||||||
"avatarPath": "a.jpg",
|
|
||||||
"sign": "这些都是测试数据,实际使用请严格按照该格式返回"
|
|
||||||
}
|
|
||||||
return {
|
return {
|
||||||
IMData: {
|
user: {
|
||||||
friends: [],
|
id: "superadmin",
|
||||||
groups: [],
|
displayName: "IMUI super",
|
||||||
|
avatar:
|
||||||
|
"https://ss3.bdstatic.com/70cFv8Sh_Q1YnxGkpoWK1HF6hhy/it/u=4085009425,1005454674&fm=26&gp=0.jpg"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
mounted: function () {
|
mounted() {
|
||||||
setInterval(() => {
|
const contactData1 = {
|
||||||
this.IMData.friends.push({ ...this.friendData })
|
id: "1",
|
||||||
this.friendData.id++
|
displayName: "工作协作群",
|
||||||
}, 1000)
|
avatar:
|
||||||
|
"https://img.ivsky.com/img/tupian/li/201903/24/richu_riluo-015.jpg",
|
||||||
|
type: "single",
|
||||||
|
index: "A",
|
||||||
|
unread: 0,
|
||||||
|
lastSendTime: 1566047865417,
|
||||||
|
lastContent: "2"
|
||||||
|
};
|
||||||
|
const contactData2 = {
|
||||||
|
id: "2",
|
||||||
|
displayName: "马林",
|
||||||
|
avatar: "https://img.ivsky.com/img/tupian/li/201902/27/yanjing_meinv.jpg",
|
||||||
|
type: "single",
|
||||||
|
index: "B",
|
||||||
|
click(next) {
|
||||||
|
next();
|
||||||
|
},
|
||||||
|
renderContainer: () => {
|
||||||
|
return <h1 style="text-indent:20px">自定义页面</h1>;
|
||||||
|
},
|
||||||
|
lastSendTime: 1345209465000,
|
||||||
|
lastContent: "12312",
|
||||||
|
unread: 2
|
||||||
|
};
|
||||||
|
const contactData3 = {
|
||||||
|
id: "3",
|
||||||
|
displayName: "范君",
|
||||||
|
avatar:
|
||||||
|
"https://img.ivsky.com/img/tupian/li/201903/21/huahuan_xiaonvhai.jpg",
|
||||||
|
type: "many",
|
||||||
|
index: "C",
|
||||||
|
lastSendTime: 3
|
||||||
|
};
|
||||||
|
|
||||||
|
const { IMUI } = this.$refs;
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
//IMUI.openDrawer();
|
||||||
|
// IMUI.openDrawer(() => {
|
||||||
|
// return [
|
||||||
|
// <h1>123</h1>,
|
||||||
|
// <h1>123</h1>,
|
||||||
|
// <h1>123</h1>,
|
||||||
|
// <h1>123</h1>,
|
||||||
|
// <h1>123</h1>,
|
||||||
|
// <h1>123</h1>,
|
||||||
|
// <h1>123</h1>,
|
||||||
|
// <h1>123</h1>,
|
||||||
|
// <h1>123</h1>,
|
||||||
|
// <h1>123</h1>,
|
||||||
|
// <h1>123</h1>,
|
||||||
|
// <h1>123</h1>,
|
||||||
|
// <h1>123</h1>,
|
||||||
|
// <h1>123</h1>
|
||||||
|
// ];
|
||||||
|
// });
|
||||||
|
// setTimeout(() => {
|
||||||
|
// IMUI.openDrawer(() => {
|
||||||
|
// return <h1>124563</h1>;
|
||||||
|
// });
|
||||||
|
// }, 2000);
|
||||||
|
}, 2000);
|
||||||
|
//[contactData1, contactData2, contactData3]
|
||||||
|
let data = [
|
||||||
|
{ ...contactData1 },
|
||||||
|
{ ...contactData2 },
|
||||||
|
{ ...contactData3 }
|
||||||
|
//...Array(100).fill(contactData1)
|
||||||
|
];
|
||||||
|
|
||||||
|
IMUI.initContacts(data);
|
||||||
|
IMUI.initEmoji([
|
||||||
|
{
|
||||||
|
label: "表情",
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
name: "1f600",
|
||||||
|
title: "微笑",
|
||||||
|
src: "https://twemoji.maxcdn.com/2/72x72/1f600.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "1f62c",
|
||||||
|
title: "微笑",
|
||||||
|
src: "https://twemoji.maxcdn.com/2/72x72/1f62c.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "1f601",
|
||||||
|
title: "微笑",
|
||||||
|
src: "https://twemoji.maxcdn.com/2/72x72/1f601.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "1f602",
|
||||||
|
title: "微笑",
|
||||||
|
src: "https://twemoji.maxcdn.com/2/72x72/1f602.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "1f923",
|
||||||
|
title: "微笑",
|
||||||
|
src: "https://twemoji.maxcdn.com/2/72x72/1f923.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "1f973",
|
||||||
|
title: "微笑",
|
||||||
|
src: "https://twemoji.maxcdn.com/2/72x72/1f973.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "1f603",
|
||||||
|
title: "微笑",
|
||||||
|
src: "https://twemoji.maxcdn.com/2/72x72/1f603.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "1f604",
|
||||||
|
title: "微笑",
|
||||||
|
src: "https://twemoji.maxcdn.com/2/72x72/1f604.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "1f605",
|
||||||
|
title: "微笑",
|
||||||
|
src: "https://twemoji.maxcdn.com/2/72x72/1f605.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "1f606",
|
||||||
|
title: "微笑",
|
||||||
|
src: "https://twemoji.maxcdn.com/2/72x72/1f606.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "1f607",
|
||||||
|
title: "微笑",
|
||||||
|
src: "https://twemoji.maxcdn.com/2/72x72/1f607.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "1f609",
|
||||||
|
title: "微笑",
|
||||||
|
src: "https://twemoji.maxcdn.com/2/72x72/1f609.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "1f60a",
|
||||||
|
title: "微笑",
|
||||||
|
src: "https://twemoji.maxcdn.com/2/72x72/1f60a.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "1f642",
|
||||||
|
title: "微笑",
|
||||||
|
src: "https://twemoji.maxcdn.com/2/72x72/1f642.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "1f643",
|
||||||
|
title: "微笑",
|
||||||
|
src: "https://twemoji.maxcdn.com/2/72x72/1f643.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "1263a",
|
||||||
|
title: "微笑",
|
||||||
|
src: "https://twemoji.maxcdn.com/2/72x72/263a.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "1f60b",
|
||||||
|
title: "微笑",
|
||||||
|
src: "https://twemoji.maxcdn.com/2/72x72/1f60b.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "1f60c",
|
||||||
|
title: "微笑",
|
||||||
|
src: "https://twemoji.maxcdn.com/2/72x72/1f60c.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "1f60d",
|
||||||
|
title: "微笑",
|
||||||
|
src: "https://twemoji.maxcdn.com/2/72x72/1f60d.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "1f970",
|
||||||
|
title: "微笑",
|
||||||
|
src: "https://twemoji.maxcdn.com/2/72x72/1f970.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "1f618",
|
||||||
|
title: "微笑",
|
||||||
|
src: "https://twemoji.maxcdn.com/2/72x72/1f618.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "1f617",
|
||||||
|
title: "微笑",
|
||||||
|
src: "https://twemoji.maxcdn.com/2/72x72/1f617.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "1f619",
|
||||||
|
title: "微笑",
|
||||||
|
src: "https://twemoji.maxcdn.com/2/72x72/1f619.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "1f61a",
|
||||||
|
title: "微笑",
|
||||||
|
src: "https://twemoji.maxcdn.com/2/72x72/1f61a.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "1f61c",
|
||||||
|
title: "微笑",
|
||||||
|
src: "https://twemoji.maxcdn.com/2/72x72/1f61c.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "1f92a",
|
||||||
|
title: "微笑",
|
||||||
|
src: "https://twemoji.maxcdn.com/2/72x72/1f92a.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "1f928",
|
||||||
|
title: "微笑",
|
||||||
|
src: "https://twemoji.maxcdn.com/2/72x72/1f928.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "1f9d0",
|
||||||
|
title: "微笑",
|
||||||
|
src: "https://twemoji.maxcdn.com/2/72x72/1f9d0.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "1f61d",
|
||||||
|
title: "微笑",
|
||||||
|
src: "https://twemoji.maxcdn.com/2/72x72/1f61d.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "1f61b",
|
||||||
|
title: "微笑",
|
||||||
|
src: "https://twemoji.maxcdn.com/2/72x72/1f61b.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "1f911",
|
||||||
|
title: "微笑",
|
||||||
|
src: "https://twemoji.maxcdn.com/2/72x72/1f911.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "1f913",
|
||||||
|
title: "微笑",
|
||||||
|
src: "https://twemoji.maxcdn.com/2/72x72/1f913.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "1f60e",
|
||||||
|
title: "微笑",
|
||||||
|
src: "https://twemoji.maxcdn.com/2/72x72/1f60e.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "1f929",
|
||||||
|
title: "微笑",
|
||||||
|
src: "https://twemoji.maxcdn.com/2/72x72/1f929.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "1f921",
|
||||||
|
title: "微笑",
|
||||||
|
src: "https://twemoji.maxcdn.com/2/72x72/1f921.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "1f920",
|
||||||
|
title: "微笑",
|
||||||
|
src: "https://twemoji.maxcdn.com/2/72x72/1f920.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "1f917",
|
||||||
|
title: "微笑",
|
||||||
|
src: "https://twemoji.maxcdn.com/2/72x72/1f917.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "1f60f",
|
||||||
|
title: "微笑",
|
||||||
|
src: "https://twemoji.maxcdn.com/2/72x72/1f60f.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "1f636",
|
||||||
|
title: "微笑",
|
||||||
|
src: "https://twemoji.maxcdn.com/2/72x72/1f636.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "1f610",
|
||||||
|
title: "微笑",
|
||||||
|
src: "https://twemoji.maxcdn.com/2/72x72/1f610.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "1f611",
|
||||||
|
title: "微笑",
|
||||||
|
src: "https://twemoji.maxcdn.com/2/72x72/1f611.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "1f612",
|
||||||
|
title: "微笑",
|
||||||
|
src: "https://twemoji.maxcdn.com/2/72x72/1f612.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "1f644",
|
||||||
|
title: "微笑",
|
||||||
|
src: "https://twemoji.maxcdn.com/2/72x72/1f644.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "1f914",
|
||||||
|
title: "微笑",
|
||||||
|
src: "https://twemoji.maxcdn.com/2/72x72/1f914.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "1f925",
|
||||||
|
title: "微笑",
|
||||||
|
src: "https://twemoji.maxcdn.com/2/72x72/1f925.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "1f92d",
|
||||||
|
title: "微笑",
|
||||||
|
src: "https://twemoji.maxcdn.com/2/72x72/1f92d.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "1f92b",
|
||||||
|
title: "微笑",
|
||||||
|
src: "https://twemoji.maxcdn.com/2/72x72/1f92b.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "1f92c",
|
||||||
|
title: "微笑",
|
||||||
|
src: "https://twemoji.maxcdn.com/2/72x72/1f92c.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "1f92f",
|
||||||
|
title: "微笑",
|
||||||
|
src: "https://twemoji.maxcdn.com/2/72x72/1f92f.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "1f633",
|
||||||
|
title: "微笑",
|
||||||
|
src: "https://twemoji.maxcdn.com/2/72x72/1f633.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "1f61e",
|
||||||
|
title: "微笑",
|
||||||
|
src: "https://twemoji.maxcdn.com/2/72x72/1f61e.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "1f61f",
|
||||||
|
title: "微笑",
|
||||||
|
src: "https://twemoji.maxcdn.com/2/72x72/1f61f.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "1f620",
|
||||||
|
title: "微笑",
|
||||||
|
src: "https://twemoji.maxcdn.com/2/72x72/1f620.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "1f621",
|
||||||
|
title: "微笑",
|
||||||
|
src: "https://twemoji.maxcdn.com/2/72x72/1f621.png"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "武器",
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
name: "wx",
|
||||||
|
src: "http://"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
IMUI.updateContact("3", {
|
||||||
|
unread: 100,
|
||||||
|
//displayName: "123",
|
||||||
|
lastSendTime: 3,
|
||||||
|
lastContent: "你好123"
|
||||||
|
});
|
||||||
|
}, 2000);
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
pullFriendsMessage (data, resolve, reject) {
|
changeDrawer(contact) {
|
||||||
resolve([{ ...this.friendData }])
|
this.$refs.IMUI.changeDrawer(() => {
|
||||||
//resolve([...this.friendData.id])
|
return [<h2>自定义抽屉</h2>, contact.displayName];
|
||||||
this.friendData.id++
|
});
|
||||||
},
|
},
|
||||||
pullGroups () {
|
handleChangeContact(contact) {
|
||||||
|
this.$refs.IMUI.updateContact(contact.id, {
|
||||||
|
//displayName: "123",
|
||||||
|
unread: 0
|
||||||
|
});
|
||||||
|
this.$refs.IMUI.closeDrawer();
|
||||||
|
},
|
||||||
|
handleSend(message, next, file) {
|
||||||
|
setTimeout(() => {
|
||||||
|
next();
|
||||||
|
}, 1000);
|
||||||
|
},
|
||||||
|
handlePullMessages(contact, next) {
|
||||||
|
const messages = [
|
||||||
|
{
|
||||||
|
id: "8ad7e98e-5225-4892-8131-4b2ee7797599",
|
||||||
|
type: "text",
|
||||||
|
status: "succeed",
|
||||||
|
sendTime: 1564926674646,
|
||||||
|
fromContactId: "superadmin",
|
||||||
|
fromUser: {
|
||||||
|
id: "hehe",
|
||||||
|
displayName: "I KNOEW",
|
||||||
|
avatar:
|
||||||
|
"https://ss3.bdstatic.com/70cFv8Sh_Q1YnxGkpoWK1HF6hhy/it/u=4085009425,1005454674&fm=26&gp=0.jpg"
|
||||||
|
},
|
||||||
|
content: "测试消息哦..."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "8ad7e98e-5225-4892-8131-4b2ee7797599",
|
||||||
|
type: "text",
|
||||||
|
status: "succeed",
|
||||||
|
sendTime: 1564926674646,
|
||||||
|
fromContactId: "superadmin",
|
||||||
|
fromUser: {
|
||||||
|
id: "superadmin",
|
||||||
|
displayName: "超级飞机",
|
||||||
|
avatar:
|
||||||
|
"https://ss3.bdstatic.com/70cFv8Sh_Q1YnxGkpoWK1HF6hhy/it/u=4085009425,1005454674&fm=26&gp=0.jpg"
|
||||||
|
},
|
||||||
|
content: "测试消息哦..."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "8ad7e98e-5225-4892-8131-4b2ee7797599",
|
||||||
|
type: "text",
|
||||||
|
status: "succeed",
|
||||||
|
sendTime: 1564926674646,
|
||||||
|
fromContactId: "superadmin",
|
||||||
|
fromUser: {
|
||||||
|
id: "hehe",
|
||||||
|
displayName: "I KNOEW",
|
||||||
|
avatar:
|
||||||
|
"https://ss3.bdstatic.com/70cFv8Sh_Q1YnxGkpoWK1HF6hhy/it/u=4085009425,1005454674&fm=26&gp=0.jpg"
|
||||||
|
},
|
||||||
|
content: "测试消息哦..."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "8ad7e98e-5225-4892-8131-4b2ee7797599",
|
||||||
|
type: "text",
|
||||||
|
status: "succeed",
|
||||||
|
sendTime: 1564926674646,
|
||||||
|
fromContactId: "superadmin",
|
||||||
|
fromUser: {
|
||||||
|
id: "superadmin",
|
||||||
|
displayName: "超级飞机",
|
||||||
|
avatar:
|
||||||
|
"https://ss3.bdstatic.com/70cFv8Sh_Q1YnxGkpoWK1HF6hhy/it/u=4085009425,1005454674&fm=26&gp=0.jpg"
|
||||||
|
},
|
||||||
|
content: "测试消息哦..."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "8ad7e98e-5225-4892-8131-4b2ee7797599",
|
||||||
|
type: "text",
|
||||||
|
status: "succeed",
|
||||||
|
sendTime: 1564926674646,
|
||||||
|
fromContactId: "superadmin",
|
||||||
|
fromUser: {
|
||||||
|
id: "hehe",
|
||||||
|
displayName: "I KNOEW",
|
||||||
|
avatar:
|
||||||
|
"https://ss3.bdstatic.com/70cFv8Sh_Q1YnxGkpoWK1HF6hhy/it/u=4085009425,1005454674&fm=26&gp=0.jpg"
|
||||||
|
},
|
||||||
|
content: "测试消息哦..."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "8ad7e98e-5225-4892-8131-4b2ee7797599",
|
||||||
|
type: "text",
|
||||||
|
status: "succeed",
|
||||||
|
sendTime: 1564926674646,
|
||||||
|
fromContactId: "superadmin",
|
||||||
|
fromUser: {
|
||||||
|
id: "superadmin",
|
||||||
|
displayName: "超级飞机",
|
||||||
|
avatar:
|
||||||
|
"https://ss3.bdstatic.com/70cFv8Sh_Q1YnxGkpoWK1HF6hhy/it/u=4085009425,1005454674&fm=26&gp=0.jpg"
|
||||||
|
},
|
||||||
|
content: "测试消息哦..."
|
||||||
}
|
}
|
||||||
|
];
|
||||||
|
next(messages);
|
||||||
|
},
|
||||||
|
handleChangeMenu() {},
|
||||||
|
openCustomContainer() {}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="sass"></style>
|
<style lang="stylus">
|
||||||
|
body
|
||||||
|
background #384558 !important
|
||||||
|
.imui-center
|
||||||
|
position absolute
|
||||||
|
top 50%
|
||||||
|
left 50%
|
||||||
|
transform translate(-50%,-50%)
|
||||||
|
.more
|
||||||
|
font-size 32px
|
||||||
|
line-height 18px
|
||||||
|
height 32px
|
||||||
|
position absolute
|
||||||
|
top 6px
|
||||||
|
right 14px
|
||||||
|
cursor pointer
|
||||||
|
user-select none
|
||||||
|
color #999
|
||||||
|
&:active
|
||||||
|
color #000
|
||||||
|
</style>
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 6.7 KiB |
@@ -1,114 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="hello">
|
|
||||||
<h1>{{ msg }}</h1>
|
|
||||||
<p>
|
|
||||||
For a guide and recipes on how to configure / customize this project,<br />
|
|
||||||
check out the
|
|
||||||
<a href="https://cli.vuejs.org" target="_blank" rel="noopener"
|
|
||||||
>vue-cli documentation</a
|
|
||||||
>.
|
|
||||||
</p>
|
|
||||||
<h3>Installed CLI Plugins</h3>
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-babel"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener"
|
|
||||||
>babel</a
|
|
||||||
>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-eslint"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener"
|
|
||||||
>eslint</a
|
|
||||||
>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<h3>Essential Links</h3>
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<a href="https://vuejs.org" target="_blank" rel="noopener">Core Docs</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="https://forum.vuejs.org" target="_blank" rel="noopener"
|
|
||||||
>Forum</a
|
|
||||||
>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="https://chat.vuejs.org" target="_blank" rel="noopener"
|
|
||||||
>Community Chat</a
|
|
||||||
>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="https://twitter.com/vuejs" target="_blank" rel="noopener"
|
|
||||||
>Twitter</a
|
|
||||||
>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="https://news.vuejs.org" target="_blank" rel="noopener">News</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<h3>Ecosystem</h3>
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<a href="https://router.vuejs.org" target="_blank" rel="noopener"
|
|
||||||
>vue-router</a
|
|
||||||
>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="https://vuex.vuejs.org" target="_blank" rel="noopener">vuex</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
href="https://github.com/vuejs/vue-devtools#vue-devtools"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener"
|
|
||||||
>vue-devtools</a
|
|
||||||
>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="https://vue-loader.vuejs.org" target="_blank" rel="noopener"
|
|
||||||
>vue-loader</a
|
|
||||||
>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
href="https://github.com/vuejs/awesome-vue"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener"
|
|
||||||
>awesome-vue</a
|
|
||||||
>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
name: "HelloWorld",
|
|
||||||
props: {
|
|
||||||
msg: String
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
|
||||||
<style scoped lang="scss">
|
|
||||||
h3 {
|
|
||||||
margin: 40px 0 0;
|
|
||||||
}
|
|
||||||
ul {
|
|
||||||
list-style-type: none;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
li {
|
|
||||||
display: inline-block;
|
|
||||||
margin: 0 10px;
|
|
||||||
}
|
|
||||||
a {
|
|
||||||
color: #42b983;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
+2
-2
@@ -1,7 +1,7 @@
|
|||||||
import Vue from "vue";
|
import Vue from "vue";
|
||||||
import App from "./App.vue";
|
import App from "./App.vue";
|
||||||
import LemonIM from "../packages";
|
import LemonIMUI from "../packages";
|
||||||
Vue.use(LemonIM);
|
Vue.use(LemonIMUI);
|
||||||
|
|
||||||
Vue.config.productionTip = false;
|
Vue.config.productionTip = false;
|
||||||
|
|
||||||
|
|||||||
-1
Submodule lemon-im deleted from ad3c67e0d2
Generated
+1238
-2031
File diff suppressed because it is too large
Load Diff
+11
-11
@@ -1,14 +1,13 @@
|
|||||||
{
|
{
|
||||||
"name": "lemon-im",
|
"name": "lemon-imui",
|
||||||
"version": "0.1.0",
|
"version": "1.0.2",
|
||||||
"private": true,
|
"main": "dist/index.umd.min.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"serve": "vue-cli-service serve",
|
"serve": "vue-cli-service serve",
|
||||||
"build": "vue-cli-service build",
|
"build": "vue-cli-service build --target lib --name index packages/index.js",
|
||||||
"lint": "vue-cli-service lint"
|
"lint": "vue-cli-service lint"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"element-ui": "^2.8.2",
|
|
||||||
"vue": "^2.6.10"
|
"vue": "^2.6.10"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -16,12 +15,13 @@
|
|||||||
"@vue/cli-plugin-eslint": "^3.6.0",
|
"@vue/cli-plugin-eslint": "^3.6.0",
|
||||||
"@vue/cli-service": "^3.6.0",
|
"@vue/cli-service": "^3.6.0",
|
||||||
"@vue/eslint-config-prettier": "^4.0.1",
|
"@vue/eslint-config-prettier": "^4.0.1",
|
||||||
"babel-eslint": "^10.0.1",
|
"babel-eslint": "^10.0.2",
|
||||||
"babel-plugin-component": "^1.1.1",
|
|
||||||
"eslint": "^5.16.0",
|
"eslint": "^5.16.0",
|
||||||
"eslint-plugin-vue": "^5.0.0",
|
"eslint-plugin-vue": "^5.2.3",
|
||||||
"node-sass": "^4.9.0",
|
"stylus": "^0.54.5",
|
||||||
"sass-loader": "^7.1.0",
|
"stylus-loader": "^3.0.2",
|
||||||
"vue-template-compiler": "^2.5.21"
|
"vue-template-compiler": "^2.5.21"
|
||||||
}
|
},
|
||||||
|
"author": "fanjun",
|
||||||
|
"license": "MIT"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "LemonAvatar",
|
||||||
|
props: {
|
||||||
|
src: String,
|
||||||
|
icon: {
|
||||||
|
type: String,
|
||||||
|
default: "lemon-icon-people"
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
type: Number,
|
||||||
|
default: 32
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
imageFinishLoad: true
|
||||||
|
};
|
||||||
|
},
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
style={this.style}
|
||||||
|
class="lemon-avatar"
|
||||||
|
on-click={e => this.$emit("click", e)}
|
||||||
|
>
|
||||||
|
{this.imageFinishLoad && <i class={this.icon} />}
|
||||||
|
<img src={this.src} onLoad={this._handleLoad} />
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
style() {
|
||||||
|
const size = `${this.size}px`;
|
||||||
|
return {
|
||||||
|
width: size,
|
||||||
|
height: size,
|
||||||
|
lineHeight: size,
|
||||||
|
fontSize: `${this.size / 2}px`
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
_handleLoad() {
|
||||||
|
this.imageFinishLoad = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<style lang="stylus">
|
||||||
|
@import '~styles/utils/index'
|
||||||
|
+b(lemon-avatar)
|
||||||
|
font-variant tabular-nums
|
||||||
|
line-height 1.5
|
||||||
|
box-sizing border-box
|
||||||
|
margin 0
|
||||||
|
padding 0
|
||||||
|
list-style none
|
||||||
|
display inline-block
|
||||||
|
text-align center
|
||||||
|
background #ccc
|
||||||
|
color rgba(255,255,255,0.7)
|
||||||
|
white-space nowrap
|
||||||
|
position relative
|
||||||
|
overflow hidden
|
||||||
|
vertical-align middle
|
||||||
|
border-radius 4px
|
||||||
|
img
|
||||||
|
width 100%
|
||||||
|
height 100%
|
||||||
|
display block
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "LemonBadge",
|
||||||
|
props: {
|
||||||
|
count: [Number, Boolean],
|
||||||
|
overflowCount: {
|
||||||
|
type: Number,
|
||||||
|
default: 99
|
||||||
|
}
|
||||||
|
},
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<span class="lemon-badge">
|
||||||
|
{this.$slots.default}
|
||||||
|
{this.count !== 0 && this.count !== undefined && (
|
||||||
|
<span
|
||||||
|
class={[
|
||||||
|
"lemon-badge__label",
|
||||||
|
this.isDot && "lemon-badge__label--dot"
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
{this.label}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
isDot() {
|
||||||
|
return this.count === true;
|
||||||
|
},
|
||||||
|
label() {
|
||||||
|
if (this.isDot) return "";
|
||||||
|
return this.count > this.overflowCount
|
||||||
|
? `${this.overflowCount}+`
|
||||||
|
: this.count;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<style lang="stylus">
|
||||||
|
@import '~styles/utils/index'
|
||||||
|
+b(lemon-badge)
|
||||||
|
position relative
|
||||||
|
display inline-block
|
||||||
|
+e(label)
|
||||||
|
border-radius 10px
|
||||||
|
background #f5222d
|
||||||
|
color #fff
|
||||||
|
text-align center
|
||||||
|
font-size 12px
|
||||||
|
font-weight normal
|
||||||
|
white-space nowrap
|
||||||
|
box-shadow 0 0 0 1px #fff
|
||||||
|
z-index 10
|
||||||
|
position absolute
|
||||||
|
transform translateX(50%)
|
||||||
|
transform-origin 100%
|
||||||
|
display inline-block
|
||||||
|
padding 0 4px
|
||||||
|
height 18px
|
||||||
|
line-height 17px
|
||||||
|
min-width 10px
|
||||||
|
top -4px
|
||||||
|
right 6px
|
||||||
|
+m(dot)
|
||||||
|
width 10px
|
||||||
|
height 10px
|
||||||
|
min-width auto
|
||||||
|
padding 0
|
||||||
|
top -3px
|
||||||
|
right 2px
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "LemonButton",
|
||||||
|
props: {
|
||||||
|
disabled: Boolean
|
||||||
|
},
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
class="lemon-button"
|
||||||
|
disabled={this.disabled}
|
||||||
|
type="button"
|
||||||
|
on-click={this._handleClick}
|
||||||
|
>
|
||||||
|
{this.$slots.default}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
_handleClick(e) {
|
||||||
|
this.$emit("click", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<style lang="stylus">
|
||||||
|
@import '~styles/utils/index'
|
||||||
|
+b(lemon-button)
|
||||||
|
outline none
|
||||||
|
line-height 1.499
|
||||||
|
display inline-block
|
||||||
|
font-weight 400
|
||||||
|
text-align center
|
||||||
|
touch-action manipulation
|
||||||
|
cursor pointer
|
||||||
|
background-image none
|
||||||
|
border 1px solid #ddd
|
||||||
|
box-sizing border-box
|
||||||
|
white-space nowrap
|
||||||
|
padding 0 15px
|
||||||
|
font-size 14px
|
||||||
|
border-radius 4px
|
||||||
|
height 32px
|
||||||
|
user-select none
|
||||||
|
transition all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1)
|
||||||
|
color rgba(0, 0, 0, 0.65)
|
||||||
|
background-color #fff
|
||||||
|
box-shadow 0 2px 0 rgba(0, 0, 0, 0.015)
|
||||||
|
text-shadow 0 -1px 0 rgba(0, 0, 0, 0.12)
|
||||||
|
&:hover:not([disabled])
|
||||||
|
border-color #666
|
||||||
|
color #333
|
||||||
|
&:active
|
||||||
|
background-color #ddd
|
||||||
|
&[disabled]
|
||||||
|
cursor not-allowed
|
||||||
|
color #aaa
|
||||||
|
background #eee
|
||||||
|
</style>
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class='lemon-contact-list'>
|
|
||||||
<div class="lemon-contact-item"
|
|
||||||
v-for="item in control.friends"
|
|
||||||
:key="item.id"
|
|
||||||
@click="control._changeMessageView(item)">
|
|
||||||
{{item.diaplayName}}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
name: 'ContactList',
|
|
||||||
inject: ["control"],
|
|
||||||
data () {
|
|
||||||
return {
|
|
||||||
|
|
||||||
};
|
|
||||||
},
|
|
||||||
computed: {},
|
|
||||||
watch: {},
|
|
||||||
methods: {
|
|
||||||
|
|
||||||
},
|
|
||||||
created () {
|
|
||||||
|
|
||||||
},
|
|
||||||
mounted () {
|
|
||||||
},
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<style lang='scss'>
|
|
||||||
@import '~styles/utils/index';
|
|
||||||
@include b(contact-item) {
|
|
||||||
padding: 10px 15px;
|
|
||||||
cursor: pointer;
|
|
||||||
&:hover {
|
|
||||||
background: #000;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -0,0 +1,134 @@
|
|||||||
|
<script>
|
||||||
|
import { isString, isToday } from "utils/validate";
|
||||||
|
import { timeFormat } from "utils";
|
||||||
|
export default {
|
||||||
|
name: "LemonContact",
|
||||||
|
components: {},
|
||||||
|
data() {
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
contact: Object,
|
||||||
|
simple: Boolean,
|
||||||
|
timeFormat: {
|
||||||
|
type: Function,
|
||||||
|
default(val) {
|
||||||
|
return timeFormat(val, isToday(val) ? "h:i" : "y/m/d");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
render() {
|
||||||
|
const { contact } = this;
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
class={["lemon-contact", { "lemon-contact--name-center": this.simple }]}
|
||||||
|
on-click={e => this._handleClick(e, contact)}
|
||||||
|
>
|
||||||
|
<lemon-badge
|
||||||
|
count={!this.simple ? contact.unread : 0}
|
||||||
|
class="lemon-contact__avatar"
|
||||||
|
native-on-click={e => this._handleBubbleClick(e, contact)}
|
||||||
|
>
|
||||||
|
<lemon-avatar
|
||||||
|
size={40}
|
||||||
|
native-on-click={e => this._handleAvatarClick(e, contact)}
|
||||||
|
src={contact.avatar}
|
||||||
|
/>
|
||||||
|
</lemon-badge>
|
||||||
|
<div class="lemon-contact__inner">
|
||||||
|
<p class="lemon-contact__label">
|
||||||
|
<span class="lemon-contact__name">{contact.displayName}</span>
|
||||||
|
{!this.simple && (
|
||||||
|
<span class="lemon-contact__time">
|
||||||
|
{this.timeFormat(contact.lastSendTime)}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
{!this.simple && (
|
||||||
|
<p class="lemon-contact__content">
|
||||||
|
{isString(contact.lastContent) ? (
|
||||||
|
<span domProps={{ innerHTML: contact.lastContent }} />
|
||||||
|
) : (
|
||||||
|
contact.lastContent
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
created() {},
|
||||||
|
mounted() {},
|
||||||
|
computed: {},
|
||||||
|
watch: {},
|
||||||
|
methods: {
|
||||||
|
_handleClick(e, data) {
|
||||||
|
this.$emit("click", data);
|
||||||
|
},
|
||||||
|
_handleAvatarClick(e, data) {
|
||||||
|
e.stopPropagation();
|
||||||
|
this.$emit("avatar-click", data);
|
||||||
|
},
|
||||||
|
_handleBubbleClick(e, data) {
|
||||||
|
e.stopPropagation();
|
||||||
|
this.$emit("bubble-click", data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<style lang="stylus">
|
||||||
|
@import '~styles/utils/index'
|
||||||
|
+b(lemon-contact)
|
||||||
|
padding 10px 14px
|
||||||
|
cursor pointer
|
||||||
|
user-select none
|
||||||
|
box-sizing border-box
|
||||||
|
overflow hidden
|
||||||
|
background #efefef
|
||||||
|
p
|
||||||
|
margin 0
|
||||||
|
+m(active)
|
||||||
|
background #bebdbd
|
||||||
|
&:hover:not(.lemon-contact--active)
|
||||||
|
background #e3e3e3
|
||||||
|
.el-badge__content
|
||||||
|
border-color #ddd
|
||||||
|
+e(avatar)
|
||||||
|
float left
|
||||||
|
margin-right 10px
|
||||||
|
img
|
||||||
|
display block
|
||||||
|
.ant-badge-count
|
||||||
|
display inline-block
|
||||||
|
padding 0 4px
|
||||||
|
height 18px
|
||||||
|
line-height 18px
|
||||||
|
min-width 18px
|
||||||
|
top -4px
|
||||||
|
right 7px
|
||||||
|
+e(label)
|
||||||
|
display flex
|
||||||
|
+e(time)
|
||||||
|
font-size 12px
|
||||||
|
line-height 18px
|
||||||
|
padding-left 6px
|
||||||
|
color #999
|
||||||
|
white-space nowrap
|
||||||
|
+e(name)
|
||||||
|
display block
|
||||||
|
width 100%
|
||||||
|
ellipsis()
|
||||||
|
+e(content)
|
||||||
|
font-size 12px
|
||||||
|
color #999
|
||||||
|
ellipsis()
|
||||||
|
img
|
||||||
|
height 14px
|
||||||
|
display inline-block
|
||||||
|
vertical-align middle
|
||||||
|
margin 0 1px
|
||||||
|
+m(name-center)
|
||||||
|
+e(label)
|
||||||
|
padding-bottom 0
|
||||||
|
line-height 38px
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,295 @@
|
|||||||
|
<script>
|
||||||
|
import { toEmojiName } from "utils";
|
||||||
|
const exec = (val, command = "insertHTML") => {
|
||||||
|
document.execCommand(command, false, val);
|
||||||
|
};
|
||||||
|
const selection = window.getSelection();
|
||||||
|
let lastSelectionRange;
|
||||||
|
let emojiData = [];
|
||||||
|
export default {
|
||||||
|
name: "LemonEditor",
|
||||||
|
components: {},
|
||||||
|
props: {},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
submitDisabled: true,
|
||||||
|
accept: ""
|
||||||
|
};
|
||||||
|
},
|
||||||
|
created() {},
|
||||||
|
mounted() {
|
||||||
|
//this.$refs.fileInput.addEventListener("change", this._handleChangeFile);
|
||||||
|
},
|
||||||
|
computed: {},
|
||||||
|
watch: {},
|
||||||
|
render() {
|
||||||
|
//<a-popover trigger="click" overlay-class-name="lemon-editor__emoji">
|
||||||
|
return (
|
||||||
|
<div class="lemon-editor">
|
||||||
|
<input
|
||||||
|
style="display:none"
|
||||||
|
type="file"
|
||||||
|
multiple="multiple"
|
||||||
|
ref="fileInput"
|
||||||
|
accept={this.accept}
|
||||||
|
onChange={this._handleChangeFile}
|
||||||
|
/>
|
||||||
|
<div class="lemon-editor__tool">
|
||||||
|
{emojiData.length > 0 && (
|
||||||
|
<lemon-popover class="lemon-editor__emoji">
|
||||||
|
<template slot="content">{this._renderEmojiTabs()}</template>
|
||||||
|
<div class="lemon-editor__tool-item">
|
||||||
|
<i class="lemon-icon-emoji" />
|
||||||
|
</div>
|
||||||
|
</lemon-popover>
|
||||||
|
)}
|
||||||
|
<div
|
||||||
|
class="lemon-editor__tool-item"
|
||||||
|
on-click={() => this._handleSelectFile("*")}
|
||||||
|
>
|
||||||
|
<i class="lemon-icon-folder" />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="lemon-editor__tool-item"
|
||||||
|
on-click={() => this._handleSelectFile("image/*")}
|
||||||
|
>
|
||||||
|
<i class="lemon-icon-image" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="lemon-editor__inner">
|
||||||
|
<div
|
||||||
|
class="lemon-editor__input"
|
||||||
|
ref="textarea"
|
||||||
|
contenteditable="true"
|
||||||
|
on-keyup={this._handleKeyup}
|
||||||
|
on-keydown={this._handleKeydown}
|
||||||
|
on-paste={this._handlePaste}
|
||||||
|
on-click={this._handleClick}
|
||||||
|
on-input={this._handleInput}
|
||||||
|
spellcheck="false"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="lemon-editor__footer">
|
||||||
|
<div class="lemon-editor__tip">使用 ctrl + enter 快捷发送消息</div>
|
||||||
|
<div class="lemon-editor__submit">
|
||||||
|
<lemon-button
|
||||||
|
disabled={this.submitDisabled}
|
||||||
|
on-click={this._handleSend}
|
||||||
|
>
|
||||||
|
发 送
|
||||||
|
</lemon-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
_saveLastRange() {
|
||||||
|
lastSelectionRange = selection.getRangeAt(0);
|
||||||
|
},
|
||||||
|
_focusLastRange() {
|
||||||
|
this.$refs.textarea.focus();
|
||||||
|
if (lastSelectionRange) {
|
||||||
|
selection.removeAllRanges();
|
||||||
|
selection.addRange(lastSelectionRange);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_handleClick() {
|
||||||
|
this._saveLastRange();
|
||||||
|
},
|
||||||
|
_handleInput() {
|
||||||
|
this._checkSubmitDisabled();
|
||||||
|
},
|
||||||
|
_renderEmojiTabs() {
|
||||||
|
const renderImageGrid = items => {
|
||||||
|
return items.map(item => (
|
||||||
|
<img
|
||||||
|
src={item.src}
|
||||||
|
title={item.title}
|
||||||
|
class="lemon-editor__emoji-item"
|
||||||
|
on-click={() => this._handleSelectEmoji(item)}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
};
|
||||||
|
if (emojiData[0].label) {
|
||||||
|
const nodes = emojiData.map((item, index) => {
|
||||||
|
return (
|
||||||
|
<div slot="tab-pane" index={index} tab={item.label}>
|
||||||
|
{renderImageGrid(item.children)}
|
||||||
|
{renderImageGrid(item.children)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
return <lemon-tabs style="width: 412px">{nodes}</lemon-tabs>;
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<div class="lemon-tabs-content" style="width:406px">
|
||||||
|
{renderImageGrid(emojiData)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_handleSelectEmoji(item) {
|
||||||
|
this._focusLastRange();
|
||||||
|
exec(`<img emoji-name="${item.name}" src="${item.src}"></img>`);
|
||||||
|
this._saveLastRange();
|
||||||
|
},
|
||||||
|
async _handleSelectFile(accept) {
|
||||||
|
this.accept = accept;
|
||||||
|
await this.$nextTick();
|
||||||
|
this.$refs.fileInput.click();
|
||||||
|
},
|
||||||
|
_handlePaste(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
const { clipboardData } = e;
|
||||||
|
const text = clipboardData.getData("text");
|
||||||
|
exec(text, "insertText");
|
||||||
|
// Array.from(clipboardData.items).forEach(item => {
|
||||||
|
// console.log(item.type);
|
||||||
|
// });
|
||||||
|
//e.target.innerText = text;
|
||||||
|
},
|
||||||
|
_handleKeyup(e) {
|
||||||
|
this._saveLastRange();
|
||||||
|
//this._checkSubmitDisabled();
|
||||||
|
},
|
||||||
|
_handleKeydown(e) {
|
||||||
|
const { keyCode } = e;
|
||||||
|
if (keyCode == 13) {
|
||||||
|
// e.preventDefault();
|
||||||
|
// document.execCommand("defaultParagraphSeparator", false, false);
|
||||||
|
// exec("<br>");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getFormatValue() {
|
||||||
|
return toEmojiName(
|
||||||
|
this.$refs.textarea.innerHTML
|
||||||
|
.replace(/<br>|<\/br>/, "")
|
||||||
|
.replace(/<div>|<p>/g, "\r\n")
|
||||||
|
.replace(/<\/div>|<\/p>/g, "")
|
||||||
|
);
|
||||||
|
},
|
||||||
|
_checkSubmitDisabled() {
|
||||||
|
this.submitDisabled = !this.$refs.textarea.innerHTML.trim();
|
||||||
|
},
|
||||||
|
_handleSend(e) {
|
||||||
|
const text = this.getFormatValue();
|
||||||
|
this.$emit("send", text);
|
||||||
|
this.clear();
|
||||||
|
this._checkSubmitDisabled();
|
||||||
|
},
|
||||||
|
_handleChangeFile(e) {
|
||||||
|
const { fileInput } = this.$refs;
|
||||||
|
Array.from(fileInput.files).forEach(file => {
|
||||||
|
this.$emit("upload", file);
|
||||||
|
});
|
||||||
|
fileInput.value = "";
|
||||||
|
},
|
||||||
|
clear() {
|
||||||
|
this.$refs.textarea.innerHTML = "";
|
||||||
|
},
|
||||||
|
initEmoji(data) {
|
||||||
|
emojiData = data;
|
||||||
|
this.$forceUpdate();
|
||||||
|
// this.emoji = [
|
||||||
|
// {
|
||||||
|
// label: "表情",
|
||||||
|
// name: "face",
|
||||||
|
// data: [
|
||||||
|
// {
|
||||||
|
// name: "wx",
|
||||||
|
// src: "微笑"
|
||||||
|
// }
|
||||||
|
// ]
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// label: "武器",
|
||||||
|
// name: "wa",
|
||||||
|
// data: [
|
||||||
|
// {
|
||||||
|
// name: "wx",
|
||||||
|
// src: "微笑"
|
||||||
|
// }
|
||||||
|
// ]
|
||||||
|
// }
|
||||||
|
// ];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<style lang="stylus">
|
||||||
|
@import '~styles/utils/index'
|
||||||
|
gap = 10px;
|
||||||
|
+b(lemon-editor)
|
||||||
|
height 200px
|
||||||
|
flex-column()
|
||||||
|
+e(tool)
|
||||||
|
display flex
|
||||||
|
height 40px
|
||||||
|
align-items center
|
||||||
|
padding-left 5px
|
||||||
|
+e(tool-item)
|
||||||
|
cursor pointer
|
||||||
|
padding 4px gap
|
||||||
|
height 28px
|
||||||
|
color #999
|
||||||
|
transition all ease .3s
|
||||||
|
[class^='lemon-icon-']
|
||||||
|
line-height 26px
|
||||||
|
font-size 22px
|
||||||
|
&:hover
|
||||||
|
color #333
|
||||||
|
+e(inner)
|
||||||
|
flex 1
|
||||||
|
overflow-x hidden
|
||||||
|
overflow-y auto
|
||||||
|
scrollbar-light()
|
||||||
|
+e(input)
|
||||||
|
height 100%
|
||||||
|
box-sizing border-box
|
||||||
|
border none
|
||||||
|
outline none
|
||||||
|
padding 0 gap
|
||||||
|
scrollbar-light()
|
||||||
|
p,div
|
||||||
|
margin 0
|
||||||
|
img
|
||||||
|
height 20px
|
||||||
|
padding 0 2px
|
||||||
|
pointer-events none
|
||||||
|
vertical-align middle
|
||||||
|
+e(footer)
|
||||||
|
display flex
|
||||||
|
height 52px
|
||||||
|
justify-content flex-end
|
||||||
|
padding 0 gap
|
||||||
|
align-items center
|
||||||
|
+e(tip)
|
||||||
|
margin-right 10px
|
||||||
|
font-size 12px
|
||||||
|
color #999
|
||||||
|
user-select none
|
||||||
|
+e(emoji)
|
||||||
|
user-select none
|
||||||
|
.lemon-popover
|
||||||
|
background #f6f6f6
|
||||||
|
.lemon-popover__content
|
||||||
|
padding 0
|
||||||
|
.lemon-popover__arrow
|
||||||
|
background #f6f6f6
|
||||||
|
.lemon-tabs-content
|
||||||
|
box-sizing border-box
|
||||||
|
padding 8px
|
||||||
|
height 200px
|
||||||
|
overflow-x hidden
|
||||||
|
overflow-y auto
|
||||||
|
scrollbar-light()
|
||||||
|
margin-bottom 8px
|
||||||
|
+e(emoji-item)
|
||||||
|
cursor pointer
|
||||||
|
width 22px
|
||||||
|
padding 4px
|
||||||
|
border-radius 4px
|
||||||
|
&:hover
|
||||||
|
background #e9e9e9
|
||||||
|
</style>
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class=''>
|
|
||||||
LemonGroupList
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
name: 'GroupList',
|
|
||||||
components: {},
|
|
||||||
data () {
|
|
||||||
return {
|
|
||||||
|
|
||||||
};
|
|
||||||
},
|
|
||||||
computed: {},
|
|
||||||
watch: {},
|
|
||||||
methods: {
|
|
||||||
|
|
||||||
},
|
|
||||||
created () {
|
|
||||||
|
|
||||||
},
|
|
||||||
mounted () {
|
|
||||||
|
|
||||||
},
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<style lang='scss'>
|
|
||||||
</style>
|
|
||||||
@@ -0,0 +1,890 @@
|
|||||||
|
<script>
|
||||||
|
import { useScopedSlot, fastDone, generateUUID } from "utils";
|
||||||
|
import { isFunction, isString, isEmpty } from "utils/validate";
|
||||||
|
import {
|
||||||
|
DEFAULT_MENUS,
|
||||||
|
DEFAULT_MENU_LASTMESSAGES,
|
||||||
|
DEFAULT_MENU_CONTACTS
|
||||||
|
} from "utils/constant";
|
||||||
|
import lastContentRender from "../lastContentRender";
|
||||||
|
|
||||||
|
import MemoryCache from "utils/cache/memory";
|
||||||
|
|
||||||
|
const CacheContactContainer = new MemoryCache();
|
||||||
|
const CacheMenuContainer = new MemoryCache();
|
||||||
|
const CacheMessageLoaded = new MemoryCache();
|
||||||
|
|
||||||
|
import {
|
||||||
|
//constraintContactMessages,
|
||||||
|
constraintContact
|
||||||
|
//constraintMessage
|
||||||
|
} from "utils/constraint";
|
||||||
|
const messages = {};
|
||||||
|
const emojiMap = {};
|
||||||
|
let renderDrawerContent = () => {};
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "LemonImui",
|
||||||
|
provide() {
|
||||||
|
return {
|
||||||
|
IMUI: this
|
||||||
|
};
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
/**
|
||||||
|
* 消息时间格式化规则
|
||||||
|
*/
|
||||||
|
messageTimeFormat: Function,
|
||||||
|
/**
|
||||||
|
* 联系人最新消息时间格式化规则
|
||||||
|
*/
|
||||||
|
contactTimeFormat: Function,
|
||||||
|
/**
|
||||||
|
* 初始化时是否隐藏抽屉
|
||||||
|
*/
|
||||||
|
hideDrawer: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 初始化时是否隐藏导航按钮上的头像
|
||||||
|
*/
|
||||||
|
hideMenuAvatar: Boolean,
|
||||||
|
user: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
drawerVisible: !this.hideDrawer,
|
||||||
|
currentContactId: "",
|
||||||
|
activeSidebar: DEFAULT_MENU_LASTMESSAGES,
|
||||||
|
contacts: [],
|
||||||
|
menus: []
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return this._renderWrapper([
|
||||||
|
this._renderMenu(),
|
||||||
|
this._renderSidebarMessage(),
|
||||||
|
this._renderSidebarContact(),
|
||||||
|
this._renderContainer(),
|
||||||
|
this._renderDrawer()
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.initMenus();
|
||||||
|
},
|
||||||
|
async mounted() {
|
||||||
|
await this.$nextTick();
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
currentMessages() {
|
||||||
|
return messages[this.currentContactId] || [];
|
||||||
|
},
|
||||||
|
currentContact() {
|
||||||
|
return this.contacts.find(item => item.id == this.currentContactId) || {};
|
||||||
|
},
|
||||||
|
currentMenu() {
|
||||||
|
return this.menus.find(item => item.name == this.activeSidebar) || {};
|
||||||
|
},
|
||||||
|
currentIsDefSidebar() {
|
||||||
|
return DEFAULT_MENUS.includes(this.activeSidebar);
|
||||||
|
},
|
||||||
|
lastMessages() {
|
||||||
|
const data = this.contacts.filter(item => !isEmpty(item.lastContent));
|
||||||
|
data.sort((a1, a2) => {
|
||||||
|
return a2.lastSendTime - a1.lastSendTime;
|
||||||
|
});
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
activeSidebar() {}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
_menuIsContacts() {
|
||||||
|
return this.activeSidebar == DEFAULT_MENU_CONTACTS;
|
||||||
|
},
|
||||||
|
_menuIsMessages() {
|
||||||
|
return this.activeSidebar == DEFAULT_MENU_LASTMESSAGES;
|
||||||
|
},
|
||||||
|
_createMessage(message) {
|
||||||
|
return {
|
||||||
|
...{
|
||||||
|
id: generateUUID(),
|
||||||
|
type: "text",
|
||||||
|
status: "going",
|
||||||
|
sendTime: new Date().getTime(),
|
||||||
|
toContactId: this.currentContactId,
|
||||||
|
fromUser: {
|
||||||
|
...this.user
|
||||||
|
}
|
||||||
|
},
|
||||||
|
...message
|
||||||
|
};
|
||||||
|
// const message = {
|
||||||
|
// id: "123",
|
||||||
|
// status: "succeed",
|
||||||
|
// type: "image",
|
||||||
|
// sendTime: 12312312312,
|
||||||
|
// content: "asdas",
|
||||||
|
// fromContactId: "123",
|
||||||
|
// fromUser: { id: "123", displayName: "123", avatar: "123",}
|
||||||
|
// }
|
||||||
|
},
|
||||||
|
// _setDefMessages(id) {
|
||||||
|
// //this.messages[id] = this.messages[id] || [];
|
||||||
|
// if (!messages[id]) {
|
||||||
|
// this.$set(messages, id, []);
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
appendMessage(message, contactId = this.currentContactId) {
|
||||||
|
this._addMessage(message, contactId, 1);
|
||||||
|
this.messageViewToBottom();
|
||||||
|
},
|
||||||
|
_emitSend(message, next, file) {
|
||||||
|
this.$emit(
|
||||||
|
"send",
|
||||||
|
message,
|
||||||
|
(replaceMessage = { status: "succeed" }) => {
|
||||||
|
next();
|
||||||
|
message = Object.assign(message, replaceMessage);
|
||||||
|
this.forceUpdateMessage(message.id);
|
||||||
|
},
|
||||||
|
file
|
||||||
|
);
|
||||||
|
},
|
||||||
|
_handleSend(text) {
|
||||||
|
const message = this._createMessage({ content: text });
|
||||||
|
this.appendMessage(message);
|
||||||
|
this._emitSend(message, () => {
|
||||||
|
this.updateContact(message.toContactId, {
|
||||||
|
lastContent: lastContentRender[message.type].call(this, message),
|
||||||
|
lastSendTime: message.sendTime
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
_handleUpload(file) {
|
||||||
|
const imageTypes = ["image/gif", "image/jpeg", "image/png"];
|
||||||
|
let joinMessage;
|
||||||
|
if (imageTypes.includes(file.type)) {
|
||||||
|
joinMessage = {
|
||||||
|
type: "image",
|
||||||
|
content: URL.createObjectURL(file)
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
joinMessage = {
|
||||||
|
type: "file",
|
||||||
|
fileSize: file.size,
|
||||||
|
fileName: file.name,
|
||||||
|
content: ""
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const message = this._createMessage(joinMessage);
|
||||||
|
this.appendMessage(message);
|
||||||
|
this._emitSend(
|
||||||
|
message,
|
||||||
|
() => {
|
||||||
|
this.updateContact(message.toContactId, {
|
||||||
|
lastContent: lastContentRender[message.type].call(this, message),
|
||||||
|
lastSendTime: message.sendTime
|
||||||
|
});
|
||||||
|
},
|
||||||
|
file
|
||||||
|
);
|
||||||
|
},
|
||||||
|
_handleReachTop(next) {
|
||||||
|
// const messages = {
|
||||||
|
// id: "8ad7e98e-5225-4892-8131-4b2ee7797599",
|
||||||
|
// type: "text",
|
||||||
|
// status: "succeed",
|
||||||
|
// sendTime: 1564926674646,
|
||||||
|
// fromContactId: "superadmin",
|
||||||
|
// fromUser: {
|
||||||
|
// id: "hehe",
|
||||||
|
// displayName: "I KNOEW",
|
||||||
|
// avatar:
|
||||||
|
// "https://ss3.bdstatic.com/70cFv8Sh_Q1YnxGkpoWK1HF6hhy/it/u=4085009425,1005454674&fm=26&gp=0.jpg"
|
||||||
|
// },
|
||||||
|
// content: "测试消息哦..."
|
||||||
|
// };
|
||||||
|
this.$emit(
|
||||||
|
"pull-messages",
|
||||||
|
this.currentContact,
|
||||||
|
(messages, isEnd = false) => {
|
||||||
|
this._addMessage(
|
||||||
|
Array(10).fill(messages[1]),
|
||||||
|
this.currentContactId,
|
||||||
|
0
|
||||||
|
);
|
||||||
|
CacheMessageLoaded.set(this.currentContactId, isEnd);
|
||||||
|
next(isEnd);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
// setTimeout(() => {
|
||||||
|
// CacheMessageLoaded.set(this.currentContactId, isEnd);
|
||||||
|
// }, 2000);
|
||||||
|
},
|
||||||
|
clearCacheContainer(name) {
|
||||||
|
CacheContactContainer.remove(name);
|
||||||
|
CacheMenuContainer.remove(name);
|
||||||
|
},
|
||||||
|
_renderWrapper(children) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
class={[
|
||||||
|
"lemon-wrapper",
|
||||||
|
this.drawerVisible && "lemon-wrapper--drawer-show"
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
_renderMenu() {
|
||||||
|
const menuItem = this._renderMenuItem();
|
||||||
|
return (
|
||||||
|
<div class="lemon-menu">
|
||||||
|
{this.hideMenuAvatar == false && (
|
||||||
|
<lemon-avatar
|
||||||
|
on-click={e => {
|
||||||
|
console.log("menu avatar click");
|
||||||
|
}}
|
||||||
|
class="lemon-menu__avatar"
|
||||||
|
src="https://ss3.bdstatic.com/70cFv8Sh_Q1YnxGkpoWK1HF6hhy/it/u=400062461,2874561526&fm=26&gp=0.jpg"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{menuItem.top}
|
||||||
|
{this.$slots.menu}
|
||||||
|
<div class="lemon-menu__bottom">
|
||||||
|
{this.$slots["menu-bottom"]}
|
||||||
|
{menuItem.bottom}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
_renderMenuAvatar() {
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
_renderMenuItem() {
|
||||||
|
const top = [];
|
||||||
|
const bottom = [];
|
||||||
|
this.menus.forEach(item => {
|
||||||
|
const { name, title, unread, render, click } = item;
|
||||||
|
const node = (
|
||||||
|
<div
|
||||||
|
class={[
|
||||||
|
"lemon-menu__item",
|
||||||
|
{ "lemon-menu__item--active": this.activeSidebar == name }
|
||||||
|
]}
|
||||||
|
on-click={() => {
|
||||||
|
fastDone(click, () => {
|
||||||
|
if (name) this.changeMenu(name);
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
title={title}
|
||||||
|
>
|
||||||
|
<lemon-badge count={unread}>{render(item)}</lemon-badge>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
item.isBottom === true ? bottom.push(node) : top.push(node);
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
top,
|
||||||
|
bottom
|
||||||
|
};
|
||||||
|
},
|
||||||
|
_renderSidebarMessage() {
|
||||||
|
return this._renderSidebar(
|
||||||
|
this.lastMessages.map(contact => {
|
||||||
|
return this._renderContact(
|
||||||
|
{
|
||||||
|
contact,
|
||||||
|
timeFormat: this.contactTimeFormat
|
||||||
|
},
|
||||||
|
() => this.changeContact(contact.id)
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
DEFAULT_MENU_LASTMESSAGES
|
||||||
|
);
|
||||||
|
},
|
||||||
|
_renderContact(props, onClick) {
|
||||||
|
const {
|
||||||
|
click: customClick,
|
||||||
|
renderContainer,
|
||||||
|
id: contactId
|
||||||
|
} = props.contact;
|
||||||
|
const click = () => {
|
||||||
|
fastDone(customClick, () => {
|
||||||
|
onClick();
|
||||||
|
this._customContainerReady(
|
||||||
|
renderContainer,
|
||||||
|
CacheContactContainer,
|
||||||
|
contactId
|
||||||
|
);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<lemon-contact
|
||||||
|
class={{
|
||||||
|
"lemon-contact--active": this.currentContactId == props.contact.id
|
||||||
|
}}
|
||||||
|
props={props}
|
||||||
|
on-click={click}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
_renderSidebarContact() {
|
||||||
|
let prevIndex;
|
||||||
|
return this._renderSidebar(
|
||||||
|
this.contacts.map(contact => {
|
||||||
|
contact.index = contact.index.replace(/\[[0-9]*\]/, "");
|
||||||
|
const node = [
|
||||||
|
contact.index !== prevIndex && (
|
||||||
|
<p class="lemon-sidebar__label">{contact.index}</p>
|
||||||
|
),
|
||||||
|
this._renderContact(
|
||||||
|
{
|
||||||
|
contact: contact,
|
||||||
|
simple: true
|
||||||
|
},
|
||||||
|
() => this.changeContact(contact.id)
|
||||||
|
)
|
||||||
|
];
|
||||||
|
prevIndex = contact.index;
|
||||||
|
return node;
|
||||||
|
}),
|
||||||
|
DEFAULT_MENU_CONTACTS
|
||||||
|
);
|
||||||
|
},
|
||||||
|
_renderSidebar(children, name) {
|
||||||
|
return (
|
||||||
|
<div class="lemon-sidebar" v-show={this.activeSidebar == name}>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
_renderDrawer() {
|
||||||
|
return this._menuIsMessages() && this.currentContactId ? (
|
||||||
|
<div class="lemon-drawer">
|
||||||
|
{renderDrawerContent()}
|
||||||
|
{useScopedSlot(this.$scopedSlots.drawer, "", this.currentContact)}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
""
|
||||||
|
);
|
||||||
|
},
|
||||||
|
_isContactContainerCache(name) {
|
||||||
|
return name.startsWith("contact#");
|
||||||
|
},
|
||||||
|
_renderContainer() {
|
||||||
|
const nodes = [];
|
||||||
|
const cls = "lemon-container";
|
||||||
|
const curact = this.currentContact;
|
||||||
|
let defIsShow = true;
|
||||||
|
for (const name in CacheContactContainer.get()) {
|
||||||
|
const show = curact.id == name && this.currentIsDefSidebar;
|
||||||
|
defIsShow = !show;
|
||||||
|
nodes.push(
|
||||||
|
<div class={cls} v-show={show}>
|
||||||
|
{CacheContactContainer.get(name)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
for (const name in CacheMenuContainer.get()) {
|
||||||
|
nodes.push(
|
||||||
|
<div
|
||||||
|
class={cls}
|
||||||
|
v-show={this.activeSidebar == name && !this.currentIsDefSidebar}
|
||||||
|
>
|
||||||
|
{CacheMenuContainer.get(name)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
nodes.push(
|
||||||
|
<div
|
||||||
|
class={cls}
|
||||||
|
v-show={this._menuIsMessages() && defIsShow && curact.id}
|
||||||
|
>
|
||||||
|
<div class="lemon-container__title">
|
||||||
|
<div class="lemon-container__displayname">
|
||||||
|
{useScopedSlot(
|
||||||
|
this.$scopedSlots["contact-title"],
|
||||||
|
curact.displayName,
|
||||||
|
curact
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<lemon-messages
|
||||||
|
ref="messages"
|
||||||
|
time-format={this.messageTimeFormat}
|
||||||
|
reverse-user-id={this.user.id}
|
||||||
|
on-reach-top={this._handleReachTop}
|
||||||
|
messages={this.currentMessages}
|
||||||
|
/>
|
||||||
|
<lemon-editor
|
||||||
|
ref="editor"
|
||||||
|
onSend={this._handleSend}
|
||||||
|
onUpload={this._handleUpload}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
nodes.push(
|
||||||
|
<div class={cls} v-show={!curact.id}>
|
||||||
|
{this.$slots.cover}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
nodes.push(
|
||||||
|
<div
|
||||||
|
class={cls}
|
||||||
|
v-show={this._menuIsContacts() && defIsShow && curact.id}
|
||||||
|
>
|
||||||
|
{useScopedSlot(
|
||||||
|
this.$scopedSlots["contact-info"],
|
||||||
|
<div class="lemon-contact-info">
|
||||||
|
<lemon-avatar src={curact.avatar} size={90} />
|
||||||
|
<h4>{curact.displayName}</h4>
|
||||||
|
<lemon-button
|
||||||
|
on-click={() => {
|
||||||
|
this.changeContact(curact.id, DEFAULT_MENU_LASTMESSAGES);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{" "}
|
||||||
|
发送消息{" "}
|
||||||
|
</lemon-button>
|
||||||
|
</div>,
|
||||||
|
curact
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
return nodes;
|
||||||
|
},
|
||||||
|
_addContact(data, t) {
|
||||||
|
const type = {
|
||||||
|
0: "unshift",
|
||||||
|
1: "push"
|
||||||
|
}[t];
|
||||||
|
constraintContact(data);
|
||||||
|
//this.contacts[type](cloneDeep(data));
|
||||||
|
this.contacts[type](data);
|
||||||
|
},
|
||||||
|
_addMessage(data, contactId, t) {
|
||||||
|
const type = {
|
||||||
|
0: "unshift",
|
||||||
|
1: "push"
|
||||||
|
}[t];
|
||||||
|
if (!Array.isArray(data)) data = [data];
|
||||||
|
messages[contactId] = messages[contactId] || [];
|
||||||
|
messages[contactId][type](...data);
|
||||||
|
//console.log(messages[contactId]);
|
||||||
|
this.forceUpdateMessage();
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 设置最新消息DOM
|
||||||
|
* @param {String} messageType 消息类型
|
||||||
|
* @param {Function} render 返回消息 vnode
|
||||||
|
*/
|
||||||
|
setLastContentRender(messageType, render) {
|
||||||
|
lastContentRender[messageType] = render;
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 将字符串内的 EmojiItem.name 替换为 img
|
||||||
|
* @param {String} str 被替换的字符串
|
||||||
|
* @return {String} 替换后的字符串
|
||||||
|
*/
|
||||||
|
replaceEmojiName(str) {
|
||||||
|
return str.replace(/\[!(\w+)\]/gi, (str, match) => {
|
||||||
|
const file = match;
|
||||||
|
return emojiMap[file]
|
||||||
|
? `<img src="${emojiMap[file]}" />`
|
||||||
|
: `[!${match}]`;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 将当前聊天窗口滚动到底部
|
||||||
|
*/
|
||||||
|
messageViewToBottom() {
|
||||||
|
this.$refs.messages.scrollToBottom();
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 改变聊天对象
|
||||||
|
* @param contactId 联系人 id
|
||||||
|
*/
|
||||||
|
changeContact(contactId, menuName) {
|
||||||
|
if (this.currentContactId == contactId) {
|
||||||
|
this.currentContactId = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (menuName) {
|
||||||
|
this.changeMenu(menuName);
|
||||||
|
}
|
||||||
|
this.currentContactId = contactId;
|
||||||
|
this.$emit("change-contact", this.currentContact);
|
||||||
|
if (isFunction(this.currentContact.renderContainer)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this._menuIsMessages()) {
|
||||||
|
if (!CacheMessageLoaded.has(contactId)) {
|
||||||
|
this.$refs.messages.resetLoadState();
|
||||||
|
}
|
||||||
|
if (!messages[contactId]) {
|
||||||
|
this.$emit(
|
||||||
|
"pull-messages",
|
||||||
|
this.currentContact,
|
||||||
|
(messages, isEnd) => {
|
||||||
|
this._addMessage(messages, contactId, 0);
|
||||||
|
this.messageViewToBottom();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
this.messageViewToBottom();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 删除一条聊天消息
|
||||||
|
* @param messageId 消息 id
|
||||||
|
* @param contactId 联系人 id
|
||||||
|
*/
|
||||||
|
removeMessage(messageId, contactId) {
|
||||||
|
const index = this.findMessageIndexById(messageId, contactId);
|
||||||
|
if (index !== -1) {
|
||||||
|
messages[contactId].splice(index, 1);
|
||||||
|
this.forceUpdateMessage();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 修改聊天一条聊天消息
|
||||||
|
* @param {Message} data 根据 data.id 查找聊天消息并覆盖传入的值
|
||||||
|
* @param contactId 联系人 id
|
||||||
|
*/
|
||||||
|
updateMessage(messageId, contactId, data) {
|
||||||
|
const index = this.findMessageIndexById(messageId, contactId);
|
||||||
|
if (index !== -1) {
|
||||||
|
messages[contactId][index] = {
|
||||||
|
...messages[contactId][index],
|
||||||
|
...data
|
||||||
|
};
|
||||||
|
this.forceUpdateMessage(messageId);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 手动更新对话消息
|
||||||
|
* @param {String} messageId 消息ID,如果为空则更新当前聊天窗口的所有消息
|
||||||
|
*/
|
||||||
|
forceUpdateMessage(messageId) {
|
||||||
|
if (!messageId) {
|
||||||
|
this.$refs.messages.$forceUpdate();
|
||||||
|
} else {
|
||||||
|
const components = this.$refs.messages.$refs.message;
|
||||||
|
if (components) {
|
||||||
|
const messageComponent = components.find(
|
||||||
|
com => com.$attrs.message.id == messageId
|
||||||
|
);
|
||||||
|
if (messageComponent) messageComponent.$forceUpdate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_customContainerReady(render, cacheDrive, key) {
|
||||||
|
if (isFunction(render) && !cacheDrive.has(key)) {
|
||||||
|
cacheDrive.set(key, render.call(this));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 切换左侧按钮
|
||||||
|
* @param {String} name 按钮 name
|
||||||
|
*/
|
||||||
|
changeMenu(name) {
|
||||||
|
this.$emit("change-menu", name);
|
||||||
|
this.activeSidebar = name;
|
||||||
|
const { renderContainer } = this.currentMenu;
|
||||||
|
this._customContainerReady(renderContainer, CacheMenuContainer, name);
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 初始化编辑框的 Emoji 表情列表,是 Lemon-editor.initEmoji 的代理方法
|
||||||
|
* @param {Array<Emoji,EmojiItem>} data emoji 数据
|
||||||
|
* Emoji = {label: 表情,children: [{name: wx,title: 微笑,src: url}]} 分组
|
||||||
|
* EmojiItem = {name: wx,title: 微笑,src: url} 无分组
|
||||||
|
*/
|
||||||
|
initEmoji(data) {
|
||||||
|
this.$refs.editor.initEmoji(data);
|
||||||
|
if (data[0].label) {
|
||||||
|
data = data.flatMap(item => item.children);
|
||||||
|
}
|
||||||
|
data.forEach(({ name, src }) => (emojiMap[name] = src));
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 初始化左侧按钮
|
||||||
|
* @param {Array<Menu>} data 按钮数据
|
||||||
|
*/
|
||||||
|
initMenus(data) {
|
||||||
|
const defaultMenus = [
|
||||||
|
{
|
||||||
|
name: DEFAULT_MENU_LASTMESSAGES,
|
||||||
|
title: "聊天",
|
||||||
|
unread: 0,
|
||||||
|
click: null,
|
||||||
|
render: menu => {
|
||||||
|
return <i class="lemon-icon-message" />;
|
||||||
|
},
|
||||||
|
isBottom: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: DEFAULT_MENU_CONTACTS,
|
||||||
|
title: "通讯录",
|
||||||
|
unread: 0,
|
||||||
|
click: null,
|
||||||
|
render: menu => {
|
||||||
|
return <i class="lemon-icon-addressbook" />;
|
||||||
|
},
|
||||||
|
isBottom: false
|
||||||
|
}
|
||||||
|
];
|
||||||
|
let menus = [];
|
||||||
|
if (Array.isArray(data)) {
|
||||||
|
const indexMap = {
|
||||||
|
lastMessages: 0,
|
||||||
|
contacts: 1
|
||||||
|
};
|
||||||
|
const indexKeys = Object.keys(indexMap);
|
||||||
|
menus = data.map(item => {
|
||||||
|
if (indexKeys.includes(item.name)) {
|
||||||
|
return {
|
||||||
|
...defaultMenus[indexMap[item.name]],
|
||||||
|
...item,
|
||||||
|
...{ renderContainer: null }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
menus = defaultMenus;
|
||||||
|
}
|
||||||
|
this.menus = menus;
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 初始化联系人数据
|
||||||
|
* @param {Array<Contact>} data 联系人列表
|
||||||
|
*/
|
||||||
|
initContacts(data) {
|
||||||
|
this.contacts.push(...data);
|
||||||
|
this.sortContacts();
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 使用 联系人的 index 值进行排序
|
||||||
|
*/
|
||||||
|
sortContacts() {
|
||||||
|
this.contacts.sort((a, b) => {
|
||||||
|
return a.index.localeCompare(b.index);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 修改联系人数据
|
||||||
|
* @param {Contact} data 修改的数据,根据 data.id 查找联系人并覆盖传入的值
|
||||||
|
*/
|
||||||
|
updateContact(contactId, data) {
|
||||||
|
delete data.id;
|
||||||
|
delete data.toContactId;
|
||||||
|
|
||||||
|
const index = this.findContactIndexById(contactId);
|
||||||
|
if (index !== -1) {
|
||||||
|
const { unread } = data;
|
||||||
|
if (isString(unread)) {
|
||||||
|
if (unread.indexOf("+") === 0 || unread.indexOf("-") === 0) {
|
||||||
|
data.unread =
|
||||||
|
parseInt(unread) + parseInt(this.contacts[index].unread);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.$set(this.contacts, index, {
|
||||||
|
...this.contacts[index],
|
||||||
|
...data
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 根据 id 查找联系人的索引
|
||||||
|
* @param contactId 联系人 id
|
||||||
|
* @return {Number} 联系人索引,未找到返回 -1
|
||||||
|
*/
|
||||||
|
findContactIndexById(contactId) {
|
||||||
|
return this.contacts.findIndex(item => item.id == contactId);
|
||||||
|
},
|
||||||
|
findMessageIndexById(messageId, contactId) {
|
||||||
|
const msg = messages[contactId];
|
||||||
|
if (isEmpty(msg)) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return msg.findIndex(item => item.id == messageId);
|
||||||
|
},
|
||||||
|
findMessageById(messageId, contactId) {
|
||||||
|
const index = this.findMessageIndexById(messageId, contactId);
|
||||||
|
if (index !== -1) return messages[contactId][index];
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 返回所有联系人
|
||||||
|
* @return {Array<Contact>}
|
||||||
|
*/
|
||||||
|
getContacts() {
|
||||||
|
return this.contacts;
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 返回所有消息
|
||||||
|
* @return {Object<Contact.id,Message>}
|
||||||
|
*/
|
||||||
|
getMessages() {
|
||||||
|
return messages;
|
||||||
|
},
|
||||||
|
// appendContact(data) {
|
||||||
|
// this._addContact(data, 0);
|
||||||
|
// },
|
||||||
|
// prependContact(data) {
|
||||||
|
// this._addContact(data, 1);
|
||||||
|
// },
|
||||||
|
// addContactMessage(data) {
|
||||||
|
// this._addContact(data, 0);
|
||||||
|
// },
|
||||||
|
// prependContactMessage(data) {
|
||||||
|
// this._addContact(data, 1);
|
||||||
|
// },
|
||||||
|
// appendMessage(data) {},
|
||||||
|
// prependMessage(data) {},
|
||||||
|
// removeContact(contactId) {},
|
||||||
|
// removeContactMessage(contactId) {},
|
||||||
|
// removeContactAll(contactId) {},
|
||||||
|
/**
|
||||||
|
* 将自定义的HTML显示在主窗口内
|
||||||
|
*/
|
||||||
|
openrenderContainer(vnode) {
|
||||||
|
//renderContainerQueue[this.activeSidebar] = vnode;
|
||||||
|
//this.$slots._renderContainer = vnode;
|
||||||
|
},
|
||||||
|
changeDrawer(render) {
|
||||||
|
this.drawerVisible = !this.drawerVisible;
|
||||||
|
if (this.drawerVisible == true) this.openDrawer(render);
|
||||||
|
},
|
||||||
|
openDrawer(render) {
|
||||||
|
renderDrawerContent = render || new Function();
|
||||||
|
this.drawerVisible = true;
|
||||||
|
},
|
||||||
|
closeDrawer() {
|
||||||
|
this.drawerVisible = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<style lang="stylus">
|
||||||
|
wrapper-width = 850px
|
||||||
|
drawer-width = 200px
|
||||||
|
bezier = cubic-bezier(0.645, 0.045, 0.355, 1)
|
||||||
|
@import '~styles/utils/index'
|
||||||
|
|
||||||
|
+b(lemon-wrapper)
|
||||||
|
width wrapper-width
|
||||||
|
height 580px
|
||||||
|
display flex
|
||||||
|
font-size 14px
|
||||||
|
border-radius 5px
|
||||||
|
//mask-image radial-gradient(circle, white 100%, black 100%)
|
||||||
|
background #efefef
|
||||||
|
transition all .4s bezier
|
||||||
|
border-radius 4px
|
||||||
|
p
|
||||||
|
margin 0
|
||||||
|
img
|
||||||
|
vertical-align middle
|
||||||
|
border-style none
|
||||||
|
+b(lemon-menu)
|
||||||
|
flex-column()
|
||||||
|
align-items center
|
||||||
|
width 60px
|
||||||
|
background #1d232a
|
||||||
|
padding 15px 0
|
||||||
|
position relative
|
||||||
|
user-select none
|
||||||
|
+e(bottom)
|
||||||
|
flex-column()
|
||||||
|
position absolute
|
||||||
|
bottom 0
|
||||||
|
+e(avatar)
|
||||||
|
margin-bottom 20px
|
||||||
|
cursor pointer
|
||||||
|
+e(item)
|
||||||
|
color #999
|
||||||
|
cursor pointer
|
||||||
|
padding 14px 10px
|
||||||
|
max-width 100%
|
||||||
|
+m(active)
|
||||||
|
color #0fd547
|
||||||
|
&:hover:not(.lemon-menu__item--active)
|
||||||
|
color #eee
|
||||||
|
word-break()
|
||||||
|
> *
|
||||||
|
font-size 24px
|
||||||
|
.ant-badge-count
|
||||||
|
display inline-block
|
||||||
|
padding 0 4px
|
||||||
|
height 18px
|
||||||
|
line-height 16px
|
||||||
|
min-width 18px
|
||||||
|
.ant-badge-count
|
||||||
|
.ant-badge-dot
|
||||||
|
box-shadow 0 0 0 1px #1d232a
|
||||||
|
+b(lemon-sidebar)
|
||||||
|
width 250px
|
||||||
|
background #efefef
|
||||||
|
overflow-y auto
|
||||||
|
scrollbar-light()
|
||||||
|
+e(label)
|
||||||
|
padding 6px 14px 6px 14px
|
||||||
|
color #666
|
||||||
|
font-size 12px
|
||||||
|
margin 0
|
||||||
|
+b(lemon-contact--active)
|
||||||
|
background #d9d9d9
|
||||||
|
+b(lemon-container)
|
||||||
|
flex 1
|
||||||
|
flex-column()
|
||||||
|
background #f4f4f4
|
||||||
|
word-break()
|
||||||
|
position relative
|
||||||
|
z-index 2
|
||||||
|
+e(title)
|
||||||
|
padding 15px 15px
|
||||||
|
+e(displayname)
|
||||||
|
font-size 16px
|
||||||
|
+b(lemon-messages)
|
||||||
|
flex 1
|
||||||
|
height auto
|
||||||
|
+b(lemon-drawer)
|
||||||
|
position absolute
|
||||||
|
top 0
|
||||||
|
right 0
|
||||||
|
overflow hidden
|
||||||
|
width drawer-width
|
||||||
|
background #f4f4f4
|
||||||
|
transition width .4s bezier
|
||||||
|
height 100%
|
||||||
|
z-index 1
|
||||||
|
//border-left 1px solid #e9e9e9
|
||||||
|
box-sizing border-box
|
||||||
|
+b(lemon-wrapper)
|
||||||
|
+m(drawer-show)
|
||||||
|
+b(lemon-drawer)
|
||||||
|
right -200px
|
||||||
|
+b(lemon-contact-info)
|
||||||
|
flex-column()
|
||||||
|
justify-content center
|
||||||
|
align-items center
|
||||||
|
height 100%
|
||||||
|
h4
|
||||||
|
font-size 16px
|
||||||
|
font-weight normal
|
||||||
|
margin 10px 0 20px 0
|
||||||
|
user-select none
|
||||||
|
</style>
|
||||||
@@ -1,226 +0,0 @@
|
|||||||
<template>
|
|
||||||
<el-container class="lemon-container lemon-container--center"
|
|
||||||
ref="container">
|
|
||||||
<el-aside class="lemon-sidebar"
|
|
||||||
width="240px">
|
|
||||||
<ul class="lemon-tab">
|
|
||||||
<li v-for="item in tabList"
|
|
||||||
:key="item.name"
|
|
||||||
:tab-name="item.name"
|
|
||||||
:class="['lemon-tab__item', item.name == currentTab && 'lemon-tab__item--active']"
|
|
||||||
@click="tabChange(item.name)">
|
|
||||||
<span :class="item.icon"></span>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<div class="lemon-tabview">
|
|
||||||
<div class="lemon-tabview__item"
|
|
||||||
v-for="item in tabList"
|
|
||||||
v-show="item.name == currentTab"
|
|
||||||
:key="item.name"
|
|
||||||
:tabview-name="item.name">
|
|
||||||
<component :is="item.componentName"
|
|
||||||
@changeMessageView="_changeMessageView"></component>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</el-aside>
|
|
||||||
<el-container class="lemon-main">
|
|
||||||
<el-header class="lemon-header"
|
|
||||||
height="48px">
|
|
||||||
宜宾劲越二手车市场(上江北) (500)
|
|
||||||
</el-header>
|
|
||||||
<el-main>
|
|
||||||
<lemon-message-view></lemon-message-view>
|
|
||||||
</el-main>
|
|
||||||
<el-main>工具欄</el-main>
|
|
||||||
</el-container>
|
|
||||||
</el-container>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import LemonContactList from '../contact-list'
|
|
||||||
import LemonGroupList from '../group-list'
|
|
||||||
import LemonMessageList from '../message-list'
|
|
||||||
import LemonMessageView from '../message-view'
|
|
||||||
const components = {
|
|
||||||
LemonContactList,
|
|
||||||
LemonGroupList,
|
|
||||||
LemonMessageList,
|
|
||||||
LemonMessageView
|
|
||||||
}
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'LemonIm',
|
|
||||||
components,
|
|
||||||
provide () {
|
|
||||||
return {
|
|
||||||
control: this
|
|
||||||
}
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
friends: {
|
|
||||||
type: Array,
|
|
||||||
default: () => []
|
|
||||||
},
|
|
||||||
groups: {
|
|
||||||
type: Array,
|
|
||||||
default: () => []
|
|
||||||
}
|
|
||||||
},
|
|
||||||
data () {
|
|
||||||
this.tabList = [{
|
|
||||||
name: 'message',
|
|
||||||
icon: 'el-icon-s-comment',
|
|
||||||
componentName: 'lemon-message-list',
|
|
||||||
}, {
|
|
||||||
name: 'contact',
|
|
||||||
icon: 'el-icon-user-solid',
|
|
||||||
componentName: 'lemon-contact-list',
|
|
||||||
}, {
|
|
||||||
name: 'group',
|
|
||||||
icon: 'el-icon-message-solid',
|
|
||||||
componentName: 'lemon-group-list',
|
|
||||||
}]
|
|
||||||
return {
|
|
||||||
//当前会话对象的ID 根据 currentTab
|
|
||||||
currentTab: 'contact',
|
|
||||||
//当前聊天用户ID 根据聊天类型不一样 代表用户ID 群组ID 讨论组ID
|
|
||||||
currentMessageId: null,
|
|
||||||
//聊天视图是否加载中
|
|
||||||
messageViewloading: true,
|
|
||||||
//群聊天记录 groupId => [message]
|
|
||||||
groupMessageData: {
|
|
||||||
|
|
||||||
},
|
|
||||||
//好友聊天记录 friendId => [message]
|
|
||||||
friendMessageData: {
|
|
||||||
|
|
||||||
}
|
|
||||||
};
|
|
||||||
},
|
|
||||||
created () {
|
|
||||||
},
|
|
||||||
mounted () {
|
|
||||||
this._initialize()
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
//聊天窗口中的数据
|
|
||||||
messageViewData () {
|
|
||||||
return this.friendMessageData[this.currentMessageId]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
watch: {},
|
|
||||||
methods: {
|
|
||||||
//左侧选项卡切换
|
|
||||||
tabChange (name) {
|
|
||||||
this.currentTab = name;
|
|
||||||
this.findNode(`.lemon-tabview`).scrollTop = '0px'
|
|
||||||
},
|
|
||||||
//打开一个群组聊天窗口
|
|
||||||
openGroupMessageView (group) {
|
|
||||||
this._changeMessageViewComplete(group.id)
|
|
||||||
},
|
|
||||||
//打开普通聊天窗口
|
|
||||||
openFriendMessageView (friend) {
|
|
||||||
if (!this.friendMessageData[friend.id]) {
|
|
||||||
this.$emit('pull-friends-message', friend, (newData) => {
|
|
||||||
this._changeMessageViewComplete(friend.id)
|
|
||||||
this.friendMessageData[friend.id] = [...newData]
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
this._changeMessageViewComplete(friend.id)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
_changeMessageViewComplete (id) {
|
|
||||||
this.messageViewloading = false
|
|
||||||
this.currentMessageId = id
|
|
||||||
},
|
|
||||||
_changeMessageView (item) {
|
|
||||||
this.openFriendMessageView(item)
|
|
||||||
/**
|
|
||||||
const resolve = (newData) => {
|
|
||||||
this.messageViewloading = false
|
|
||||||
this.
|
|
||||||
}
|
|
||||||
const reject = () => {
|
|
||||||
this.messageViewloading = false
|
|
||||||
}
|
|
||||||
this.$emit('pullFriendsMessage', item, {
|
|
||||||
resolve,
|
|
||||||
reject
|
|
||||||
})
|
|
||||||
*/
|
|
||||||
},
|
|
||||||
_openMessageView () {
|
|
||||||
|
|
||||||
},
|
|
||||||
findNode (query) {
|
|
||||||
return this.$refs.container.$el.querySelector(query)
|
|
||||||
},
|
|
||||||
_initialize () {
|
|
||||||
|
|
||||||
},
|
|
||||||
//将左侧的滚动条重置到顶部
|
|
||||||
_tabviewScrollToTop () {
|
|
||||||
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<style lang='scss'>
|
|
||||||
@import '~styles/utils/index';
|
|
||||||
body {
|
|
||||||
background: #8d9198;
|
|
||||||
}
|
|
||||||
|
|
||||||
@include b(container) {
|
|
||||||
width: 900px;
|
|
||||||
height: 700px;
|
|
||||||
@include m(center) {
|
|
||||||
@include position-center(fixed);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@include b(sidebar) {
|
|
||||||
background: #1f252d;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
color: #fff;
|
|
||||||
overflow: hidden;
|
|
||||||
.el-tabs--card {
|
|
||||||
.el-tabs__nav,
|
|
||||||
.el-tabs__item,
|
|
||||||
.el-tabs__header {
|
|
||||||
border: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@include b(tab) {
|
|
||||||
display: flex;
|
|
||||||
width: 100%;
|
|
||||||
@include e(item) {
|
|
||||||
cursor: pointer;
|
|
||||||
line-height: 56px;
|
|
||||||
text-align: center;
|
|
||||||
flex: 1;
|
|
||||||
transition: all ease-in-out 0.3s;
|
|
||||||
font-size: 22px;
|
|
||||||
color: #6d6d6d;
|
|
||||||
@include m(active) {
|
|
||||||
color: #11d207;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@include b(tabview) {
|
|
||||||
flex: 1;
|
|
||||||
overflow-y: auto;
|
|
||||||
@include scrollbar-dark();
|
|
||||||
|
|
||||||
@include e(item) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@include b(main) {
|
|
||||||
background: #eceef1;
|
|
||||||
}
|
|
||||||
@include b(header) {
|
|
||||||
line-height: 48px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class='lemon-message-list'>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
name: 'MessageList',
|
|
||||||
inject: ["control"],
|
|
||||||
data () {
|
|
||||||
return {
|
|
||||||
|
|
||||||
};
|
|
||||||
},
|
|
||||||
created () {
|
|
||||||
|
|
||||||
},
|
|
||||||
mounted () {
|
|
||||||
},
|
|
||||||
computed: {},
|
|
||||||
watch: {},
|
|
||||||
methods: {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<style lang='scss'>
|
|
||||||
</style>
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class='lemon-message-view'
|
|
||||||
v-loading="control.messageViewloading"
|
|
||||||
element-loading-background="transparent">
|
|
||||||
{{ control.messageViewData }}
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
name: 'MessageView',
|
|
||||||
components: {},
|
|
||||||
inject: ["control"],
|
|
||||||
created () {
|
|
||||||
|
|
||||||
},
|
|
||||||
mounted () {
|
|
||||||
},
|
|
||||||
computed: {},
|
|
||||||
watch: {},
|
|
||||||
methods: {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<style lang='scss'>
|
|
||||||
@import '~styles/utils/index';
|
|
||||||
@include b(message-view) {
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -0,0 +1,178 @@
|
|||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "lemonMessageBasic",
|
||||||
|
props: {
|
||||||
|
message: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
timeFormat: {
|
||||||
|
type: Function,
|
||||||
|
default: () => ""
|
||||||
|
},
|
||||||
|
reverse: Boolean,
|
||||||
|
hiddenTitle: Boolean
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
render() {
|
||||||
|
const { fromUser, status, sendTime } = this.message;
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
class={[
|
||||||
|
"lemon-message",
|
||||||
|
{
|
||||||
|
"lemon-message--reverse": this.reverse,
|
||||||
|
"lemon-message--hidden-title": this.hiddenTitle
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<div class="lemon-message__avatar">
|
||||||
|
<lemon-avatar
|
||||||
|
size={36}
|
||||||
|
shape="square"
|
||||||
|
src={fromUser.avatar}
|
||||||
|
on-click={() => {
|
||||||
|
console.log("message avatar click");
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="lemon-message__inner">
|
||||||
|
<div class="lemon-message__title">
|
||||||
|
<span
|
||||||
|
on-click={() => {
|
||||||
|
console.log("message displayname click");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{fromUser.displayName}
|
||||||
|
</span>
|
||||||
|
<span class="lemon-message__time">{this.timeFormat(sendTime)}</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="lemon-message__content"
|
||||||
|
on-click={() => {
|
||||||
|
console.log("message content click");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{this.useScopedSlots("content", this.message)}
|
||||||
|
</div>
|
||||||
|
<div class="lemon-message__status">{this._renderStatue(status)}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
created() {},
|
||||||
|
mounted() {},
|
||||||
|
computed: {},
|
||||||
|
watch: {},
|
||||||
|
methods: {
|
||||||
|
_renderStatue(status) {
|
||||||
|
if (status == "going") {
|
||||||
|
return <i class="lemon-icon-loading lemonani-spin" />;
|
||||||
|
} else if (status == "failed") {
|
||||||
|
return (
|
||||||
|
<i
|
||||||
|
class="lemon-icon-prompt"
|
||||||
|
title="重发消息"
|
||||||
|
style={{
|
||||||
|
color: "#ff2525",
|
||||||
|
cursor: "pointer"
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
useScopedSlots(name, params, defVnode = "", context = this) {
|
||||||
|
return context.$scopedSlots[name]
|
||||||
|
? context.$scopedSlots[name](params)
|
||||||
|
: defVnode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<style lang="stylus">
|
||||||
|
@import '~styles/utils/index'
|
||||||
|
arrow()
|
||||||
|
content ' '
|
||||||
|
position absolute
|
||||||
|
top 6px
|
||||||
|
width 0
|
||||||
|
height 0
|
||||||
|
border 4px solid transparent
|
||||||
|
+b(lemon-message)
|
||||||
|
display flex
|
||||||
|
padding 10px 0
|
||||||
|
+e(time)
|
||||||
|
color #bbb
|
||||||
|
padding 0 4px
|
||||||
|
+e(inner)
|
||||||
|
position relative
|
||||||
|
+e(avatar)
|
||||||
|
padding-right 10px
|
||||||
|
user-select none
|
||||||
|
.lemon-avatar
|
||||||
|
cursor pointer
|
||||||
|
+e(title)
|
||||||
|
display flex
|
||||||
|
font-size 12px
|
||||||
|
line-height 14px
|
||||||
|
padding-bottom 6px
|
||||||
|
user-select none
|
||||||
|
color #999
|
||||||
|
+e(content)
|
||||||
|
font-size 14px
|
||||||
|
line-height 20px
|
||||||
|
padding 8px 10px
|
||||||
|
background #fff
|
||||||
|
border-radius 4px
|
||||||
|
position relative
|
||||||
|
margin 0 46px 0 0
|
||||||
|
img
|
||||||
|
video
|
||||||
|
background #e9e9e9
|
||||||
|
height 100px
|
||||||
|
&:before
|
||||||
|
arrow()
|
||||||
|
left -4px
|
||||||
|
border-left none
|
||||||
|
border-right-color #fff
|
||||||
|
+e(status)
|
||||||
|
position absolute
|
||||||
|
top 23px
|
||||||
|
right 20px
|
||||||
|
color #aaa
|
||||||
|
font-size 20px
|
||||||
|
+m(reverse)
|
||||||
|
flex-direction row-reverse
|
||||||
|
+e(title)
|
||||||
|
flex-direction row-reverse
|
||||||
|
+e(status)
|
||||||
|
left 20px
|
||||||
|
right auto
|
||||||
|
+e(content)
|
||||||
|
background #35d863
|
||||||
|
margin 0 0 0 46px
|
||||||
|
&:before
|
||||||
|
arrow()
|
||||||
|
left auto
|
||||||
|
right -4px
|
||||||
|
border-right none
|
||||||
|
border-left-color #35d863
|
||||||
|
+e(title)
|
||||||
|
text-align right
|
||||||
|
+e(avatar)
|
||||||
|
padding-right 0
|
||||||
|
padding-left 10px
|
||||||
|
+m(hidden-title)
|
||||||
|
+e(status)
|
||||||
|
top 7px
|
||||||
|
+e(title)
|
||||||
|
display none
|
||||||
|
+e(content)
|
||||||
|
&:before
|
||||||
|
top 14px
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "lemonMessageEvent",
|
||||||
|
inheritAttrs: false,
|
||||||
|
render() {
|
||||||
|
const { content } = this.$attrs.message;
|
||||||
|
return (
|
||||||
|
<div class="lemon-message lemon-message-event">
|
||||||
|
<span class="lemon-message-event__content">{content}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<style lang="stylus">
|
||||||
|
@import '~styles/utils/index'
|
||||||
|
+b(lemon-message-event)
|
||||||
|
+e(content)
|
||||||
|
user-select none
|
||||||
|
display inline-block
|
||||||
|
background #e9e9e9
|
||||||
|
color #aaa
|
||||||
|
font-size 12px
|
||||||
|
margin 0 auto
|
||||||
|
padding 5px 10px
|
||||||
|
border-radius 4px
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
<script>
|
||||||
|
import { formatByte } from "utils";
|
||||||
|
export default {
|
||||||
|
name: "lemonMessageFile",
|
||||||
|
inheritAttrs: false,
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<lemon-message-basic
|
||||||
|
class="lemon-message-file"
|
||||||
|
props={{ ...this.$attrs }}
|
||||||
|
scopedSlots={{
|
||||||
|
content: props => [
|
||||||
|
<div class="lemon-message-file__inner">
|
||||||
|
<p class="lemon-message-file__name">{props.fileName}</p>
|
||||||
|
<p class="lemon-message-file__byte">
|
||||||
|
{formatByte(props.fileSize)}
|
||||||
|
</p>
|
||||||
|
</div>,
|
||||||
|
<div class="lemon-message-file__sfx">
|
||||||
|
<i class="lemon-icon-attah" />
|
||||||
|
</div>
|
||||||
|
]
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<style lang="stylus">
|
||||||
|
@import '~styles/utils/index'
|
||||||
|
+b(lemon-message-file)
|
||||||
|
+b(lemon-message)
|
||||||
|
+e(content)
|
||||||
|
display flex
|
||||||
|
cursor pointer
|
||||||
|
width 200px
|
||||||
|
background #fff
|
||||||
|
padding 12px 18px
|
||||||
|
overflow hidden
|
||||||
|
p
|
||||||
|
margin 0
|
||||||
|
+e(tip)
|
||||||
|
display none
|
||||||
|
+e(inner)
|
||||||
|
flex 1
|
||||||
|
+e(name)
|
||||||
|
font-size 14px
|
||||||
|
+e(byte)
|
||||||
|
font-size 12px
|
||||||
|
color #aaa
|
||||||
|
+e(sfx)
|
||||||
|
display flex
|
||||||
|
align-items center
|
||||||
|
justify-content center
|
||||||
|
font-weight bold
|
||||||
|
user-select none
|
||||||
|
font-size 34px
|
||||||
|
color #ccc
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "lemonMessageImage",
|
||||||
|
inheritAttrs: false,
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<lemon-message-basic
|
||||||
|
class="lemon-message-image"
|
||||||
|
props={{ ...this.$attrs }}
|
||||||
|
scopedSlots={{
|
||||||
|
content: props => <img src={props.content} />
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<style lang="stylus">
|
||||||
|
@import '~styles/utils/index'
|
||||||
|
+b(lemon-message-image)
|
||||||
|
+b(lemon-message)
|
||||||
|
+e(content)
|
||||||
|
padding 0
|
||||||
|
cursor pointer
|
||||||
|
overflow hidden
|
||||||
|
img
|
||||||
|
max-width 100%
|
||||||
|
min-width 100px
|
||||||
|
display block
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
<script>
|
||||||
|
import IMUIProxy from "mixins/IMUIProxy";
|
||||||
|
export default {
|
||||||
|
name: "lemonMessageText",
|
||||||
|
inheritAttrs: false,
|
||||||
|
mixins: [IMUIProxy],
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<lemon-message-basic
|
||||||
|
class="lemon-message-text"
|
||||||
|
props={{ ...this.$attrs }}
|
||||||
|
scopedSlots={{
|
||||||
|
content: props => {
|
||||||
|
const content = this.IMUI.replaceEmojiName(props.content);
|
||||||
|
return <span domProps={{ innerHTML: content }} />;
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<style lang="stylus">
|
||||||
|
@import '~styles/utils/index'
|
||||||
|
+b(lemon-message-text)
|
||||||
|
+b(lemon-message)
|
||||||
|
+e(content)
|
||||||
|
img
|
||||||
|
width 18px
|
||||||
|
height 18px
|
||||||
|
display inline-block
|
||||||
|
background transparent
|
||||||
|
padding 0 2px
|
||||||
|
vertical-align middle
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,142 @@
|
|||||||
|
<script>
|
||||||
|
import { hoursTimeFormat } from "utils";
|
||||||
|
export default {
|
||||||
|
name: "LemonMessages",
|
||||||
|
components: {},
|
||||||
|
props: {
|
||||||
|
reverseUserId: String,
|
||||||
|
timeRange: {
|
||||||
|
type: Number,
|
||||||
|
default: 1
|
||||||
|
},
|
||||||
|
timeFormat: {
|
||||||
|
type: Function,
|
||||||
|
default(val) {
|
||||||
|
return hoursTimeFormat(val);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
messages: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
loading: false,
|
||||||
|
loadend: false
|
||||||
|
};
|
||||||
|
},
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div class="lemon-messages" ref="wrap" on-scroll={this._handleScroll}>
|
||||||
|
<div
|
||||||
|
class={[
|
||||||
|
"lemon-messages__load",
|
||||||
|
`lemon-messages__load--${this.loadend ? "end" : "ing"}`
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
{this.loadend ? this._renderLoadEnd() : this._renderLoading()}
|
||||||
|
</div>
|
||||||
|
{this.messages.map((message, index) => {
|
||||||
|
const node = [];
|
||||||
|
const tagName = `lemon-message-${message.type}`;
|
||||||
|
const prev = this.messages[index - 1];
|
||||||
|
if (
|
||||||
|
prev &&
|
||||||
|
this.msecRange &&
|
||||||
|
message.sendTime - prev.sendTime > this.msecRange
|
||||||
|
) {
|
||||||
|
node.push(
|
||||||
|
<lemon-message-event
|
||||||
|
attrs={{
|
||||||
|
message: {
|
||||||
|
id: "__time__",
|
||||||
|
type: "event",
|
||||||
|
content: this.timeFormat(message.sendTime)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
node.push(
|
||||||
|
<tagName
|
||||||
|
ref="message"
|
||||||
|
refInFor={true}
|
||||||
|
attrs={{
|
||||||
|
timeFormat: this.msecRange > 0 ? () => {} : this.timeFormat,
|
||||||
|
message: message,
|
||||||
|
reverse: this.reverseUserId == message.fromUser.id,
|
||||||
|
hiddenTitle: false
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
return node;
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
msecRange() {
|
||||||
|
return this.timeRange * 1000 * 60;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {},
|
||||||
|
methods: {
|
||||||
|
_renderLoading() {
|
||||||
|
return <i class="lemon-icon-loading lemonani-spin" />;
|
||||||
|
},
|
||||||
|
_renderLoadEnd() {
|
||||||
|
return <span>暂无消息</span>;
|
||||||
|
},
|
||||||
|
resetLoadState() {
|
||||||
|
this.loading = false;
|
||||||
|
this.loadend = false;
|
||||||
|
},
|
||||||
|
async _handleScroll(e) {
|
||||||
|
const { target } = e;
|
||||||
|
if (
|
||||||
|
target.scrollTop == 0 &&
|
||||||
|
this.loading == false &&
|
||||||
|
this.loadend == false
|
||||||
|
) {
|
||||||
|
this.loading = true;
|
||||||
|
await this.$nextTick();
|
||||||
|
const hst = target.scrollHeight;
|
||||||
|
|
||||||
|
this.$emit("reach-top", async isEnd => {
|
||||||
|
await this.$nextTick();
|
||||||
|
target.scrollTop = target.scrollHeight - hst;
|
||||||
|
this.loading = false;
|
||||||
|
this.loadend = !!isEnd;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async scrollToBottom() {
|
||||||
|
await this.$nextTick();
|
||||||
|
const { wrap } = this.$refs;
|
||||||
|
if (wrap) wrap.scrollTop = wrap.scrollHeight;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {},
|
||||||
|
mounted() {}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<style lang="stylus">
|
||||||
|
@import '~styles/utils/index'
|
||||||
|
+b(lemon-messages)
|
||||||
|
height 400px
|
||||||
|
overflow-x hidden
|
||||||
|
overflow-y auto
|
||||||
|
scrollbar-light()
|
||||||
|
padding 10px 15px
|
||||||
|
+e(time)
|
||||||
|
text-align center
|
||||||
|
font-size 12px
|
||||||
|
+e(load)
|
||||||
|
font-size 12px
|
||||||
|
text-align center
|
||||||
|
color #999
|
||||||
|
line-height 30px
|
||||||
|
+m(ing)
|
||||||
|
font-size 22px
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,143 @@
|
|||||||
|
<script>
|
||||||
|
const popoverCloseQueue = [];
|
||||||
|
const popoverCloseAll = () => popoverCloseQueue.forEach(callback => callback());
|
||||||
|
const triggerEvents = {
|
||||||
|
hover(el) {},
|
||||||
|
focus(el) {
|
||||||
|
el.addEventListener("focus", e => {
|
||||||
|
this.changeVisible();
|
||||||
|
});
|
||||||
|
el.addEventListener("blur", e => {
|
||||||
|
this.changeVisible();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
click(el) {
|
||||||
|
el.addEventListener("click", e => {
|
||||||
|
e.stopPropagation();
|
||||||
|
this.changeVisible();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
contextmenu(el) {
|
||||||
|
el.addEventListener("contextmenu", e => {
|
||||||
|
e.preventDefault();
|
||||||
|
this.changeVisible();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
export default {
|
||||||
|
name: "LemonPopover",
|
||||||
|
props: {
|
||||||
|
trigger: {
|
||||||
|
type: String,
|
||||||
|
default: "click",
|
||||||
|
validator(val) {
|
||||||
|
return Object.keys(triggerEvents).includes(val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
popoverStyle: {},
|
||||||
|
visible: false
|
||||||
|
};
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
document.addEventListener("click", this._documentClickEvent);
|
||||||
|
popoverCloseQueue.push(this.close);
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
triggerEvents[this.trigger].call(this, this.$slots.default[0].elm);
|
||||||
|
},
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<span style="position:relative">
|
||||||
|
<transition name="slide-top">
|
||||||
|
{this.visible && (
|
||||||
|
<div
|
||||||
|
class="lemon-popover"
|
||||||
|
ref="popover"
|
||||||
|
style={this.popoverStyle}
|
||||||
|
on-click={e => e.stopPropagation()}
|
||||||
|
>
|
||||||
|
<div class="lemon-popover__title" />
|
||||||
|
<div class="lemon-popover__content">{this.$slots.content}</div>
|
||||||
|
<div class="lemon-popover__arrow" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</transition>
|
||||||
|
{this.$slots.default}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
destroyed() {
|
||||||
|
document.removeEventListener("click", this._documentClickEvent);
|
||||||
|
},
|
||||||
|
computed: {},
|
||||||
|
watch: {
|
||||||
|
async visible(val) {
|
||||||
|
if (val) {
|
||||||
|
await this.$nextTick();
|
||||||
|
const defaultEl = this.$slots.default[0].elm;
|
||||||
|
const contentEl = this.$refs.popover;
|
||||||
|
|
||||||
|
this.popoverStyle = {
|
||||||
|
top: `-${contentEl.offsetHeight + 10}px`,
|
||||||
|
left: `${defaultEl.offsetWidth / 2 - contentEl.offsetWidth / 2}px`
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
_documentClickEvent(e) {
|
||||||
|
e.stopPropagation();
|
||||||
|
if (this.visible) this.close();
|
||||||
|
},
|
||||||
|
changeVisible() {
|
||||||
|
this.visible ? this.close() : this.open();
|
||||||
|
},
|
||||||
|
open() {
|
||||||
|
popoverCloseAll();
|
||||||
|
this.visible = true;
|
||||||
|
},
|
||||||
|
close() {
|
||||||
|
this.visible = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<style lang="stylus">
|
||||||
|
@import '~styles/utils/index'
|
||||||
|
+b(lemon-popover)
|
||||||
|
border 1px solid #eee
|
||||||
|
border-radius 4px
|
||||||
|
font-size 14px
|
||||||
|
font-variant tabular-nums
|
||||||
|
line-height 1.5
|
||||||
|
color rgba(0, 0, 0, 0.65)
|
||||||
|
z-index 10
|
||||||
|
background-color #fff
|
||||||
|
border-radius 4px
|
||||||
|
box-shadow 0 2px 8px rgba(0, 0, 0, 0.15)
|
||||||
|
position absolute
|
||||||
|
transform-origin 50% 150%
|
||||||
|
+e(content)
|
||||||
|
padding 15px
|
||||||
|
box-sizing border-box
|
||||||
|
position relative
|
||||||
|
z-index 1
|
||||||
|
+e(arrow)
|
||||||
|
left 50%
|
||||||
|
transform translateX(-50%) rotate(45deg)
|
||||||
|
position absolute
|
||||||
|
z-index 0
|
||||||
|
bottom -4px
|
||||||
|
box-shadow 3px 3px 7px rgba(0, 0, 0, 0.07)
|
||||||
|
width 8px
|
||||||
|
height 8px
|
||||||
|
background #fff
|
||||||
|
.slide-top-leave-active ,.slide-top-enter-active
|
||||||
|
transition all .3s cubic-bezier(0.645, 0.045, 0.355, 1)
|
||||||
|
.slide-top-enter, .slide-top-leave-to
|
||||||
|
transform translateY(-10px) scale(.8)
|
||||||
|
opacity 0
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "LemonTabs",
|
||||||
|
props: {
|
||||||
|
activeIndex: String
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
active: this.activeIndex
|
||||||
|
};
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
if (!this.active) {
|
||||||
|
this.active = this.$slots["tab-pane"][0].data.attrs.index;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
render() {
|
||||||
|
const pane = [];
|
||||||
|
const nav = [];
|
||||||
|
this.$slots["tab-pane"].map(vnode => {
|
||||||
|
const { tab, index } = vnode.data.attrs;
|
||||||
|
pane.push(
|
||||||
|
<div class="lemon-tabs-content__pane" v-show={this.active == index}>
|
||||||
|
{vnode}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
nav.push(
|
||||||
|
<div
|
||||||
|
class={[
|
||||||
|
"lemon-tabs-nav__item",
|
||||||
|
this.active == index && "lemon-tabs-nav__item--active"
|
||||||
|
]}
|
||||||
|
on-click={() => this._handleNavClick(index)}
|
||||||
|
>
|
||||||
|
{tab}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
return (
|
||||||
|
<div class="lemon-tabs">
|
||||||
|
<div class="lemon-tabs-content">{pane}</div>
|
||||||
|
<div class="lemon-tabs-nav">{nav}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
_handleNavClick(index) {
|
||||||
|
this.active = index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<style lang="stylus">
|
||||||
|
@import '~styles/utils/index'
|
||||||
|
pane-color = #f6f6f6
|
||||||
|
+b(lemon-tabs)
|
||||||
|
background pane-color
|
||||||
|
+b(lemon-tabs-content)
|
||||||
|
width 100%
|
||||||
|
height 100%
|
||||||
|
padding 15px
|
||||||
|
+e(pane)
|
||||||
|
//scrollbar-light()
|
||||||
|
//overflow-y auto
|
||||||
|
height 100%
|
||||||
|
width 100%
|
||||||
|
+b(lemon-tabs-nav)
|
||||||
|
display flex
|
||||||
|
background #eee
|
||||||
|
+e(item)
|
||||||
|
line-height 38px
|
||||||
|
padding 0 15px
|
||||||
|
cursor pointer
|
||||||
|
transition all .3s cubic-bezier(0.645, 0.045, 0.355, 1)
|
||||||
|
+m(active)
|
||||||
|
background pane-color
|
||||||
|
</style>
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
import Vue from "vue";
|
|
||||||
|
|
||||||
import {
|
|
||||||
Button,
|
|
||||||
Icon,
|
|
||||||
Row,
|
|
||||||
Col,
|
|
||||||
Container,
|
|
||||||
Header,
|
|
||||||
Main,
|
|
||||||
Aside,
|
|
||||||
Loading
|
|
||||||
} from "element-ui";
|
|
||||||
Vue.use(Button);
|
|
||||||
Vue.use(Icon);
|
|
||||||
Vue.use(Row);
|
|
||||||
Vue.use(Col);
|
|
||||||
Vue.use(Container);
|
|
||||||
Vue.use(Header);
|
|
||||||
Vue.use(Main);
|
|
||||||
Vue.use(Aside);
|
|
||||||
Vue.use(Loading.directive);
|
|
||||||
|
|
||||||
Vue.prototype.$loading = Loading.service;
|
|
||||||
+35
-4
@@ -1,8 +1,39 @@
|
|||||||
import "./styles/common/index.scss";
|
import "./plugins";
|
||||||
import "./element-ui";
|
//import "./element-ui";
|
||||||
import Lemon from "./components/lemon";
|
|
||||||
|
import LemonTabs from "./components/tabs";
|
||||||
|
import LemonPopover from "./components/popover";
|
||||||
|
import LemonButton from "./components/button";
|
||||||
|
import LemonBadge from "./components/badge";
|
||||||
|
import LemonAvatar from "./components/avatar";
|
||||||
|
import LemonContact from "./components/contact";
|
||||||
|
import LemonEditor from "./components/editor";
|
||||||
|
import LemonMessages from "./components/messages";
|
||||||
|
import LemonMessageBasic from "./components/message/basic";
|
||||||
|
import LemonMessageText from "./components/message/text";
|
||||||
|
import lemonMessageImage from "./components/message/image";
|
||||||
|
import lemonMessageFile from "./components/message/file";
|
||||||
|
import lemonMessageEvent from "./components/message/event";
|
||||||
|
|
||||||
|
import LemonIMUI from "./components/index";
|
||||||
|
import "./styles/common/index.styl";
|
||||||
const version = "0.1";
|
const version = "0.1";
|
||||||
const components = [Lemon];
|
const components = [
|
||||||
|
LemonIMUI,
|
||||||
|
LemonContact,
|
||||||
|
LemonMessages,
|
||||||
|
LemonEditor,
|
||||||
|
LemonAvatar,
|
||||||
|
LemonBadge,
|
||||||
|
LemonButton,
|
||||||
|
LemonPopover,
|
||||||
|
LemonTabs,
|
||||||
|
LemonMessageBasic,
|
||||||
|
LemonMessageText,
|
||||||
|
lemonMessageImage,
|
||||||
|
lemonMessageFile,
|
||||||
|
lemonMessageEvent,
|
||||||
|
];
|
||||||
const install = (Vue, opts = {}) => {
|
const install = (Vue, opts = {}) => {
|
||||||
components.forEach(component => {
|
components.forEach(component => {
|
||||||
Vue.component(component.name, component);
|
Vue.component(component.name, component);
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
export default {
|
||||||
|
voice(message) {
|
||||||
|
return "[语音]";
|
||||||
|
},
|
||||||
|
file(message) {
|
||||||
|
return "[文件]";
|
||||||
|
},
|
||||||
|
video(message) {
|
||||||
|
return "[视频]";
|
||||||
|
},
|
||||||
|
image(message) {
|
||||||
|
return "[图片]";
|
||||||
|
},
|
||||||
|
text(message) {
|
||||||
|
return this.replaceEmojiName(message.content);
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
textMessage = {
|
textMessage = {
|
||||||
msgId: "msgid",
|
msgId: "msgid",
|
||||||
status: "send_going",
|
status: "send_going",
|
||||||
msgType: "text",
|
type: "text",
|
||||||
isOutgoing: true,
|
isOutgoing: true,
|
||||||
text: "text",
|
text: "text",
|
||||||
fromUser: {},
|
fromUser: {},
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
|
|
||||||
imageMessage = {
|
imageMessage = {
|
||||||
msgId: "msgid",
|
msgId: "msgid",
|
||||||
msgType: "image",
|
type: "image",
|
||||||
isOutGoing: true,
|
isOutGoing: true,
|
||||||
mediaPath: "image path",
|
mediaPath: "image path",
|
||||||
fromUser: {},
|
fromUser: {},
|
||||||
@@ -22,7 +22,7 @@ imageMessage = {
|
|||||||
videoMessage = { // video message
|
videoMessage = { // video message
|
||||||
msgId: "msgid",
|
msgId: "msgid",
|
||||||
status: "send_failed",
|
status: "send_failed",
|
||||||
msgType: "video",
|
type: "video",
|
||||||
isOutGoing: true,
|
isOutGoing: true,
|
||||||
druation: number,
|
druation: number,
|
||||||
mediaPath: "voice path",
|
mediaPath: "voice path",
|
||||||
@@ -31,7 +31,7 @@ videoMessage = { // video message
|
|||||||
}
|
}
|
||||||
customMessage = { // custom message
|
customMessage = { // custom message
|
||||||
msgId: "msgid",
|
msgId: "msgid",
|
||||||
msgType: "custom",
|
type: "custom",
|
||||||
status: "send_failed",
|
status: "send_failed",
|
||||||
isOutgoing: true,
|
isOutgoing: true,
|
||||||
contentSize: {height: 100, width: 100},
|
contentSize: {height: 100, width: 100},
|
||||||
@@ -41,7 +41,7 @@ customMessage = { // custom message
|
|||||||
}
|
}
|
||||||
eventMessage = { // event message
|
eventMessage = { // event message
|
||||||
msgId: "msgid",
|
msgId: "msgid",
|
||||||
msgType: "event",
|
type: "event",
|
||||||
text: "the event text"
|
text: "the event text"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
export default {
|
||||||
|
inject: ["IMUI"],
|
||||||
|
methods: {}
|
||||||
|
};
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
Data
|
||||||
|
# 联系人数据
|
||||||
|
contact:{
|
||||||
|
// 联系人唯一ID
|
||||||
|
id: "1",
|
||||||
|
// 名字
|
||||||
|
displayName: "范佳奕",
|
||||||
|
// 头像
|
||||||
|
avatar:
|
||||||
|
"https://ss3.bdstatic.com/70cFv8Sh_Q1YnxGkpoWK1HF6hhy/it/u=4259300811,497831842&fm=26&gp=0.jpg",
|
||||||
|
// 类型 single | many
|
||||||
|
type: "single",
|
||||||
|
// 联系人索引, 默认按照 localeCompare 排序, 指定顺序 '[1]群组'
|
||||||
|
index: "B",
|
||||||
|
// 最近一条消息
|
||||||
|
message: {
|
||||||
|
// 未读数量 , true 显示红点
|
||||||
|
unread: 0,
|
||||||
|
// 发送时间
|
||||||
|
sendTime: 1,
|
||||||
|
// 内容
|
||||||
|
content: "2",
|
||||||
|
// 类型 voice | file | video | image | text
|
||||||
|
type: "image"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
# 消息数据
|
||||||
|
message:{
|
||||||
|
// 消息唯一ID
|
||||||
|
id: "123",
|
||||||
|
// 消息状态 going | succeed | failed
|
||||||
|
status: "succeed",
|
||||||
|
// 消息类型 voice | file | video | image | text
|
||||||
|
type: "image",
|
||||||
|
// 发送时间
|
||||||
|
sendTime: 12312312312,
|
||||||
|
// 消息内容 | 消息链接
|
||||||
|
content: "asdas",
|
||||||
|
// 接收方 ID 单人聊天(联系人ID) | 多人聊天(群ID)
|
||||||
|
toContactId: "123",
|
||||||
|
// type = file 文件大小
|
||||||
|
fileSize: 1231,
|
||||||
|
// type = file 文件名称
|
||||||
|
fileName: 'asdasd.doc'
|
||||||
|
// 发送消息的联系人
|
||||||
|
fromUser: { id: "123", displayName: "123", avatar: "123", type: "single" }
|
||||||
|
}
|
||||||
|
# 左侧按钮配置
|
||||||
|
menu:[{
|
||||||
|
# 指定 menu 唯一名称,保留字段 contacts(通讯录) lastMessages(最近消息)
|
||||||
|
name: "123",
|
||||||
|
# 鼠标停留显示文字
|
||||||
|
title: "通讯录",
|
||||||
|
# 标记未读数, 数字显示具体值, true 显示红点
|
||||||
|
unread: 0,
|
||||||
|
# 按钮的DOM元素
|
||||||
|
render: menu => {
|
||||||
|
return <span>T</span>;
|
||||||
|
},
|
||||||
|
# 点击按钮打开的页面,如果 name 为保留字段,该属性失效
|
||||||
|
renderContainer: () => {
|
||||||
|
return <lemon-contact contact={this.lastMessages[0]} />;
|
||||||
|
},
|
||||||
|
# 按钮点击事件,会阻止默认的行为,调用 next() 进入下一步
|
||||||
|
click: next => {
|
||||||
|
next();
|
||||||
|
},
|
||||||
|
# 是否为底部按钮
|
||||||
|
isBottom: false
|
||||||
|
}]
|
||||||
|
emoji:[{
|
||||||
|
label: "表情",
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
name: "1f600",
|
||||||
|
title: "微笑",
|
||||||
|
src: "https://twemoji.maxcdn.com/2/72x72/1f600.png"
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}]
|
||||||
|
|
||||||
|
Methods
|
||||||
|
# 初始化左侧 menu 数据
|
||||||
|
initMenus(data:[menu])
|
||||||
|
# 初始化联系人数据
|
||||||
|
initContacts(data:[contact])
|
||||||
|
# 修改联系人数据
|
||||||
|
updateContact(contactId:联系人ID,data:联系人数据,会与现有的数据合并)
|
||||||
|
# 切换 menu
|
||||||
|
changeMenu(menuName)
|
||||||
|
# 打开一个聊天窗口 传入 contactId 或 contact(如果当前不存在这个联系人,根据此数据新建一个)
|
||||||
|
openMessage(contactId | contact)
|
||||||
Vendored
+13
@@ -0,0 +1,13 @@
|
|||||||
|
|
||||||
|
.lemonani-spin
|
||||||
|
display inline-block
|
||||||
|
animation lemonani-spin 1s infinite
|
||||||
|
@keyframes lemonani-spin{
|
||||||
|
0%{
|
||||||
|
transform rotate(0deg)
|
||||||
|
}
|
||||||
|
100%{
|
||||||
|
transform rotate(360deg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
@font-face {
|
||||||
|
font-family: 'lemon-icons';
|
||||||
|
src: url('//at.alicdn.com/t/font_1312162_neqltsj20an.eot');
|
||||||
|
src: url('//at.alicdn.com/t/font_1312162_neqltsj20an.eot?#iefix') format('embedded-opentype'),
|
||||||
|
url('//at.alicdn.com/t/font_1312162_neqltsj20an.woff2') format('woff2'),
|
||||||
|
url('//at.alicdn.com/t/font_1312162_neqltsj20an.woff') format('woff'),
|
||||||
|
url('//at.alicdn.com/t/font_1312162_neqltsj20an.ttf') format('truetype'),
|
||||||
|
url('//at.alicdn.com/t/font_1312162_neqltsj20an.svg#iconfont') format('svg');
|
||||||
|
|
||||||
|
}
|
||||||
|
[class^='lemon-icon-'],
|
||||||
|
[class*=' lemon-icon-']
|
||||||
|
font-family lemon-icons !important
|
||||||
|
speak none
|
||||||
|
font-style normal
|
||||||
|
font-weight 400
|
||||||
|
font-variant normal
|
||||||
|
text-transform none
|
||||||
|
line-height 1
|
||||||
|
vertical-align baseline
|
||||||
|
display inline-block
|
||||||
|
|
||||||
|
.lemon-icon-loading:before
|
||||||
|
content '\e633'
|
||||||
|
.lemon-icon-prompt:before
|
||||||
|
content '\e71b'
|
||||||
|
.lemon-icon-message:before
|
||||||
|
content '\e84a'
|
||||||
|
.lemon-icon-emoji:before
|
||||||
|
content '\e6f6'
|
||||||
|
.lemon-icon-attah:before
|
||||||
|
content '\e7e1'
|
||||||
|
.lemon-icon-image:before
|
||||||
|
content '\e7de'
|
||||||
|
.lemon-icon-folder:before
|
||||||
|
content '\e7d1'
|
||||||
|
.lemon-icon-people:before
|
||||||
|
content '\e715'
|
||||||
|
.lemon-icon-group:before
|
||||||
|
content '\e6ff'
|
||||||
|
.lemon-icon-addressbook:before
|
||||||
|
content '\e6e2'
|
||||||
@@ -1 +0,0 @@
|
|||||||
@import './normalize';
|
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
//@import './normalize';
|
||||||
|
@import './animate';
|
||||||
|
@import './icons';
|
||||||
|
|
||||||
|
// .lemon-tabs.ant-tabs-small.ant-tabs-card
|
||||||
|
// .ant-tabs-card-bar
|
||||||
|
// .ant-tabs-nav-container
|
||||||
|
// height 30px
|
||||||
|
// .ant-tabs-tab
|
||||||
|
// line-height 28px
|
||||||
|
// font-size 12px
|
||||||
|
// .ant-tabs-content
|
||||||
|
// .ant-tabs-tabpane
|
||||||
|
// padding 10px
|
||||||
|
|
||||||
|
|
||||||
|
// .lemon-tabs.ant-tabs-card
|
||||||
|
// background #eee
|
||||||
|
// overflow hidden
|
||||||
|
// >.ant-tabs-content
|
||||||
|
// height 120px
|
||||||
|
// margin-top -16px
|
||||||
|
// background #f6f6f6
|
||||||
|
// >.ant-tabs-tabpane
|
||||||
|
// background #f6f6f6
|
||||||
|
// padding 16px
|
||||||
|
// >.ant-tabs-bar
|
||||||
|
// border-color #f6f6f6
|
||||||
|
// user-select none
|
||||||
|
// .ant-tabs-tab
|
||||||
|
// border-color transparent
|
||||||
|
// background transparent
|
||||||
|
// border none
|
||||||
|
// border-radius 0
|
||||||
|
// margin 0
|
||||||
|
// .ant-tabs-tab-active
|
||||||
|
// border-color #f6f6f6
|
||||||
|
// background #f6f6f6
|
||||||
|
// .ant-tabs-tab-arrow-show
|
||||||
|
// top -2px
|
||||||
|
.lemon-tabs.ant-tabs-card
|
||||||
|
background #eee
|
||||||
|
border-radius 4px
|
||||||
|
overflow hidden
|
||||||
|
.ant-tabs-content
|
||||||
|
background #f6f6f6
|
||||||
|
.ant-tabs-bottom-bar
|
||||||
|
margin-top 0
|
||||||
|
border 0
|
||||||
|
.ant-tabs-card-bar
|
||||||
|
.ant-tabs-tab
|
||||||
|
border-color transparent
|
||||||
|
background transparent
|
||||||
|
border none
|
||||||
|
border-radius 0
|
||||||
|
margin-right 0
|
||||||
|
.ant-tabs-tab-active
|
||||||
|
background #f6f6f6
|
||||||
-38
@@ -1,38 +0,0 @@
|
|||||||
/**
|
|
||||||
* 基本样式入口
|
|
||||||
*/
|
|
||||||
|
|
||||||
html {
|
|
||||||
-webkit-tap-highlight-color: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
a,
|
|
||||||
input,
|
|
||||||
button,
|
|
||||||
textarea {
|
|
||||||
&:focus {
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ol,
|
|
||||||
ul {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
list-style: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
input,
|
|
||||||
button,
|
|
||||||
textarea {
|
|
||||||
font: inherit;
|
|
||||||
color: inherit;
|
|
||||||
}
|
|
||||||
+23
@@ -0,0 +1,23 @@
|
|||||||
|
html
|
||||||
|
-webkit-tap-highlight-color transparent
|
||||||
|
body
|
||||||
|
margin 0
|
||||||
|
a
|
||||||
|
text-decoration none
|
||||||
|
a
|
||||||
|
input
|
||||||
|
button
|
||||||
|
textarea
|
||||||
|
&:focus
|
||||||
|
outline none
|
||||||
|
ol
|
||||||
|
ul
|
||||||
|
margin 0
|
||||||
|
padding 0
|
||||||
|
list-style none
|
||||||
|
input
|
||||||
|
button
|
||||||
|
textarea
|
||||||
|
font inherit
|
||||||
|
color inherit
|
||||||
|
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
// bem-sugar.styl --- Bem mixins for stylus language
|
||||||
|
//
|
||||||
|
// Copyright (c) 2017 Ilya Obuhov
|
||||||
|
//
|
||||||
|
// Author: Ilya Obuhov <iobuhov.mail@gmail.com>
|
||||||
|
// URL: https://github.com/iobuhov/stylus-bem-sugar
|
||||||
|
|
||||||
|
|
||||||
|
e-prefix ?= '__'
|
||||||
|
m-prefix ?= '--'
|
||||||
|
m-delimiter ?= '_'
|
||||||
|
group-store = ()
|
||||||
|
|
||||||
|
str()
|
||||||
|
join('', arguments)
|
||||||
|
|
||||||
|
b(name)
|
||||||
|
.{name}
|
||||||
|
{block}
|
||||||
|
|
||||||
|
group()
|
||||||
|
caller = called-from[0]
|
||||||
|
level = length(called-from) + 1
|
||||||
|
elements = group-store[level]
|
||||||
|
selector = ()
|
||||||
|
parent = null
|
||||||
|
{join(',', elements)}
|
||||||
|
{block}
|
||||||
|
group-store[level] = null
|
||||||
|
|
||||||
|
m(mod, val=null)
|
||||||
|
val = val && m-delimiter + val
|
||||||
|
mod = m-prefix + mod
|
||||||
|
mod = val ? mod + val : mod
|
||||||
|
caller = called-from[0]
|
||||||
|
if caller in ('group')
|
||||||
|
level = length(called-from)
|
||||||
|
mod = str('&', mod)
|
||||||
|
if group-store[level] == null
|
||||||
|
group-store[level] = mod
|
||||||
|
else
|
||||||
|
push(group-store[level], mod)
|
||||||
|
&{mod}
|
||||||
|
{block}
|
||||||
|
|
||||||
|
e(element)
|
||||||
|
element = e-prefix + element
|
||||||
|
caller = called-from[0]
|
||||||
|
gcaller = called-from[1]
|
||||||
|
if caller in ('group')
|
||||||
|
level = length(called-from)
|
||||||
|
if gcaller in ('e' 'm')
|
||||||
|
element = str('& ^[0]', element)
|
||||||
|
else
|
||||||
|
element = str('^[0]', element)
|
||||||
|
if group-store[level] == null
|
||||||
|
group-store[level] = element
|
||||||
|
else
|
||||||
|
push(group-store[level], element)
|
||||||
|
else
|
||||||
|
if caller in ('e' 'm')
|
||||||
|
& ^[0]{element}
|
||||||
|
{block}
|
||||||
|
else
|
||||||
|
&{element}
|
||||||
|
{block}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
flex-column()
|
||||||
|
display flex
|
||||||
|
flex-direction column
|
||||||
|
|
||||||
|
scrollbar-theme($color=#1f252d, $background=#6d6d6d)
|
||||||
|
&::-webkit-scrollbar
|
||||||
|
width 5px
|
||||||
|
height 5px
|
||||||
|
|
||||||
|
&::-webkit-scrollbar-track-piece
|
||||||
|
background-color $background
|
||||||
|
|
||||||
|
&::-webkit-scrollbar-thumb:vertical
|
||||||
|
height 5px
|
||||||
|
background-color $color
|
||||||
|
|
||||||
|
&::-webkit-scrollbar-thumb:horizontal
|
||||||
|
width 5px
|
||||||
|
background-color $background
|
||||||
|
|
||||||
|
scrollbar-dark()
|
||||||
|
scrollbar-theme()
|
||||||
|
|
||||||
|
scrollbar-light()
|
||||||
|
scrollbar-theme(#aaa, transparent)
|
||||||
|
|
||||||
|
|
||||||
|
vertical-center()
|
||||||
|
&::after
|
||||||
|
display inline-block
|
||||||
|
content ''
|
||||||
|
height 100%
|
||||||
|
vertical-align middle
|
||||||
|
|
||||||
|
position-center($type fixed)
|
||||||
|
position $type
|
||||||
|
top 50%
|
||||||
|
left 50%
|
||||||
|
transform translate(-50%, -50%)
|
||||||
|
ellipsis()
|
||||||
|
text-overflow ellipsis
|
||||||
|
overflow hidden
|
||||||
|
white-space nowrap
|
||||||
|
word-break()
|
||||||
|
word-break break-all
|
||||||
|
word-wrap break-word
|
||||||
|
white-space pre-wrap
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
@import './mixins';
|
|
||||||
@import './var';
|
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
@import './functional';
|
||||||
|
@import './bem';
|
||||||
|
@import './var';
|
||||||
|
|
||||||
|
|
||||||
@@ -1,94 +0,0 @@
|
|||||||
//BEM
|
|
||||||
$fi-namespace: 'lemon';
|
|
||||||
|
|
||||||
@mixin b($block) {
|
|
||||||
$selector: $fi-namespace + '-' + $block;
|
|
||||||
.#{$selector} {
|
|
||||||
@content;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@mixin e($element) {
|
|
||||||
$selector: '__' + $element;
|
|
||||||
&#{$selector} {
|
|
||||||
@content;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@mixin m($modifier) {
|
|
||||||
$selector: '--' + $modifier;
|
|
||||||
&#{$selector} {
|
|
||||||
@content;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@mixin bem($block, $element: false, $modifier: false) {
|
|
||||||
$selector: '.';
|
|
||||||
@if $block {
|
|
||||||
$selector: $fi-namespace + '-' + $block;
|
|
||||||
}
|
|
||||||
@if $element {
|
|
||||||
$selector: $selector + '__' + $element;
|
|
||||||
}
|
|
||||||
@if $modifier {
|
|
||||||
$selector: $selector + '--' + $modifier;
|
|
||||||
}
|
|
||||||
& .#{$selector} {
|
|
||||||
@content;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@mixin user-select($value) {
|
|
||||||
-moz-user-select: $value;
|
|
||||||
-webkit-user-select: $value;
|
|
||||||
-ms-user-select: $value;
|
|
||||||
}
|
|
||||||
|
|
||||||
@mixin scrollbar-theme($color: #1f252d, $background: #6d6d6d) {
|
|
||||||
&::-webkit-scrollbar {
|
|
||||||
width: 5px;
|
|
||||||
height: 5px;
|
|
||||||
}
|
|
||||||
&::-webkit-scrollbar-track-piece {
|
|
||||||
background-color: $background;
|
|
||||||
}
|
|
||||||
&::-webkit-scrollbar-thumb:vertical {
|
|
||||||
height: 5px;
|
|
||||||
background-color: $color;
|
|
||||||
}
|
|
||||||
&::-webkit-scrollbar-thumb:horizontal {
|
|
||||||
width: 5px;
|
|
||||||
background-color: $background;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@mixin scrollbar-dark() {
|
|
||||||
@include scrollbar-theme();
|
|
||||||
}
|
|
||||||
@mixin scrollbar-light() {
|
|
||||||
@include scrollbar-theme(#aaa, #fff);
|
|
||||||
}
|
|
||||||
|
|
||||||
@mixin vertical-center {
|
|
||||||
$selector: &;
|
|
||||||
@at-root {
|
|
||||||
#{$selector}::after {
|
|
||||||
display: inline-block;
|
|
||||||
content: '';
|
|
||||||
height: 100%;
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@mixin position-center($type: fixed) {
|
|
||||||
position: $type;
|
|
||||||
top: 50%;
|
|
||||||
left: 50%;
|
|
||||||
transform: translate(-50%, -50%);
|
|
||||||
}
|
|
||||||
|
|
||||||
@mixin ellipsis {
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
@mixin arrow {
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
//$color-primary: #2977fa;
|
|
||||||
$color-primary: #1bc213;
|
|
||||||
$color-light: #fff;
|
|
||||||
/* 头像 */
|
|
||||||
$avatar-size: 45px;
|
|
||||||
$avatar-radius: 50%;
|
|
||||||
|
|
||||||
/** 标题 */
|
|
||||||
$title-background: $color-primary;
|
|
||||||
$title-color: $color-light;
|
|
||||||
$title-height: 44px;
|
|
||||||
/* 气泡 */
|
|
||||||
$bubble-background: $color-primary;
|
|
||||||
$bubble-color: $color-light;
|
|
||||||
$bubble-radius: 12px;
|
|
||||||
|
|
||||||
$bubble-self-background: #e7ebef;
|
|
||||||
$bubble-self-color: #606d84;
|
|
||||||
|
|
||||||
/* 输入框 */
|
|
||||||
$editor-textarea-height: 40px;
|
|
||||||
$editor-textarea-radius: 5px;
|
|
||||||
|
|
||||||
$editor-submit-disable-color: #bcbcbc;
|
|
||||||
$editor-submit-disable-background: #ebebeb;
|
|
||||||
$editor-submit-radius: $editor-textarea-radius;
|
|
||||||
/*
|
|
||||||
$bubble-self-background: #e7ebef;
|
|
||||||
$bubble-self-color: #3a465d;
|
|
||||||
*/
|
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
//color-primary #2977fa
|
||||||
|
color-primary = #1bc213
|
||||||
|
color-light = #fff
|
||||||
|
/* 头像 */
|
||||||
|
avatar-size = 45px
|
||||||
|
avatar-radius = 50%
|
||||||
|
|
||||||
|
/** 标题 */
|
||||||
|
title-background = color-primary
|
||||||
|
title-color = color-light
|
||||||
|
title-height = 44px
|
||||||
|
/* 气泡 */
|
||||||
|
bubble-background = color-primary
|
||||||
|
bubble-color = color-light
|
||||||
|
bubble-radius = 12px
|
||||||
|
|
||||||
|
bubble-self-background = #e7ebef
|
||||||
|
bubble-self-color = #606d84
|
||||||
|
|
||||||
|
/* 输入框 */
|
||||||
|
editor-textarea-height = 40px
|
||||||
|
editor-textarea-radius = 5px
|
||||||
|
|
||||||
|
editor-submit-disable-color = #bcbcbc
|
||||||
|
editor-submit-disable-background = #ebebeb
|
||||||
|
editor-submit-radius = editor-textarea-radius
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
export default function(a, b) {
|
|
||||||
return a.filter(x => b.includes(x));
|
|
||||||
}
|
|
||||||
Vendored
+39
@@ -0,0 +1,39 @@
|
|||||||
|
export default class MemoryCache {
|
||||||
|
constructor() {
|
||||||
|
this.table = {};
|
||||||
|
}
|
||||||
|
get(key) {
|
||||||
|
return key ? this.table[key] : this.table;
|
||||||
|
}
|
||||||
|
set(key, val) {
|
||||||
|
this.table[key] = val;
|
||||||
|
}
|
||||||
|
// setOnly(key, val) {
|
||||||
|
// if (!this.has(key)) this.set(key, val);
|
||||||
|
// }
|
||||||
|
remove(key) {
|
||||||
|
if (key) {
|
||||||
|
delete this.table[key];
|
||||||
|
} else {
|
||||||
|
this.table = {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
has(key) {
|
||||||
|
return !!this.table[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// export default {
|
||||||
|
// data: {},
|
||||||
|
// get(name) {
|
||||||
|
// console.log(this.data);
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
// class MemoryCache {
|
||||||
|
// constructor() {
|
||||||
|
// super();
|
||||||
|
// }
|
||||||
|
// get($name) {
|
||||||
|
// console.log(1);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// export default MemoryCache;
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
export const EMIT_AVATAR_CLICK = "avatar-click";
|
||||||
|
|
||||||
|
export const DEFAULT_MENU_LASTMESSAGES = "lastMessages";
|
||||||
|
export const DEFAULT_MENU_CONTACTS = "contacts";
|
||||||
|
export const DEFAULT_MENUS = [DEFAULT_MENU_LASTMESSAGES, DEFAULT_MENU_CONTACTS];
|
||||||
|
/**
|
||||||
|
* 聊天消息类型
|
||||||
|
*/
|
||||||
|
export const MESSAGE_TYPE = ["voice", "file", "video", "image", "text"];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 聊天消息状态
|
||||||
|
*/
|
||||||
|
export const MESSAGE_STATUS = ["going", "succeed", "failed"];
|
||||||
|
|
||||||
|
export const CONTACT_TYPE = ["many", "single"];
|
||||||
@@ -0,0 +1,130 @@
|
|||||||
|
import { MESSAGE_TYPE, MESSAGE_STATUS, CONTACT_TYPE } from "utils/constant";
|
||||||
|
import { error } from "utils";
|
||||||
|
import { isPlainObject } from "utils/validate";
|
||||||
|
|
||||||
|
const constraintContactBasic = data =>
|
||||||
|
constraintObject(data, {
|
||||||
|
id: true,
|
||||||
|
displayName: true,
|
||||||
|
avatar: true,
|
||||||
|
type: {
|
||||||
|
required: true,
|
||||||
|
has: CONTACT_TYPE
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const constraintMessageBasic = data =>
|
||||||
|
constraintObject(data, {
|
||||||
|
content: true,
|
||||||
|
sendTime: true,
|
||||||
|
type: {
|
||||||
|
required: true,
|
||||||
|
has: MESSAGE_TYPE
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// constraintContact({
|
||||||
|
// id: "123",
|
||||||
|
// displayName: "123asd",
|
||||||
|
// avatar: "123",
|
||||||
|
// type: "single",
|
||||||
|
// message: {
|
||||||
|
// unread: 0,
|
||||||
|
// sendTime: 12312312,
|
||||||
|
// content: "12312312",
|
||||||
|
// type: "image"
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
|
||||||
|
constraintContact({
|
||||||
|
id: "123",
|
||||||
|
displayName: "123asd",
|
||||||
|
avatar: "123",
|
||||||
|
type: "single",
|
||||||
|
unread: 0,
|
||||||
|
lastSendTime: "",
|
||||||
|
subText: "12312312"
|
||||||
|
// message: {
|
||||||
|
// unread: 0,
|
||||||
|
// sendTime: 12312312,
|
||||||
|
// content: "12312312",
|
||||||
|
// type: "image"
|
||||||
|
// }
|
||||||
|
});
|
||||||
|
|
||||||
|
// constraintRecentContact({
|
||||||
|
// fromContactId: 0,
|
||||||
|
// unread: 0,
|
||||||
|
// sendTime: 12312312,
|
||||||
|
// content: "12312312"
|
||||||
|
// });
|
||||||
|
|
||||||
|
constraintMessage({
|
||||||
|
id: "123",
|
||||||
|
status: "succeed",
|
||||||
|
type: "image",
|
||||||
|
sendTime: 12312312312,
|
||||||
|
content: "asdas",
|
||||||
|
fromContactId: "123",
|
||||||
|
fromUser: { id: "123", displayName: "123", avatar: "123", type: "single" }
|
||||||
|
});
|
||||||
|
export function constraintObject(data, options) {
|
||||||
|
if (!data || !isPlainObject(data)) {
|
||||||
|
error("argument must be an object");
|
||||||
|
}
|
||||||
|
Object.keys(options).forEach(k => {
|
||||||
|
const option = options[k];
|
||||||
|
const val = data[k];
|
||||||
|
if ((option === true || option.required === true) && val === undefined) {
|
||||||
|
error(`"${k}" cannot be "${val}" `);
|
||||||
|
} else if (option.has && !option.has.includes(val)) {
|
||||||
|
error(
|
||||||
|
`"${k}" cannot be "${val}",can only be the following data "${
|
||||||
|
option.has
|
||||||
|
}"`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// export function constraintRecentContact(data) {
|
||||||
|
// constraintContact(data);
|
||||||
|
// constraintMessageBasic(data.message);
|
||||||
|
// constraintObject(data, {
|
||||||
|
// unread: true
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
export function constraintContact(data) {
|
||||||
|
constraintContactBasic(data);
|
||||||
|
// constraintObject(data, {
|
||||||
|
// unread: true,
|
||||||
|
// lastSendTime: true,
|
||||||
|
// lastContent: true
|
||||||
|
// });
|
||||||
|
}
|
||||||
|
export function constraintMessage(data) {
|
||||||
|
constraintObject(data, {
|
||||||
|
status: {
|
||||||
|
required: true,
|
||||||
|
has: MESSAGE_STATUS
|
||||||
|
},
|
||||||
|
fromContactId: true
|
||||||
|
});
|
||||||
|
constraintMessageBasic(data);
|
||||||
|
constraintContactBasic(data.fromUser);
|
||||||
|
let options = {};
|
||||||
|
switch (data.type) {
|
||||||
|
case "file":
|
||||||
|
options = {
|
||||||
|
fileSize: true,
|
||||||
|
fileName: true
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
case "text":
|
||||||
|
options = {
|
||||||
|
text: true
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
constraintObject(data, options);
|
||||||
|
}
|
||||||
@@ -0,0 +1,128 @@
|
|||||||
|
import { isPlainObject, isFunction } from "utils/validate";
|
||||||
|
/**
|
||||||
|
* 使用某个组件上的作用域插槽
|
||||||
|
* @param {VueComponent} inject
|
||||||
|
* @param {String} slotName
|
||||||
|
* @param {Node} defaultElement
|
||||||
|
* @param {Object} props
|
||||||
|
*/
|
||||||
|
export function useScopedSlot(slot, def, props) {
|
||||||
|
return slot ? slot(props) : def;
|
||||||
|
}
|
||||||
|
export function padZero(val) {
|
||||||
|
return val < 10 ? `0${val}` : val;
|
||||||
|
}
|
||||||
|
export function hoursTimeFormat(t) {
|
||||||
|
const date = new Date(t);
|
||||||
|
const nowDate = new Date();
|
||||||
|
const Y = t => {
|
||||||
|
return t.getFullYear();
|
||||||
|
};
|
||||||
|
const MD = t => {
|
||||||
|
return `${t.getMonth() + 1}-${t.getDate()}`;
|
||||||
|
};
|
||||||
|
const dateY = Y(date);
|
||||||
|
const nowDateY = Y(nowDate);
|
||||||
|
|
||||||
|
let format;
|
||||||
|
if (dateY !== nowDateY) {
|
||||||
|
format = "y年m月d日 h:i";
|
||||||
|
} else if (`${dateY}-${MD(date)}` === `${nowDateY}-${MD(nowDate)}`) {
|
||||||
|
format = "h:i";
|
||||||
|
} else {
|
||||||
|
format = "m月d日 h:i";
|
||||||
|
}
|
||||||
|
return timeFormat(t, format);
|
||||||
|
}
|
||||||
|
export function timeFormat(t, format) {
|
||||||
|
if (!format) format = "y-m-d h:i:s";
|
||||||
|
if (t) t = new Date(t);
|
||||||
|
else t = new Date();
|
||||||
|
const formatArr = [
|
||||||
|
t.getFullYear().toString(),
|
||||||
|
padZero((t.getMonth() + 1).toString()),
|
||||||
|
padZero(t.getDate().toString()),
|
||||||
|
padZero(t.getHours().toString()),
|
||||||
|
padZero(t.getMinutes().toString()),
|
||||||
|
padZero(t.getSeconds().toString())
|
||||||
|
];
|
||||||
|
const reg = "ymdhis";
|
||||||
|
for (let i = 0; i < formatArr.length; i++) {
|
||||||
|
format = format.replace(reg.charAt(i), formatArr[i]);
|
||||||
|
}
|
||||||
|
return format;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fastDone(event, callback) {
|
||||||
|
if (isFunction(event)) {
|
||||||
|
event(() => {
|
||||||
|
callback();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 获取数组相交的值组成新数组
|
||||||
|
* @param {Array} a
|
||||||
|
* @param {Array} b
|
||||||
|
*/
|
||||||
|
export function arrayIntersect(a, b) {
|
||||||
|
return a.filter(x => b.includes(x));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function error(text) {
|
||||||
|
throw new Error(text);
|
||||||
|
}
|
||||||
|
export function cloneDeep(obj) {
|
||||||
|
const newobj = { ...obj };
|
||||||
|
for (const key in newobj) {
|
||||||
|
const val = newobj[key];
|
||||||
|
if (isPlainObject(val)) {
|
||||||
|
newobj[key] = cloneDeep(val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return newobj;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function mergeDeep(o1, o2) {
|
||||||
|
for (const key in o2) {
|
||||||
|
if (isPlainObject(o1[key])) {
|
||||||
|
o1[key] = mergeDeep(o1[key], o2[key]);
|
||||||
|
} else {
|
||||||
|
o1[key] = o2[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return o1;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function toEmojiName(str) {
|
||||||
|
return str.replace(/<img emoji-name=\"([^\"]*?)\" [^>]*>/gi, "[!$1]");
|
||||||
|
}
|
||||||
|
export function formatByte(value) {
|
||||||
|
if (null == value || value == "") {
|
||||||
|
return "0 Bytes";
|
||||||
|
}
|
||||||
|
var unitArr = ["B", "K", "M", "G", "T", "P", "E", "Z", "Y"];
|
||||||
|
var index = 0;
|
||||||
|
var srcsize = parseFloat(value);
|
||||||
|
index = Math.floor(Math.log(srcsize) / Math.log(1024));
|
||||||
|
var size = srcsize / Math.pow(1024, index);
|
||||||
|
size = parseFloat(size.toFixed(2));
|
||||||
|
return size + unitArr[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function generateUUID() {
|
||||||
|
var d = new Date().getTime();
|
||||||
|
if (window.performance && typeof window.performance.now === "function") {
|
||||||
|
d += performance.now(); //use high-precision timer if available
|
||||||
|
}
|
||||||
|
var uuid = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function(
|
||||||
|
c
|
||||||
|
) {
|
||||||
|
var r = (d + Math.random() * 16) % 16 | 0;
|
||||||
|
d = Math.floor(d / 16);
|
||||||
|
return (c == "x" ? r : (r & 0x3) | 0x8).toString(16);
|
||||||
|
});
|
||||||
|
return uuid;
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
export function isPlainObject(obj) {
|
||||||
|
return Object.prototype.toString.call(obj) === "[object Object]";
|
||||||
|
}
|
||||||
|
export function isString(str) {
|
||||||
|
return typeof str == "string";
|
||||||
|
}
|
||||||
|
export function isToday(time) {
|
||||||
|
return new Date().getTime() - time < 86400000;
|
||||||
|
}
|
||||||
|
export function isEmpty(obj) {
|
||||||
|
if (!obj) return true;
|
||||||
|
if (Array.isArray(obj) && obj.length == 0) return true;
|
||||||
|
if (isPlainObject(obj) && Object.values(obj).length == 0) return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
export function isUrl(str) {
|
||||||
|
const reg =
|
||||||
|
"^((https|http|ftp|rtsp|mms)?://)" +
|
||||||
|
"?(([0-9a-z_!~*'().&=+$%-]+: )?[0-9a-z_!~*'().&=+$%-]+@)?" + //ftp的user@
|
||||||
|
"(([0-9]{1,3}.){3}[0-9]{1,3}" + // IP形式的URL- 199.194.52.184
|
||||||
|
"|" + // 允许IP和DOMAIN(域名)
|
||||||
|
"([0-9a-z_!~*'()-]+.)*" + // 域名- www.
|
||||||
|
"([0-9a-z][0-9a-z-]{0,61})?[0-9a-z]." + // 二级域名
|
||||||
|
"[a-z]{2,6})" + // first level domain- .com or .museum
|
||||||
|
"(:[0-9]{1,4})?" + // 端口- :80
|
||||||
|
"((/?)|" + // 如果没有文件名,则不需要斜杠
|
||||||
|
"(/[0-9a-z_!~*'().;?:@&=+$,%#-]+)+/?)$";
|
||||||
|
return new RegExp(reg).test(str) ? true : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isFunction(val) {
|
||||||
|
return val && typeof val === "function";
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isEng(val) {
|
||||||
|
return /^[A-Za-z]+$/.test(val);
|
||||||
|
}
|
||||||
+1
-1
@@ -8,7 +8,7 @@
|
|||||||
content="width=device-width, initial-scale=1.0,maximum-scale=1.0, user-scalable=no"
|
content="width=device-width, initial-scale=1.0,maximum-scale=1.0, user-scalable=no"
|
||||||
/>
|
/>
|
||||||
<link rel="icon" href="<%= BASE_URL %>favicon.ico" />
|
<link rel="icon" href="<%= BASE_URL %>favicon.ico" />
|
||||||
<title>在线客服</title>
|
<title>Lemon-IMUI</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<noscript>
|
<noscript>
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ module.exports = {
|
|||||||
filename: "index.html"
|
filename: "index.html"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
productionSourceMap:false,
|
||||||
configureWebpack: {
|
configureWebpack: {
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
|
|||||||
Reference in New Issue
Block a user