Commit 7c3fe629 by zhousil

不同类型消息样式分离

parent 55070bdd
...@@ -25,63 +25,87 @@ ...@@ -25,63 +25,87 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { Component, Ref } from "vue-property-decorator"; import { Component, Ref } from "vue-property-decorator";
import BaseMessage from "./index"; import BaseMessage from "./index";
import VoiceIcon from "./voice.vue"; import VoiceIcon from "./voice.vue";
@Component({ components: { VoiceIcon } }) @Component({ components: { VoiceIcon } })
export default class Index extends BaseMessage { export default class Index extends BaseMessage {
@Ref("audio") @Ref("audio")
private readonly audioRef!: HTMLAudioElement; private readonly audioRef!: HTMLAudioElement;
private playing = false; private playing = false;
private get duration() { private get duration() {
const v = this.messageBody.msg.duration as number; const v = this.messageBody.msg.duration as number;
return v || 0; return v || 0;
} }
private get durationInSecond() { private get durationInSecond() {
return Math.round(this.duration / 1000); return Math.round(this.duration / 1000);
} }
private get getVoiceMessageWidth() { private get getVoiceMessageWidth() {
if (this.fileFailed2Load) { if (this.fileFailed2Load) {
return 35; return 35;
}
const d = this.duration / 1000;
if (d <= 3) {
return 60;
}
if (d >= 60) {
return 200;
}
return 60 + d;
} }
const d = this.duration / 1000;
if (d <= 3) { private play() {
return 60; if (this.audioRef?.paused) {
this.audioRef?.load();
this.audioRef?.play();
} else {
this.audioRef?.pause();
}
} }
if (d >= 60) { private onPlay() {
return 200; this.playing = true;
} }
return 60 + d; private onPause() {
} this.playing = false;
}
private play() { mounted() {
if (this.audioRef?.paused) { this.buildMessageUrl();
this.audioRef?.load();
this.audioRef?.play();
} else {
this.audioRef?.pause();
} }
} }
</script>
private onPlay() { <style lang="less" scoped>
this.playing = true; .voice-message {
} height: 40px;
width: 200px;
private onPause() { &.can-play {
this.playing = false; cursor: pointer;
} }
mounted() { i {
this.buildMessageUrl(); font-size: 16px;
}
} }
} .my-message {
</script> .voice-message {
> div {
flex-flow: row-reverse;
}
<style lang="less" scoped></style> svg {
transform: rotateY(180deg);
}
}
}
</style>
...@@ -30,60 +30,69 @@ ...@@ -30,60 +30,69 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { Component } from "vue-property-decorator"; import { Component } from "vue-property-decorator";
import BaseMessage from "./index"; import BaseMessage from "./index";
import FileIcon from "./file-icon.vue"; import FileIcon from "./file-icon.vue";
import { FileType, getFileType } from "./file-controller"; import { FileType, getFileType } from "./file-controller";
const k = 1024, const k = 1024,
m = 1024 * k, m = 1024 * k,
g = 1024 * m, g = 1024 * m,
t = 1024 * g; t = 1024 * g;
function formatSize(size: number) { function formatSize(size: number) {
if (size === undefined || size === null) { if (size === undefined || size === null) {
return ""; return "";
} }
if (size < k) { if (size < k) {
return size + " B"; return size + " B";
} }
if (size < m) { if (size < m) {
return Number((size / k).toFixed(2)) + " KB"; return Number((size / k).toFixed(2)) + " KB";
} }
if (size < g) { if (size < g) {
return Number((size / m).toFixed(2)) + " MB"; return Number((size / m).toFixed(2)) + " MB";
}
if (size < t) {
return Number((size / g).toFixed(2)) + " GB";
}
return Number((size / t).toFixed(2)) + " TB";
}
@Component({ components: { FileIcon } })
export default class Index extends BaseMessage {
private get getAttachment() {
if (this.messageBody) {
return this.messageBody.msg.name;
} }
return "文件下载"; if (size < t) {
return Number((size / g).toFixed(2)) + " GB";
}
return Number((size / t).toFixed(2)) + " TB";
} }
private get fileIcon() { @Component({ components: { FileIcon } })
if (this.value) { export default class Index extends BaseMessage {
return getFileType(this.messageBody.msg.name); private get getAttachment() {
if (this.messageBody) {
return this.messageBody.msg.name;
}
return "文件下载";
} }
return FileType.Others; private get fileIcon() {
} if (this.value) {
return getFileType(this.messageBody.msg.name);
}
private format(v: number) { return FileType.Others;
return formatSize(v); }
}
mounted() { private format(v: number) {
this.buildMessageUrl(); return formatSize(v);
}
mounted() {
this.buildMessageUrl();
}
} }
}
</script> </script>
<style lang="less" scoped></style> <style lang="less" scoped>
.file-message {
background-color: transparent !important;
border-radius: 4px !important;
border: 1px solid #c5d4e5;
.file-message-name {
max-width: 130px;
}
}
</style>
...@@ -16,24 +16,57 @@ ...@@ -16,24 +16,57 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { Component } from "vue-property-decorator"; import { Component } from "vue-property-decorator";
import { FileType } from "./file-controller"; import { FileType } from "./file-controller";
import BaseMessage from "./index"; import BaseMessage from "./index";
import FileIcon from "./file-icon.vue"; import FileIcon from "./file-icon.vue";
@Component({ components: { FileIcon } }) @Component({ components: { FileIcon } })
export default class Index extends BaseMessage { export default class Index extends BaseMessage {
private readonly image404 = FileType.Image_404; private readonly image404 = FileType.Image_404;
private onImageError() { private onImageError() {
this.fileFailed2Load = true; this.fileFailed2Load = true;
this.messageRealUrl = ""; this.messageRealUrl = "";
} }
mounted() { mounted() {
this.buildMessageUrl(); this.buildMessageUrl();
}
} }
}
</script> </script>
<style lang="less" scoped></style> <style lang="less" scoped>
.image-message {
background-color: transparent !important;
border-radius: 4px !important;
border: 1px solid #c5d4e5;
line-height: 1;
max-width: 300px;
box-sizing: content-box;
img {
width: 100%;
}
&.image-404 {
background: #f7f8fa;
display: flex;
align-items: center;
justify-content: center;
.file-icon {
margin: 0;
}
}
/deep/ .file-icon {
margin-left: 0;
}
}
.my-message {
&.image-message:not(.image-404) {
background-color: transparent !important;
border-radius: 4px !important;
border: 1px solid #c5d4e5;
}
}
</style>
...@@ -6,31 +6,34 @@ ...@@ -6,31 +6,34 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { replaceText2Link } from "@/customer-service/utils"; import { replaceText2Link } from "@/customer-service/utils";
import xim from "@/customer-service/xim"; import xim from "@/customer-service/xim";
import { Component } from "vue-property-decorator"; import { Component } from "vue-property-decorator";
import BaseMessage from "./index"; import BaseMessage from "./index";
@Component({ components: {} }) @Component({ components: {} })
export default class Index extends BaseMessage { export default class Index extends BaseMessage {
private readonly emptyText = " "; private readonly emptyText = " ";
private format2Link(text: string) { private format2Link(text: string) {
let t = replaceText2Link(text); let t = replaceText2Link(text);
const keywords = xim.getMatchedTextKeywords(); const keywords = xim.getMatchedTextKeywords();
for (const item of keywords) { for (const item of keywords) {
const r = new RegExp(item, "g"); const r = new RegExp(item, "g");
t = t.replace(r, `<span class="highlight">${item}</span>`); t = t.replace(r, `<span class="highlight">${item}</span>`);
}
return t;
} }
return t;
} }
}
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
.inline-text { .inline-text {
/deep/ .highlight { display: inline-block;
color: #e87005; white-space: pre-wrap;
text-align: left;
/deep/ .highlight {
color: #e87005;
}
} }
} </style>
</style> \ No newline at end of file
...@@ -25,4 +25,15 @@ export default class Index extends BaseMessage { ...@@ -25,4 +25,15 @@ export default class Index extends BaseMessage {
} }
</script> </script>
<style lang="less" scoped></style> <style lang="less" scoped>
.video-message {
height: 160px;
width: 200px;
background-color: #000 !important;
border-radius: 0 !important;
svg {
cursor: pointer;
}
}
</style>
...@@ -3,11 +3,20 @@ ...@@ -3,11 +3,20 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { Component } from "vue-property-decorator"; import { Component } from "vue-property-decorator";
import BaseMessage from "./index"; import BaseMessage from "./index";
@Component({ components: {} }) @Component({ components: {} })
export default class Index extends BaseMessage {} export default class Index extends BaseMessage {}
</script> </script>
<style lang="less" scoped></style> <style lang="less" scoped>
.my-message {
.withdraw-message {
background-color: transparent !important;
padding: 0 4px;
font-size: 12px;
color: #999;
}
}
</style>
...@@ -19,6 +19,9 @@ ...@@ -19,6 +19,9 @@
<component <component
:is="messageComponent" :is="messageComponent"
:user-name="userName" :user-name="userName"
:class="{
'my-message': isMyMessage,
}"
v-if="messageComponent" v-if="messageComponent"
v-model="data" v-model="data"
@open="openFile" @open="openFile"
...@@ -94,561 +97,463 @@ ...@@ -94,561 +97,463 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { Component, Inject, Mixins, Prop } from "vue-property-decorator"; import { Component, Inject, Mixins, Prop } from "vue-property-decorator";
import { Filters } from "../mixin/filter"; import { Filters } from "../mixin/filter";
import * as dto from "../model"; import * as dto from "../model";
import chat from "./../xim"; import chat from "./../xim";
import { import {
isAudio, isAudio,
isImage, isImage,
isVideo, isVideo,
MAX_FILE_SIZE, MAX_FILE_SIZE,
MAX_IMAGE_SIZE, MAX_IMAGE_SIZE,
} from "./message-item/file-controller"; } from "./message-item/file-controller";
import WhoReadList from "./who-read-list.vue"; import WhoReadList from "./who-read-list.vue";
import avatar from "@/customer-service/components/avatar.vue"; import avatar from "@/customer-service/components/avatar.vue";
import { chatStore, ChatStore } from "@/customer-service/store/model"; import { chatStore, ChatStore } from "@/customer-service/store/model";
import ximInstance from "../xim/xim"; import ximInstance from "../xim/xim";
import { dbController } from "../database"; import { dbController } from "../database";
import ImageMessage from "./message-item/image-message.vue"; import ImageMessage from "./message-item/image-message.vue";
import FileMessage from "./message-item/file-message.vue"; import FileMessage from "./message-item/file-message.vue";
import AudioMessage from "./message-item/audio-message.vue"; import AudioMessage from "./message-item/audio-message.vue";
import VideoMessage from "./message-item/video-message.vue"; import VideoMessage from "./message-item/video-message.vue";
import TextMessage from "./message-item/text-message.vue"; import TextMessage from "./message-item/text-message.vue";
import WithdrawMessage from "./message-item/withdraw-message.vue"; import WithdrawMessage from "./message-item/withdraw-message.vue";
import xim from "./../xim"; import xim from "./../xim";
const twoMinutes = 2 * 60 * 1000; const twoMinutes = 2 * 60 * 1000;
const messageMapping = new Map<dto.MessageType, string>([ const messageMapping = new Map<dto.MessageType, string>([
[dto.MessageType.Image, "image-message"], [dto.MessageType.Image, "image-message"],
[dto.MessageType.File, "file-message"], [dto.MessageType.File, "file-message"],
[dto.MessageType.Video, "video-message"], [dto.MessageType.Video, "video-message"],
[dto.MessageType.Voice, "audio-message"], [dto.MessageType.Voice, "audio-message"],
[dto.MessageType.Text, "text-message"], [dto.MessageType.Text, "text-message"],
[dto.MessageType.Withdraw, "withdraw-message"], [dto.MessageType.Withdraw, "withdraw-message"],
]); ]);
@Component({ @Component({
components: { components: {
WhoReadList, WhoReadList,
avatar, avatar,
ImageMessage, ImageMessage,
FileMessage, FileMessage,
AudioMessage, AudioMessage,
VideoMessage, VideoMessage,
TextMessage, TextMessage,
WithdrawMessage, WithdrawMessage,
}, },
}) })
export default class Message extends Mixins(Filters) { export default class Message extends Mixins(Filters) {
@chatStore.State(ChatStore.STATE_CHAT_CURRENT_USER_UID) @chatStore.State(ChatStore.STATE_CHAT_CURRENT_USER_UID)
private readonly chatMyId!: ChatStore.STATE_CHAT_CURRENT_USER_UID; private readonly chatMyId!: ChatStore.STATE_CHAT_CURRENT_USER_UID;
@chatStore.Getter(ChatStore.GETTER_CURRENT_CHAT_PRESENT_MEMBERS) @chatStore.Getter(ChatStore.GETTER_CURRENT_CHAT_PRESENT_MEMBERS)
private readonly chatMembers!: ChatStore.GETTER_CURRENT_CHAT_PRESENT_MEMBERS; private readonly chatMembers!: ChatStore.GETTER_CURRENT_CHAT_PRESENT_MEMBERS;
@chatStore.State(ChatStore.STATE_CHAT_SOURCE) @chatStore.State(ChatStore.STATE_CHAT_SOURCE)
private readonly chatSource!: ChatStore.STATE_CHAT_SOURCE; private readonly chatSource!: ChatStore.STATE_CHAT_SOURCE;
@chatStore.State(ChatStore.STATE_CHAT_CURRENT_CHAT_ID) @chatStore.State(ChatStore.STATE_CHAT_CURRENT_CHAT_ID)
private readonly chatId!: ChatStore.STATE_CHAT_CURRENT_CHAT_ID; private readonly chatId!: ChatStore.STATE_CHAT_CURRENT_CHAT_ID;
@chatStore.Mutation(ChatStore.MUTATION_WITHDRAW) @chatStore.Mutation(ChatStore.MUTATION_WITHDRAW)
private readonly executeWithDraw!: ChatStore.MUTATION_WITHDRAW; private readonly executeWithDraw!: ChatStore.MUTATION_WITHDRAW;
@chatStore.Action(ChatStore.ACTION_SET_HANDLED) @chatStore.Action(ChatStore.ACTION_SET_HANDLED)
private readonly setHandled!: ChatStore.ACTION_SET_HANDLED; private readonly setHandled!: ChatStore.ACTION_SET_HANDLED;
@Prop({ type: Object, default: () => Object.create(null) }) @Prop({ type: Object, default: () => Object.create(null) })
private readonly data!: dto.Message; private readonly data!: dto.Message;
@Prop() @Prop()
private readonly isSendingMessage!: boolean; private readonly isSendingMessage!: boolean;
@Prop() @Prop()
private readonly failed!: boolean; private readonly failed!: boolean;
@Prop({ default: "circle" }) @Prop({ default: "circle" })
private readonly shape!: string; private readonly shape!: string;
@Inject({ default: false }) readonly showReadSummary!: boolean; @Inject({ default: false }) readonly showReadSummary!: boolean;
private readonly backend = chat.isBackend(); private readonly backend = chat.isBackend();
private messageComponent = ""; private messageComponent = "";
private readListVisibility = false; private readListVisibility = false;
private org = ""; private org = "";
private readerListOffset = false; private readerListOffset = false;
private defaultMessageHandledStatus = dto.MessageHandled.Default; private defaultMessageHandledStatus = dto.MessageHandled.Default;
private get canWithdraw() { private get canWithdraw() {
if (this.backend && this.data) { if (this.backend && this.data) {
return new Date().valueOf() - this.data.ts * 1000 < twoMinutes; return new Date().valueOf() - this.data.ts * 1000 < twoMinutes;
}
return false;
} }
return false;
}
private get isWithdrawMessage() {
return this.data.type === dto.MessageType.Withdraw;
}
private get isAllRead() { private get isWithdrawMessage() {
return this.data.read_count >= this.data.total_read_count; return this.data.type === dto.MessageType.Withdraw;
} }
private get messageBody(): { eid?: string; oid?: string; msg: any } { private get isAllRead() {
if (this.data) { return this.data.read_count >= this.data.total_read_count;
try {
return { ...this.data, msg: JSON.parse(this.data.msg) };
} catch {
return {
...this.data,
msg: JSON.parse(this.data.msg.replace(/\n/g, "\\n")),
};
}
} }
return { msg: { text: "" } }; private get messageBody(): { eid?: string; oid?: string; msg: any } {
} if (this.data) {
try {
return { ...this.data, msg: JSON.parse(this.data.msg) };
} catch {
return {
...this.data,
msg: JSON.parse(this.data.msg.replace(/\n/g, "\\n")),
};
}
}
private get handled() { return { msg: { text: "" } };
if (this.data) {
return this.defaultMessageHandledStatus || this.data.handled;
} }
return dto.MessageHandled.Default;
}
private get isMyMessage() { private get handled() {
if (this.isSendingMessage) { if (this.data) {
return true; return this.defaultMessageHandledStatus || this.data.handled;
}
return dto.MessageHandled.Default;
} }
const senderEid = this.messageBody.eid; private get isMyMessage() {
return senderEid!.toString() === this.chatMyId!.toString(); if (this.isSendingMessage) {
} return true;
}
created() { const senderEid = this.messageBody.eid;
this.messageComponent = messageMapping.get(this.messageType) as string; return senderEid!.toString() === this.chatMyId!.toString();
} }
created() {
this.messageComponent = messageMapping.get(this.messageType) as string;
}
private get userName() { private get userName() {
if (this.chatMembers) { if (this.chatMembers) {
const t = this.chatMembers.find((i) => i.eid === this.data.eid); const t = this.chatMembers.find((i) => i.eid === this.data.eid);
if (t) { if (t) {
return t.name; return t.name;
}
} }
return "";
} }
return "";
}
private get avatar() { private get avatar() {
const avatar = chat.getUserMapping(); const avatar = chat.getUserMapping();
if (this.isSendingMessage) { if (this.isSendingMessage) {
if (avatar && this.chatMyId) { if (avatar && this.chatMyId) {
const user = avatar[this.chatMyId]; const user = avatar[this.chatMyId];
if (user && user.avatar) { if (user && user.avatar) {
return user.avatar; return user.avatar;
}
} }
} }
}
if (avatar && this.data) { if (avatar && this.data) {
const value = avatar[this.data.eid]; const value = avatar[this.data.eid];
if (value && value.avatar) { if (value && value.avatar) {
return value.avatar; return value.avatar;
}
} }
}
return ""; return "";
} }
private get messageType() { private get messageType() {
const type = this.data?.type; const type = this.data?.type;
if (type === "file") { if (type === "file") {
const name = this.messageBody?.msg.name; const name = this.messageBody?.msg.name;
if (name) { if (name) {
const size = this.messageBody?.msg.size; const size = this.messageBody?.msg.size;
if (size) { if (size) {
const outImageSize = size > MAX_IMAGE_SIZE; const outImageSize = size > MAX_IMAGE_SIZE;
if (!outImageSize && isImage(name)) { if (!outImageSize && isImage(name)) {
return dto.MessageType.Image; return dto.MessageType.Image;
}
const outSize = size > MAX_FILE_SIZE;
if (!outSize) {
if (isAudio(name)) {
return dto.MessageType.Voice;
} }
if (isVideo(name)) { const outSize = size > MAX_FILE_SIZE;
return dto.MessageType.Video; if (!outSize) {
if (isAudio(name)) {
return dto.MessageType.Voice;
}
if (isVideo(name)) {
return dto.MessageType.Video;
}
} }
} }
} }
} }
return type;
} }
return type;
}
private get isTextMessage() { private get isTextMessage() {
return this.messageType === dto.MessageType.Text; return this.messageType === dto.MessageType.Text;
} }
private get matchKeywords() { private get matchKeywords() {
if (this.isTextMessage && !this.isMyMessage) { if (this.isTextMessage && !this.isMyMessage) {
const m = this.messageBody.msg as { text: string }; const m = this.messageBody.msg as { text: string };
if (m && m.text) { if (m && m.text) {
const keywords = xim.getMatchedTextKeywords(); const keywords = xim.getMatchedTextKeywords();
return keywords.find((i) => m.text.includes(i)); return keywords.find((i) => m.text.includes(i));
}
} }
return false;
} }
return false;
}
private isCustomer() { private isCustomer() {
return !this.showReadSummary; return !this.showReadSummary;
}
private openFile(url: string) {
if (this.isSendingMessage) {
return;
} }
if (this.failed) {
return; private openFile(url: string) {
if (this.isSendingMessage) {
return;
}
if (this.failed) {
return;
}
const copy = { ...this.messageBody.msg };
copy.url = url;
this.$emit("open", { type: this.messageType, msg: copy });
} }
const copy = { ...this.messageBody.msg };
copy.url = url;
this.$emit("open", { type: this.messageType, msg: copy });
}
private withdraw() { private withdraw() {
ximInstance.withdraw(this.chatId!, this.data.id).finally(() => { ximInstance.withdraw(this.chatId!, this.data.id).finally(() => {
dbController dbController
.removeMessage(this.chatId!, this.data.id) .removeMessage(this.chatId!, this.data.id)
.finally(() => { .finally(() => {
this.executeWithDraw(this.data.id); this.executeWithDraw(this.data.id);
this.$emit("withdraw", this.data.id); this.$emit("withdraw", this.data.id);
}); });
}); });
} }
private openReaderList(e: MouseEvent) { private openReaderList(e: MouseEvent) {
this.readerListOffset = e.x < 450; this.readerListOffset = e.x < 450;
this.readListVisibility = true; this.readListVisibility = true;
} }
private executeHandled() { private executeHandled() {
this.setHandled({ this.setHandled({
id: this.data.id, id: this.data.id,
value: (this.defaultMessageHandledStatus = value: (this.defaultMessageHandledStatus =
dto.MessageHandled.Handled), dto.MessageHandled.Handled),
}); });
this.closeKeywordPopover(); this.closeKeywordPopover();
} }
private ignoredKeyword() { private ignoredKeyword() {
this.setHandled({ this.setHandled({
id: this.data.id, id: this.data.id,
value: (this.defaultMessageHandledStatus = value: (this.defaultMessageHandledStatus =
dto.MessageHandled.Ignored), dto.MessageHandled.Ignored),
}); });
this.closeKeywordPopover(); this.closeKeywordPopover();
} }
private closeKeywordPopover() { private closeKeywordPopover() {
document.body.click(); document.body.click();
}
} }
}
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
.message-con { .message-con {
padding-bottom: 20px; padding-bottom: 20px;
margin-right: 15px; margin-right: 15px;
position: relative; position: relative;
&.my-message {
.msg-avatar {
margin-right: 0;
margin-left: 10px;
}
.msg-detail {
margin-top: 0;
background-color: #dbf2ff;
border-radius: 8px 0 8px 8px;
&.image-message:not(.image-404), &.my-message {
&.file-message { .msg-avatar {
background-color: transparent; margin-right: 0;
border-radius: 4px; margin-left: 10px;
border: 1px solid #c5d4e5;
} }
&.voice-message { .msg-detail {
> div { margin-top: 0;
flex-flow: row-reverse; background-color: #dbf2ff;
} border-radius: 8px 0 8px 8px;
}
svg { .msg-read {
transform: rotateY(180deg); display: inline-block;
} color: #bfe1ff;
margin-right: 15px;
user-select: none;
flex: none;
margin-top: auto;
} }
&.video-message { .download-icon {
background-color: #000; margin-right: 15px;
border-radius: 0; margin-left: 0;
margin-top: 0;
} }
&.withdraw-message { .withdraw {
background-color: transparent;
padding: 0 4px;
font-size: 12px;
color: #999; color: #999;
position: absolute;
bottom: 0;
right: 0;
font-size: 12px;
display: none;
cursor: pointer;
} }
}
.msg-read {
display: inline-block;
color: #bfe1ff;
margin-right: 15px;
user-select: none;
flex: none;
margin-top: auto;
}
.download-icon { &:hover {
margin-right: 15px; .withdraw {
margin-left: 0; display: inline-block;
margin-top: 0; }
}
} }
.withdraw { &.offset-bottom {
color: #999; margin-bottom: 30px;
position: absolute;
bottom: 0;
right: 0;
font-size: 12px;
display: none;
cursor: pointer;
} }
&:hover { > i {
.withdraw { height: 16px;
display: inline-block; font-size: 16px;
} margin-right: 10px;
} }
} }
&.offset-bottom { .msg-avatar {
margin-bottom: 30px;
}
> i {
height: 16px;
font-size: 16px;
margin-right: 10px; margin-right: 10px;
flex: 40px 0 0;
} }
}
.msg-avatar {
margin-right: 10px;
flex: 40px 0 0;
}
i.msg-avatar {
font-size: 30px;
background-color: #c0c4cc;
border-radius: 4px;
width: 40px;
height: 40px;
&:before { i.msg-avatar {
position: relative; font-size: 30px;
left: 5px; background-color: #c0c4cc;
top: 5px;
color: #fff;
}
}
.msg-name {
font-size: 14px;
color: #888;
text-align: right;
margin-bottom: 3px;
min-height: 16px;
&.algin-left {
text-align: left;
}
}
.msg-content {
text-align: right;
&.algin-left {
text-align: left;
}
}
.msg-detail {
margin-top: 10px;
font-size: 14px;
line-height: 20px;
background: #f5f6fa;
border-radius: 0px 8px 8px;
padding: 10px;
word-break: break-word;
&.image-message,
&.file-message {
background-color: transparent;
border-radius: 4px; border-radius: 4px;
border: 1px solid #c5d4e5; width: 40px;
} height: 40px;
&.image-message {
line-height: 1;
&.image-404 {
background: #f7f8fa;
display: flex;
align-items: center;
justify-content: center;
.file-icon { &:before {
margin: 0; position: relative;
} left: 5px;
top: 5px;
color: #fff;
} }
}
/deep/ .file-icon { .msg-name {
margin-left: 0; font-size: 14px;
color: #888;
text-align: right;
margin-bottom: 3px;
min-height: 16px;
&.algin-left {
text-align: left;
} }
} }
&.voice-message { .msg-content {
height: 40px; text-align: right;
width: 200px; &.algin-left {
text-align: left;
&.can-play {
cursor: pointer;
} }
}
i { .msg-detail {
font-size: 16px; margin-top: 10px;
font-size: 14px;
line-height: 20px;
background: #f5f6fa;
border-radius: 0px 8px 8px;
padding: 10px;
word-break: break-word;
/deep/ img {
max-width: 300px;
} }
} }
&.video-message { .download-icon {
height: 160px; cursor: pointer;
width: 200px; text-decoration: none;
background-color: #000; margin-left: 15px;
border-radius: 0; margin-top: 42px;
svg { i {
cursor: pointer; color: #fff;
font-size: 14px;
} }
} }
&.inline-text { .no-selection {
display: inline-block; user-select: none;
white-space: pre-wrap;
text-align: left;
} }
.file-message-name { .pointer {
max-width: 130px; cursor: pointer;
} }
/deep/ img { .all {
max-width: 300px; color: #4389f8;
} }
}
.download-icon { .match-keyword {
cursor: pointer; background-color: #fff3e0;
text-decoration: none; border-radius: 13px;
margin-left: 15px; padding: 3px 8px;
margin-top: 42px; color: #e87005;
position: absolute;
i { bottom: -10px;
color: #fff; left: 0;
font-size: 14px; cursor: pointer;
} font-size: 12px;
}
.no-selection {
user-select: none;
}
.image-message { &.handled {
max-width: 300px; color: #59ba7b;
box-sizing: content-box; background-color: #f0f0f0;
img { cursor: default;
width: 100%; }
}
}
.pointer {
cursor: pointer;
}
.all {
color: #4389f8;
}
.match-keyword {
background-color: #fff3e0;
border-radius: 13px;
padding: 3px 8px;
color: #e87005;
position: absolute;
bottom: -10px;
left: 0;
cursor: pointer;
font-size: 12px;
&.handled {
color: #59ba7b;
background-color: #f0f0f0;
cursor: default;
}
&.ignored { &.ignored {
color: #999; color: #999;
background-color: #f0f0f0; background-color: #f0f0f0;
cursor: default; cursor: default;
} }
i { i {
margin-right: 5px; margin-right: 5px;
}
} }
}
.keyword-action {
list-style: none;
li { .keyword-action {
list-style: none; list-style: none;
padding: 8px 10px;
color: #077aec;
cursor: pointer;
&:hover { li {
background-color: #f5f6fa; list-style: none;
padding: 8px 10px;
color: #077aec;
cursor: pointer;
&:hover {
background-color: #f5f6fa;
}
} }
} }
}
</style> </style>
<style lang="less"> <style lang="less">
.match-keyword-popover { .match-keyword-popover {
padding: 0; padding: 0;
} }
</style> </style>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment