mirror of
https://gitee.com/beijing_hongye_huicheng/lilishop-ui.git
synced 2025-12-19 09:25:53 +08:00
IM
This commit is contained in:
313
im/src/components/chat/TalkCodeBlock.vue
Normal file
313
im/src/components/chat/TalkCodeBlock.vue
Normal file
@@ -0,0 +1,313 @@
|
||||
<template>
|
||||
<div class="lum-dialog-mask">
|
||||
<div class="container animated bounceInDown" :class="{ 'full-screen': isFullScreen }">
|
||||
<el-container class="full-height">
|
||||
<el-header class="header no-padding" height="50px">
|
||||
<div class="tools">
|
||||
<span>选择编程语言: </span>
|
||||
<el-select v-model="language" size="mini" filterable placeholder="语言类型" :disabled="!editMode">
|
||||
<el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value" />
|
||||
</el-select>
|
||||
</div>
|
||||
<i class="el-icon-close close-btn" @click="close" />
|
||||
<i class="iconfont icon-full-screen" :class="{
|
||||
'icon-tuichuquanping': isFullScreen,
|
||||
'icon-quanping ': !isFullScreen,
|
||||
}" :title="isFullScreen ? '关闭全屏模式' : '打开全屏模式'" @click="isFullScreen = !isFullScreen" />
|
||||
</el-header>
|
||||
|
||||
<el-main class="main no-padding">
|
||||
<PrismEditor class="peditor" style="border-radius: 0" :code="code" :language="language" :line-numbers="true"
|
||||
@change="codeChanged" />
|
||||
</el-main>
|
||||
|
||||
<el-footer class="footer no-padding" height="50px">
|
||||
<div class="code-num">
|
||||
<span>代码字数:{{ code.length }}字</span>
|
||||
<span v-show="code.length > 10000 && editMode" class="code-warning">
|
||||
(字数不能超过10000字)
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="buttom-group">
|
||||
<el-button size="small" plain @click="close">
|
||||
{{ editMode ? '取消编辑' : '关闭预览' }}
|
||||
</el-button>
|
||||
|
||||
<el-button v-show="editMode" type="primary" size="small" @click="submit">发送代码
|
||||
</el-button>
|
||||
</div>
|
||||
</el-footer>
|
||||
</el-container>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import PrismEditor from "vue-prism-editor";
|
||||
import "vue-prism-editor/dist/VuePrismEditor.css";
|
||||
import "prismjs/themes/prism-okaidia.css";
|
||||
import Vue from "vue";
|
||||
import { Select, Option } from "element-ui";
|
||||
Vue.use(Select);
|
||||
Vue.use(Option);
|
||||
|
||||
export default {
|
||||
name: "TalkCodeBlock",
|
||||
components: {
|
||||
PrismEditor,
|
||||
},
|
||||
props: {
|
||||
loadCode: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
loadLang: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
editMode: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
language: "",
|
||||
code: "",
|
||||
options: [
|
||||
{
|
||||
value: "css",
|
||||
label: "css",
|
||||
},
|
||||
{
|
||||
value: "less",
|
||||
label: "less",
|
||||
},
|
||||
{
|
||||
value: "javascript",
|
||||
label: "javascript",
|
||||
},
|
||||
{
|
||||
value: "json",
|
||||
label: "json",
|
||||
},
|
||||
{
|
||||
value: "bash",
|
||||
label: "bash",
|
||||
},
|
||||
{
|
||||
value: "c",
|
||||
label: "c",
|
||||
},
|
||||
{
|
||||
value: "cil",
|
||||
label: "cil",
|
||||
},
|
||||
{
|
||||
value: "docker",
|
||||
label: "docker",
|
||||
},
|
||||
{
|
||||
value: "git",
|
||||
label: "git",
|
||||
},
|
||||
{
|
||||
value: "go",
|
||||
label: "go",
|
||||
},
|
||||
{
|
||||
value: "java",
|
||||
label: "java",
|
||||
},
|
||||
{
|
||||
value: "lua",
|
||||
label: "lua",
|
||||
},
|
||||
{
|
||||
value: "nginx",
|
||||
label: "nginx",
|
||||
},
|
||||
{
|
||||
value: "objectivec",
|
||||
label: "objectivec",
|
||||
},
|
||||
{
|
||||
value: "php",
|
||||
label: "php",
|
||||
},
|
||||
{
|
||||
value: "python",
|
||||
label: "python",
|
||||
},
|
||||
{
|
||||
value: "ruby",
|
||||
label: "ruby",
|
||||
},
|
||||
{
|
||||
value: "rust",
|
||||
label: "rust",
|
||||
},
|
||||
{
|
||||
value: "sql",
|
||||
label: "sql",
|
||||
},
|
||||
{
|
||||
value: "swift",
|
||||
label: "swift",
|
||||
},
|
||||
{
|
||||
value: "vim",
|
||||
label: "vim",
|
||||
},
|
||||
{
|
||||
value: "visual-basic",
|
||||
label: "visual-basic",
|
||||
},
|
||||
{
|
||||
value: "shell",
|
||||
label: "shell",
|
||||
},
|
||||
],
|
||||
isFullScreen: false,
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
loadCode(value) {
|
||||
this.code = value;
|
||||
},
|
||||
loadLang(value) {
|
||||
this.language = value;
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.code = this.loadCode;
|
||||
this.language = this.loadLang;
|
||||
},
|
||||
methods: {
|
||||
submit() {
|
||||
if (!this.code) {
|
||||
this.$message.error("代码块不能为空...");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.language == "") {
|
||||
this.$message.error("请选择语言");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.code.length > 10000) {
|
||||
this.$message.error("代码字数不能超过10000字!!!");
|
||||
return false;
|
||||
}
|
||||
this.$emit("confirm", {
|
||||
language: this.language,
|
||||
code: this.code,
|
||||
});
|
||||
},
|
||||
close() {
|
||||
this.$emit("close");
|
||||
},
|
||||
codeChanged(code) {
|
||||
this.code = code;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.container {
|
||||
width: 80%;
|
||||
max-width: 800px;
|
||||
height: 600px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2px 8px 0 rgba(31, 35, 41, 0.2);
|
||||
transition: 0.5s ease;
|
||||
background: #2d2d2d;
|
||||
|
||||
.header {
|
||||
position: relative;
|
||||
background-color: white;
|
||||
|
||||
.close-btn {
|
||||
position: absolute;
|
||||
right: 12px;
|
||||
top: 13px;
|
||||
font-size: 24px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.icon-full-screen {
|
||||
position: absolute;
|
||||
right: 45px;
|
||||
top: 13px;
|
||||
font-size: 20px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.tools {
|
||||
line-height: 50px;
|
||||
padding-left: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.footer {
|
||||
background-color: #3c3c3c;
|
||||
padding-right: 20px;
|
||||
line-height: 50px;
|
||||
|
||||
.code-num {
|
||||
float: left;
|
||||
color: white;
|
||||
padding-left: 10px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.code-warning {
|
||||
color: red;
|
||||
}
|
||||
|
||||
.buttom-group {
|
||||
float: right;
|
||||
height: 100%;
|
||||
line-height: 50px;
|
||||
text-align: right;
|
||||
|
||||
button {
|
||||
border-radius: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.full-screen {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
/deep/ .el-input__inner {
|
||||
border-radius: 0;
|
||||
width: 130px;
|
||||
}
|
||||
|
||||
/deep/ pre {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
/deep/ .prism-editor-wrapper pre::-webkit-scrollbar {
|
||||
background-color: #272822;
|
||||
}
|
||||
|
||||
/deep/ .prism-editor-wrapper pre::-webkit-scrollbar-thumb {
|
||||
background-color: #41413f;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/deep/ .prism-editor-wrapper::-webkit-scrollbar {
|
||||
background-color: #272822;
|
||||
}
|
||||
|
||||
/deep/ .prism-editor-wrapper::-webkit-scrollbar-thumb {
|
||||
background-color: rgb(114, 112, 112);
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
139
im/src/components/chat/TalkForwardRecord.vue
Normal file
139
im/src/components/chat/TalkForwardRecord.vue
Normal file
@@ -0,0 +1,139 @@
|
||||
<template>
|
||||
<div class="lum-dialog-mask" v-show="isShow">
|
||||
<el-container class="lum-dialog-box" v-outside="close">
|
||||
<el-header class="no-padding header" height="60px">
|
||||
<p>会话记录 ({{ records.length }})</p>
|
||||
<p class="tools">
|
||||
<i class="el-icon-close" @click="close" />
|
||||
</p>
|
||||
</el-header>
|
||||
<el-main class="no-padding main" v-loading="loading">
|
||||
<el-scrollbar class="full-height" tag="section" :native="false">
|
||||
<div v-for="record in records" :key="record.id" class="message-group">
|
||||
<div class="left-box">
|
||||
<el-avatar
|
||||
fit="contain"
|
||||
shape="square"
|
||||
:size="30"
|
||||
:src="record.avatar"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="right-box">
|
||||
<div class="msg-header">
|
||||
<span class="name">
|
||||
{{
|
||||
record.nickname_remarks
|
||||
? record.nickname_remarks
|
||||
: record.nickname
|
||||
}}
|
||||
</span>
|
||||
<el-divider direction="vertical" />
|
||||
<span class="time">{{ record.created_at }}</span>
|
||||
</div>
|
||||
|
||||
<!-- 文本消息 -->
|
||||
<text-message
|
||||
v-if="record.msg_type == 1"
|
||||
:content="record.content"
|
||||
/>
|
||||
|
||||
<!-- 文件 - 图片消息 -->
|
||||
<image-message
|
||||
v-else-if="record.msg_type == 2 && record.file.file_type == 1"
|
||||
:src="record.file.file_url"
|
||||
/>
|
||||
|
||||
<!-- 文件 - 音频消息 -->
|
||||
<audio-message
|
||||
v-else-if="record.msg_type == 2 && record.file.file_type == 2"
|
||||
:src="record.file.file_url"
|
||||
/>
|
||||
|
||||
<!-- 文件 - 视频消息 -->
|
||||
<video-message
|
||||
v-else-if="record.msg_type == 2 && record.file.file_type == 3"
|
||||
/>
|
||||
|
||||
<!-- 文件 - 其它格式文件 -->
|
||||
<file-message
|
||||
v-else-if="record.msg_type == 2 && record.file.file_type == 4"
|
||||
:file="record.file"
|
||||
:record_id="record.id"
|
||||
/>
|
||||
|
||||
<!-- 代码块消息 -->
|
||||
<code-message
|
||||
v-else-if="record.msg_type == 4"
|
||||
:code="record.code_block.code"
|
||||
:lang="record.code_block.code_lang"
|
||||
/>
|
||||
|
||||
<div v-else class="other-message">未知消息类型</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
</el-main>
|
||||
</el-container>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { ServeGetForwardRecords } from '@/api/chat'
|
||||
|
||||
export default {
|
||||
name: 'TalkForwardRecord',
|
||||
data() {
|
||||
return {
|
||||
record_id: 0,
|
||||
records: [],
|
||||
loading: false,
|
||||
isShow: false,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
open(record_id) {
|
||||
if (record_id !== this.record_id) {
|
||||
this.record_id = record_id
|
||||
this.records = []
|
||||
this.loadRecords()
|
||||
}
|
||||
|
||||
this.isShow = true
|
||||
},
|
||||
close() {
|
||||
this.isShow = false
|
||||
},
|
||||
loadRecords() {
|
||||
this.loading = true
|
||||
ServeGetForwardRecords({
|
||||
record_id: this.record_id,
|
||||
})
|
||||
.then(res => {
|
||||
if (res.code == 200) {
|
||||
this.records = res.data.rows
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.lum-dialog-mask {
|
||||
z-index: 99999;
|
||||
}
|
||||
|
||||
.lum-dialog-box {
|
||||
width: 500px;
|
||||
max-width: 500px;
|
||||
height: 600px;
|
||||
}
|
||||
|
||||
/deep/.el-scrollbar__wrap {
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
@import '~@/assets/css/talk/talk-records.less';
|
||||
</style>
|
||||
602
im/src/components/chat/TalkSearchRecord.vue
Normal file
602
im/src/components/chat/TalkSearchRecord.vue
Normal file
@@ -0,0 +1,602 @@
|
||||
<template>
|
||||
<div class="lum-dialog-mask">
|
||||
<el-container class="lum-dialog-box" :class="{ 'full-screen': fullscreen }">
|
||||
<el-header height="60px" class="header">
|
||||
<p>消息管理器</p>
|
||||
<p class="title">
|
||||
<span>{{ query.talk_type == 1 ? "好友" : "群" }}【{{ title }}】</span>
|
||||
</p>
|
||||
<p class="tools">
|
||||
<i
|
||||
class="iconfont"
|
||||
style="transform: scale(0.85)"
|
||||
:class="fullscreen ? 'icon-tuichuquanping' : 'icon-quanping'"
|
||||
@click="fullscreen = !fullscreen"
|
||||
/>
|
||||
<i class="el-icon-close" @click="$emit('close')" />
|
||||
</p>
|
||||
</el-header>
|
||||
|
||||
<el-header height="38px" class="sub-header">
|
||||
<i
|
||||
class="iconfont pointer"
|
||||
:class="{ 'icon-shouqi2': broadside, 'icon-zhankai': !broadside }"
|
||||
@click="triggerBroadside"
|
||||
/>
|
||||
<div class="search-box no-select">
|
||||
<i class="el-icon-search" />
|
||||
<input
|
||||
v-model="search.keyword"
|
||||
type="text"
|
||||
maxlength="30"
|
||||
placeholder="关键字搜索"
|
||||
@keyup.enter="searchText($event)"
|
||||
/>
|
||||
</div>
|
||||
</el-header>
|
||||
|
||||
<el-container class="full-height ov-hidden">
|
||||
<el-aside width="200px" class="broadside" v-show="broadside">
|
||||
<el-container class="full-height">
|
||||
<el-header height="40px" class="aside-header">
|
||||
<div
|
||||
class="item"
|
||||
:class="{ selected: contacts.show == 'friends' }"
|
||||
@click="contacts.show = 'friends'"
|
||||
>
|
||||
我的好友({{ contacts.friends.length }})
|
||||
</div>
|
||||
<div class="item-shuxian">|</div>
|
||||
<div
|
||||
class="item"
|
||||
:class="{ selected: contacts.show == 'groups' }"
|
||||
@click="contacts.show = 'groups'"
|
||||
>
|
||||
我的群组({{ contacts.groups.length }})
|
||||
</div>
|
||||
</el-header>
|
||||
<el-main class="no-padding">
|
||||
<el-scrollbar class="full-height" tag="section" :native="false">
|
||||
<div
|
||||
v-for="item in contacts[contacts.show]"
|
||||
class="contacts-item pointer"
|
||||
:class="{
|
||||
selected:
|
||||
query.talk_type == item.type &&
|
||||
query.receiver_id == item.id,
|
||||
}"
|
||||
:key="item.id"
|
||||
@click="triggerMenuItem(item)"
|
||||
>
|
||||
<div class="avatar">
|
||||
<el-avatar :size="20" :src="item.avatar">
|
||||
<img src="~@/assets/image/detault-avatar.jpg" />
|
||||
</el-avatar>
|
||||
</div>
|
||||
<div class="content" v-text="item.name"></div>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
</el-main>
|
||||
</el-container>
|
||||
</el-aside>
|
||||
|
||||
<!-- 聊天记录阅览 -->
|
||||
<el-main v-show="showBox == 0" class="no-padding">
|
||||
<el-container class="full-height">
|
||||
<el-header height="40px" class="type-items">
|
||||
<span
|
||||
v-for="tab in tabType"
|
||||
:class="{ active: query.msg_type == tab.type }"
|
||||
@click="triggerLoadType(tab.type)"
|
||||
>{{ tab.name }}
|
||||
</span>
|
||||
</el-header>
|
||||
|
||||
<el-main
|
||||
v-if="records.isEmpty"
|
||||
class="history-record animated fadeIn"
|
||||
>
|
||||
<div class="empty-records">
|
||||
<img src="~@/assets/image/chat-search-no-message.png" />
|
||||
<p>暂无聊天记录</p>
|
||||
</div>
|
||||
</el-main>
|
||||
|
||||
<el-main v-else class="history-record">
|
||||
<el-scrollbar class="full-height" tag="section" :native="false">
|
||||
<div
|
||||
v-for="record in records.items"
|
||||
:key="record.id"
|
||||
class="message-group"
|
||||
>
|
||||
<div class="left-box">
|
||||
<el-avatar
|
||||
shape="square"
|
||||
fit="contain"
|
||||
:size="30"
|
||||
:src="record.avatar"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="right-box">
|
||||
<div class="msg-header">
|
||||
<span class="name">
|
||||
{{
|
||||
record.nickname_remarks
|
||||
? record.nickname_remarks
|
||||
: record.nickname
|
||||
}}
|
||||
</span>
|
||||
<el-divider direction="vertical" />
|
||||
<span class="time">{{ record.created_at }}</span>
|
||||
</div>
|
||||
|
||||
<!-- 文本消息 -->
|
||||
<text-message
|
||||
v-if="record.msg_type == 1"
|
||||
:content="record.content"
|
||||
/>
|
||||
|
||||
<!-- 文件 - 图片消息 -->
|
||||
<image-message
|
||||
v-else-if="
|
||||
record.msg_type == 2 && record.file.file_type == 1
|
||||
"
|
||||
:src="record.file.file_url"
|
||||
/>
|
||||
|
||||
<!-- 文件 - 音频消息 -->
|
||||
<audio-message
|
||||
v-else-if="
|
||||
record.msg_type == 2 && record.file.file_type == 2
|
||||
"
|
||||
:src="record.file.file_url"
|
||||
/>
|
||||
|
||||
<!-- 文件 - 视频消息 -->
|
||||
<video-message
|
||||
v-else-if="
|
||||
record.msg_type == 2 && record.file.file_type == 3
|
||||
"
|
||||
/>
|
||||
|
||||
<!-- 文件 - 其它格式文件 -->
|
||||
<file-message
|
||||
v-else-if="
|
||||
record.msg_type == 2 && record.file.file_type == 4
|
||||
"
|
||||
:file="record.file"
|
||||
:record_id="record.id"
|
||||
/>
|
||||
|
||||
<!-- 会话记录消息 -->
|
||||
<forward-message
|
||||
v-else-if="record.msg_type == 3"
|
||||
:forward="record.forward"
|
||||
:record_id="record.id"
|
||||
/>
|
||||
|
||||
<!-- 代码块消息 -->
|
||||
<code-message
|
||||
v-else-if="record.msg_type == 4"
|
||||
:code="record.code_block.code"
|
||||
:lang="record.code_block.code_lang"
|
||||
/>
|
||||
|
||||
<!-- 投票消息 -->
|
||||
<vote-message
|
||||
v-else-if="record.msg_type == 5"
|
||||
:record_id="record.id"
|
||||
:vote="record.vote"
|
||||
/>
|
||||
|
||||
<div v-else class="other-message">未知消息类型</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 数据加载栏 -->
|
||||
<div v-show="records.loadStatus == 1" class="load-button blue">
|
||||
<i class="el-icon-loading" />
|
||||
<span>加载数据中...</span>
|
||||
</div>
|
||||
<div v-show="records.loadStatus == 0" class="load-button">
|
||||
<i class="el-icon-arrow-down" />
|
||||
<span @click="loadChatRecord">加载更多...</span>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
</el-main>
|
||||
</el-container>
|
||||
</el-main>
|
||||
</el-container>
|
||||
</el-container>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { ServeGetContacts } from "@/api/contacts";
|
||||
import { ServeFindTalkRecords } from "@/api/chat";
|
||||
import { formatSize as renderSize, download, imgZoom } from "@/utils/functions";
|
||||
|
||||
export default {
|
||||
name: "TalkSearchRecord",
|
||||
props: {
|
||||
params: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {
|
||||
talk_type: 0,
|
||||
receiver_id: 0,
|
||||
title: "",
|
||||
};
|
||||
},
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
fullscreen: false,
|
||||
|
||||
user_id: this.$store.state.user.id,
|
||||
title: "",
|
||||
|
||||
// 侧边栏相关信息
|
||||
broadside: false,
|
||||
contacts: {
|
||||
show: "friends",
|
||||
friends: [],
|
||||
groups: [],
|
||||
},
|
||||
|
||||
query: {
|
||||
talk_type: 0,
|
||||
receiver_id: 0,
|
||||
msg_type: 0,
|
||||
},
|
||||
|
||||
// 用户聊天记录
|
||||
records: {
|
||||
record_id: 0,
|
||||
items: [],
|
||||
isEmpty: false,
|
||||
loadStatus: 0,
|
||||
},
|
||||
|
||||
showBox: 0,
|
||||
|
||||
tabType: [
|
||||
{ name: "全部", type: 0 },
|
||||
{ name: "文件", type: 2 },
|
||||
{ name: "会话记录", type: 3 },
|
||||
{ name: "代码块", type: 4 },
|
||||
{ name: "群投票", type: 5 },
|
||||
],
|
||||
|
||||
search: {
|
||||
keyword: "", // 关键字查询
|
||||
date: "", // 时间查询
|
||||
page: 1, // 当前分页
|
||||
totalPage: 50, // 总分页
|
||||
items: [], // 数据列表
|
||||
isShowDate: false,
|
||||
},
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.title = this.params.title;
|
||||
this.query = {
|
||||
talk_type: this.params.talk_type,
|
||||
receiver_id: this.params.receiver_id,
|
||||
msg_type: 0,
|
||||
};
|
||||
|
||||
this.loadChatRecord(0);
|
||||
},
|
||||
created() {
|
||||
this.loadFriends();
|
||||
},
|
||||
methods: {
|
||||
download,
|
||||
renderSize,
|
||||
|
||||
// 获取图片信息
|
||||
getImgStyle(url) {
|
||||
return imgZoom(url, 200);
|
||||
},
|
||||
|
||||
// 获取会话记录消息名称
|
||||
getForwardTitle(item) {
|
||||
let arr = [...new Set(item.map((v) => v.nickname))];
|
||||
return arr.join("、") + "的会话记录";
|
||||
},
|
||||
|
||||
// 获取好友列表
|
||||
loadFriends() {
|
||||
ServeGetContacts().then(({ code, data }) => {
|
||||
if (code == 200) {
|
||||
this.contacts.friends = data.map((item) => {
|
||||
return {
|
||||
id: item.id,
|
||||
type: 1,
|
||||
avatar: item.avatar,
|
||||
name: item.friend_remark ? item.friend_remark : item.nickname,
|
||||
};
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// 左侧联系人菜单点击事件
|
||||
triggerMenuItem(item) {
|
||||
this.title = item.name;
|
||||
this.query.talk_type = item.type;
|
||||
this.query.receiver_id = item.id;
|
||||
this.showBox = 0;
|
||||
|
||||
this.triggerLoadType(0);
|
||||
},
|
||||
|
||||
// 加载历史记录
|
||||
loadChatRecord() {
|
||||
let data = {
|
||||
talk_type: this.query.talk_type,
|
||||
receiver_id: this.query.receiver_id,
|
||||
record_id: this.records.record_id,
|
||||
msg_type: this.query.msg_type,
|
||||
};
|
||||
|
||||
if (this.records.loadStatus == 1) return;
|
||||
|
||||
this.records.loadStatus = 1;
|
||||
ServeFindTalkRecords(data)
|
||||
.then((res) => {
|
||||
if (res.code != 200) return;
|
||||
|
||||
let records = data.record_id == 0 ? [] : this.records.items;
|
||||
|
||||
records.push(...res.data.rows);
|
||||
|
||||
this.records.items = records;
|
||||
this.records.loadStatus =
|
||||
res.data.rows.length < res.data.limit ? 2 : 0;
|
||||
|
||||
if (this.records.items.length == 0) {
|
||||
this.records.isEmpty = true;
|
||||
} else {
|
||||
this.records.record_id =
|
||||
this.records.items[this.records.items.length - 1].id;
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
this.records.loadStatus = 0;
|
||||
});
|
||||
},
|
||||
|
||||
triggerLoadType(type) {
|
||||
this.records.record_id = 0;
|
||||
this.query.msg_type = type;
|
||||
this.records.isEmpty = false;
|
||||
this.records.items = [];
|
||||
|
||||
this.loadChatRecord();
|
||||
},
|
||||
|
||||
searchText() {
|
||||
if (this.search.keyword == "") {
|
||||
this.showBox = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
this.$notify.info({
|
||||
title: "消息",
|
||||
message: "查询功能正在开发中...",
|
||||
});
|
||||
},
|
||||
|
||||
triggerBroadside() {
|
||||
this.broadside = !this.broadside;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
/deep/.el-scrollbar__wrap {
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.lum-dialog-mask {
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.lum-dialog-box {
|
||||
width: 100%;
|
||||
height: 600px;
|
||||
max-width: 800px;
|
||||
transition: 1s ease;
|
||||
|
||||
&.full-screen {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
max-width: unset;
|
||||
margin: 0;
|
||||
border-radius: 0px;
|
||||
}
|
||||
|
||||
.sub-header {
|
||||
height: 38px;
|
||||
line-height: 38px;
|
||||
font-size: 12px;
|
||||
border-bottom: 1px solid #f9f4f4;
|
||||
margin-top: 10px;
|
||||
padding: 0 10px;
|
||||
position: relative;
|
||||
|
||||
i {
|
||||
font-size: 22px;
|
||||
color: #6f6a6a;
|
||||
}
|
||||
|
||||
.search-box {
|
||||
position: absolute;
|
||||
width: 230px;
|
||||
height: 32px;
|
||||
top: 2px;
|
||||
right: 10px;
|
||||
background: #f9f4f4;
|
||||
border-radius: 5px;
|
||||
|
||||
i {
|
||||
position: absolute;
|
||||
left: 10px;
|
||||
top: 8px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
input {
|
||||
position: absolute;
|
||||
left: 35px;
|
||||
top: 3px;
|
||||
height: 25px;
|
||||
width: 184px;
|
||||
color: #7d7171;
|
||||
background: #f9f4f4;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.broadside {
|
||||
@border: 1px solid #f9f9f9;
|
||||
border-right: @border;
|
||||
user-select: none;
|
||||
transition: 3s ease;
|
||||
|
||||
.aside-header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
height: 100%;
|
||||
border-bottom: @border;
|
||||
padding: 0;
|
||||
|
||||
> div {
|
||||
text-align: center;
|
||||
line-height: 40px;
|
||||
font-size: 13px;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.item {
|
||||
flex: 1;
|
||||
cursor: pointer;
|
||||
|
||||
&.selected {
|
||||
color: #66b1ff;
|
||||
}
|
||||
}
|
||||
|
||||
.item-shuxian {
|
||||
flex-basis: 1px;
|
||||
flex-shrink: 0;
|
||||
color: rgb(232 224 224);
|
||||
}
|
||||
}
|
||||
|
||||
.contacts-item {
|
||||
height: 35px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding-left: 10px;
|
||||
position: relative;
|
||||
|
||||
.avatar {
|
||||
flex-basis: 40px;
|
||||
flex-shrink: 0;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.content {
|
||||
flex: 1 1;
|
||||
height: 100%;
|
||||
line-height: 35px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
font-size: 13px;
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&.selected {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* first box */
|
||||
.type-items {
|
||||
padding: 0 0 0 10px;
|
||||
line-height: 40px;
|
||||
user-select: none;
|
||||
border-bottom: 1px solid #f9f4f4;
|
||||
|
||||
.active {
|
||||
color: #03a9f4;
|
||||
font-weight: 500;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
span {
|
||||
height: 40px;
|
||||
width: 45px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
margin: 0 10px;
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
}
|
||||
}
|
||||
|
||||
.history-record {
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
.load-button {
|
||||
width: 100%;
|
||||
height: 35px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: row;
|
||||
|
||||
&.blue {
|
||||
color: #51b2ff;
|
||||
}
|
||||
|
||||
span {
|
||||
margin-left: 5px;
|
||||
font-size: 13px;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
}
|
||||
|
||||
.empty-records {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
color: #cccccc;
|
||||
font-weight: 300;
|
||||
font-size: 14px;
|
||||
|
||||
img {
|
||||
width: 100px;
|
||||
}
|
||||
}
|
||||
|
||||
@import "~@/assets/css/talk/talk-records.less";
|
||||
</style>
|
||||
193
im/src/components/chat/messaege/AudioMessage.vue
Normal file
193
im/src/components/chat/messaege/AudioMessage.vue
Normal file
@@ -0,0 +1,193 @@
|
||||
<template>
|
||||
<div class="audio-message">
|
||||
<div class="videodisc">
|
||||
<div class="disc" :class="{ play: isPlay }" @click="toPlay">
|
||||
<i v-if="loading" class="el-icon-loading" />
|
||||
<i v-else-if="isPlay" class="el-icon-video-pause" />
|
||||
<i v-else class="el-icon-video-play" />
|
||||
<audio
|
||||
ref="audio"
|
||||
type="audio/mp3"
|
||||
:src="src"
|
||||
@timeupdate="timeupdate"
|
||||
@ended="ended"
|
||||
@canplay="canplay"
|
||||
></audio>
|
||||
</div>
|
||||
</div>
|
||||
<div class="detail">
|
||||
<div class="text">
|
||||
<i class="el-icon-service" />
|
||||
<span>{{ getCurrDuration }} / {{ getTotalDuration }}</span>
|
||||
</div>
|
||||
<div class="process">
|
||||
<el-progress :percentage="progress" :show-text="false" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
function formatSeconds(value) {
|
||||
var theTime = parseInt(value) // 秒
|
||||
var theTime1 = 0 // 分
|
||||
var theTime2 = 0 // 小时
|
||||
if (theTime > 60) {
|
||||
theTime1 = parseInt(theTime / 60)
|
||||
theTime = parseInt(theTime % 60)
|
||||
if (theTime1 > 60) {
|
||||
theTime2 = parseInt(theTime1 / 60)
|
||||
theTime1 = parseInt(theTime1 % 60)
|
||||
}
|
||||
}
|
||||
|
||||
var result = '' + parseInt(theTime) //秒
|
||||
if (10 > theTime > 0) {
|
||||
result = '0' + parseInt(theTime) //秒
|
||||
} else {
|
||||
result = '' + parseInt(theTime) //秒
|
||||
}
|
||||
|
||||
if (10 > theTime1 > 0) {
|
||||
result = '0' + parseInt(theTime1) + ':' + result //分,不足两位数,首位补充0,
|
||||
} else {
|
||||
result = '' + parseInt(theTime1) + ':' + result //分
|
||||
}
|
||||
if (theTime2 > 0) {
|
||||
result = '' + parseInt(theTime2) + ':' + result //时
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'AudioMessage',
|
||||
props: {
|
||||
src: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: true,
|
||||
isPlay: false,
|
||||
duration: 0,
|
||||
currentTime: 0,
|
||||
progress: 0,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
getTotalDuration() {
|
||||
return formatSeconds(this.duration)
|
||||
},
|
||||
getCurrDuration() {
|
||||
return formatSeconds(this.currentTime)
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
toPlay() {
|
||||
if (this.loading) {
|
||||
return
|
||||
}
|
||||
|
||||
let audio = this.$refs.audio
|
||||
if (this.isPlay) {
|
||||
audio.pause()
|
||||
} else {
|
||||
audio.play()
|
||||
}
|
||||
this.isPlay = !this.isPlay
|
||||
},
|
||||
|
||||
// 当目前的播放位置已更改时
|
||||
timeupdate() {
|
||||
let audio = this.$refs.audio
|
||||
this.currentTime = audio.currentTime
|
||||
this.progress = (audio.currentTime / audio.duration) * 100
|
||||
},
|
||||
|
||||
// 当浏览器可以播放音频/视频时
|
||||
canplay() {
|
||||
this.duration = this.$refs.audio.duration
|
||||
this.loading = false
|
||||
},
|
||||
|
||||
// 当目前的播放列表已结束时
|
||||
ended() {
|
||||
this.isPlay = false
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style scoped lang="less">
|
||||
.audio-message {
|
||||
width: 200px;
|
||||
height: 60px;
|
||||
border-radius: 5px;
|
||||
background: #ffffff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border: 1px solid #03a9f4;
|
||||
overflow: hidden;
|
||||
|
||||
> div {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.videodisc {
|
||||
flex-basis: 60px;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
.disc {
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
background: #e9e5e5;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
transition: ease 0.5;
|
||||
|
||||
&.play {
|
||||
background: #ff5722;
|
||||
box-shadow: 0 0 4px 0px #f76a3e;
|
||||
}
|
||||
|
||||
i {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
&:active i {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.detail {
|
||||
flex: 1 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding-top: 10px;
|
||||
|
||||
.text {
|
||||
width: 90%;
|
||||
font-size: 12px;
|
||||
i {
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.process {
|
||||
padding-top: 10px;
|
||||
height: 20px;
|
||||
width: 90%;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
148
im/src/components/chat/messaege/CodeMessage.vue
Normal file
148
im/src/components/chat/messaege/CodeMessage.vue
Normal file
@@ -0,0 +1,148 @@
|
||||
<template>
|
||||
<div
|
||||
class="code-message"
|
||||
:class="{
|
||||
'max-height': lineNumber > 6,
|
||||
'max-width': maxwidth,
|
||||
'full-screen': fullscreen,
|
||||
}"
|
||||
>
|
||||
<i
|
||||
:class="
|
||||
fullscreen ? 'el-icon-close' : 'iconfont icon-tubiao_chakangongyi'
|
||||
"
|
||||
@click="fullscreen = !fullscreen"
|
||||
/>
|
||||
<pre class="lum-scrollbar" v-html="formatCode(code, lang)" />
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import Prism from 'prismjs'
|
||||
import 'prismjs/themes/prism-okaidia.css'
|
||||
|
||||
export default {
|
||||
name: 'CodeMessage',
|
||||
props: {
|
||||
code: {
|
||||
type: [String, Number],
|
||||
default: '',
|
||||
},
|
||||
lang: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
maxwidth: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
fullscreen: false,
|
||||
lineNumber: 0,
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.lineNumber = this.code.split(/\n/).length
|
||||
},
|
||||
methods: {
|
||||
formatCode(code, lang) {
|
||||
try {
|
||||
return Prism.highlight(code, Prism.languages[lang], lang) + '<br/>'
|
||||
} catch (error) {
|
||||
return code
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.code-message {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
border-radius: 5px;
|
||||
box-sizing: border-box;
|
||||
|
||||
&.max-width {
|
||||
max-width: 500px;
|
||||
}
|
||||
|
||||
&.max-height {
|
||||
height: 208px;
|
||||
}
|
||||
|
||||
i {
|
||||
position: absolute;
|
||||
right: 0px;
|
||||
top: 0px;
|
||||
font-size: 16px;
|
||||
cursor: pointer;
|
||||
color: white;
|
||||
display: inline-block;
|
||||
opacity: 0;
|
||||
width: 50px;
|
||||
height: 30px;
|
||||
background: #171616;
|
||||
text-align: center;
|
||||
line-height: 30px;
|
||||
border-radius: 0 0 0px 8px;
|
||||
transition: 1s ease;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
i {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
pre {
|
||||
box-sizing: border-box;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
overflow: auto;
|
||||
padding: 10px;
|
||||
line-height: 24px;
|
||||
background: #272822;
|
||||
color: #d5d4d4;
|
||||
font-family: SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono',
|
||||
'Courier New', monospace;
|
||||
font-size: 85%;
|
||||
|
||||
&.lum-scrollbar {
|
||||
&::-webkit-scrollbar {
|
||||
background-color: black;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.full-screen {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
max-width: unset;
|
||||
max-height: unset;
|
||||
border-radius: 0px;
|
||||
background: #272822;
|
||||
z-index: 99999999;
|
||||
|
||||
i {
|
||||
position: fixed;
|
||||
top: 15px;
|
||||
right: 15px;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 50%;
|
||||
font-size: 24px;
|
||||
|
||||
&:active {
|
||||
box-shadow: 0 0 5px 0px #ccc;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
145
im/src/components/chat/messaege/FileMessage.vue
Normal file
145
im/src/components/chat/messaege/FileMessage.vue
Normal file
@@ -0,0 +1,145 @@
|
||||
<template>
|
||||
<div class="file-message">
|
||||
<div class="main">
|
||||
<div class="ext">{{ ext }}</div>
|
||||
<div class="file-box">
|
||||
<p class="info">
|
||||
<span class="name">{{ fileName }}</span>
|
||||
<span class="size">({{ fileSize }})</span>
|
||||
</p>
|
||||
<p class="notice">文件已成功发送, 文件助手永久保存</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer">
|
||||
<a @click="download(record_id)">下载</a>
|
||||
<a>在线预览</a>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { formatSize, download } from '@/utils/functions'
|
||||
export default {
|
||||
name: 'FileMessage',
|
||||
props: {
|
||||
file: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
record_id: {
|
||||
type: Number,
|
||||
required: true,
|
||||
default: 0,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
file_id: 0,
|
||||
ext: '',
|
||||
fileName: '',
|
||||
fileSize: '',
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.file_id = this.file.id
|
||||
this.ext = this.file.file_suffix.toUpperCase()
|
||||
this.fileName = this.file.original_name
|
||||
this.fileSize = formatSize(this.file.file_size)
|
||||
},
|
||||
methods: {
|
||||
download,
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.file-message {
|
||||
width: 250px;
|
||||
height: 85px;
|
||||
background: white;
|
||||
box-shadow: 0 0 5px 0px #e8e4e4;
|
||||
padding: 10px;
|
||||
border-radius: 3px;
|
||||
transition: all 0.5s;
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0 0 5px 0px #cac6c6;
|
||||
}
|
||||
|
||||
.main {
|
||||
height: 45px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
.ext {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 45px;
|
||||
height: 45px;
|
||||
flex-shrink: 0;
|
||||
color: #ffffff;
|
||||
background: #49a4ff;
|
||||
border-radius: 5px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.file-box {
|
||||
flex: 1 1;
|
||||
height: 45px;
|
||||
margin-left: 10px;
|
||||
overflow: hidden;
|
||||
|
||||
.info {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
height: 24px;
|
||||
color: rgb(76, 76, 76);
|
||||
font-size: 14px;
|
||||
|
||||
.name {
|
||||
flex: 1 auto;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.size {
|
||||
font-size: 12px;
|
||||
color: #cac6c6;
|
||||
}
|
||||
}
|
||||
|
||||
.notice {
|
||||
height: 25px;
|
||||
line-height: 25px;
|
||||
font-size: 12px;
|
||||
color: #929191;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.footer {
|
||||
height: 30px;
|
||||
line-height: 37px;
|
||||
color: #409eff;
|
||||
text-align: right;
|
||||
font-size: 12px;
|
||||
border-top: 1px solid #eff7ef;
|
||||
margin-top: 10px;
|
||||
|
||||
a {
|
||||
margin: 0 3px;
|
||||
user-select: none;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
color: royalblue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
116
im/src/components/chat/messaege/ForwardMessage.vue
Normal file
116
im/src/components/chat/messaege/ForwardMessage.vue
Normal file
@@ -0,0 +1,116 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="forward-message" @click="catForwardRecords">
|
||||
<div class="title">{{ title }}</div>
|
||||
<div v-for="(record, index) in records" :key="index" class="lists">
|
||||
<p>
|
||||
<span>{{ record.nickname }}:</span>
|
||||
<span>{{ record.text }}</span>
|
||||
</p>
|
||||
</div>
|
||||
<div class="footer">
|
||||
<span>转发:聊天会话记录 ({{ num }}条)</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 会话记录查看器 -->
|
||||
<talk-forward-record ref="forwardRecordsRef" />
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import TalkForwardRecord from '@/components/chat/TalkForwardRecord'
|
||||
export default {
|
||||
name: 'ForwardMessage',
|
||||
components: {
|
||||
TalkForwardRecord,
|
||||
},
|
||||
props: {
|
||||
forward: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
record_id: {
|
||||
type: Number,
|
||||
required: true,
|
||||
default: 0,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
title: '',
|
||||
records: [],
|
||||
num: 0,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
catForwardRecords() {
|
||||
this.$refs.forwardRecordsRef.open(this.record_id)
|
||||
},
|
||||
getForwardTitle(list) {
|
||||
let arr = [...new Set(list.map(v => v.nickname))]
|
||||
return arr.join('、') + '的会话记录'
|
||||
},
|
||||
},
|
||||
created() {
|
||||
let forward = this.forward
|
||||
|
||||
this.num = forward.num
|
||||
this.records = forward.list
|
||||
this.title = this.getForwardTitle(this.records)
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
/* 会话记录消息 */
|
||||
.forward-message {
|
||||
width: 250px;
|
||||
min-height: 95px;
|
||||
max-height: 150px;
|
||||
border-radius: 3px;
|
||||
background-color: white;
|
||||
padding: 3px 10px;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 0 5px 0px #e8e4e4;
|
||||
text-align: left;
|
||||
user-select: none;
|
||||
|
||||
.title {
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
font-size: 14px;
|
||||
color: #565353;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.lists p {
|
||||
height: 18px;
|
||||
line-height: 18px;
|
||||
font-size: 10px;
|
||||
color: #aaa9a9;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
font-weight: 100;
|
||||
}
|
||||
|
||||
.footer {
|
||||
height: 32px;
|
||||
line-height: 35px;
|
||||
color: #858282;
|
||||
border-top: 1px solid #f1ebeb;
|
||||
font-size: 12px;
|
||||
margin-top: 12px;
|
||||
font-weight: 300;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0 0 5px 0px #cac6c6;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
150
im/src/components/chat/messaege/FriendApplyMessage.vue
Normal file
150
im/src/components/chat/messaege/FriendApplyMessage.vue
Normal file
@@ -0,0 +1,150 @@
|
||||
<template>
|
||||
<!-- 好友申请消息 -->
|
||||
<div class="apply-card">
|
||||
<div class="card-header">
|
||||
<img class="avatar" :src="avatar" />
|
||||
<div class="nickname">No. {{ nickname }}</div>
|
||||
<div class="datetime">{{ datetime }}</div>
|
||||
<div class="remarks">
|
||||
<span>备注信息:{{ remarks }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-footer">
|
||||
<div class="mini-button" @click="handle(1)">同意</div>
|
||||
<el-divider direction="vertical"></el-divider>
|
||||
<div class="mini-button" @click="handle(2)">拒绝</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'FriendApplyMessage',
|
||||
props: {
|
||||
data: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
},
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
avatar:
|
||||
'http://im-img.gzydong.club/media/images/avatar/20210602/60b6f03598ed0104301.png',
|
||||
nickname: '独特态度',
|
||||
datetime: '05/09 12:13 分',
|
||||
remarks: '编辑个签,展示我的独特态度 展示我的独特态度。',
|
||||
apply_id: 0,
|
||||
}
|
||||
},
|
||||
created() {},
|
||||
methods: {
|
||||
handle(type) {
|
||||
alert(type)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.apply-card {
|
||||
position: relative;
|
||||
width: 170px;
|
||||
min-height: 180px;
|
||||
border-radius: 15px;
|
||||
overflow: hidden;
|
||||
transition: all 0.5s;
|
||||
box-sizing: border-box;
|
||||
background-image: linear-gradient(-84deg, #1ab6ff 0, #1ab6ff 0, #82c1f3 100%);
|
||||
// #028fff
|
||||
|
||||
&:hover {
|
||||
transform: scale(1.02);
|
||||
}
|
||||
|
||||
.card-header {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 135px;
|
||||
|
||||
.avatar {
|
||||
position: absolute;
|
||||
top: 18px;
|
||||
left: 8px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
background: white;
|
||||
}
|
||||
|
||||
.nickname {
|
||||
position: absolute;
|
||||
top: 15px;
|
||||
right: 8px;
|
||||
width: 90px;
|
||||
height: 25px;
|
||||
font-size: 10px;
|
||||
text-align: center;
|
||||
line-height: 25px;
|
||||
color: white;
|
||||
border-bottom: 1px dashed white;
|
||||
}
|
||||
|
||||
.datetime {
|
||||
position: absolute;
|
||||
top: 42px;
|
||||
right: 11.5px;
|
||||
color: white;
|
||||
font-size: 10px;
|
||||
transform: scale(0.9);
|
||||
}
|
||||
|
||||
.remarks {
|
||||
position: absolute;
|
||||
bottom: 5px;
|
||||
color: white;
|
||||
font-size: 10px;
|
||||
padding: 3px 5px;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 3;
|
||||
overflow: hidden;
|
||||
transform: scale(0.95);
|
||||
}
|
||||
}
|
||||
|
||||
.card-footer {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
border-top: 1px solid white;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-sizing: border-box;
|
||||
|
||||
/deep/.el-divider {
|
||||
background: white;
|
||||
}
|
||||
|
||||
.mini-button {
|
||||
display: flex;
|
||||
width: 50px;
|
||||
height: 25px;
|
||||
margin: 0 10px;
|
||||
text-align: center;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 13px;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
51
im/src/components/chat/messaege/ImageMessage.vue
Normal file
51
im/src/components/chat/messaege/ImageMessage.vue
Normal file
@@ -0,0 +1,51 @@
|
||||
<template>
|
||||
<div class="image-message no-select">
|
||||
<el-image
|
||||
fit="cover"
|
||||
:src="src"
|
||||
:lazy="true"
|
||||
:style="getImgStyle(src)"
|
||||
:preview-src-list="[src]"
|
||||
>
|
||||
<div slot="error" class="image-slot">图片加载失败...</div>
|
||||
<div slot="placeholder" class="image-slot">图片加载中...</div>
|
||||
</el-image>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { imgZoom } from '@/utils/functions'
|
||||
export default {
|
||||
name: 'ImageMessage',
|
||||
props: {
|
||||
src: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
getImgStyle(url) {
|
||||
return imgZoom(url, 200)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.image-message {
|
||||
/deep/.el-image {
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
background: #f1efef;
|
||||
|
||||
.image-slot {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
font-size: 13px;
|
||||
color: #908686;
|
||||
background: #efeaea;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
74
im/src/components/chat/messaege/InviteMessage.vue
Normal file
74
im/src/components/chat/messaege/InviteMessage.vue
Normal file
@@ -0,0 +1,74 @@
|
||||
<template>
|
||||
<div class="invite-message">
|
||||
<div v-if="invite.type == 1 || invite.type == 3" class="system-msg">
|
||||
<a @click="toUser(invite.operate_user.id)">
|
||||
{{ invite.operate_user.nickname }}
|
||||
</a>
|
||||
<span>{{ invite.type == 1 ? '邀请了' : '将' }}</span>
|
||||
<template v-for="(user, uidx) in invite.users">
|
||||
<a @click="toUser(user.id)">{{ user.nickname }}</a>
|
||||
<em v-show="uidx < invite.users.length - 1">、</em>
|
||||
</template>
|
||||
<span>{{ invite.type == 1 ? '加入了群聊' : '踢出了群聊' }}</span>
|
||||
</div>
|
||||
|
||||
<div v-else-if="invite.type == 2" class="system-msg">
|
||||
<a @click="toUser(invite.operate_user.id)">
|
||||
{{ invite.operate_user.nickname }}
|
||||
</a>
|
||||
<span>退出了群聊</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name: 'InviteMessage',
|
||||
props: {
|
||||
invite: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
toUser(user_id) {
|
||||
this.$emit('cat', user_id)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.invite-message {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.system-msg {
|
||||
margin: 10px auto;
|
||||
background-color: #f5f5f5;
|
||||
font-size: 11px;
|
||||
line-height: 30px;
|
||||
padding: 0 8px;
|
||||
word-break: break-all;
|
||||
word-wrap: break-word;
|
||||
color: #979191;
|
||||
user-select: none;
|
||||
font-weight: 300;
|
||||
display: inline-block;
|
||||
border-radius: 3px;
|
||||
|
||||
span {
|
||||
margin: 0 5px;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #939596;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
|
||||
&:hover {
|
||||
color: black;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
101
im/src/components/chat/messaege/LoginMessage.vue
Normal file
101
im/src/components/chat/messaege/LoginMessage.vue
Normal file
@@ -0,0 +1,101 @@
|
||||
<template>
|
||||
<!-- 登录消息 -->
|
||||
<div class="login-message">
|
||||
<h4>登录操作通知</h4>
|
||||
<p>登录时间:{{ datetime }} (CST)</p>
|
||||
<p>IP地址:{{ ip }}</p>
|
||||
<p>登录地点:{{ address }}</p>
|
||||
<p>登录设备:{{ platform }}</p>
|
||||
<p>异常原因:{{ reason }}</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { parseTime } from '@/utils/functions'
|
||||
|
||||
export default {
|
||||
name: 'LoginMessage',
|
||||
props: {
|
||||
detail: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
datetime: '',
|
||||
ip: '',
|
||||
address: '',
|
||||
platform: '',
|
||||
reason: '常用设备登录',
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.ip = this.detail.ip
|
||||
this.datetime = parseTime(
|
||||
this.detail.created_at,
|
||||
'{y}年{m}月{d}日 {h}:{i}:{s}'
|
||||
)
|
||||
this.address = this.detail.address
|
||||
this.reason = this.detail.reason
|
||||
|
||||
this.platform =
|
||||
this.getExploreName(this.detail.agent) +
|
||||
' / ' +
|
||||
this.getExploreOs(this.detail.agent)
|
||||
},
|
||||
methods: {
|
||||
getExploreName(userAgent = '') {
|
||||
if (userAgent.indexOf('Opera') > -1 || userAgent.indexOf('OPR') > -1) {
|
||||
return 'Opera'
|
||||
} else if (
|
||||
userAgent.indexOf('compatible') > -1 &&
|
||||
userAgent.indexOf('MSIE') > -1
|
||||
) {
|
||||
return 'IE'
|
||||
} else if (userAgent.indexOf('Edge') > -1) {
|
||||
return 'Edge'
|
||||
} else if (userAgent.indexOf('Firefox') > -1) {
|
||||
return 'Firefox'
|
||||
} else if (
|
||||
userAgent.indexOf('Safari') > -1 &&
|
||||
userAgent.indexOf('Chrome') == -1
|
||||
) {
|
||||
return 'Safari'
|
||||
} else if (
|
||||
userAgent.indexOf('Chrome') > -1 &&
|
||||
userAgent.indexOf('Safari') > -1
|
||||
) {
|
||||
return 'Chrome'
|
||||
} else {
|
||||
return 'Unkonwn'
|
||||
}
|
||||
},
|
||||
|
||||
getExploreOs(userAgent = '') {
|
||||
if (userAgent.indexOf('Mac OS') > -1) {
|
||||
return 'Mac OS'
|
||||
} else {
|
||||
return 'Windows'
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.login-message {
|
||||
width: 300px;
|
||||
min-height: 50px;
|
||||
background: #f7f7f7;
|
||||
border-radius: 5px;
|
||||
padding: 15px;
|
||||
|
||||
p {
|
||||
font-size: 13px;
|
||||
margin: 10px 0;
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
26
im/src/components/chat/messaege/ReplyMessage.vue
Normal file
26
im/src/components/chat/messaege/ReplyMessage.vue
Normal file
@@ -0,0 +1,26 @@
|
||||
<template>
|
||||
<div class="reply-message">这是回复的消息[预留]</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'ReplyMessage',
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
created() {},
|
||||
methods: {},
|
||||
}
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.reply-message {
|
||||
margin-top: 5px;
|
||||
min-height: 28px;
|
||||
background: #f7f1f1;
|
||||
line-height: 28px;
|
||||
font-size: 12px;
|
||||
padding: 0 10px;
|
||||
border-radius: 3px;
|
||||
color: #a7a2a2;
|
||||
}
|
||||
</style>
|
||||
66
im/src/components/chat/messaege/RevokeMessage.vue
Normal file
66
im/src/components/chat/messaege/RevokeMessage.vue
Normal file
@@ -0,0 +1,66 @@
|
||||
<template>
|
||||
<div class="revoke-message">
|
||||
<div class="content">
|
||||
<span v-if="$store.state.user.id == item.user_id">
|
||||
你撤回了一条消息 | {{ sendTime(item.created_at) }}
|
||||
</span>
|
||||
<span v-else-if="item.talk_type == 1">
|
||||
对方撤回了一条消息 | {{ sendTime(item.created_at) }}
|
||||
</span>
|
||||
<span v-else>
|
||||
"{{ item.nickname }}" 撤回了一条消息 | {{ sendTime(item.created_at) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { formatTime as sendTime } from "@/utils/functions";
|
||||
|
||||
export default {
|
||||
name: "RevokeMessage",
|
||||
props: {
|
||||
item: {
|
||||
type: Object,
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
sendTime,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.revoke-message {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
||||
.content {
|
||||
margin: 10px auto;
|
||||
background-color: #f5f5f5;
|
||||
font-size: 11px;
|
||||
line-height: 30px;
|
||||
padding: 0 8px;
|
||||
word-break: break-all;
|
||||
word-wrap: break-word;
|
||||
color: #979191;
|
||||
user-select: none;
|
||||
font-weight: 300;
|
||||
display: inline-block;
|
||||
border-radius: 3px;
|
||||
|
||||
span {
|
||||
margin: 0 5px;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #939596;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
|
||||
&:hover {
|
||||
color: black;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
39
im/src/components/chat/messaege/SystemTextMessage.vue
Normal file
39
im/src/components/chat/messaege/SystemTextMessage.vue
Normal file
@@ -0,0 +1,39 @@
|
||||
<template>
|
||||
<div class="system-text-message">
|
||||
<div class="content">{{ content }}</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { formatTime as sendTime } from '@/utils/functions'
|
||||
|
||||
export default {
|
||||
name: 'SystemTextMessage',
|
||||
props: {
|
||||
content: String,
|
||||
},
|
||||
methods: {
|
||||
sendTime,
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.system-text-message {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
||||
.content {
|
||||
margin: 10px auto;
|
||||
background-color: #f5f5f5;
|
||||
font-size: 11px;
|
||||
line-height: 30px;
|
||||
padding: 0 8px;
|
||||
word-break: break-all;
|
||||
word-wrap: break-word;
|
||||
color: #979191;
|
||||
user-select: none;
|
||||
font-weight: 300;
|
||||
display: inline-block;
|
||||
border-radius: 3px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
109
im/src/components/chat/messaege/TextMessage.vue
Normal file
109
im/src/components/chat/messaege/TextMessage.vue
Normal file
@@ -0,0 +1,109 @@
|
||||
<template>
|
||||
<div
|
||||
class="text-message"
|
||||
:class="{
|
||||
left: float == 'left',
|
||||
right: float == 'right',
|
||||
'max-width': !fullWidth,
|
||||
}"
|
||||
>
|
||||
<div v-if="arrow" class="arrow"></div>
|
||||
<pre v-html="html" />
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { textReplaceLink } from "@/utils/functions";
|
||||
import { textReplaceEmoji } from "@/utils/emojis";
|
||||
|
||||
export default {
|
||||
name: "TextMessage",
|
||||
props: {
|
||||
content: {
|
||||
type: [String, Number],
|
||||
default: "",
|
||||
},
|
||||
float: {
|
||||
type: String,
|
||||
default: "left",
|
||||
},
|
||||
fullWidth: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
arrow: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
html: "",
|
||||
};
|
||||
},
|
||||
created() {
|
||||
const text = textReplaceLink(
|
||||
this.content,
|
||||
this.float == "right" ? "#ffffff" : "rgb(9 149 208)"
|
||||
);
|
||||
|
||||
this.html = textReplaceEmoji(text);
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
@bg-left-color: #f5f5f5;
|
||||
@bg-right-color: #1ebafc;
|
||||
|
||||
.text-message {
|
||||
position: relative;
|
||||
min-width: 30px;
|
||||
min-height: 30px;
|
||||
border-radius: 5px;
|
||||
padding: 5px;
|
||||
|
||||
.arrow {
|
||||
position: absolute;
|
||||
width: 0;
|
||||
height: 0;
|
||||
font-size: 0;
|
||||
border: 5px solid;
|
||||
top: 6px;
|
||||
left: -10px;
|
||||
}
|
||||
|
||||
&.max-width {
|
||||
max-width: calc(100% - 50px);
|
||||
}
|
||||
|
||||
&.left {
|
||||
color: #3a3a3a;
|
||||
background: @bg-left-color;
|
||||
|
||||
.arrow {
|
||||
border-color: transparent @bg-left-color transparent transparent;
|
||||
}
|
||||
}
|
||||
|
||||
&.right {
|
||||
color: #fff;
|
||||
background: @bg-right-color;
|
||||
|
||||
.arrow {
|
||||
right: -10px;
|
||||
left: unset;
|
||||
border-color: transparent transparent transparent @bg-right-color;
|
||||
}
|
||||
}
|
||||
|
||||
pre {
|
||||
white-space: pre-wrap;
|
||||
overflow: hidden;
|
||||
word-break: break-word;
|
||||
word-wrap: break-word;
|
||||
font-size: 15px;
|
||||
padding: 3px 10px;
|
||||
font-family: "Microsoft YaHei";
|
||||
line-height: 25px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
19
im/src/components/chat/messaege/UserCardMessage.vue
Normal file
19
im/src/components/chat/messaege/UserCardMessage.vue
Normal file
@@ -0,0 +1,19 @@
|
||||
<template>
|
||||
<!-- 用户卡片消息 - 预留 -->
|
||||
<div></div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'UserCardMessage',
|
||||
components: {},
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
computed: {},
|
||||
watch: {},
|
||||
methods: {},
|
||||
created() {},
|
||||
}
|
||||
</script>
|
||||
<style lang="less" scoped></style>
|
||||
18
im/src/components/chat/messaege/VideoMessage.vue
Normal file
18
im/src/components/chat/messaege/VideoMessage.vue
Normal file
@@ -0,0 +1,18 @@
|
||||
<template>
|
||||
<div class="video-message">
|
||||
视频消息
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'VideoMessage',
|
||||
components: {},
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
methods: {},
|
||||
created() {},
|
||||
}
|
||||
</script>
|
||||
<style lang="less" scoped></style>
|
||||
134
im/src/components/chat/messaege/VisitCardMessage.vue
Normal file
134
im/src/components/chat/messaege/VisitCardMessage.vue
Normal file
@@ -0,0 +1,134 @@
|
||||
<template>
|
||||
<div class="visit-card-message">
|
||||
<div class="user flex-center">
|
||||
<div class="avatar flex-center">
|
||||
<el-avatar :size="28" :src="avatar" />
|
||||
</div>
|
||||
<div class="content flex-center">
|
||||
<p class="ellipsis">{{ nickname }}</p>
|
||||
</div>
|
||||
<div class="tools flex-center">
|
||||
<span class="flex-center pointer">
|
||||
<i class="el-icon-plus" /> 加好友
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sign"><span>个性签名 : </span>{{ sign }}</div>
|
||||
<div class="share no-select ellipsis">
|
||||
<a class="pointer" @click="openVisitCard(friendId)">你是谁?</a>
|
||||
分享了用户名片,可点击添加好友 ...
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name: 'VisitCardMessage',
|
||||
data() {
|
||||
return {
|
||||
userId: 0,
|
||||
friendId: 0,
|
||||
avatar:
|
||||
'http://im-serve0.gzydong.club/static/image/sys-head/2019012107542668696.jpg',
|
||||
sign:
|
||||
'这个社会,是赢家通吃,输者一无所有,社会,永远都是只以成败论英雄。',
|
||||
nickname:
|
||||
'氨基酸纳氨基酸纳氨基酸纳氨基酸纳氨基酸纳氨基酸纳氨基酸纳氨基酸纳',
|
||||
}
|
||||
},
|
||||
created() {},
|
||||
methods: {
|
||||
openVisitCard(user_id) {
|
||||
this.$emit('openVisitCard', user_id)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.ellipsis {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.visit-card-message {
|
||||
min-height: 130px;
|
||||
min-width: 100px;
|
||||
max-width: 300px;
|
||||
border-radius: 5px;
|
||||
padding: 10px;
|
||||
box-sizing: border-box;
|
||||
border: 1px solid #ece5e5;
|
||||
transition: all 0.5s;
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0 0 8px #e2d3d3;
|
||||
transform: scale(1.01);
|
||||
}
|
||||
|
||||
.user {
|
||||
height: 40px;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
> div {
|
||||
height: inherit;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
flex-basis: 30px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.content {
|
||||
flex: 1 1;
|
||||
margin: 0 10px;
|
||||
font-size: 14px;
|
||||
justify-content: flex-start;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.tools {
|
||||
flex-basis: 60px;
|
||||
flex-shrink: 0;
|
||||
|
||||
span {
|
||||
width: 65px;
|
||||
height: 30px;
|
||||
background: #409eff;
|
||||
color: white;
|
||||
font-size: 13px;
|
||||
border-radius: 20px;
|
||||
padding: 0 8px;
|
||||
transform: scale(0.8);
|
||||
user-select: none;
|
||||
|
||||
&:active {
|
||||
background: #83b0f3;
|
||||
transform: scale(0.83);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.sign {
|
||||
min-height: 22px;
|
||||
line-height: 22px;
|
||||
border-radius: 3px;
|
||||
padding: 5px 8px;
|
||||
background: #f3f5f7;
|
||||
color: #7d7d7d;
|
||||
font-size: 12px;
|
||||
margin: 10px 0;
|
||||
span {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.share {
|
||||
font-size: 12px;
|
||||
color: #7d7d7d;
|
||||
a {
|
||||
color: #4cabf7;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
16
im/src/components/chat/messaege/VoiceMessage.vue
Normal file
16
im/src/components/chat/messaege/VoiceMessage.vue
Normal file
@@ -0,0 +1,16 @@
|
||||
<template>
|
||||
<div class="voice-message"></div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'VoiceMessage',
|
||||
components: {},
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
methods: {},
|
||||
created() {},
|
||||
}
|
||||
</script>
|
||||
<style lang="less" scoped></style>
|
||||
299
im/src/components/chat/messaege/VoteMessage.vue
Normal file
299
im/src/components/chat/messaege/VoteMessage.vue
Normal file
@@ -0,0 +1,299 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="vote-message">
|
||||
<div class="vote-from">
|
||||
<div class="vheader">
|
||||
<p>
|
||||
{{ answer_mode == 1 ? "[多选投票]" : "[单选投票]" }}
|
||||
<i
|
||||
v-show="is_vote"
|
||||
class="pointer"
|
||||
:class="{
|
||||
'el-icon-loading': refresh,
|
||||
'el-icon-refresh': !refresh,
|
||||
}"
|
||||
title="刷新投票结果"
|
||||
@click="loadRefresh"
|
||||
></i>
|
||||
</p>
|
||||
<p>{{ title }}</p>
|
||||
</div>
|
||||
|
||||
<template v-if="is_vote">
|
||||
<div class="vbody">
|
||||
<div class="vote-view" v-for="(option, index) in options">
|
||||
<p class="vote-option">{{ option.value }}. {{ option.text }}</p>
|
||||
<p class="vote-census">
|
||||
{{ option.num }} 票 {{ option.progress }}%
|
||||
</p>
|
||||
<p class="vote-progress">
|
||||
<el-progress
|
||||
:show-text="false"
|
||||
:percentage="parseInt(option.progress)"
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="vfooter vote-view">
|
||||
<p>应参与人数:{{ answer_num }} 人</p>
|
||||
<p>实际参与人数:{{ answered_num }} 人</p>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="vbody">
|
||||
<p class="option" v-for="(option, index) in options">
|
||||
<el-checkbox
|
||||
v-model="option.is_checked"
|
||||
@change="toSelect2(option)"
|
||||
/>
|
||||
<span @click="toSelect(option, index)" style="margin-left: 10px">
|
||||
{{ option.value }} 、{{ option.text }}
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
<div class="vfooter">
|
||||
<el-button plain round @click="toVote">
|
||||
{{ isUserVote ? "立即投票" : "请选择进行投票" }}
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ServeConfirmVoteHandle } from "@/api/chat";
|
||||
|
||||
export default {
|
||||
name: "VoteMessage",
|
||||
props: {
|
||||
vote: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
record_id: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
answer_mode: 0,
|
||||
title: "啊谁叫你打开你卡沙发那,那就是看、卡收纳是你",
|
||||
radio_value: "",
|
||||
options: [],
|
||||
is_vote: false,
|
||||
answer_num: 0,
|
||||
answered_num: 0,
|
||||
refresh: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
isUserVote() {
|
||||
return this.options.some((iten) => {
|
||||
return iten.is_checked;
|
||||
});
|
||||
},
|
||||
},
|
||||
created() {
|
||||
let user_id = this.$store.state.user.id;
|
||||
let { detail, statistics, vote_users } = this.vote;
|
||||
|
||||
this.answer_mode = detail.answer_mode;
|
||||
this.answer_num = detail.answer_num;
|
||||
this.answered_num = detail.answered_num;
|
||||
|
||||
detail.answer_option.forEach((item) => {
|
||||
this.options.push({
|
||||
value: item.key,
|
||||
text: item.value,
|
||||
is_checked: false,
|
||||
num: 0,
|
||||
progress: "00.0",
|
||||
});
|
||||
});
|
||||
|
||||
this.is_vote = vote_users.some((value) => {
|
||||
return value == user_id;
|
||||
});
|
||||
|
||||
this.updateStatistics(statistics);
|
||||
},
|
||||
methods: {
|
||||
loadRefresh() {
|
||||
this.refresh = true;
|
||||
|
||||
setTimeout(() => {
|
||||
this.refresh = false;
|
||||
}, 500);
|
||||
},
|
||||
updateStatistics(data) {
|
||||
let count = data.count;
|
||||
this.options.forEach((option) => {
|
||||
option.num = data.options[option.value];
|
||||
|
||||
if (count > 0) {
|
||||
option.progress = (data.options[option.value] / count) * 100;
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
toSelect(option, index) {
|
||||
if (this.answer_mode == 0) {
|
||||
this.options.forEach((option) => {
|
||||
option.is_checked = false;
|
||||
});
|
||||
}
|
||||
|
||||
this.options[index].is_checked = !option.is_checked;
|
||||
},
|
||||
toSelect2(option) {
|
||||
if (this.answer_mode == 0) {
|
||||
this.options.forEach((item) => {
|
||||
if (option.value == item.value) {
|
||||
item.is_checked = option.is_checked;
|
||||
} else {
|
||||
item.is_checked = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
toVote() {
|
||||
if (this.isUserVote == false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let items = [];
|
||||
this.options.forEach((item) => {
|
||||
if (item.is_checked) {
|
||||
items.push(item.value);
|
||||
}
|
||||
});
|
||||
|
||||
ServeConfirmVoteHandle({
|
||||
record_id: this.record_id,
|
||||
options: items.join(","),
|
||||
}).then((res) => {
|
||||
if (res.code == 200) {
|
||||
this.is_vote = true;
|
||||
this.updateStatistics(res.data);
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.vote-message {
|
||||
width: 300px;
|
||||
min-height: 150px;
|
||||
border: 1px solid #eceff1;
|
||||
box-sizing: border-box;
|
||||
border-radius: 5px;
|
||||
overflow: hidden;
|
||||
|
||||
.vote-from {
|
||||
width: 100%;
|
||||
|
||||
.vheader {
|
||||
min-height: 50px;
|
||||
background: #4e83fd;
|
||||
padding: 8px;
|
||||
position: relative;
|
||||
p {
|
||||
margin: 3px 0;
|
||||
&:first-child {
|
||||
color: rgb(245, 237, 237);
|
||||
font-size: 13px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
|
||||
&::before {
|
||||
content: "投票";
|
||||
position: absolute;
|
||||
font-size: 60px;
|
||||
color: white;
|
||||
opacity: 0.1;
|
||||
top: -5px;
|
||||
right: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.vbody {
|
||||
min-height: 80px;
|
||||
width: 100%;
|
||||
padding: 5px 15px;
|
||||
box-sizing: border-box;
|
||||
.option {
|
||||
margin: 14px 0px;
|
||||
font-size: 13px;
|
||||
span {
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
line-height: 22px;
|
||||
}
|
||||
|
||||
.el-radio {
|
||||
margin-right: 0;
|
||||
.el-radio__label {
|
||||
padding-left: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.vfooter {
|
||||
height: 55px;
|
||||
text-align: center;
|
||||
box-sizing: border-box;
|
||||
|
||||
.el-button {
|
||||
width: 80%;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
&.vote-view {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
padding-left: 15px;
|
||||
|
||||
p {
|
||||
border-left: 2px solid #2196f3;
|
||||
padding-left: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.vote-view {
|
||||
width: 100%;
|
||||
min-height: 30px;
|
||||
margin: 15px 0;
|
||||
box-sizing: border-box;
|
||||
|
||||
> p {
|
||||
margin: 6px 0px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.vote-option {
|
||||
min-height: 20px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.vote-census {
|
||||
height: 20px;
|
||||
line-height: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
33
im/src/components/chat/messaege/index.js
Normal file
33
im/src/components/chat/messaege/index.js
Normal file
@@ -0,0 +1,33 @@
|
||||
import AudioMessage from './AudioMessage.vue';
|
||||
import CodeMessage from './CodeMessage.vue';
|
||||
import ForwardMessage from './ForwardMessage.vue';
|
||||
import ImageMessage from './ImageMessage.vue';
|
||||
import TextMessage from './TextMessage.vue';
|
||||
import VideoMessage from './VideoMessage.vue';
|
||||
import VoiceMessage from './VoiceMessage.vue';
|
||||
import SystemTextMessage from './SystemTextMessage.vue';
|
||||
import FileMessage from './FileMessage.vue';
|
||||
import InviteMessage from './InviteMessage.vue';
|
||||
import RevokeMessage from './RevokeMessage.vue';
|
||||
import VisitCardMessage from './VisitCardMessage.vue';
|
||||
import ReplyMessage from './ReplyMessage.vue';
|
||||
import VoteMessage from './VoteMessage.vue';
|
||||
import LoginMessage from './LoginMessage.vue';
|
||||
|
||||
export {
|
||||
AudioMessage,
|
||||
CodeMessage,
|
||||
ForwardMessage,
|
||||
ImageMessage,
|
||||
TextMessage,
|
||||
VideoMessage,
|
||||
VoiceMessage,
|
||||
SystemTextMessage,
|
||||
FileMessage,
|
||||
InviteMessage,
|
||||
RevokeMessage,
|
||||
VisitCardMessage,
|
||||
ReplyMessage,
|
||||
VoteMessage,
|
||||
LoginMessage
|
||||
}
|
||||
130
im/src/components/chat/panel/OtherLink.vue
Normal file
130
im/src/components/chat/panel/OtherLink.vue
Normal file
@@ -0,0 +1,130 @@
|
||||
<template>
|
||||
<el-tabs v-model="activeName" @tab-click="handleClick" type="card">
|
||||
<el-tab-pane :label="toUser.storeFlag ? '想要咨询' : '他的足迹'" name="history">
|
||||
<div style="margin-left: 12px;" v-if="toUser.storeFlag">
|
||||
<GoodsLink :goodsDetail="goodsDetail" v-if="toUser.userId === goodsDetail.storeId"/>
|
||||
<FootPrint :list="footPrintList"/>
|
||||
</div>
|
||||
<div v-else>
|
||||
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="店铺信息" name="UserInfo" v-if="toUser.storeFlag">
|
||||
<div v-if="toUser.storeFlag">
|
||||
<StoreDetail :storeInfo="storeInfo"/>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Tabs, TabPane } from 'element-ui'
|
||||
import { ServeGetStoreDetail, ServeGetUserDetail, ServeGetFootPrint } from '@/api/user'
|
||||
import { ServeGetGoodsDetail } from '@/api/goods'
|
||||
import StoreDetail from "@/components/chat/panel/template/storeDetail.vue";
|
||||
import FootPrint from "@/components/chat/panel/template/footPrint.vue";
|
||||
import GoodsLink from "@/components/chat/panel/template/goodsLink.vue";
|
||||
export default {
|
||||
components: {
|
||||
"el-tabs": Tabs,
|
||||
"el-tab-pane": TabPane,
|
||||
StoreDetail,
|
||||
FootPrint,
|
||||
GoodsLink
|
||||
},
|
||||
props: {
|
||||
toUser: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
id: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
goodsParams: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
activeName: 'history',
|
||||
storeInfo: {}, //店铺信息
|
||||
memberInfo: {}, //会员信息
|
||||
footPrintParams: {
|
||||
memberId: '',
|
||||
storeId: '',
|
||||
},
|
||||
goodsDetail: {},
|
||||
footPrintList: [],
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
console.log(this.id)
|
||||
console.log(this.toUser)
|
||||
if(this.toUser.storeFlag){
|
||||
this.getStoreDetail()
|
||||
}else{
|
||||
this.getMemberDetail()
|
||||
}
|
||||
this.getFootPrint()
|
||||
if(this.goodsParams){
|
||||
this.getGoodsDetail()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getStoreDetail() {
|
||||
ServeGetStoreDetail(this.toUser.userId).then(res => {
|
||||
if (res.success) {
|
||||
this.storeInfo = res.result
|
||||
}
|
||||
})
|
||||
},
|
||||
handleClick(){},
|
||||
getMemberDetail() {
|
||||
ServeGetUserDetail(this.toUser.userId).then(res => {
|
||||
if (res.success) {
|
||||
this.memberInfo = res.result
|
||||
}
|
||||
})
|
||||
},
|
||||
getGoodsDetail(){
|
||||
ServeGetGoodsDetail(this.goodsParams).then(res => {
|
||||
if(res.success){
|
||||
this.goodsDetail = res.result.data
|
||||
}
|
||||
})
|
||||
},
|
||||
getFootPrint(){
|
||||
if(this.toUser.storeFlag){
|
||||
this.footPrintParams.memberId = this.id
|
||||
this.footPrintParams.storeId = this.toUser.userId
|
||||
}else{
|
||||
this.footPrintParams.memberId = this.toUser.userId
|
||||
this.footPrintParams.storeId = this.id
|
||||
}
|
||||
console.log(this.footPrintParams)
|
||||
ServeGetFootPrint(this.footPrintParams).then(res => {
|
||||
res.result.records.forEach((item,index) => {
|
||||
if(item.goodsId === this.goodsParams.goodsId){
|
||||
res.result.records.splice(index,1)
|
||||
}
|
||||
});
|
||||
this.footPrintList = res.result.records
|
||||
})
|
||||
//删除掉刚加入的商品
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
/deep/ .el-tabs__nav {
|
||||
height: 60px;
|
||||
line-height: 60px;
|
||||
}
|
||||
/deep/ .el-tab-pane {
|
||||
margin-left: 12px;
|
||||
}
|
||||
</style>
|
||||
278
im/src/components/chat/panel/PanelHeader.vue
Normal file
278
im/src/components/chat/panel/PanelHeader.vue
Normal file
@@ -0,0 +1,278 @@
|
||||
<template>
|
||||
<el-header id="panel-header">
|
||||
<div class="module left-module">
|
||||
<span
|
||||
class="icon-badge"
|
||||
v-show="params.is_robot == 0"
|
||||
:class="{ 'red-color': params.talk_type == 1 }"
|
||||
>
|
||||
{{ params.talk_type == 1 ? '好友' : '群组' }}
|
||||
</span>
|
||||
<span class="nickname">{{ params.nickname }}</span>
|
||||
<span v-show="params.talk_type == 2" class="num">({{ groupNum }})</span>
|
||||
</div>
|
||||
|
||||
<div v-show="params.talk_type == 1 && params.is_robot == 0" class="module center-module">
|
||||
<p class="online">
|
||||
<span v-show="isOnline" class="online-status"></span>
|
||||
<span>{{ isOnline ? '在线' : '离线' }}</span>
|
||||
</p>
|
||||
<p class="keyboard-status" v-show="isKeyboard">对方正在输入 ...</p>
|
||||
</div>
|
||||
|
||||
<div class="module right-module" >
|
||||
<el-tooltip content="历史消息" placement="top">
|
||||
<p v-show="params.is_robot == 0">
|
||||
<i class="el-icon-time" @click="triggerEvent('history')" />
|
||||
</p>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="群公告" placement="top">
|
||||
<p v-show="params.talk_type == 2">
|
||||
<i class="iconfont icon-gonggao2" @click="triggerEvent('notice')" />
|
||||
</p>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="群设置" placement="top">
|
||||
<p v-show="params.talk_type == 2">
|
||||
<i class="el-icon-setting" @click="triggerEvent('setting')" />
|
||||
</p>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</el-header>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
data: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {
|
||||
talk_type: 0,
|
||||
receiver_id: 0,
|
||||
params: 0,
|
||||
nickname: '',
|
||||
}
|
||||
},
|
||||
},
|
||||
online: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
keyboard: {
|
||||
type: [Boolean, Number],
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
params: {
|
||||
talk_type: 0,
|
||||
receiver_id: 0,
|
||||
params: 0,
|
||||
nickname: '',
|
||||
},
|
||||
isOnline: false,
|
||||
isKeyboard: false,
|
||||
groupNum: 0,
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.setParamsData(this.data)
|
||||
this.setOnlineStatus(this.online)
|
||||
},
|
||||
watch: {
|
||||
data(value) {
|
||||
this.setParamsData(value)
|
||||
},
|
||||
online(value) {
|
||||
this.setOnlineStatus(value)
|
||||
},
|
||||
keyboard(value) {
|
||||
this.isKeyboard = value
|
||||
setTimeout(() => {
|
||||
this.isKeyboard = false
|
||||
}, 2000)
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
setOnlineStatus(value) {
|
||||
this.isOnline = value
|
||||
},
|
||||
setParamsData(object) {
|
||||
Object.assign(this.params, object)
|
||||
},
|
||||
setGroupNum(value) {
|
||||
this.groupNum = value
|
||||
},
|
||||
triggerEvent(event_name) {
|
||||
this.$emit('event', event_name)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
#panel-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
box-sizing: border-box;
|
||||
border-bottom: 1px solid #f5eeee;
|
||||
|
||||
.module {
|
||||
width: 100%/3;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.left-module {
|
||||
padding-right: 5px;
|
||||
|
||||
.icon-badge {
|
||||
background: rgb(81 139 254);
|
||||
height: 18px;
|
||||
line-height: 18px;
|
||||
padding: 1px 3px;
|
||||
font-size: 10px;
|
||||
color: white;
|
||||
border-radius: 3px;
|
||||
margin-right: 8px;
|
||||
flex-shrink: 0;
|
||||
|
||||
&.red-color {
|
||||
background: #f97348;
|
||||
}
|
||||
}
|
||||
|
||||
.nickname {
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
|
||||
.center-module {
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
|
||||
.online {
|
||||
color: #cccccc;
|
||||
font-weight: 300;
|
||||
font-size: 15px;
|
||||
|
||||
&.color {
|
||||
color: #1890ff;
|
||||
}
|
||||
|
||||
.online-status {
|
||||
position: relative;
|
||||
top: -1px;
|
||||
display: inline-block;
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
vertical-align: middle;
|
||||
border-radius: 50%;
|
||||
position: relative;
|
||||
background-color: #1890ff;
|
||||
margin-right: 5px;
|
||||
|
||||
&:after {
|
||||
position: absolute;
|
||||
top: -1px;
|
||||
left: -1px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: 1px solid #1890ff;
|
||||
border-radius: 50%;
|
||||
-webkit-animation: antStatusProcessing 1.2s ease-in-out infinite;
|
||||
animation: antStatusProcessing 1.2s ease-in-out infinite;
|
||||
content: '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.keyboard-status {
|
||||
height: 20px;
|
||||
line-height: 18px;
|
||||
font-size: 10px;
|
||||
animation: inputfade 600ms infinite;
|
||||
-webkit-animation: inputfade 600ms infinite;
|
||||
}
|
||||
}
|
||||
|
||||
.right-module {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
|
||||
p {
|
||||
cursor: pointer;
|
||||
margin: 0 8px;
|
||||
font-size: 20px;
|
||||
color: #828f95;
|
||||
&:active i {
|
||||
font-size: 26px;
|
||||
transform: scale(1.3);
|
||||
transition: ease 0.5s;
|
||||
color: red;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* css 动画 */
|
||||
@keyframes inputfade {
|
||||
from {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
50% {
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@-webkit-keyframes inputfade {
|
||||
from {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
50% {
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@-webkit-keyframes antStatusProcessing {
|
||||
0% {
|
||||
-webkit-transform: scale(0.8);
|
||||
transform: scale(0.8);
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
to {
|
||||
-webkit-transform: scale(2.4);
|
||||
transform: scale(2.4);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes antStatusProcessing {
|
||||
0% {
|
||||
-webkit-transform: scale(0.8);
|
||||
transform: scale(0.8);
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
to {
|
||||
-webkit-transform: scale(2.4);
|
||||
transform: scale(2.4);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
101
im/src/components/chat/panel/PanelToolbar.vue
Normal file
101
im/src/components/chat/panel/PanelToolbar.vue
Normal file
@@ -0,0 +1,101 @@
|
||||
<template>
|
||||
<div class="multi-select">
|
||||
<div class="multi-title">
|
||||
<span>已选中:{{ value }} 条消息</span>
|
||||
</div>
|
||||
<div class="multi-main">
|
||||
<div class="btn-group">
|
||||
<div
|
||||
class="multi-icon pointer"
|
||||
@click="$emit('event', 'merge_forward')"
|
||||
>
|
||||
<i class="el-icon-position" />
|
||||
</div>
|
||||
<p>合并转发</p>
|
||||
</div>
|
||||
<div class="btn-group">
|
||||
<div class="multi-icon pointer" @click="$emit('event', 'forward')">
|
||||
<i class="el-icon-position" />
|
||||
</div>
|
||||
<p>逐条转发</p>
|
||||
</div>
|
||||
<div class="btn-group">
|
||||
<div class="multi-icon pointer" @click="$emit('event', 'delete')">
|
||||
<i class="el-icon-delete" />
|
||||
</div>
|
||||
<p>批量删除</p>
|
||||
</div>
|
||||
<div class="btn-group">
|
||||
<div class="multi-icon pointer" @click="$emit('event', 'close')">
|
||||
<i class="el-icon-close" />
|
||||
</div>
|
||||
<p>关闭</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
value: {
|
||||
default: 0,
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.multi-select {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
|
||||
.multi-title {
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
line-height: 50px;
|
||||
text-align: center;
|
||||
color: #878484;
|
||||
font-weight: 300;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.multi-main {
|
||||
.btn-group {
|
||||
display: inline-block;
|
||||
width: 70px;
|
||||
height: 70px;
|
||||
margin-right: 15px;
|
||||
|
||||
.multi-icon {
|
||||
width: 45px;
|
||||
height: 45px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 50%;
|
||||
margin: 0 auto;
|
||||
border: 1px solid transparent;
|
||||
|
||||
&:hover {
|
||||
color: red;
|
||||
border-color: red;
|
||||
background: transparent;
|
||||
font-size: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 12px;
|
||||
margin-top: 8px;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
1103
im/src/components/chat/panel/TalkPanel.vue
Normal file
1103
im/src/components/chat/panel/TalkPanel.vue
Normal file
File diff suppressed because it is too large
Load Diff
71
im/src/components/chat/panel/template/footPrint.vue
Normal file
71
im/src/components/chat/panel/template/footPrint.vue
Normal file
@@ -0,0 +1,71 @@
|
||||
<template>
|
||||
<div>
|
||||
最近浏览
|
||||
<dl>
|
||||
<dd v-for="(item, index) in list">
|
||||
<div class="base" @click="linkToGoods(item.goodsId,item.id)">
|
||||
<div>
|
||||
<img :src="item.thumbnail" class="image" />
|
||||
</div>
|
||||
<div style="margin-left: 13px">
|
||||
<a>{{ item.goodsName }}</a>
|
||||
<div>
|
||||
<span style="color: red;">¥{{ item.price }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</dd>
|
||||
</dl>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Tag, button } from 'element-ui'
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
}
|
||||
},
|
||||
components: {
|
||||
"el-tag": Tag,
|
||||
"el-button": button,
|
||||
},
|
||||
methods:{
|
||||
},
|
||||
props: {
|
||||
list: {
|
||||
type: Array,
|
||||
default: [],
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.store-button {
|
||||
background-color: white;
|
||||
border-color: #F56C6C;
|
||||
}
|
||||
|
||||
.base {
|
||||
margin-top: 5px;
|
||||
height: 120px;
|
||||
display: flex;
|
||||
|
||||
div {
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.image {
|
||||
height: 100px;
|
||||
margin-top: 3px;
|
||||
width: 100px
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.separate {
|
||||
margin-top: 8px;
|
||||
}
|
||||
</style>
|
||||
168
im/src/components/chat/panel/template/goodsLink.vue
Normal file
168
im/src/components/chat/panel/template/goodsLink.vue
Normal file
@@ -0,0 +1,168 @@
|
||||
<template>
|
||||
<div>
|
||||
当前浏览
|
||||
<div class="base">
|
||||
<div>
|
||||
<img :src="goodsDetail.thumbnail" class="image" />
|
||||
</div>
|
||||
<div style="margin-left: 13px">
|
||||
<a @click="linkToGoods(goodsDetail.goodsId,goodsDetail.id)"> {{ goodsDetail.goodsName }} </a>
|
||||
<div>
|
||||
<span style="color: red;">¥{{ goodsDetail.price }}</span>
|
||||
</div>
|
||||
<el-button class="store-button" type="danger" v-if="!sendFlag" size="mini" @click="submitSendMessage()"
|
||||
plain>发送</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<hr class="separate" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Tag, button } from 'element-ui'
|
||||
import { mapState, mapGetters } from "vuex";
|
||||
import SocketInstance from "@/im-server/socket-instance";
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
sendFlag: false,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(["talkItems"]),
|
||||
...mapState({
|
||||
id: (state) => state.user.id,
|
||||
index_name: (state) => state.dialogue.index_name,
|
||||
toUser: (state) => state.user.toUser,
|
||||
}),
|
||||
},
|
||||
mounted(){
|
||||
|
||||
},
|
||||
components: {
|
||||
"el-tag": Tag,
|
||||
"el-button": button,
|
||||
Storage
|
||||
},
|
||||
methods: {
|
||||
toGoods() {
|
||||
alert("toGoods")
|
||||
},
|
||||
toMessage() {
|
||||
alert(JSON.stringify(this.toUser))
|
||||
alert("toMessage")
|
||||
},
|
||||
// 回车键发送消息回调事件
|
||||
submitSendMessage() {
|
||||
console.log("发送");
|
||||
const context = this.goodsDetail
|
||||
const record = {
|
||||
operation_type: "MESSAGE",
|
||||
to: this.toUser.userId,
|
||||
from: this.id,
|
||||
message_type: "GOODS",
|
||||
context: context,
|
||||
talk_id: this.toUser.id,
|
||||
};
|
||||
SocketInstance.emit("event_talk", record);
|
||||
|
||||
this.$store.commit("UPDATE_TALK_ITEM", {
|
||||
index_name: this.index_name,
|
||||
draft_text: "",
|
||||
});
|
||||
|
||||
/**
|
||||
* 插入数据
|
||||
*/
|
||||
const insterChat = {
|
||||
createTime: this.formateDateAndTimeToString(new Date()),
|
||||
fromUser: this.id,
|
||||
toUser: record.to,
|
||||
isRead: false,
|
||||
messageType: "GOODS",
|
||||
text: context,
|
||||
float: "right",
|
||||
};
|
||||
|
||||
console.log("insterChat", insterChat);
|
||||
// console.log("插入对话记录",'')
|
||||
// 插入对话记录
|
||||
this.$store.commit("PUSH_DIALOGUE", insterChat);
|
||||
// 获取聊天面板元素节点
|
||||
let el = document.getElementById("lumenChatPanel");
|
||||
// 判断的滚动条是否在底部
|
||||
let isBottom =
|
||||
Math.ceil(el.scrollTop) + el.clientHeight >= el.scrollHeight;
|
||||
|
||||
if (isBottom || record.to == this.id) {
|
||||
this.$nextTick(() => {
|
||||
el.scrollTop = el.scrollHeight;
|
||||
});
|
||||
} else {
|
||||
this.$store.commit("SET_TLAK_UNREAD_MESSAGE", {
|
||||
content: content,
|
||||
nickname: record.name,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
formateDateAndTimeToString(date) {
|
||||
var hours = date.getHours();
|
||||
var mins = date.getMinutes();
|
||||
var secs = date.getSeconds();
|
||||
var msecs = date.getMilliseconds();
|
||||
if (hours < 10) hours = "0" + hours;
|
||||
if (mins < 10) mins = "0" + mins;
|
||||
if (secs < 10) secs = "0" + secs;
|
||||
if (msecs < 10) secs = "0" + msecs;
|
||||
return (
|
||||
this.formatDateToString(date) + " " + hours + ":" + mins + ":" + secs
|
||||
);
|
||||
},
|
||||
|
||||
formatDateToString(date) {
|
||||
var year = date.getFullYear();
|
||||
var month = date.getMonth() + 1;
|
||||
var day = date.getDate();
|
||||
if (month < 10) month = "0" + month;
|
||||
if (day < 10) day = "0" + day;
|
||||
return year + "-" + month + "-" + day;
|
||||
},
|
||||
},
|
||||
props: {
|
||||
goodsDetail: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.store-button {
|
||||
background-color: white;
|
||||
border-color: #F56C6C;
|
||||
}
|
||||
|
||||
.base {
|
||||
margin-top: 5px;
|
||||
height: 120px;
|
||||
display: flex;
|
||||
|
||||
div {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.image {
|
||||
height: 100px;
|
||||
margin-top: 3px;
|
||||
width: 100px
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.separate {
|
||||
margin-top: 8px;
|
||||
}
|
||||
</style>
|
||||
75
im/src/components/chat/panel/template/storeDetail.vue
Normal file
75
im/src/components/chat/panel/template/storeDetail.vue
Normal file
@@ -0,0 +1,75 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="base" >
|
||||
<div>
|
||||
<img :src="storeInfo.storeLogo" class="image"/>
|
||||
</div>
|
||||
<div style="margin-left: 13px">
|
||||
<div class="div-zoom">
|
||||
{{ storeInfo.storeName }}
|
||||
<el-tag type="danger" v-if=" storeInfo.selfOperated " size="mini">自营</el-tag>
|
||||
</div>
|
||||
<div>
|
||||
联系方式: {{ storeInfo.memberName }}
|
||||
</div>
|
||||
<div>
|
||||
<el-button class="store-button" type="danger" @click="linkToStore(storeInfo.id)" size="mini" plain >进入店铺</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<hr class="separate"/>
|
||||
<div class="separate">店铺评分: <span>{{ storeInfo.serviceScore }}</span></div>
|
||||
<div class="separate">服务评分: <span>{{ storeInfo.descriptionScore }}</span></div>
|
||||
<div class="separate">物流评分: <span>{{ storeInfo.deliveryScore }}</span></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Tag,button } from 'element-ui'
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
}
|
||||
},
|
||||
components: {
|
||||
"el-tag": Tag,
|
||||
"el-button": button,
|
||||
},
|
||||
methods:{
|
||||
},
|
||||
props: {
|
||||
storeInfo: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.store-button{
|
||||
background-color: white;
|
||||
border-color: #F56C6C;
|
||||
}
|
||||
|
||||
.base{
|
||||
margin-top: 5px;
|
||||
height: 120px;
|
||||
display: flex;
|
||||
|
||||
div {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.image{
|
||||
height: 100px;
|
||||
margin-top: 3px;
|
||||
width: 100px
|
||||
}
|
||||
|
||||
}
|
||||
.separate{
|
||||
margin-top: 8px;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user