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_BASE_API = 'https://pnkx.top/prod-api'
|
||||
|
@ -1,8 +1,8 @@
|
||||
# 生产环境配置
|
||||
ENV = 'production'
|
||||
|
||||
# pei你看雪博客/开发环境/websocket地址
|
||||
VUE_APP_SOCKET = 'wss://pnkx.top/websocket/websocket'
|
||||
|
||||
# 若依管理系统/开发环境
|
||||
# 生产环境
|
||||
VUE_APP_BASE_API = '/prod-api'
|
||||
|
||||
# 生产环境websocket地址
|
||||
VUE_APP_SOCKET = 'wss://pnkx.top/websocket/websocket'
|
||||
|
@ -14,7 +14,6 @@
|
||||
"core-js": "^3.6.5",
|
||||
"element-ui": "^2.13.2",
|
||||
"es6-promise": "^4.2.8",
|
||||
"js-cookie": "^2.2.1",
|
||||
"node-sass": "^4.14.1",
|
||||
"sass-loader": "^8.0.0",
|
||||
"vue": "^2.6.11",
|
||||
@ -35,8 +34,8 @@
|
||||
"eslint-plugin-promise": "^4.2.1",
|
||||
"eslint-plugin-standard": "^4.0.0",
|
||||
"eslint-plugin-vue": "^6.2.2",
|
||||
"less": "^3.0.4",
|
||||
"less-loader": "^7.3.0",
|
||||
"sass": "1.32.0",
|
||||
"sass-loader": "10.1.0",
|
||||
"vue-template-compiler": "^2.6.11"
|
||||
}
|
||||
}
|
||||
|
21
src/App.vue
21
src/App.vue
@ -1,10 +1,21 @@
|
||||
<template>
|
||||
<div id="app">
|
||||
<router-view/>
|
||||
<chat/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="less">
|
||||
<script>
|
||||
import Chat from '@/components/Chat/index.vue'
|
||||
|
||||
export default {
|
||||
name: 'App',
|
||||
components: {
|
||||
Chat
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
html, body{
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
@ -14,5 +25,11 @@
|
||||
background-image: url('./assets/main-bg.png');
|
||||
background-size: 100% 100%;
|
||||
background-position: center;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
#app {
|
||||
width: 60vw;
|
||||
}
|
||||
</style>
|
||||
|
@ -1,22 +1,32 @@
|
||||
import request from '../utils/request'
|
||||
import request from '@/utils/request'
|
||||
|
||||
/**
|
||||
* 登录聊天室
|
||||
*/
|
||||
export function loginChat () {
|
||||
return request({
|
||||
url: '/admin/chat/loginChat',
|
||||
method: 'post'
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送信息
|
||||
*/
|
||||
export function sendMessage (params) {
|
||||
return request({
|
||||
url: '/customer/sendMessage',
|
||||
url: '/admin/chat/sendMessage',
|
||||
method: 'post',
|
||||
data: params
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 登录聊天室
|
||||
* 获取信息
|
||||
*/
|
||||
export function loginChat(params) {
|
||||
export function getMessageRecord (params) {
|
||||
return request({
|
||||
url: '/customer/loginChat',
|
||||
url: '/admin/chat/getMessageRecord',
|
||||
method: 'post',
|
||||
data: params
|
||||
})
|
||||
@ -27,9 +37,33 @@ export function loginChat(params) {
|
||||
*/
|
||||
export function signOut (params) {
|
||||
return request({
|
||||
url: '/customer/signOut',
|
||||
url: '/admin/chat/signOut',
|
||||
method: 'post',
|
||||
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 App from './App.vue';
|
||||
import router from './router';
|
||||
import store from './store';
|
||||
import ElementUI from 'element-ui';
|
||||
import 'element-ui/lib/theme-chalk/index.css';
|
||||
import axios from 'axios';
|
||||
Vue.prototype.$axios = axios;
|
||||
Vue.use(ElementUI);
|
||||
import Vue from 'vue'
|
||||
import App from './App.vue'
|
||||
import store from './store'
|
||||
import ElementUI from 'element-ui'
|
||||
import 'element-ui/lib/theme-chalk/index.css'
|
||||
import axios from 'axios'
|
||||
Vue.prototype.$axios = axios
|
||||
Vue.use(ElementUI)
|
||||
Vue.config.productionTip = false
|
||||
|
||||
new Vue({
|
||||
router,
|
||||
store,
|
||||
render: h => h(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 Vuex from 'vuex'
|
||||
import { getInfo } from '@/api'
|
||||
import { getToken } from '@/utils/auth'
|
||||
|
||||
Vue.use(Vuex)
|
||||
|
||||
export default new Vuex.Store({
|
||||
state: {
|
||||
// 用户信息
|
||||
userInfo: {}
|
||||
},
|
||||
mutations: {
|
||||
SET_USER_INFO: (state, userInfo) => {
|
||||
state.userInfo = userInfo
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
},
|
||||
modules: {
|
||||
}
|
||||
// 获取用户信息
|
||||
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: {}
|
||||
})
|
||||
|
@ -1,15 +1,17 @@
|
||||
import Cookies from 'js-cookie'
|
||||
|
||||
/**
|
||||
* 保存到localStorage中
|
||||
* @type {string}
|
||||
*/
|
||||
const TokenKey = 'Admin-Token'
|
||||
|
||||
export function getToken() {
|
||||
return Cookies.get(TokenKey)
|
||||
return localStorage.getItem(TokenKey)
|
||||
}
|
||||
|
||||
export function setToken(token) {
|
||||
return Cookies.set(TokenKey, token)
|
||||
return localStorage.setItem(TokenKey, token)
|
||||
}
|
||||
|
||||
export function removeToken() {
|
||||
return Cookies.remove(TokenKey)
|
||||
return localStorage.removeItem(TokenKey)
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
import axios from 'axios'
|
||||
import { Notification, MessageBox, Message } from 'element-ui'
|
||||
import store from '@/store'
|
||||
import {Message, Notification} from 'element-ui'
|
||||
import {getToken} from '@/utils/auth'
|
||||
import errorCode from '@/utils/errorCode'
|
||||
|
||||
|
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