feat: 重新调整
This commit is contained in:
parent
1a298d2e97
commit
7a448c536f
@ -1,11 +1,8 @@
|
|||||||
# 开发环境配置
|
# 开发环境配置
|
||||||
# ENV = 'development'
|
ENV = 'development'
|
||||||
|
|
||||||
# 路由懒加载
|
# 开发环境
|
||||||
# VUE_CLI_BABEL_TRANSPILE_MODULES = true
|
VUE_APP_BASE_API = '/dev-api'
|
||||||
|
|
||||||
# pei你看雪博客/开发环境/websocket地址
|
# 开发环境websocket地址
|
||||||
VUE_APP_SOCKET = 'ws://localhost:8068/websocket'
|
VUE_APP_SOCKET = 'ws://localhost:8068/websocket'
|
||||||
|
|
||||||
# 若依管理系统/开发环境
|
|
||||||
VUE_APP_BASE_API = 'https://pnkx.top/prod-api'
|
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
# 生产环境配置
|
# 生产环境配置
|
||||||
ENV = 'production'
|
ENV = 'production'
|
||||||
|
|
||||||
# pei你看雪博客/开发环境/websocket地址
|
# 生产环境
|
||||||
VUE_APP_SOCKET = 'wss://pnkx.top/websocket/websocket'
|
|
||||||
|
|
||||||
# 若依管理系统/开发环境
|
|
||||||
VUE_APP_BASE_API = '/prod-api'
|
VUE_APP_BASE_API = '/prod-api'
|
||||||
|
|
||||||
|
# 生产环境websocket地址
|
||||||
|
VUE_APP_SOCKET = 'wss://pnkx.top/websocket/websocket'
|
||||||
|
@ -14,7 +14,6 @@
|
|||||||
"core-js": "^3.6.5",
|
"core-js": "^3.6.5",
|
||||||
"element-ui": "^2.13.2",
|
"element-ui": "^2.13.2",
|
||||||
"es6-promise": "^4.2.8",
|
"es6-promise": "^4.2.8",
|
||||||
"js-cookie": "^2.2.1",
|
|
||||||
"node-sass": "^4.14.1",
|
"node-sass": "^4.14.1",
|
||||||
"sass-loader": "^8.0.0",
|
"sass-loader": "^8.0.0",
|
||||||
"vue": "^2.6.11",
|
"vue": "^2.6.11",
|
||||||
@ -35,8 +34,8 @@
|
|||||||
"eslint-plugin-promise": "^4.2.1",
|
"eslint-plugin-promise": "^4.2.1",
|
||||||
"eslint-plugin-standard": "^4.0.0",
|
"eslint-plugin-standard": "^4.0.0",
|
||||||
"eslint-plugin-vue": "^6.2.2",
|
"eslint-plugin-vue": "^6.2.2",
|
||||||
"less": "^3.0.4",
|
"sass": "1.32.0",
|
||||||
"less-loader": "^7.3.0",
|
"sass-loader": "10.1.0",
|
||||||
"vue-template-compiler": "^2.6.11"
|
"vue-template-compiler": "^2.6.11"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
21
src/App.vue
21
src/App.vue
@ -1,10 +1,21 @@
|
|||||||
<template>
|
<template>
|
||||||
<div id="app">
|
<div id="app">
|
||||||
<router-view/>
|
<chat/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="less">
|
<script>
|
||||||
|
import Chat from '@/components/Chat/index.vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'App',
|
||||||
|
components: {
|
||||||
|
Chat
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
html, body{
|
html, body{
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
@ -14,5 +25,11 @@
|
|||||||
background-image: url('./assets/main-bg.png');
|
background-image: url('./assets/main-bg.png');
|
||||||
background-size: 100% 100%;
|
background-size: 100% 100%;
|
||||||
background-position: center;
|
background-position: center;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
#app {
|
||||||
|
width: 60vw;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -1,22 +1,32 @@
|
|||||||
import request from '../utils/request'
|
import request from '@/utils/request'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 发送信息
|
* 登录聊天室
|
||||||
*/
|
*/
|
||||||
export function sendMessage(params) {
|
export function loginChat () {
|
||||||
return request({
|
return request({
|
||||||
url: '/customer/sendMessage',
|
url: '/admin/chat/loginChat',
|
||||||
|
method: 'post'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送信息
|
||||||
|
*/
|
||||||
|
export function sendMessage (params) {
|
||||||
|
return request({
|
||||||
|
url: '/admin/chat/sendMessage',
|
||||||
method: 'post',
|
method: 'post',
|
||||||
data: params
|
data: params
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 登录聊天室
|
* 获取信息
|
||||||
*/
|
*/
|
||||||
export function loginChat(params) {
|
export function getMessageRecord (params) {
|
||||||
return request({
|
return request({
|
||||||
url: '/customer/loginChat',
|
url: '/admin/chat/getMessageRecord',
|
||||||
method: 'post',
|
method: 'post',
|
||||||
data: params
|
data: params
|
||||||
})
|
})
|
||||||
@ -25,11 +35,35 @@ export function loginChat(params) {
|
|||||||
/**
|
/**
|
||||||
* 退出聊天室
|
* 退出聊天室
|
||||||
*/
|
*/
|
||||||
export function signOut(params) {
|
export function signOut (params) {
|
||||||
return request({
|
return request({
|
||||||
url: '/customer/signOut',
|
url: '/admin/chat/signOut',
|
||||||
method: 'post',
|
method: 'post',
|
||||||
data: params
|
data: params
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 查询文件记录列表
|
||||||
|
export function listFile (query) {
|
||||||
|
return request({
|
||||||
|
url: '/system/file/list',
|
||||||
|
method: 'get',
|
||||||
|
params: query
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据字典类型查询字典数据信息
|
||||||
|
export function getDicts (dictType) {
|
||||||
|
return request({
|
||||||
|
url: '/client/dictType/' + dictType,
|
||||||
|
method: 'get'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取用户详细信息
|
||||||
|
export function getInfo () {
|
||||||
|
return request({
|
||||||
|
url: '/getInfo',
|
||||||
|
method: 'get'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
BIN
src/assets/images/emoji/emoji.png
Normal file
BIN
src/assets/images/emoji/emoji.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 488 KiB |
30
src/assets/js/common.js
Normal file
30
src/assets/js/common.js
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
/**
|
||||||
|
* 颜色数组
|
||||||
|
*/
|
||||||
|
export const colorArray = [
|
||||||
|
'#5A8DEE',
|
||||||
|
'#CD594B',
|
||||||
|
'#F8CE5E',
|
||||||
|
'#4B9E65'
|
||||||
|
]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 博客url
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
export const BLOG_URL = 'https://pnkx.top/'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 图片的文件类型
|
||||||
|
*/
|
||||||
|
export const IMAGE_TYPE = ['image/jpeg', '.jpg', '.jpeg', '.gif', '.bmp', '.webp']
|
||||||
|
|
||||||
|
/**
|
||||||
|
* websocket消息类型
|
||||||
|
* @type {{}}
|
||||||
|
*/
|
||||||
|
export const WEBSOCKET_MESSAGE_TYPE = {
|
||||||
|
LOGIN: 'login',
|
||||||
|
LOG_OUT: 'log_out',
|
||||||
|
CHAT_MESSAGE: 'chat_message'
|
||||||
|
}
|
Binary file not shown.
Before Width: | Height: | Size: 6.7 KiB |
339
src/components/Chat/emoji.vue
Normal file
339
src/components/Chat/emoji.vue
Normal file
File diff suppressed because one or more lines are too long
295
src/components/Chat/file.vue
Normal file
295
src/components/Chat/file.vue
Normal file
@ -0,0 +1,295 @@
|
|||||||
|
<!--
|
||||||
|
* @File: file
|
||||||
|
* @Author: 裴浩宇
|
||||||
|
* @Date: 2023/6/7 15:25
|
||||||
|
* @Description: 选择文件
|
||||||
|
-->
|
||||||
|
<template>
|
||||||
|
<div class="app-container">
|
||||||
|
<el-form :inline="true" :model="queryParams" label-width="68px" ref="queryForm" v-show="showSearch">
|
||||||
|
<el-form-item label="上传端口" prop="port">
|
||||||
|
<el-select placeholder="请选择上传端口" v-model="queryParams.port">
|
||||||
|
<el-option
|
||||||
|
:key="item"
|
||||||
|
:label="item"
|
||||||
|
:value="item"
|
||||||
|
v-for="item in ['博客管理端', '博客客户端', '外部接口端']">
|
||||||
|
</el-option>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="文件名称" prop="name">
|
||||||
|
<el-input
|
||||||
|
@keyup.enter.native="handleQuery"
|
||||||
|
clearable
|
||||||
|
placeholder="请输入文件名称"
|
||||||
|
size="small"
|
||||||
|
v-model="queryParams.name"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="分类" prop="type">
|
||||||
|
<el-select v-model="queryParams.type" placeholder="分类" clearable>
|
||||||
|
<el-option
|
||||||
|
v-for="dict in typeOptions"
|
||||||
|
:key="dict.dictValue"
|
||||||
|
:label="dict.dictLabel"
|
||||||
|
:value="dict.dictValue"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="排序字段" prop="orderByColumn">
|
||||||
|
<el-select placeholder="请选择排序字段" v-model="queryParams.orderByColumn" clearable @change="handleQuery">
|
||||||
|
<el-option
|
||||||
|
:key="item.value"
|
||||||
|
:label="item.label"
|
||||||
|
:value="item.value"
|
||||||
|
v-for="item in [{label: '创建时间', value: 'createTime'},{label: '点赞', value: 'thumb'},{label: '浏览', value: 'browse'}]">
|
||||||
|
</el-option>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="排序类型" prop="isAsc">
|
||||||
|
<el-select placeholder="请选择排序类型" v-model="queryParams.isAsc" clearable @change="handleQuery">
|
||||||
|
<el-option
|
||||||
|
:key="item.value"
|
||||||
|
:label="item.label"
|
||||||
|
:value="item.value"
|
||||||
|
v-for="item in [{label: '升序', value: 'asc'},{label: '降序', value: 'desc'}]">
|
||||||
|
</el-option>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button @click="handleQuery" icon="el-icon-search" size="mini" type="primary">搜索</el-button>
|
||||||
|
<el-button @click="resetQuery" icon="el-icon-refresh" size="mini">重置</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
|
||||||
|
<el-table height="40vh" :data="fileList" @selection-change="handleSelectionChange" v-loading="loading">
|
||||||
|
<el-table-column align="center" type="selection" width="55"/>
|
||||||
|
<el-table-column align="center" label="文件缩略图">
|
||||||
|
<template slot-scope="scope">
|
||||||
|
<el-image :preview-src-list="imageList"
|
||||||
|
v-if="scope.row.isPicture"
|
||||||
|
:src="scope.row.url"
|
||||||
|
fit="scale-down"
|
||||||
|
style="width: 3rem; height: 3rem;">
|
||||||
|
<div slot="error" class="image-slot invalid-svg">
|
||||||
|
<svg-icon icon-class="已失效2"/>
|
||||||
|
</div>
|
||||||
|
</el-image>
|
||||||
|
<div v-else class="format">
|
||||||
|
{{ scope.row.name.slice(scope.row.name.lastIndexOf('.') + 1) }}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column align="center" label="文件名称" show-overflow-tooltip>
|
||||||
|
<template slot-scope="scope">
|
||||||
|
<span
|
||||||
|
@click="copyCode('https://pnkx.top/prod-api/profile'+scope.row.filePath.slice(21))"
|
||||||
|
class="theme-blue-text">{{ scope.row.name }}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column align="center" label="上传端口" prop="port"/>
|
||||||
|
<el-table-column align="center" label="分类" prop="type" :formatter="typeFormat"/>
|
||||||
|
<el-table-column align="center" label="上传时间" prop="createTime"/>
|
||||||
|
<el-table-column align="center" label="备注" prop="remark"/>
|
||||||
|
<el-table-column align="center" class-name="small-padding fixed-width" label="操作" width="100">
|
||||||
|
<template slot-scope="scope">
|
||||||
|
<el-button icon="el-icon-finished"
|
||||||
|
size="mini"
|
||||||
|
type="text"
|
||||||
|
@click="handleSelect(scope.row)"
|
||||||
|
>选择
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
|
||||||
|
<pagination
|
||||||
|
:limit.sync="queryParams.pageSize"
|
||||||
|
:page.sync="queryParams.pageNum"
|
||||||
|
:total="total"
|
||||||
|
@pagination="getList"
|
||||||
|
v-show="total>0"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { getDicts, listFile } from '@/api'
|
||||||
|
import pagination from '@/components/Pagination/index.vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'file',
|
||||||
|
components: {
|
||||||
|
pagination
|
||||||
|
},
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
// 图片格式
|
||||||
|
photoFormat: ['bmp', 'gif', 'jpg', 'jpeg', 'png'],
|
||||||
|
// 图片列表
|
||||||
|
imageList: [],
|
||||||
|
// 遮罩层
|
||||||
|
loading: true,
|
||||||
|
// 选中数组
|
||||||
|
ids: [],
|
||||||
|
// 非单个禁用
|
||||||
|
single: true,
|
||||||
|
// 非多个禁用
|
||||||
|
multiple: true,
|
||||||
|
// 显示搜索条件
|
||||||
|
showSearch: true,
|
||||||
|
// 总条数
|
||||||
|
total: 0,
|
||||||
|
// 文件记录表格数据
|
||||||
|
fileList: [],
|
||||||
|
// 查询参数
|
||||||
|
queryParams: {
|
||||||
|
pageNum: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
orderByColumn: 'createTime',
|
||||||
|
isAsc: 'desc',
|
||||||
|
type: '',
|
||||||
|
port: null,
|
||||||
|
name: null,
|
||||||
|
filePath: null,
|
||||||
|
version: null
|
||||||
|
},
|
||||||
|
// 类型列表
|
||||||
|
typeOptions: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created () {
|
||||||
|
this.getList()
|
||||||
|
getDicts('sys_file_type').then(response => {
|
||||||
|
this.typeOptions = response.data
|
||||||
|
})
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
/**
|
||||||
|
* 选择
|
||||||
|
*/
|
||||||
|
handleSelect (row) {
|
||||||
|
this.$emit('select-file', row)
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 翻译字典
|
||||||
|
* @param datas
|
||||||
|
* @param value
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
selectDictLabel (datas, value) {
|
||||||
|
const actions = []
|
||||||
|
Object.keys(datas).some((key) => {
|
||||||
|
if (datas[key].dictValue === ('' + value)) {
|
||||||
|
actions.push(datas[key].dictLabel)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return actions.join('')
|
||||||
|
},
|
||||||
|
// 菜单状态字典翻译
|
||||||
|
typeFormat (row, column) {
|
||||||
|
if (!row.type) {
|
||||||
|
return '暂未分类'
|
||||||
|
}
|
||||||
|
return this.selectDictLabel(this.typeOptions, row.type)
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 单击复制到粘贴板
|
||||||
|
*/
|
||||||
|
copyCode (content) {
|
||||||
|
this.$copyText(content).then(res => {
|
||||||
|
this.$notify.success('已成功复制,可直接去粘贴')
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
this.$notify.error('复制失败')
|
||||||
|
})
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 判断是否是图片
|
||||||
|
* @param name
|
||||||
|
*/
|
||||||
|
judgePicture (name) {
|
||||||
|
let result = false
|
||||||
|
this.photoFormat.forEach(item => {
|
||||||
|
if (name.endsWith(item)) {
|
||||||
|
result = true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return result
|
||||||
|
},
|
||||||
|
/** 查询文件记录列表 */
|
||||||
|
getList () {
|
||||||
|
this.loading = true
|
||||||
|
listFile(this.queryParams).then(response => {
|
||||||
|
this.fileList = response.rows
|
||||||
|
this.imageList = []
|
||||||
|
this.fileList.forEach(item => {
|
||||||
|
if (this.judgePicture(item.name)) {
|
||||||
|
item.isPicture = true
|
||||||
|
this.imageList.push(item.url)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
this.total = response.total
|
||||||
|
this.loading = false
|
||||||
|
})
|
||||||
|
},
|
||||||
|
// 表单重置
|
||||||
|
reset () {
|
||||||
|
this.form = {
|
||||||
|
id: null,
|
||||||
|
name: null,
|
||||||
|
filePath: null,
|
||||||
|
type: null,
|
||||||
|
version: null,
|
||||||
|
createBy: null,
|
||||||
|
createTime: null,
|
||||||
|
updateBy: null,
|
||||||
|
updateTime: null,
|
||||||
|
remark: null
|
||||||
|
}
|
||||||
|
this.resetForm('form')
|
||||||
|
},
|
||||||
|
/** 搜索按钮操作 */
|
||||||
|
handleQuery () {
|
||||||
|
this.queryParams.pageNum = 1
|
||||||
|
this.getList()
|
||||||
|
},
|
||||||
|
/** 重置按钮操作 */
|
||||||
|
resetQuery () {
|
||||||
|
this.resetForm('queryForm')
|
||||||
|
this.handleQuery()
|
||||||
|
},
|
||||||
|
// 多选框选中数据
|
||||||
|
handleSelectionChange (selection) {
|
||||||
|
this.ids = selection.map(item => item.id)
|
||||||
|
this.single = selection.length !== 1
|
||||||
|
this.multiple = !selection.length
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.format {
|
||||||
|
background-color: #5A8DEE;
|
||||||
|
padding: 0.5rem;
|
||||||
|
color: #fff;
|
||||||
|
border-radius: 0.3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.img-list {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
margin-top: 1rem;
|
||||||
|
|
||||||
|
.image {
|
||||||
|
width: 10rem;
|
||||||
|
margin: 0 1rem 1rem 0;
|
||||||
|
|
||||||
|
.image-name {
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
919
src/components/Chat/index.vue
Normal file
919
src/components/Chat/index.vue
Normal file
@ -0,0 +1,919 @@
|
|||||||
|
<!--
|
||||||
|
* @File: index
|
||||||
|
* @Author: 裴浩宇
|
||||||
|
* @Date: 2023/6/6 10:01
|
||||||
|
* @Description: 聊天室
|
||||||
|
-->
|
||||||
|
<template>
|
||||||
|
<div class="page">
|
||||||
|
<div class="chat-room">
|
||||||
|
<div :style="chat" class="chat" @click="previewImage($event)">
|
||||||
|
<div class="record" ref="record">
|
||||||
|
<div class="phone-info">
|
||||||
|
<div class="phone-online-number">
|
||||||
|
{{ `在线人数(${memberList.length})` }}
|
||||||
|
</div>
|
||||||
|
<i class="el-icon-edit-outline" @click="openInfo"/>
|
||||||
|
</div>
|
||||||
|
<div class="more" v-if="recordList.length > 50 && showRecordList.length < recordList.length"
|
||||||
|
@click="handleGetMore">
|
||||||
|
<svg-icon icon-class="time"/>
|
||||||
|
查看更多消息
|
||||||
|
</div>
|
||||||
|
<div class="record-one" :class="Number(one.userId) === Number(userInfo.id) ? 'is-me' : ''"
|
||||||
|
v-for="(one, index) in showRecordList"
|
||||||
|
:key="one.userId + index">
|
||||||
|
<div class="left">
|
||||||
|
<el-avatar :size="36" shape="square" :src="one.avatar">
|
||||||
|
<img src="https://cube.elemecdn.com/e/fd/0fc7d20532fdaf769a25683617711png.png"/>
|
||||||
|
</el-avatar>
|
||||||
|
</div>
|
||||||
|
<div class="right">
|
||||||
|
<div class="nick-name" v-if="one.userId === userInfo.id">
|
||||||
|
<el-tooltip class="item" effect="dark" content="回复" placement="top">
|
||||||
|
<i class="el-icon-s-comment" @click="handleReply(one)"></i>
|
||||||
|
</el-tooltip>
|
||||||
|
<el-tooltip class="item" effect="dark" :content="one.senTime" placement="top">
|
||||||
|
<span class="time">{{ parseTime(one.senTime, '{h}:{i}') }}</span>
|
||||||
|
</el-tooltip>
|
||||||
|
({{ one.location && one.location.ip }})
|
||||||
|
{{ one.nickName }}
|
||||||
|
</div>
|
||||||
|
<div class="nick-name" v-else>
|
||||||
|
{{ one.nickName }}({{ one.ip }})
|
||||||
|
<el-tooltip class="item" effect="dark" :content="one.senTime" placement="top">
|
||||||
|
<span class="time">{{ parseTime(one.senTime, '{h}:{i}') }}</span>
|
||||||
|
</el-tooltip>
|
||||||
|
<el-tooltip class="item" effect="dark" content="回复" placement="top">
|
||||||
|
<i class="el-icon-s-comment" @click="handleReply(one)"></i>
|
||||||
|
</el-tooltip>
|
||||||
|
</div>
|
||||||
|
<div class="message" v-html="one.message"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="input-box">
|
||||||
|
<div class="function-button">
|
||||||
|
<el-popover
|
||||||
|
placement="top"
|
||||||
|
title="选择表情"
|
||||||
|
trigger="click">
|
||||||
|
<emoji @select-icon="handleSelectEmoji"/>
|
||||||
|
<i class="el-icon-star-off" slot="reference"></i>
|
||||||
|
</el-popover>
|
||||||
|
<el-popover
|
||||||
|
placement="top"
|
||||||
|
title="选择文件"
|
||||||
|
trigger="click">
|
||||||
|
<file @select-file="handleSelectFile"/>
|
||||||
|
<i class="el-icon-folder" slot="reference"></i>
|
||||||
|
</el-popover>
|
||||||
|
</div>
|
||||||
|
<div class="textarea" contenteditable
|
||||||
|
ref="message" @keyup.enter="sendMessage"></div>
|
||||||
|
<el-button
|
||||||
|
:loading="sendMessageLoading"
|
||||||
|
size="mini"
|
||||||
|
@click="sendMessage"
|
||||||
|
type="primary">
|
||||||
|
发 送
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div :style="roomInfo" class="room-info">
|
||||||
|
<div class="user-info">
|
||||||
|
<i class="el-icon-arrow-left return" @click="returnChat"/>
|
||||||
|
<div class="message-board-right" v-loading="userInfoLoading">
|
||||||
|
<div class="user-head">
|
||||||
|
<el-image :src="userInfo.avatar" fit="scale-down">
|
||||||
|
<div slot="error" class="image-slot">
|
||||||
|
获取图片
|
||||||
|
</div>
|
||||||
|
</el-image>
|
||||||
|
</div>
|
||||||
|
<div class="customer-name">
|
||||||
|
<div class="label">昵称:</div>
|
||||||
|
<div class="name">
|
||||||
|
<el-tooltip :content="userInfo.nickName" placement="top" effect="light">
|
||||||
|
<span>{{ userInfo.nickName }}</span>
|
||||||
|
</el-tooltip>
|
||||||
|
</div>
|
||||||
|
<div class="ip">({{ userInfo.location?.ip }})</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="member">
|
||||||
|
<div class="online-number">
|
||||||
|
{{ `在线人数(${memberList.length})` }}
|
||||||
|
</div>
|
||||||
|
<div class="member-list">
|
||||||
|
<div class="member-one" v-for="one in memberList" :key="one.userId">
|
||||||
|
<el-avatar :size="24" shape="square" :src="one.avatar">
|
||||||
|
<img src="https://cube.elemecdn.com/e/fd/0fc7d20532fdaf769a25683617711png.png"/>
|
||||||
|
</el-avatar>
|
||||||
|
<div class="nick-name">{{ one.nickName }}({{ one.location && one.location.ip }})</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- 预览图片 -->
|
||||||
|
<el-dialog :visible.sync="imgVisible" :close-on-press-escape="true" class="img-model" :modal="false">
|
||||||
|
<model-image :previewImage="imgUrl" :imgSrcList="photoUrlList" v-if="imgVisible"/>
|
||||||
|
</el-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { getMessageRecord, loginChat, sendMessage, signOut } from '@/api'
|
||||||
|
import { WEBSOCKET_MESSAGE_TYPE } from '@/assets/js/common'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'Chat',
|
||||||
|
components: {
|
||||||
|
file: () => import('./file.vue'),
|
||||||
|
emoji: () => import('./emoji.vue'),
|
||||||
|
ModelImage: () => import('@/components/ModelImage')
|
||||||
|
},
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
// websocket
|
||||||
|
websocket: {},
|
||||||
|
// 查看更多聊天记录
|
||||||
|
more: 0,
|
||||||
|
// 图片标志
|
||||||
|
imgVisible: false,
|
||||||
|
// 图片地址
|
||||||
|
imgUrl: '',
|
||||||
|
// 图片url列表
|
||||||
|
photoUrlList: [],
|
||||||
|
// 聊天样式
|
||||||
|
chat: '',
|
||||||
|
// 个人信息样式
|
||||||
|
roomInfo: '',
|
||||||
|
// 发送信息加载标志
|
||||||
|
sendMessageLoading: false,
|
||||||
|
// 个人信息
|
||||||
|
userInfo: this.$store.state.userInfo,
|
||||||
|
// 聊天信息内容
|
||||||
|
recordList: [],
|
||||||
|
// 在线人数
|
||||||
|
memberList: [],
|
||||||
|
// 用户信息加载
|
||||||
|
userInfoLoading: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
/**
|
||||||
|
* 展示消息
|
||||||
|
*/
|
||||||
|
showRecordList () {
|
||||||
|
return this.recordList.slice(-50 - this.more)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted () {
|
||||||
|
if (!this.$store.state.userInfo?.id) {
|
||||||
|
// 判断当前用户是否已拉取完user_info信息
|
||||||
|
this.$store.dispatch('GetInfo').then(res => {
|
||||||
|
this.initWebSocket()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
// 日期格式化
|
||||||
|
parseTime (time, pattern) {
|
||||||
|
if (arguments.length === 0 || !time) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
const format = pattern || '{y}-{m}-{d} {h}:{i}:{s}'
|
||||||
|
let date
|
||||||
|
if (typeof time === 'object') {
|
||||||
|
date = time
|
||||||
|
} else {
|
||||||
|
if ((typeof time === 'string') && (/^[0-9]+$/.test(time))) {
|
||||||
|
time = parseInt(time)
|
||||||
|
} else if (typeof time === 'string') {
|
||||||
|
time = time.replace(new RegExp(/-/gm), '/')
|
||||||
|
}
|
||||||
|
if ((typeof time === 'number') && (time.toString().length === 10)) {
|
||||||
|
time = time * 1000
|
||||||
|
}
|
||||||
|
date = new Date(time)
|
||||||
|
}
|
||||||
|
const formatObj = {
|
||||||
|
y: date.getFullYear(),
|
||||||
|
m: date.getMonth() + 1,
|
||||||
|
d: date.getDate(),
|
||||||
|
h: date.getHours(),
|
||||||
|
i: date.getMinutes(),
|
||||||
|
s: date.getSeconds(),
|
||||||
|
a: date.getDay()
|
||||||
|
}
|
||||||
|
const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => {
|
||||||
|
let value = formatObj[key]
|
||||||
|
// Note: getDay() returns 0 on Sunday
|
||||||
|
if (key === 'a') {
|
||||||
|
return ['日', '一', '二', '三', '四', '五', '六'][value]
|
||||||
|
}
|
||||||
|
if (result.length > 0 && value < 10) {
|
||||||
|
value = '0' + value
|
||||||
|
}
|
||||||
|
return value || 0
|
||||||
|
})
|
||||||
|
return time_str
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 滚动最下面
|
||||||
|
*/
|
||||||
|
scroll () {
|
||||||
|
const imgInterval = setInterval(() => {
|
||||||
|
let imgFlag = true
|
||||||
|
document.querySelectorAll('.record img').forEach(img => {
|
||||||
|
try {
|
||||||
|
if (img.height === 0) {
|
||||||
|
imgFlag = false
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
throw new Error()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (imgFlag) {
|
||||||
|
clearInterval(imgInterval)
|
||||||
|
const ele = this.$refs.record
|
||||||
|
ele.scrollTop = ele.scrollHeight
|
||||||
|
}
|
||||||
|
}, 100)
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 回复
|
||||||
|
*/
|
||||||
|
handleReply (message) {
|
||||||
|
const html = this.$refs.message
|
||||||
|
const reply = document.createElement('div')
|
||||||
|
const reg = /<img.*?src=["|']?(.*?)["|']*?>/g
|
||||||
|
message.message = message.message.replace(reg, '[图片]')
|
||||||
|
reply.classList.add('chat-reply')
|
||||||
|
reply.setAttribute('contentEditable', 'false')
|
||||||
|
reply.innerHTML = `<div class="reply-icon"></div>回复 ${message.nickName}:${message.message}`
|
||||||
|
if (!html.innerHTML) {
|
||||||
|
html.innerHTML = `<div>${html.innerHTML}</div>`
|
||||||
|
}
|
||||||
|
if (html.children[0] && html.children[0].getAttribute('class') === 'chat-reply') {
|
||||||
|
html.children[0].innerHTML = `<div class="reply-icon"></div>回复 ${message.nickName}:${message.message}`
|
||||||
|
} else {
|
||||||
|
html.insertBefore(reply, html.children[0])
|
||||||
|
html.innerHTML += '<br>'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 设置光标位置
|
||||||
|
*/
|
||||||
|
handlerFocus (el) {
|
||||||
|
if (typeof window.getSelection !== 'undefined' && typeof document.createRange !== 'undefined') {
|
||||||
|
const range = document.createRange()
|
||||||
|
range.selectNodeContents(el)
|
||||||
|
range.collapse(false) // 将光标定位到内容的末尾
|
||||||
|
const sel = window.getSelection()
|
||||||
|
sel.removeAllRanges()
|
||||||
|
sel.addRange(range)
|
||||||
|
} else if (typeof document.body.createTextRange !== 'undefined') {
|
||||||
|
const textRange = document.body.createTextRange()
|
||||||
|
textRange.moveToElementText(el)
|
||||||
|
textRange.collapse(false) // 将光标定位到内容的末尾
|
||||||
|
textRange.select()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 选择表情
|
||||||
|
*/
|
||||||
|
handleSelectEmoji (emoji) {
|
||||||
|
if (emoji.position) {
|
||||||
|
this.$refs.message.innerHTML += `<span contentEditable="false" class="item-icon" style="background-position: ${emoji.position}"></span>`
|
||||||
|
} else {
|
||||||
|
this.$refs.message.innerHTML += emoji
|
||||||
|
}
|
||||||
|
this.handlerFocus(this.$refs.message)
|
||||||
|
this.$refs.message.scrollTop = this.$refs.message.scrollHeight
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 选择文件
|
||||||
|
*/
|
||||||
|
handleSelectFile (file) {
|
||||||
|
if (file.isPicture) {
|
||||||
|
this.$refs.message.innerHTML += `<div class="image"><img src="${file.url}" alt="${file.name}"></div><br>`
|
||||||
|
} else {
|
||||||
|
this.$refs.message.innerHTML +=
|
||||||
|
`<div class="chat-file" contentEditable="false">
|
||||||
|
<div class="top">
|
||||||
|
<i class="el-icon-folder"></i>
|
||||||
|
${file.name}
|
||||||
|
</div>
|
||||||
|
<div class="bottom">
|
||||||
|
<a href="${file.url}">下载</a>
|
||||||
|
</div>
|
||||||
|
</div><br>`
|
||||||
|
}
|
||||||
|
this.handlerFocus(this.$refs.message)
|
||||||
|
this.$refs.message.scrollTop = this.$refs.message.scrollHeight
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 获取消息
|
||||||
|
*/
|
||||||
|
getMessageRecord () {
|
||||||
|
getMessageRecord().then(res => {
|
||||||
|
this.recordList = res.data
|
||||||
|
this.recordList.map(record => {
|
||||||
|
const userInfo = this.memberList.find(item => item.userId === record.userId)
|
||||||
|
if (!userInfo) return record
|
||||||
|
record.nickName = userInfo.nickName
|
||||||
|
record.avatar = userInfo.avatar
|
||||||
|
record.ip = userInfo.location.ip
|
||||||
|
return record
|
||||||
|
})
|
||||||
|
this.scroll()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 查看更多
|
||||||
|
*/
|
||||||
|
handleGetMore () {
|
||||||
|
this.more += 10
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 预览图片
|
||||||
|
* @param event
|
||||||
|
*/
|
||||||
|
previewImage (event) {
|
||||||
|
if (event.target.nodeName === 'IMG') {
|
||||||
|
this.photoUrlList = []
|
||||||
|
// eslint-disable-next-line no-undef
|
||||||
|
const obj = $('.markdown-body img')
|
||||||
|
for (let i = 0; i < obj.length; i++) {
|
||||||
|
this.photoUrlList.push(obj[i].getAttribute('src'))
|
||||||
|
}
|
||||||
|
this.imgVisible = true
|
||||||
|
this.imgUrl = event.target.getAttribute('src')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 发送信息
|
||||||
|
*/
|
||||||
|
sendMessage () {
|
||||||
|
if (this.sendMessageLoading) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (this.websocket.readyState !== 1) {
|
||||||
|
this.initWebSocket()
|
||||||
|
}
|
||||||
|
if (!this.$refs.message.innerHTML.replace(/<div><br><\/div>/g, '')) {
|
||||||
|
this.$message.error('发送内容不能为空,请重新输入')
|
||||||
|
this.$refs.message.innerHTML = ''
|
||||||
|
} else {
|
||||||
|
let message = this.$refs.message.innerHTML.replace(/<div><br><\/div>/g, '')
|
||||||
|
message = message.replace(/<br>/g, '')
|
||||||
|
this.$refs.message.innerHTML = ''
|
||||||
|
this.sendMessageLoading = true
|
||||||
|
sendMessage({ message: message }).then(res => {
|
||||||
|
if (res.code === 200) {
|
||||||
|
this.sendMessageLoading = false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 返回聊天
|
||||||
|
*/
|
||||||
|
returnChat () {
|
||||||
|
this.chat = 'display: flex!important;'
|
||||||
|
this.roomInfo = 'display: none!important;'
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 打开信息
|
||||||
|
*/
|
||||||
|
openInfo () {
|
||||||
|
this.chat = 'display: none!important;'
|
||||||
|
this.roomInfo = 'display: flex!important;'
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 退出登录
|
||||||
|
*/
|
||||||
|
signOut () {
|
||||||
|
signOut().then(res => {
|
||||||
|
this.memberList = res.data
|
||||||
|
this.recordList = []
|
||||||
|
this.memberList = []
|
||||||
|
this.onbeforeunload()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 初始化webSocket
|
||||||
|
*/
|
||||||
|
initWebSocket () {
|
||||||
|
// WebSocket
|
||||||
|
if ('WebSocket' in window) {
|
||||||
|
this.websocket = new WebSocket(`${process.env.VUE_APP_SOCKET}/${this.userInfo.id}`)
|
||||||
|
// 连接错误
|
||||||
|
this.websocket.onerror = this.setErrorMessage
|
||||||
|
// 连接成功
|
||||||
|
this.websocket.onopen = this.setOnopenMessage
|
||||||
|
// 收到消息的回调
|
||||||
|
this.websocket.onmessage = this.setOnmessageMessage
|
||||||
|
// 连接关闭的回调
|
||||||
|
this.websocket.onclose = this.setOncloseMessage
|
||||||
|
} else {
|
||||||
|
alert('当前浏览器 Not support websocket')
|
||||||
|
}
|
||||||
|
// 监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
|
||||||
|
window.onbeforeunload = this.onbeforeunload
|
||||||
|
},
|
||||||
|
setErrorMessage () {
|
||||||
|
console.log('WebSocket连接发生错误 状态码:' + this.websocket.readyState)
|
||||||
|
},
|
||||||
|
setOnopenMessage () {
|
||||||
|
console.log('WebSocket连接成功 状态码:' + this.websocket.readyState)
|
||||||
|
this.userInfoLoading = true
|
||||||
|
loginChat().then(res => {
|
||||||
|
if (res.data.length > 0) {
|
||||||
|
this.memberList = res.data.filter(item => {
|
||||||
|
return item.avatar && item.nickName
|
||||||
|
})
|
||||||
|
this.userInfoLoading = false
|
||||||
|
this.getMessageRecord()
|
||||||
|
} else {
|
||||||
|
this.$message.error('网络异常,请清理缓存刷新当前页面')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
setOnmessageMessage (event) {
|
||||||
|
// 根据服务器推送的消息做自己的业务处理
|
||||||
|
const data = JSON.parse(event.data)
|
||||||
|
switch (data.webSocket) {
|
||||||
|
case WEBSOCKET_MESSAGE_TYPE.CHAT_MESSAGE:
|
||||||
|
// eslint-disable-next-line no-case-declarations
|
||||||
|
const message = data.message
|
||||||
|
// 消费消息
|
||||||
|
// eslint-disable-next-line no-case-declarations
|
||||||
|
const userInfo = this.memberList.find(item => Number(item.userId) === Number(message.userId))
|
||||||
|
message.nickName = userInfo.nickName
|
||||||
|
message.avatar = userInfo.avatar
|
||||||
|
message.ip = userInfo.location.ip
|
||||||
|
this.recordList.push(message)
|
||||||
|
this.scroll()
|
||||||
|
break
|
||||||
|
case WEBSOCKET_MESSAGE_TYPE.LOGIN:
|
||||||
|
// 登录
|
||||||
|
// eslint-disable-next-line no-case-declarations
|
||||||
|
let flag = true
|
||||||
|
this.memberList.forEach(item => {
|
||||||
|
if (data.message.userId === item.userId) {
|
||||||
|
flag = false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (flag) {
|
||||||
|
this.memberList.push(data.message)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case WEBSOCKET_MESSAGE_TYPE.LOG_OUT:
|
||||||
|
// 退出登录
|
||||||
|
this.memberList = this.memberList.filter(item => {
|
||||||
|
return item.userId !== data.userId
|
||||||
|
})
|
||||||
|
break
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setOncloseMessage () {
|
||||||
|
console.log('WebSocket连接关闭 状态码:' + this.websocket.readyState)
|
||||||
|
},
|
||||||
|
onbeforeunload () {
|
||||||
|
this.closeWebSocket()
|
||||||
|
},
|
||||||
|
closeWebSocket () {
|
||||||
|
this.websocket.close()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
beforeDestroy () {
|
||||||
|
this.signOut()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@media screen and (max-width: 1000px) {
|
||||||
|
.chat-room {
|
||||||
|
height: 94% !important;
|
||||||
|
width: 94% !important;
|
||||||
|
|
||||||
|
.user-info {
|
||||||
|
.return {
|
||||||
|
display: flex !important;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
padding: 0.8rem 0.5rem;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.phone-info {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
color: #909090;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
|
||||||
|
i {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.room-info {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.phone-info {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#chat-room {
|
||||||
|
display: flex;
|
||||||
|
height: 90vh;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-room {
|
||||||
|
display: flex;
|
||||||
|
height: 82vh;
|
||||||
|
border: 1px solid #d9d9d9;
|
||||||
|
border-radius: 5px;
|
||||||
|
background-color: #F5F6F7;
|
||||||
|
|
||||||
|
.chat {
|
||||||
|
flex: 7;
|
||||||
|
border-right: 1px solid #d9d9d9;
|
||||||
|
display: flex;
|
||||||
|
flex-flow: column;
|
||||||
|
width: 70%;
|
||||||
|
|
||||||
|
::v-deep .item-icon {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
display: inline-block;
|
||||||
|
background-image: url('../../assets/images/emoji/emoji.png');
|
||||||
|
background-size: 1100% 1000%;
|
||||||
|
background-position: 0 0;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
::v-deep .chat-file {
|
||||||
|
background: #FFF;
|
||||||
|
border-radius: 0.3rem;
|
||||||
|
border: 1px solid #EBEBEB;
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
user-select: none;
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 1rem;
|
||||||
|
color: #666;
|
||||||
|
|
||||||
|
i {
|
||||||
|
font-size: 2rem;
|
||||||
|
color: #5A8DEE;
|
||||||
|
margin-right: 0.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.bottom {
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
border-top: 1px solid #EBEBEB;
|
||||||
|
color: #5A8DEE;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
text-align: right;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
::v-deep .chat-reply {
|
||||||
|
padding: 0.5rem;
|
||||||
|
color: #8F959E;
|
||||||
|
background-color: #e8e9eb;
|
||||||
|
border-radius: 0.2rem;
|
||||||
|
|
||||||
|
.reply-icon {
|
||||||
|
width: 0.2rem;
|
||||||
|
height: 1rem;
|
||||||
|
background-color: #BBBFC4;
|
||||||
|
display: inline-block;
|
||||||
|
margin-right: 0.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.record {
|
||||||
|
flex: 5;
|
||||||
|
border-bottom: 1px solid #d9d9d9;
|
||||||
|
height: 28rem;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 1rem 1rem 0 1rem;
|
||||||
|
|
||||||
|
.more {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: #2875FF;
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
|
||||||
|
.svg-icon {
|
||||||
|
margin-right: 0.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.record-one {
|
||||||
|
display: flex;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
.el-icon-s-comment {
|
||||||
|
display: block !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.left {
|
||||||
|
margin-right: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right {
|
||||||
|
.nick-name {
|
||||||
|
color: #999;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
margin-left: 0.5rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
height: 1.2rem;
|
||||||
|
|
||||||
|
.el-icon-s-comment {
|
||||||
|
display: none;
|
||||||
|
font-size: 1.4rem;
|
||||||
|
margin-left: 0.5rem;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: #5A8DEE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.message {
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
padding: 0.5rem;
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 5px;
|
||||||
|
width: fit-content;
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-end;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
::v-deep img {
|
||||||
|
max-width: 10rem;
|
||||||
|
cursor: pointer;
|
||||||
|
max-height: 10rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.is-me {
|
||||||
|
flex-flow: row-reverse !important;
|
||||||
|
|
||||||
|
.message {
|
||||||
|
-moz-border-radius-topright: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-end;
|
||||||
|
|
||||||
|
.nick-name {
|
||||||
|
text-align: right;
|
||||||
|
margin-right: 0.5rem;
|
||||||
|
|
||||||
|
.el-icon-s-comment {
|
||||||
|
margin-right: 0.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.message {
|
||||||
|
margin-left: 0 !important;
|
||||||
|
margin-right: 0.5rem;
|
||||||
|
background-color: #5A8DEE;
|
||||||
|
color: #FFFFFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message:before {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-box {
|
||||||
|
flex: 2;
|
||||||
|
padding: 0 1rem 1rem 1rem;
|
||||||
|
display: flex;
|
||||||
|
flex-flow: column;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.function-button {
|
||||||
|
padding-top: 0.6rem;
|
||||||
|
|
||||||
|
i {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
padding: 0.5rem;
|
||||||
|
margin-right: 0.4rem;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: #E7E8E9;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.textarea {
|
||||||
|
flex: 8;
|
||||||
|
padding: 0.5rem 0;
|
||||||
|
overflow: scroll;
|
||||||
|
|
||||||
|
::v-deep img {
|
||||||
|
max-width: 10rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
flex: 2;
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
padding-top: 0.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.room-info {
|
||||||
|
flex: 3;
|
||||||
|
display: flex;
|
||||||
|
flex-flow: column;
|
||||||
|
|
||||||
|
.user-info {
|
||||||
|
flex: 2;
|
||||||
|
border-bottom: 1px solid #d9d9d9;
|
||||||
|
|
||||||
|
.return {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-board-right {
|
||||||
|
display: flex;
|
||||||
|
padding: 1rem;
|
||||||
|
flex-flow: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-around;
|
||||||
|
|
||||||
|
.user-head {
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
.el-image {
|
||||||
|
border-radius: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.customer-name {
|
||||||
|
height: 2rem;
|
||||||
|
width: 100%;
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.label {
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.name {
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
color: #5A8DEE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
padding-top: 0.5rem;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.your-header {
|
||||||
|
width: 6rem;
|
||||||
|
height: 6rem;
|
||||||
|
border: 1px solid #999;
|
||||||
|
border-radius: 5px;
|
||||||
|
display: flex;
|
||||||
|
flex-flow: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
#headerPhoto {
|
||||||
|
width: 7rem;
|
||||||
|
position: absolute;
|
||||||
|
transform: scale(0.7);
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
::v-deep .image-slot {
|
||||||
|
font-size: 0.4rem;
|
||||||
|
transform: scale(0.8);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-icon {
|
||||||
|
position: absolute;
|
||||||
|
margin: -5rem -5rem 0 0;
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-picture {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
padding: 0.5rem;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.member {
|
||||||
|
flex: 5;
|
||||||
|
padding: 1rem;
|
||||||
|
|
||||||
|
.online-number {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.member-list {
|
||||||
|
.member-one {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding-top: 0.5rem;
|
||||||
|
|
||||||
|
.nick-name {
|
||||||
|
margin-left: 0.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 图片预览样式开始*/
|
||||||
|
.el-image-viewer__canvas {
|
||||||
|
width: 60%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-image-viewer__next {
|
||||||
|
right: calc(40vw + 4rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
.img-model {
|
||||||
|
::v-deep .el-dialog {
|
||||||
|
margin: 20vh 25vw !important;
|
||||||
|
width: 50vw;
|
||||||
|
height: 60vh;
|
||||||
|
background-color: rgb(222, 222, 222) !important;
|
||||||
|
|
||||||
|
.el-dialog__body {
|
||||||
|
height: 90%;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
229
src/components/ModelImage/index.vue
Normal file
229
src/components/ModelImage/index.vue
Normal file
@ -0,0 +1,229 @@
|
|||||||
|
<template>
|
||||||
|
<div class="model">
|
||||||
|
<div class="img" v-dom-drag>
|
||||||
|
<i @click="lastImg" class="el-icon-arrow-left left" v-if="imgSrcList.length > 0"/>
|
||||||
|
<img :src="url" @mousewheel.prevent="rollImg()" alt="example" class="img" ref="img"/>
|
||||||
|
<i @click="nextImg" class="el-icon-arrow-right right" v-if="imgSrcList.length > 0"/>
|
||||||
|
</div>
|
||||||
|
<div class="roll-img">
|
||||||
|
<i @click="enlarge" class="el-icon-circle-plus-outline"/>
|
||||||
|
<i @click="narrow" class="el-icon-remove-outline"/>
|
||||||
|
<i @click="downloadImage" class="el-icon-download"/>
|
||||||
|
<i @click="transformRight($event)" class="el-icon-refresh-right"/>
|
||||||
|
<i @click="transformLeft($event)" class="el-icon-refresh-left"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
previewImage: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
imgSrcList: {
|
||||||
|
type: Array,
|
||||||
|
default: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
url: this.previewImage
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
/**
|
||||||
|
* 上一张图片
|
||||||
|
*/
|
||||||
|
lastImg() {
|
||||||
|
let index = this.imgSrcList.indexOf(this.url);
|
||||||
|
if (index < 1) {
|
||||||
|
this.url = this.imgSrcList[this.imgSrcList.length - 1];
|
||||||
|
} else {
|
||||||
|
this.url = this.imgSrcList[index - 1];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 下一张图片
|
||||||
|
*/
|
||||||
|
nextImg() {
|
||||||
|
let index = this.imgSrcList.indexOf(this.url);
|
||||||
|
if (index > this.imgSrcList.length - 2) {
|
||||||
|
this.url = this.imgSrcList[0];
|
||||||
|
} else {
|
||||||
|
this.url = this.imgSrcList[index + 1];
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 下载图片
|
||||||
|
*/
|
||||||
|
downloadImage() {
|
||||||
|
this.downloadImg(this.url, '下载图片')
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 获取 blob
|
||||||
|
* @param {String} url 目标文件地址
|
||||||
|
* @return {Promise}
|
||||||
|
*/
|
||||||
|
getBlob(url) {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
const xhr = new XMLHttpRequest();
|
||||||
|
xhr.open('GET', url, true);
|
||||||
|
xhr.setRequestHeader('Access-Control-Allow-Origin', '*');
|
||||||
|
xhr.responseType = 'blob';
|
||||||
|
xhr.onload = () => {
|
||||||
|
if (xhr.status === 200) {
|
||||||
|
resolve(xhr.response);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
xhr.send();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存
|
||||||
|
* @param {Blob} blob
|
||||||
|
* @param {String} filename 想要保存的文件名称
|
||||||
|
*/
|
||||||
|
saveAs(blob, filename) {
|
||||||
|
if (window.navigator.msSaveOrOpenBlob) {
|
||||||
|
navigator.msSaveBlob(blob, filename);
|
||||||
|
} else {
|
||||||
|
const link = document.createElement('a');
|
||||||
|
const body = document.querySelector('body');
|
||||||
|
|
||||||
|
link.href = window.URL.createObjectURL(blob);
|
||||||
|
link.download = filename;
|
||||||
|
|
||||||
|
// fix Firefox
|
||||||
|
link.style.display = 'none';
|
||||||
|
body.appendChild(link);
|
||||||
|
|
||||||
|
link.click();
|
||||||
|
body.removeChild(link);
|
||||||
|
|
||||||
|
window.URL.revokeObjectURL(link.href);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 下载
|
||||||
|
* @param {String} url 目标文件地址
|
||||||
|
* @param {String} filename 想要保存的文件名称
|
||||||
|
*/
|
||||||
|
downloadImg(url, filename) {
|
||||||
|
this.getBlob(url).then(blob => {
|
||||||
|
this.saveAs(blob, filename);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 向右旋转90度
|
||||||
|
*/
|
||||||
|
transformRight() {
|
||||||
|
let current = Number(this.$refs.img.style.transform.replace(/[^0-9]/ig, "")) % 360;
|
||||||
|
this.$refs.img.style.transform = `rotate(${current + 90}deg)`
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 向左旋转90度
|
||||||
|
*/
|
||||||
|
transformLeft() {
|
||||||
|
let current = Number(this.$refs.img.style.transform.replace(/[^0-9]/ig, "")) % 360;
|
||||||
|
this.$refs.img.style.transform = `rotate(${current + 270}deg)`
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 缩小
|
||||||
|
*/
|
||||||
|
narrow() {
|
||||||
|
let zoom = parseInt(this.$refs.img.style.zoom) || 100;
|
||||||
|
this.$refs.img.style.zoom = (zoom - 10) + '%';
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 放大
|
||||||
|
*/
|
||||||
|
enlarge() {
|
||||||
|
let zoom = parseInt(this.$refs.img.style.zoom) || 100;
|
||||||
|
this.$refs.img.style.zoom = (zoom + 10) + '%';
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 图片缩放
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
rollImg() {
|
||||||
|
let zoom = parseInt(this.$refs.img.style.zoom) || 100;
|
||||||
|
zoom += event.wheelDelta / 12;
|
||||||
|
this.$refs.img.style.zoom = zoom + '%';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.model {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
img {
|
||||||
|
max-width: 80vw;
|
||||||
|
zoom: 60%;
|
||||||
|
user-select: none;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.img {
|
||||||
|
color: #ffffff;
|
||||||
|
font-size: 3rem;
|
||||||
|
|
||||||
|
.left {
|
||||||
|
position: absolute;
|
||||||
|
left: 1rem;
|
||||||
|
top: calc(50% - 2rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
.right {
|
||||||
|
position: absolute;
|
||||||
|
right: 1rem;
|
||||||
|
top: calc(50% - 2rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
i {
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: #606266;
|
||||||
|
height: 4rem;
|
||||||
|
width: 4rem;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 4rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.roll-img {
|
||||||
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
|
bottom: 2rem;
|
||||||
|
transform: translate(-50%, 0);
|
||||||
|
color: #ffffff;
|
||||||
|
font-size: 3rem;
|
||||||
|
padding: 0.5rem 1.5rem;
|
||||||
|
background-color: #606266;
|
||||||
|
border-color: #fff;
|
||||||
|
border-radius: 2rem;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
i {
|
||||||
|
cursor: pointer;
|
||||||
|
margin-right: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
i:nth-child(5) {
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
101
src/components/Pagination/index.vue
Normal file
101
src/components/Pagination/index.vue
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
<template>
|
||||||
|
<div :class="{'hidden':hidden}" class="pagination-container">
|
||||||
|
<el-pagination
|
||||||
|
:background="background"
|
||||||
|
:current-page.sync="currentPage"
|
||||||
|
:page-size.sync="pageSize"
|
||||||
|
:layout="layout"
|
||||||
|
:page-sizes="pageSizes"
|
||||||
|
:total="total"
|
||||||
|
v-bind="$attrs"
|
||||||
|
@size-change="handleSizeChange"
|
||||||
|
@current-change="handleCurrentChange"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {scrollTo} from '@/utils/scroll-to'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'Pagination',
|
||||||
|
props: {
|
||||||
|
total: {
|
||||||
|
required: true,
|
||||||
|
type: Number
|
||||||
|
},
|
||||||
|
page: {
|
||||||
|
type: Number,
|
||||||
|
default: 1
|
||||||
|
},
|
||||||
|
limit: {
|
||||||
|
type: Number,
|
||||||
|
default: 20
|
||||||
|
},
|
||||||
|
pageSizes: {
|
||||||
|
type: Array,
|
||||||
|
default() {
|
||||||
|
return [10, 20, 30, 50]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
layout: {
|
||||||
|
type: String,
|
||||||
|
default: 'total, sizes, prev, pager, next, jumper'
|
||||||
|
},
|
||||||
|
background: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
autoScroll: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
hidden: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
currentPage: {
|
||||||
|
get() {
|
||||||
|
return this.page
|
||||||
|
},
|
||||||
|
set(val) {
|
||||||
|
this.$emit('update:page', val)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
pageSize: {
|
||||||
|
get() {
|
||||||
|
return this.limit
|
||||||
|
},
|
||||||
|
set(val) {
|
||||||
|
this.$emit('update:limit', val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
handleSizeChange(val) {
|
||||||
|
this.$emit('pagination', {page: this.currentPage, limit: val})
|
||||||
|
if (this.autoScroll) {
|
||||||
|
scrollTo(0, 800)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleCurrentChange(val) {
|
||||||
|
this.$emit('pagination', {page: val, limit: this.pageSize})
|
||||||
|
if (this.autoScroll) {
|
||||||
|
scrollTo(0, 800)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.pagination-container {
|
||||||
|
padding: 32px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-container.hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
</style>
|
18
src/main.js
18
src/main.js
@ -1,16 +1,14 @@
|
|||||||
import Vue from 'vue';
|
import Vue from 'vue'
|
||||||
import App from './App.vue';
|
import App from './App.vue'
|
||||||
import router from './router';
|
import store from './store'
|
||||||
import store from './store';
|
import ElementUI from 'element-ui'
|
||||||
import ElementUI from 'element-ui';
|
import 'element-ui/lib/theme-chalk/index.css'
|
||||||
import 'element-ui/lib/theme-chalk/index.css';
|
import axios from 'axios'
|
||||||
import axios from 'axios';
|
Vue.prototype.$axios = axios
|
||||||
Vue.prototype.$axios = axios;
|
Vue.use(ElementUI)
|
||||||
Vue.use(ElementUI);
|
|
||||||
Vue.config.productionTip = false
|
Vue.config.productionTip = false
|
||||||
|
|
||||||
new Vue({
|
new Vue({
|
||||||
router,
|
|
||||||
store,
|
store,
|
||||||
render: h => h(App)
|
render: h => h(App)
|
||||||
}).$mount('#app')
|
}).$mount('#app')
|
||||||
|
@ -1,34 +0,0 @@
|
|||||||
import Vue from 'vue'
|
|
||||||
import VueRouter from 'vue-router'
|
|
||||||
|
|
||||||
Vue.use(VueRouter)
|
|
||||||
|
|
||||||
const routes = [
|
|
||||||
{
|
|
||||||
path: '/',
|
|
||||||
name: 'index',
|
|
||||||
component: () => import("../views/Index")
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
const router = new VueRouter({
|
|
||||||
routes
|
|
||||||
})
|
|
||||||
|
|
||||||
// 导航守卫
|
|
||||||
// 使用 router.beforeEach 注册一个全局前置守卫,判断用户是否登陆
|
|
||||||
router.beforeEach((to, from, next) => {
|
|
||||||
if (to.path === '/login') {
|
|
||||||
next();
|
|
||||||
} else {
|
|
||||||
let token = localStorage.getItem('Authorization');
|
|
||||||
|
|
||||||
if (token === 'null' || token === '') {
|
|
||||||
next('/login');
|
|
||||||
} else {
|
|
||||||
next();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
export default router
|
|
@ -1,15 +1,32 @@
|
|||||||
import Vue from 'vue'
|
import Vue from 'vue'
|
||||||
import Vuex from 'vuex'
|
import Vuex from 'vuex'
|
||||||
|
import { getInfo } from '@/api'
|
||||||
|
import { getToken } from '@/utils/auth'
|
||||||
|
|
||||||
Vue.use(Vuex)
|
Vue.use(Vuex)
|
||||||
|
|
||||||
export default new Vuex.Store({
|
export default new Vuex.Store({
|
||||||
state: {
|
state: {
|
||||||
|
// 用户信息
|
||||||
|
userInfo: {}
|
||||||
},
|
},
|
||||||
mutations: {
|
mutations: {
|
||||||
|
SET_USER_INFO: (state, userInfo) => {
|
||||||
|
state.userInfo = userInfo
|
||||||
|
}
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
|
// 获取用户信息
|
||||||
|
GetInfo ({ commit, state }) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
getInfo(getToken()).then(res => {
|
||||||
|
commit('SET_USER_INFO', res.user)
|
||||||
|
resolve(res)
|
||||||
|
}).catch(error => {
|
||||||
|
reject(error)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
},
|
},
|
||||||
modules: {
|
modules: {}
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
@ -1,15 +1,17 @@
|
|||||||
import Cookies from 'js-cookie'
|
/**
|
||||||
|
* 保存到localStorage中
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
const TokenKey = 'Admin-Token'
|
const TokenKey = 'Admin-Token'
|
||||||
|
|
||||||
export function getToken() {
|
export function getToken() {
|
||||||
return Cookies.get(TokenKey)
|
return localStorage.getItem(TokenKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setToken(token) {
|
export function setToken(token) {
|
||||||
return Cookies.set(TokenKey, token)
|
return localStorage.setItem(TokenKey, token)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function removeToken() {
|
export function removeToken() {
|
||||||
return Cookies.remove(TokenKey)
|
return localStorage.removeItem(TokenKey)
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import { Notification, MessageBox, Message } from 'element-ui'
|
import {Message, Notification} from 'element-ui'
|
||||||
import store from '@/store'
|
import {getToken} from '@/utils/auth'
|
||||||
import { getToken } from '@/utils/auth'
|
|
||||||
import errorCode from '@/utils/errorCode'
|
import errorCode from '@/utils/errorCode'
|
||||||
|
|
||||||
axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8'
|
axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8'
|
||||||
|
60
src/utils/scroll-to.js
Normal file
60
src/utils/scroll-to.js
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
Math.easeInOutQuad = function (t, b, c, d) {
|
||||||
|
t /= d / 2
|
||||||
|
if (t < 1) {
|
||||||
|
return c / 2 * t * t + b
|
||||||
|
}
|
||||||
|
t--
|
||||||
|
return -c / 2 * (t * (t - 2) - 1) + b
|
||||||
|
}
|
||||||
|
|
||||||
|
// requestAnimationFrame for Smart Animating http://goo.gl/sx5sts
|
||||||
|
var requestAnimFrame = (function () {
|
||||||
|
return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || function (callback) {
|
||||||
|
window.setTimeout(callback, 1000 / 60)
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Because it's so fucking difficult to detect the scrolling element, just move them all
|
||||||
|
* @param {number} amount
|
||||||
|
*/
|
||||||
|
function move(amount) {
|
||||||
|
document.documentElement.scrollTop = amount
|
||||||
|
document.body.parentNode.scrollTop = amount
|
||||||
|
document.body.scrollTop = amount
|
||||||
|
}
|
||||||
|
|
||||||
|
function position() {
|
||||||
|
return document.documentElement.scrollTop || document.body.parentNode.scrollTop || document.body.scrollTop
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {number} to
|
||||||
|
* @param {number} duration
|
||||||
|
* @param {Function} callback
|
||||||
|
*/
|
||||||
|
export function scrollTo(to, duration, callback) {
|
||||||
|
const start = position()
|
||||||
|
const change = to - start
|
||||||
|
const increment = 20
|
||||||
|
let currentTime = 0
|
||||||
|
duration = (typeof (duration) === 'undefined') ? 500 : duration
|
||||||
|
var animateScroll = function () {
|
||||||
|
// increment the time
|
||||||
|
currentTime += increment
|
||||||
|
// find the value with the quadratic in-out easing function
|
||||||
|
var val = Math.easeInOutQuad(currentTime, start, change, duration)
|
||||||
|
// move the document.body
|
||||||
|
move(val)
|
||||||
|
// do the animation unless its over
|
||||||
|
if (currentTime < duration) {
|
||||||
|
requestAnimFrame(animateScroll)
|
||||||
|
} else {
|
||||||
|
if (callback && typeof (callback) === 'function') {
|
||||||
|
// the animation is done so lets callback
|
||||||
|
callback()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
animateScroll()
|
||||||
|
}
|
@ -1,675 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div id="chat-room">
|
|
||||||
<div v-if="chatFlag" class="chat-room">
|
|
||||||
<div :style="chat" class="chat">
|
|
||||||
<div class="record" ref="record">
|
|
||||||
<div class="phone-info">
|
|
||||||
<div class="phone-online-number">
|
|
||||||
{{`在线人数(${memberList.length})`}}
|
|
||||||
</div>
|
|
||||||
<i class="el-icon-edit-outline" @click="openInfo"/>
|
|
||||||
</div>
|
|
||||||
<div class="record-one" :class="one.userId === myId ? 'is-me' : ''"
|
|
||||||
v-for="one in recordList.slice(recordList.length - 50, recordList.length)"
|
|
||||||
:key="one.userId">
|
|
||||||
<div class="left">
|
|
||||||
<el-avatar :size="36" shape="square" :src="userHeader(one.userId)">
|
|
||||||
<img src="https://cube.elemecdn.com/e/fd/0fc7d20532fdaf769a25683617711png.png"/>
|
|
||||||
</el-avatar>
|
|
||||||
</div>
|
|
||||||
<div class="right">
|
|
||||||
<div class="nick-name">{{one.nickName}}</div>
|
|
||||||
<div class="message" v-html="one.message"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="input-box">
|
|
||||||
<div class="textarea" contenteditable
|
|
||||||
ref="message" @keyup.enter="sendMessage"></div>
|
|
||||||
<div class="button">
|
|
||||||
<el-button
|
|
||||||
:loading="sendMessageLoading"
|
|
||||||
size="mini"
|
|
||||||
@click="sendMessage"
|
|
||||||
type="primary">
|
|
||||||
发 送
|
|
||||||
</el-button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div :style="roomInfo" class="room-info">
|
|
||||||
<div class="user-info">
|
|
||||||
<i class="el-icon-arrow-left return" @click="returnChat"/>
|
|
||||||
<div class="message-board-right">
|
|
||||||
<div class="your-header">
|
|
||||||
<el-image
|
|
||||||
class="header-picture"
|
|
||||||
:src="userInfo.userHeader"
|
|
||||||
fit="scale-down">
|
|
||||||
<div slot="error" class="image-slot">
|
|
||||||
请上传头像
|
|
||||||
</div>
|
|
||||||
</el-image>
|
|
||||||
</div>
|
|
||||||
<div class="customer-name">
|
|
||||||
<div class="label">昵称:</div>
|
|
||||||
<div class="name">
|
|
||||||
<el-tooltip :content="userInfo.nickName" placement="top" effect="light">
|
|
||||||
<span>{{userInfo.nickName}}</span>
|
|
||||||
</el-tooltip>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="customer-mail">
|
|
||||||
<div class="label">邮箱: </div>
|
|
||||||
<div class="name">
|
|
||||||
<el-tooltip :content="userInfo.userMailbox" placement="top" effect="light">
|
|
||||||
<span>{{userInfo.userMailbox}}</span>
|
|
||||||
</el-tooltip>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="button">
|
|
||||||
<el-button type="danger" size="mini" @click="signOut">退出</el-button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="member">
|
|
||||||
<div class="online-number">
|
|
||||||
{{`在线人数(${memberList.length})`}}
|
|
||||||
</div>
|
|
||||||
<div class="member-list">
|
|
||||||
<div class="member-one" v-for="one in memberList" :key="one.nickName">
|
|
||||||
<el-avatar :size="24" shape="square" :src="one.userHeader">
|
|
||||||
<img src="https://cube.elemecdn.com/e/fd/0fc7d20532fdaf769a25683617711png.png"/>
|
|
||||||
</el-avatar>
|
|
||||||
<div class="nick-name">{{one.nickName}}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<el-dialog
|
|
||||||
:title="title"
|
|
||||||
:visible.sync="loginVisible"
|
|
||||||
:close-on-click-modal="false"
|
|
||||||
:show-close="false"
|
|
||||||
:modal="false"
|
|
||||||
width="fit-content">
|
|
||||||
<div class="login">
|
|
||||||
<div class="your-header">
|
|
||||||
<el-image
|
|
||||||
class="header-picture"
|
|
||||||
:src="userInfo.userHeader"
|
|
||||||
fit="scale-down">
|
|
||||||
<div slot="error" class="image-slot">
|
|
||||||
请上传头像
|
|
||||||
</div>
|
|
||||||
</el-image>
|
|
||||||
<i @click="deleteHeader" class="el-icon-circle-close close-icon" v-if="userInfo.userHeader"/>
|
|
||||||
<input v-if="!userInfo.userHeader" type="file" id="headerPhoto" capture="camera" accept="image/*" @change="uploadHeader($event)"/>
|
|
||||||
</div>
|
|
||||||
<div class="customer-name">
|
|
||||||
<div class="label">昵称:</div>
|
|
||||||
<div class="name">
|
|
||||||
<el-input size="mini"
|
|
||||||
v-model="userInfo.nickName"
|
|
||||||
@keyup.enter="loginChat"
|
|
||||||
placeholder="请输入您的昵称"/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="customer-mail">
|
|
||||||
<div class="label">邮箱: </div>
|
|
||||||
<div class="name">
|
|
||||||
<el-input size="mini"
|
|
||||||
v-model="userInfo.userMailbox"
|
|
||||||
@keyup.enter="loginChat"
|
|
||||||
placeholder="请输入您的邮箱"/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="button">
|
|
||||||
<el-button type="primary" size="mini" @click="loginChat">登录</el-button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</el-dialog>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import { sendMessage, signOut, loginChat } from "../api/index.js";
|
|
||||||
import { compressImage } from '@/utils/compressImage'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'index',
|
|
||||||
data() {
|
|
||||||
let userInfo;
|
|
||||||
let chatFlag = false;
|
|
||||||
let loginVisible = true;
|
|
||||||
if (sessionStorage.getItem('loginInfo') === null || sessionStorage.getItem('loginInfo') === '') {
|
|
||||||
userInfo = {
|
|
||||||
userHeader: '',
|
|
||||||
nickName: '',
|
|
||||||
userMailbox: '',
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
userInfo = JSON.parse(sessionStorage.getItem('loginInfo'))
|
|
||||||
this.initWebSocket(userInfo);
|
|
||||||
chatFlag = true;
|
|
||||||
loginVisible = false;
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
//聊天样式
|
|
||||||
chat: '',
|
|
||||||
//个人信息样式
|
|
||||||
roomInfo: '',
|
|
||||||
//发送信息加载标志
|
|
||||||
sendMessageLoading: false,
|
|
||||||
//聊天室
|
|
||||||
chatFlag: chatFlag,
|
|
||||||
//弹框标题
|
|
||||||
title: '登录',
|
|
||||||
//个人信息
|
|
||||||
userInfo: userInfo,
|
|
||||||
//聊天信息内容
|
|
||||||
recordList: [],
|
|
||||||
//在线人数
|
|
||||||
memberList: [],
|
|
||||||
//登录框
|
|
||||||
loginVisible: loginVisible,
|
|
||||||
//压缩配置
|
|
||||||
config: {
|
|
||||||
proportion: 0.4,
|
|
||||||
quality: 0.8
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
myId() {
|
|
||||||
return 'chat' + this.userInfo.nickName + returnCitySN['cip']
|
|
||||||
},
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
recordList() {
|
|
||||||
this.$nextTick(function(){
|
|
||||||
const p = this.$refs.record;
|
|
||||||
if (p) {
|
|
||||||
p.scrollTop = p.scrollHeight;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
/**
|
|
||||||
* 返回聊天
|
|
||||||
*/
|
|
||||||
returnChat() {
|
|
||||||
this.chat = 'display: flex!important;'
|
|
||||||
this.roomInfo = 'display: none!important;'
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* 打开信息
|
|
||||||
*/
|
|
||||||
openInfo() {
|
|
||||||
this.chat = 'display: none!important;'
|
|
||||||
this.roomInfo = 'display: flex!important;'
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* 返回个人头像
|
|
||||||
*/
|
|
||||||
userHeader(id) {
|
|
||||||
let header;
|
|
||||||
this.memberList.forEach(item => {
|
|
||||||
if (id === item.userId) {
|
|
||||||
header = item.userHeader
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return header
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* 发送信息
|
|
||||||
*/
|
|
||||||
sendMessage() {
|
|
||||||
if (!this.$refs.message.innerHTML.replace(/<div><br><\/div>/g, '')) {
|
|
||||||
this.$message.error('发送内容不能为空,请重新输入');
|
|
||||||
this.$refs.message.innerHTML = '';
|
|
||||||
} else {
|
|
||||||
let params = {
|
|
||||||
userId: 'chat' + this.userInfo.nickName + returnCitySN['cip'],
|
|
||||||
message: this.$refs.message.innerHTML.replace(/<div><br><\/div>/g, ''),
|
|
||||||
};
|
|
||||||
sendMessage(params).then(res => {
|
|
||||||
if (res.code === 200) {
|
|
||||||
this.$refs.message.innerHTML = ''
|
|
||||||
this.sendMessageLoading = false;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* 退出登录
|
|
||||||
*/
|
|
||||||
signOut() {
|
|
||||||
let params = {
|
|
||||||
userId: 'chat' + this.userInfo.nickName + returnCitySN['cip'],
|
|
||||||
userHeader: this.userInfo.userHeader,
|
|
||||||
nickName: this.userInfo.nickName,
|
|
||||||
userMailbox: this.userInfo.userMailbox,
|
|
||||||
};
|
|
||||||
signOut(params).then(res => {
|
|
||||||
this.memberList = res.data;
|
|
||||||
this.onbeforeunload();
|
|
||||||
this.recordList = [];
|
|
||||||
this.memberList = [];
|
|
||||||
sessionStorage.setItem('loginInfo', '');
|
|
||||||
this.userInfo = {
|
|
||||||
userHeader: '',
|
|
||||||
nickName: '',
|
|
||||||
userMailbox: '',
|
|
||||||
};
|
|
||||||
this.chatFlag = false;
|
|
||||||
this.loginVisible = true;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* 登录
|
|
||||||
*/
|
|
||||||
loginChat() {
|
|
||||||
const reg = /^[a-zA-Z0-9]+([-_.][a-zA-Z0-9]+)*@[a-zA-Z0-9]+([-_.][a-zA-Z0-9]+)*\.[a-z]{2,}$/
|
|
||||||
if (this.userInfo.userHeader === '') {
|
|
||||||
this.$message.error('请上传您的头像')
|
|
||||||
} else if (this.userInfo.nickName === '') {
|
|
||||||
this.$message.error('请输入你的昵称')
|
|
||||||
} else if (this.userInfo.userMailbox === '' || !reg.test(this.userInfo.userMailbox)) {
|
|
||||||
this.$message.error('请输入正确格式的邮箱')
|
|
||||||
} else {
|
|
||||||
this.loginVisible = false;
|
|
||||||
this.chatFlag = true;
|
|
||||||
sessionStorage.setItem('loginInfo', JSON.stringify(this.userInfo));
|
|
||||||
this.initWebSocket();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* 初始化webSocket
|
|
||||||
*/
|
|
||||||
initWebSocket (userInfo) {
|
|
||||||
// WebSocket
|
|
||||||
if ('WebSocket' in window) {
|
|
||||||
if (userInfo) {
|
|
||||||
this.websocket = new WebSocket(`${process.env.VUE_APP_SOCKET}/${'chat' + userInfo.nickName + returnCitySN['cip']}`);
|
|
||||||
} else {
|
|
||||||
this.websocket = new WebSocket(`${process.env.VUE_APP_SOCKET}/${'chat' + this.userInfo.nickName + returnCitySN['cip']}`);
|
|
||||||
}
|
|
||||||
// 连接错误
|
|
||||||
this.websocket.onerror = this.setErrorMessage;
|
|
||||||
// 连接成功
|
|
||||||
this.websocket.onopen = this.setOnopenMessage;
|
|
||||||
// 收到消息的回调
|
|
||||||
this.websocket.onmessage = this.setOnmessageMessage;
|
|
||||||
// 连接关闭的回调
|
|
||||||
this.websocket.onclose = this.setOncloseMessage;
|
|
||||||
} else {
|
|
||||||
alert('当前浏览器 Not support websocket')
|
|
||||||
}
|
|
||||||
// 监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
|
|
||||||
window.onbeforeunload = this.onbeforeunload;
|
|
||||||
},
|
|
||||||
setErrorMessage () {
|
|
||||||
console.log('WebSocket连接发生错误 状态码:' + this.websocket.readyState)
|
|
||||||
},
|
|
||||||
setOnopenMessage () {
|
|
||||||
console.log('WebSocket连接成功 状态码:' + this.websocket.readyState);
|
|
||||||
let params = {
|
|
||||||
userId: 'chat' + this.userInfo.nickName + returnCitySN['cip'],
|
|
||||||
userHeader: this.userInfo.userHeader,
|
|
||||||
nickName: this.userInfo.nickName,
|
|
||||||
userMailbox: this.userInfo.userMailbox,
|
|
||||||
};
|
|
||||||
loginChat(params).then(res => {
|
|
||||||
if (res.data.length > 0) {
|
|
||||||
this.memberList = res.data.filter(item => {
|
|
||||||
return item.userHeader !== undefined
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
this.$message.error('网络异常,请清理缓存刷新当前页面')
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
setOnmessageMessage (event) {
|
|
||||||
// 根据服务器推送的消息做自己的业务处理
|
|
||||||
if (JSON.parse(event.data).webSocket === 'message') {
|
|
||||||
this.recordList.push(JSON.parse(event.data))
|
|
||||||
} else if (JSON.parse(event.data).webSocket === 'login') {
|
|
||||||
let flag = true;
|
|
||||||
this.memberList.forEach(item => {
|
|
||||||
if (JSON.parse(event.data).userId === item.userId) {
|
|
||||||
flag = false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (flag) {
|
|
||||||
this.memberList.push(JSON.parse(event.data))
|
|
||||||
}
|
|
||||||
} else if (JSON.parse(event.data).webSocket === 'signOut') {
|
|
||||||
this.memberList = this.memberList.filter(item => {
|
|
||||||
return item.userId !== JSON.parse(event.data).userId
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
setOncloseMessage () {
|
|
||||||
console.log('WebSocket连接关闭 状态码:' + this.websocket.readyState)
|
|
||||||
},
|
|
||||||
onbeforeunload () {
|
|
||||||
this.closeWebSocket();
|
|
||||||
},
|
|
||||||
closeWebSocket () {
|
|
||||||
this.websocket.close()
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* 删除头像
|
|
||||||
*/
|
|
||||||
deleteHeader() {
|
|
||||||
this.userInfo.userHeader = '';
|
|
||||||
},
|
|
||||||
blobToDataURL(blob,cb) {
|
|
||||||
let reader = new FileReader();
|
|
||||||
reader.onload = function (evt) {
|
|
||||||
let base64 = evt.target.result;
|
|
||||||
cb(base64)
|
|
||||||
};
|
|
||||||
reader.readAsDataURL(blob);
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* 上传头像
|
|
||||||
*/
|
|
||||||
uploadHeader(e) {
|
|
||||||
if(e.target.files[0]){
|
|
||||||
compressImage(e.target.files[0], this.config)
|
|
||||||
.then(result => { // result 为压缩后二进制文件
|
|
||||||
this.blobToDataURL(result, (base64Url) => {
|
|
||||||
this.userInfo.userHeader = base64Url;
|
|
||||||
})
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
beforeDestroy () {
|
|
||||||
this.onbeforeunload()
|
|
||||||
},
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
@media screen and (max-width: 1000px) {
|
|
||||||
.chat-room {
|
|
||||||
height: 94%!important;
|
|
||||||
width: 94%!important;
|
|
||||||
.user-info{
|
|
||||||
.return{
|
|
||||||
display: flex!important;
|
|
||||||
font-size: 1.2rem;
|
|
||||||
padding: 0.8rem 0.5rem;
|
|
||||||
position: absolute;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.phone-info{
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
color: #909090;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
i{
|
|
||||||
font-size: 1.2rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.room-info{
|
|
||||||
display: none!important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.phone-info{
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
#chat-room{
|
|
||||||
display: flex;
|
|
||||||
height: 90vh;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
.login{
|
|
||||||
display: flex;
|
|
||||||
padding: 1rem 4rem;
|
|
||||||
height: 100%;
|
|
||||||
flex-flow: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-around;
|
|
||||||
.customer-name, .customer-mail{
|
|
||||||
height: 2rem;
|
|
||||||
font-size: 0.8rem;
|
|
||||||
width: 100%;
|
|
||||||
margin-top: 1rem;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
.label{
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
.name{
|
|
||||||
white-space: nowrap;
|
|
||||||
overflow: hidden;
|
|
||||||
width: 10rem;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.button{
|
|
||||||
padding-top: 1rem;
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
.your-header{
|
|
||||||
width: 6rem;
|
|
||||||
height: 6rem;
|
|
||||||
border: 1px solid #999;
|
|
||||||
border-radius: 5px;
|
|
||||||
display: flex;
|
|
||||||
flex-flow: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
#headerPhoto{
|
|
||||||
width: 7rem;
|
|
||||||
position: absolute;
|
|
||||||
transform: scale(0.7);
|
|
||||||
margin-top: 1rem;
|
|
||||||
}
|
|
||||||
::v-deep .image-slot{
|
|
||||||
font-size: 0.4rem;
|
|
||||||
transform: scale(0.8);
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
.close-icon{
|
|
||||||
position: absolute;
|
|
||||||
margin: -5rem -5rem 0 0;
|
|
||||||
color: red;
|
|
||||||
}
|
|
||||||
.header-picture{
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
padding: 0.5rem;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
white-space: nowrap;
|
|
||||||
img{
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.chat-room{
|
|
||||||
display: flex;
|
|
||||||
height: 40rem;
|
|
||||||
width: 60rem;
|
|
||||||
border: 1px solid #d9d9d9;
|
|
||||||
border-radius: 5px;
|
|
||||||
background-color: #ffffff;
|
|
||||||
box-shadow: 4px 6px 2px #00000066;
|
|
||||||
.chat{
|
|
||||||
flex: 7;
|
|
||||||
border-right: 1px solid #d9d9d9;
|
|
||||||
display: flex;
|
|
||||||
flex-flow: column;
|
|
||||||
.record{
|
|
||||||
flex: 5;
|
|
||||||
border-bottom: 1px solid #d9d9d9;
|
|
||||||
height: 28rem;
|
|
||||||
overflow-y: auto;
|
|
||||||
padding: 1rem 1rem 0 1rem;
|
|
||||||
.record-one{
|
|
||||||
display: flex;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
.left{
|
|
||||||
margin-right: 0.5rem;
|
|
||||||
}
|
|
||||||
.right{
|
|
||||||
.nick-name{
|
|
||||||
color: #999;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
}
|
|
||||||
.message{
|
|
||||||
margin-left: 0.5rem;
|
|
||||||
padding: 0.5rem;
|
|
||||||
background-color: #fff;
|
|
||||||
border-radius: 5px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.is-me{
|
|
||||||
flex-flow:row-reverse!important;
|
|
||||||
.message{
|
|
||||||
-moz-border-radius-topright: 0;
|
|
||||||
}
|
|
||||||
.right{
|
|
||||||
.nick-name{
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
.message{
|
|
||||||
margin-left: 0!important;
|
|
||||||
margin-right: 0.5rem;
|
|
||||||
background-color: #1E6EFF;
|
|
||||||
color: #FFFFFF;
|
|
||||||
}
|
|
||||||
.message:before{
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.input-box{
|
|
||||||
flex: 2;
|
|
||||||
padding: 1rem;
|
|
||||||
display: flex;
|
|
||||||
flex-flow: column;
|
|
||||||
.textarea{
|
|
||||||
flex: 8;
|
|
||||||
padding: 1rem;
|
|
||||||
}
|
|
||||||
.button{
|
|
||||||
flex: 2;
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-end;
|
|
||||||
padding-top: 0.5rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.room-info{
|
|
||||||
flex: 3;
|
|
||||||
display: flex;
|
|
||||||
flex-flow: column;
|
|
||||||
.user-info{
|
|
||||||
flex: 2;
|
|
||||||
border-bottom: 1px solid #d9d9d9;
|
|
||||||
.return{
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
.message-board-right{
|
|
||||||
display: flex;
|
|
||||||
padding: 1rem;
|
|
||||||
flex-flow: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-around;
|
|
||||||
.customer-name, .customer-mail{
|
|
||||||
height: 2rem;
|
|
||||||
font-size: 0.8rem;
|
|
||||||
width: 100%;
|
|
||||||
margin-top: 0.5rem;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
.label{
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
.name{
|
|
||||||
white-space: nowrap;
|
|
||||||
overflow: hidden;
|
|
||||||
width: 10rem;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.button{
|
|
||||||
padding-top: 0.5rem;
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
.your-header{
|
|
||||||
width: 6rem;
|
|
||||||
height: 6rem;
|
|
||||||
border: 1px solid #999;
|
|
||||||
border-radius: 5px;
|
|
||||||
display: flex;
|
|
||||||
flex-flow: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
#headerPhoto{
|
|
||||||
width: 7rem;
|
|
||||||
position: absolute;
|
|
||||||
transform: scale(0.7);
|
|
||||||
margin-top: 1rem;
|
|
||||||
}
|
|
||||||
::v-deep .image-slot{
|
|
||||||
font-size: 0.4rem;
|
|
||||||
transform: scale(0.8);
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
.close-icon{
|
|
||||||
position: absolute;
|
|
||||||
margin: -5rem -5rem 0 0;
|
|
||||||
color: red;
|
|
||||||
}
|
|
||||||
.header-picture{
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
padding: 0.5rem;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
white-space: nowrap;
|
|
||||||
img{
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
.member{
|
|
||||||
flex: 5;
|
|
||||||
padding: 1rem;
|
|
||||||
.online-number{
|
|
||||||
|
|
||||||
}
|
|
||||||
.member-list{
|
|
||||||
.member-one{
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
padding-top: 0.5rem;
|
|
||||||
.nick-name{
|
|
||||||
margin-left: 0.5rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
Loading…
x
Reference in New Issue
Block a user