feat: 重新调整

This commit is contained in:
裴浩宇 2024-06-27 10:02:42 +08:00
parent 1a298d2e97
commit 7a448c536f
20 changed files with 2082 additions and 755 deletions

View File

@ -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'

View File

@ -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'

View File

@ -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"
}
}

View File

@ -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>

View File

@ -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'
})
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 488 KiB

30
src/assets/js/common.js Normal file
View 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

File diff suppressed because one or more lines are too long

View 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>

View 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')
}
// websocketserver
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>

View 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>

View 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>

View File

@ -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')

View File

@ -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

View File

@ -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: {}
})

View File

@ -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)
}

View File

@ -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
View 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()
}

View File

@ -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')
}
// websocketserver
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>