Commit ece66d8a by Sixong.Zhu

消息结构调整

parent 15e55bff
<template>
<div
class="msg-detail voice-message d-flex align-items-center"
:class="{ playing: playing, 'can-play': messageRealUrl }"
v-if="messageType === 'voice'"
@click.stop="play"
:style="{ width: getVoiceMessageWidth + 'px' }"
>
<div class="d-flex align-items-center" v-if="messageRealUrl">
<voice-icon :loading="playing"></voice-icon>
<audio ref="audio" @play="onPlay" @pause="onPause">
<source type="audio/aac" :src="messageRealUrl" />
</audio>
<span v-if="duration" class="duration text-nowrap text-hint"
>{{ durationInSecond }}s</span
>
</div>
<i
class="el-icon-warning-outline"
v-else-if="fileFailed2Load"
title="[语音加载失败]"
></i>
</div>
</template>
<script lang="ts">
import { Component, Ref } from "vue-property-decorator";
import BaseMessage from "./index";
import VoiceIcon from "./voice.vue";
@Component({ components: { VoiceIcon } })
export default class Index extends BaseMessage {
@Ref("audio")
private readonly audioRef!: HTMLAudioElement;
private playing = false;
private get duration() {
const v = this.messageBody.msg.duration as number;
return v || 0;
}
private get durationInSecond() {
return Math.round(this.duration / 1000);
}
private get getVoiceMessageWidth() {
if (this.fileFailed2Load) {
return 35;
}
const d = this.duration / 1000;
if (d <= 3) {
return 60;
}
if (d >= 60) {
return 200;
}
return 60 + d;
}
private play() {
if (this.audioRef?.paused) {
this.audioRef?.load();
this.audioRef?.play();
} else {
this.audioRef?.pause();
}
}
private onPlay() {
this.playing = true;
}
private onPause() {
this.playing = false;
}
mounted() {
this.buildMessageUrl();
}
}
</script>
<style lang="less" scoped></style>
...@@ -70,7 +70,7 @@ export default class FileIcon extends Vue { ...@@ -70,7 +70,7 @@ export default class FileIcon extends Vue {
.file-icon { .file-icon {
margin-left: 10px; margin-left: 10px;
svg { /deep/ svg {
max-width: 36px; max-width: 36px;
max-height: 36px; max-height: 36px;
} }
......
<template>
<div class="msg-detail file-message d-flex" @dblclick="openFile">
<div class="file-message-info">
<div
class="text-nowrap text-truncate file-message-name"
:title="messageBody.msg.name"
>
{{ messageBody.msg.name }}
</div>
<div class="text-hint">
{{ format(messageBody.msg.size) }}
</div>
</div>
<file-icon :value="fileIcon"></file-icon>
<a
class="
d-flex
align-items-center
justify-content-center
download-icon
"
:href="messageRealUrl"
:download="getAttachment"
title="下载文件"
>
<img src="~@/customer-service/imgs/download.png" alt="Download" />
</a>
</div>
</template>
<script lang="ts">
import { Component } from "vue-property-decorator";
import BaseMessage from "./index";
import FileIcon from "./file-icon.vue";
import { FileType, getFileType } from "./file-controller";
const k = 1024,
m = 1024 * k,
g = 1024 * m,
t = 1024 * g;
function formatSize(size: number) {
if (size === undefined || size === null) {
return "";
}
if (size < k) {
return size + " B";
}
if (size < m) {
return Number((size / k).toFixed(2)) + " KB";
}
if (size < g) {
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 "文件下载";
}
private get fileIcon() {
if (this.value) {
return getFileType(this.messageBody.msg.name);
}
return FileType.Others;
}
private format(v: number) {
return formatSize(v);
}
mounted() {
this.buildMessageUrl();
}
}
</script>
<style lang="less" scoped></style>
<template>
<div
class="msg-detail image-message"
:class="{ 'image-404': fileFailed2Load }"
@dblclick="openFile"
>
<img
v-if="messageRealUrl"
:src="messageRealUrl"
:title="messageBody.msg.name"
:alt="messageBody.msg.name"
@error="onImageError"
/>
<file-icon v-else-if="fileFailed2Load" :value="image404"></file-icon>
</div>
</template>
<script lang="ts">
import { Component } from "vue-property-decorator";
import { FileType } from "./file-controller";
import BaseMessage from "./index";
import FileIcon from "./file-icon.vue";
@Component({ components: { FileIcon } })
export default class Index extends BaseMessage {
private readonly image404 = FileType.Image_404;
private onImageError() {
this.fileFailed2Load = true;
this.messageRealUrl = "";
}
mounted() {
this.buildMessageUrl();
}
}
</script>
<style lang="less" scoped></style>
import { Component, Vue, Model, Prop } from "vue-property-decorator";
import { Message } from "@/customer-service/model";
import { isAccessibleUrl } from "@/customer-service/service/tools";
@Component({ components: {} })
export default class BaseMessage extends Vue {
@Model()
protected readonly value!: Message;
@Prop()
protected readonly userName!: string;
protected messageRealUrl = "";
protected fileFailed2Load = false;
protected loadingRealUrl = false;
protected get messageBody(): { eid?: string; oid?: string; msg: any } {
if (this.value) {
try {
return { ...this.value, msg: JSON.parse(this.value.msg) };
} catch {
return {
...this.value,
msg: JSON.parse(this.value.msg.replace(/\n/g, "\\n")),
};
}
}
return { msg: { text: "" } };
}
protected openFile() {
this.$emit("open", this.messageRealUrl);
}
protected buildMessageUrl() {
if (this.messageRealUrl || this.loadingRealUrl) {
return;
}
const url = this.messageBody.msg.url as string;
if (url) {
if (isAccessibleUrl(url)) {
return (this.messageRealUrl = url);
}
this.loadingRealUrl = true;
} else {
this.fileFailed2Load = true;
}
}
}
<template>
<div
class="msg-detail inline-text"
v-html="format2Link(messageBody.msg.text || emptyText)"
></div>
</template>
<script lang="ts">
import { replaceText2Link } from "@/utils/isUrl";
import { Component } from "vue-property-decorator";
import BaseMessage from "./index";
@Component({ components: {} })
export default class Index extends BaseMessage {
private readonly emptyText = " ";
private format2Link(text: string) {
return replaceText2Link(text);
}
}
</script>
<style lang="less" scoped></style>
<template>
<div
class="
msg-detail
video-message
d-flex
align-items-center
justify-content-center
"
>
<video-player-icon @click.native="openFile"></video-player-icon>
</div>
</template>
<script lang="ts">
import { Component } from "vue-property-decorator";
import BaseMessage from "./index";
import VideoPlayerIcon from "./video-player-icon.vue";
@Component({ components: { VideoPlayerIcon } })
export default class Index extends BaseMessage {
mounted() {
this.buildMessageUrl();
}
}
</script>
<style lang="less" scoped></style>
...@@ -32,20 +32,18 @@ ...@@ -32,20 +32,18 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { Component, Prop, Vue, Watch } from "vue-property-decorator" import { Component, Prop, Vue, Watch } from "vue-property-decorator";
;
@Component({ components: {} }) @Component({ components: {} })
export default class VoiceIcon extends Vue { export default class VoiceIcon extends Vue {
@Prop({ default: 25 }) @Prop({ default: 25 })
private size!: number private size!: number;
@Prop() @Prop()
private loading!: boolean private loading!: boolean;
private status = 0 private status = 0;
private interval = 0 private interval = 0;
@Watch("loading") @Watch("loading")
private onLoadingChanged() { private onLoadingChanged() {
......
<template>
<div class="msg-detail withdraw-message">{{ userName }}撤回了一条消息</div>
</template>
<script lang="ts">
import { Component } from "vue-property-decorator";
import BaseMessage from "./index";
@Component({ components: {} })
export default class Index extends BaseMessage {}
</script>
<style lang="less" scoped></style>
...@@ -197,8 +197,7 @@ export default class MessageList extends Vue { ...@@ -197,8 +197,7 @@ export default class MessageList extends Vue {
this.scollWrapper.addEventListener("scroll", this.handleScroll); this.scollWrapper.addEventListener("scroll", this.handleScroll);
this.saveScrollToBottomFunc(this.scrollToNewMsg); this.saveScrollToBottomFunc(this.scrollToNewMsg);
this.scrollToNewMsg(); this.scrollToNewMsg();
// force scroll end this.scroll2End(200);
setTimeout(() => this.scrollToNewMsg(), 100);
} }
public beforeDestroy() { public beforeDestroy() {
...@@ -216,12 +215,9 @@ export default class MessageList extends Vue { ...@@ -216,12 +215,9 @@ export default class MessageList extends Vue {
) as HTMLElement; ) as HTMLElement;
if (wrap) { if (wrap) {
if (delay) { if (delay) {
setTimeout(() => { return setTimeout(() => (wrap.scrollTop = wrap.scrollHeight), delay);
wrap.scrollTop = 100000;
}, delay);
return;
} }
wrap.scrollTop = 100000; wrap.scrollTop = wrap.scrollHeight;
} }
}); });
} }
......
...@@ -73,7 +73,7 @@ import { ...@@ -73,7 +73,7 @@ import {
MESSAGE_FILE_EMPTY, MESSAGE_FILE_EMPTY,
MESSAGE_FILE_TOO_LARGE, MESSAGE_FILE_TOO_LARGE,
MESSAGE_IMAGE_TOO_LARGE, MESSAGE_IMAGE_TOO_LARGE,
} from "../components/file-controller"; } from "../components/message-item/file-controller";
import { EmojiService } from "../service/emoji"; import { EmojiService } from "../service/emoji";
import { ChatStore } from "../store/model"; import { ChatStore } from "../store/model";
import { formatFileSize } from "../utils"; import { formatFileSize } from "../utils";
......
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