新增:对话页面
This commit is contained in:
parent
de27cfa8f6
commit
a7e8433b27
@ -39,7 +39,6 @@
|
||||
"benz-amr-recorder": "^1.1.5",
|
||||
"bpmn-js-token-simulation": "^0.10.0",
|
||||
"camunda-bpmn-moddle": "^7.0.1",
|
||||
"components": "link:@/components",
|
||||
"cropperjs": "^1.6.1",
|
||||
"crypto-js": "^4.2.0",
|
||||
"dayjs": "^1.11.10",
|
||||
@ -51,6 +50,7 @@
|
||||
"fast-xml-parser": "^4.3.2",
|
||||
"highlight.js": "^11.9.0",
|
||||
"jsencrypt": "^3.3.2",
|
||||
"lodash": "^4.17.21",
|
||||
"lodash-es": "^4.17.21",
|
||||
"min-dash": "^4.1.1",
|
||||
"mitt": "^3.0.1",
|
||||
@ -63,6 +63,7 @@
|
||||
"url": "^0.11.3",
|
||||
"video.js": "^7.21.5",
|
||||
"vue": "3.4.20",
|
||||
"vue-at": "3.0.0-alpha.2",
|
||||
"vue-dompurify-html": "^4.1.4",
|
||||
"vue-i18n": "9.9.1",
|
||||
"vue-router": "^4.3.0",
|
||||
|
BIN
src/assets/imgs/avatar/inform.png
Normal file
BIN
src/assets/imgs/avatar/inform.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 9.0 KiB |
BIN
src/assets/imgs/avatar/jiaqun2x.png
Normal file
BIN
src/assets/imgs/avatar/jiaqun2x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 27 KiB |
BIN
src/assets/imgs/avatar/theme2x.png
Normal file
BIN
src/assets/imgs/avatar/theme2x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 9.4 KiB |
41
src/components/UserStatus/index.vue
Normal file
41
src/components/UserStatus/index.vue
Normal file
@ -0,0 +1,41 @@
|
||||
<script setup lang="ts">
|
||||
/* 单聊用户在线状态 */
|
||||
import { ref } from 'vue'
|
||||
const userInfoStatus = ref({
|
||||
style: '',
|
||||
label: '',
|
||||
onlineDeviceCount: 1, //在线设备数
|
||||
deviceType: '' //在线设备类型
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="user_status_box">
|
||||
<span class="status_icon" :style="userInfoStatus.style"></span>
|
||||
<span class="os_type">{{
|
||||
userInfoStatus.onlineDeviceCount > 1
|
||||
? `多设备${userInfoStatus.label}`
|
||||
: `${userInfoStatus.deviceType.toUpperCase()}${userInfoStatus.label}`
|
||||
}}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.user_status_box {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
width: 100px;
|
||||
height: 100%;
|
||||
font-size: 7px;
|
||||
|
||||
.status_icon {
|
||||
display: inline-block;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 50%;
|
||||
margin: 0 3px;
|
||||
}
|
||||
}
|
||||
</style>
|
322
src/constant/emojis.js
Normal file
322
src/constant/emojis.js
Normal file
@ -0,0 +1,322 @@
|
||||
const emojis = [
|
||||
'😀',
|
||||
'😃',
|
||||
'😄',
|
||||
'😁',
|
||||
'😆',
|
||||
'😅',
|
||||
'🤣',
|
||||
'😂',
|
||||
'🙂',
|
||||
'🙃',
|
||||
'😉',
|
||||
'😊',
|
||||
'😇',
|
||||
'😍',
|
||||
'🤩',
|
||||
'😘',
|
||||
'😗',
|
||||
'😚',
|
||||
'😙',
|
||||
'😋',
|
||||
'😛',
|
||||
'😜',
|
||||
'🤪',
|
||||
'😝',
|
||||
'🤑',
|
||||
'🤗',
|
||||
'🤭',
|
||||
'🤫',
|
||||
'🤔',
|
||||
'🤐',
|
||||
'🤨',
|
||||
'😐',
|
||||
'😑',
|
||||
'😶',
|
||||
'😏',
|
||||
'😒',
|
||||
'🙄',
|
||||
'😬',
|
||||
'🤥',
|
||||
'😌',
|
||||
'😔',
|
||||
'😪',
|
||||
'🤤',
|
||||
'😴',
|
||||
'😷',
|
||||
'🤒',
|
||||
'🤕',
|
||||
'🤢',
|
||||
'🤮',
|
||||
'🤧',
|
||||
'😵',
|
||||
'🤯',
|
||||
'🤠',
|
||||
'😎',
|
||||
'🤓',
|
||||
'🧐',
|
||||
'😕',
|
||||
'😟',
|
||||
'🙁',
|
||||
'😮',
|
||||
'😯',
|
||||
'😲',
|
||||
'😳',
|
||||
'😦',
|
||||
'😧',
|
||||
'😨',
|
||||
'😰',
|
||||
'😥',
|
||||
'😢',
|
||||
'😭',
|
||||
'😱',
|
||||
'😖',
|
||||
'😣',
|
||||
'😞',
|
||||
'😓',
|
||||
'😩',
|
||||
'😫',
|
||||
'😤',
|
||||
'😡',
|
||||
'😠',
|
||||
'🤬',
|
||||
'😈',
|
||||
'👿',
|
||||
'💀',
|
||||
'💩',
|
||||
'🤡',
|
||||
'👹',
|
||||
'👺',
|
||||
'👻',
|
||||
'👽',
|
||||
'👾',
|
||||
'🤖',
|
||||
'😺',
|
||||
'😸',
|
||||
'😹',
|
||||
'😻',
|
||||
'😼',
|
||||
'😽',
|
||||
'🙀',
|
||||
'😿',
|
||||
'😾',
|
||||
'💋',
|
||||
'👋',
|
||||
'🤚',
|
||||
'🖐',
|
||||
'✋',
|
||||
'🖖',
|
||||
'👌',
|
||||
'🤞',
|
||||
'🤟',
|
||||
'🤘',
|
||||
'🤙',
|
||||
'👈',
|
||||
'👉',
|
||||
'👆',
|
||||
'🖕',
|
||||
'👇',
|
||||
'👍',
|
||||
'👎',
|
||||
'✊',
|
||||
'👊',
|
||||
'🤛',
|
||||
'🤜',
|
||||
'👏',
|
||||
'🙌',
|
||||
'👐',
|
||||
'🤲',
|
||||
'🤝',
|
||||
'🙏',
|
||||
'💅',
|
||||
'🤳',
|
||||
'💪',
|
||||
'👂',
|
||||
'👃',
|
||||
'🧠',
|
||||
'👀',
|
||||
'👁',
|
||||
'👅',
|
||||
'👄',
|
||||
'👶',
|
||||
'🧒',
|
||||
'👦',
|
||||
'👧',
|
||||
'🧑',
|
||||
'👱',
|
||||
'👨',
|
||||
'🧔',
|
||||
'👱',
|
||||
'👨',
|
||||
'👨',
|
||||
'👩',
|
||||
'👱',
|
||||
'👩',
|
||||
'👩',
|
||||
'👩',
|
||||
'👩',
|
||||
'🧓',
|
||||
'👴',
|
||||
'👵',
|
||||
'🙍',
|
||||
'🙅',
|
||||
'🙆',
|
||||
'💁',
|
||||
'🙋',
|
||||
'🙇',
|
||||
'🙇',
|
||||
'🙇',
|
||||
'🤦',
|
||||
'🤷',
|
||||
'🤷',
|
||||
'🤷',
|
||||
'👨⚕️',
|
||||
'👩⚕️',
|
||||
'👨🎓',
|
||||
'👩🎓',
|
||||
'👨🏫',
|
||||
'👩🏫',
|
||||
'👨⚖️',
|
||||
'👩⚖️',
|
||||
'👨🌾',
|
||||
'👩🌾',
|
||||
'👨🍳',
|
||||
'👩🍳',
|
||||
'👨🔧',
|
||||
'👩🔧',
|
||||
'👨🏭',
|
||||
'👩🏭',
|
||||
'👨💼',
|
||||
'👩💼',
|
||||
'👨🔬',
|
||||
'👩🔬',
|
||||
'👨💻',
|
||||
'👩💻',
|
||||
'👨🎤',
|
||||
'👩🎤',
|
||||
'👨🎨',
|
||||
'👩🎨',
|
||||
'👨✈️',
|
||||
'👩✈️',
|
||||
'👨🚀',
|
||||
'👩🚀',
|
||||
'👨🚒',
|
||||
'👩🚒',
|
||||
'👮',
|
||||
'👮♂️',
|
||||
'👮♀️',
|
||||
'🕵',
|
||||
'🕵️♂️',
|
||||
'🕵️♀️',
|
||||
'💂',
|
||||
'💂',
|
||||
'💂',
|
||||
'👷',
|
||||
'👷',
|
||||
'👷',
|
||||
'🤴',
|
||||
'👸',
|
||||
'👳',
|
||||
'👳',
|
||||
'👳',
|
||||
'👲',
|
||||
'🧕',
|
||||
'🤵',
|
||||
'👰',
|
||||
'🤰',
|
||||
'🤱',
|
||||
'👼',
|
||||
'🎅',
|
||||
'🤶',
|
||||
'🧙',
|
||||
'🧚',
|
||||
'🧛',
|
||||
'🧜',
|
||||
'🧝',
|
||||
'🧞',
|
||||
'🧟',
|
||||
'💆',
|
||||
'💇',
|
||||
'🚶',
|
||||
'🏃',
|
||||
'💃',
|
||||
'🕺',
|
||||
'🕴',
|
||||
'👯',
|
||||
'🧖',
|
||||
'🧖',
|
||||
'🧖',
|
||||
'🧘',
|
||||
'👭',
|
||||
'👫',
|
||||
'👬',
|
||||
'💏',
|
||||
'👨',
|
||||
'👩',
|
||||
'💑',
|
||||
'👨',
|
||||
'👩',
|
||||
'👪',
|
||||
'👨👩👦',
|
||||
'👨👩👧',
|
||||
'👨👩👧👦',
|
||||
'👨👩👦👦',
|
||||
'👨👩👧👧',
|
||||
'👨👨👦',
|
||||
'👨👨👧',
|
||||
'👨👨👧👦',
|
||||
'👩👩👦',
|
||||
'👩👩👧',
|
||||
'👩👩👧👦',
|
||||
'👩👩👦👦',
|
||||
'👩👩👧👧',
|
||||
'👨👦',
|
||||
'👨👦👦',
|
||||
'👨👧',
|
||||
'👨👧👦',
|
||||
'👨👧👧',
|
||||
'👩👦',
|
||||
'👩👦👦',
|
||||
'👩👧',
|
||||
'👩👧👦',
|
||||
'👩👧👧',
|
||||
'🗣',
|
||||
'👤',
|
||||
'👥',
|
||||
'👣',
|
||||
'🌂',
|
||||
'☂',
|
||||
'👓',
|
||||
'🕶',
|
||||
'👔',
|
||||
'👕',
|
||||
'👖',
|
||||
'🧣',
|
||||
'🧤',
|
||||
'🧥',
|
||||
'🧦',
|
||||
'👗',
|
||||
'👘',
|
||||
'👙',
|
||||
'👚',
|
||||
'👛',
|
||||
'👜',
|
||||
'👝',
|
||||
'🎒',
|
||||
'👞',
|
||||
'👟',
|
||||
'👠',
|
||||
'👡',
|
||||
'👢',
|
||||
'👑',
|
||||
'👒',
|
||||
'🎩',
|
||||
'🎓',
|
||||
'🧢',
|
||||
'⛑',
|
||||
'💄',
|
||||
'💍',
|
||||
'💼',
|
||||
]
|
||||
|
||||
export default emojis
|
77
src/constant/errorCode.js
Normal file
77
src/constant/errorCode.js
Normal file
@ -0,0 +1,77 @@
|
||||
// const ERROR_TYPE = {
|
||||
// login: 1,
|
||||
// };
|
||||
|
||||
export default {
|
||||
/* 登陆相关
|
||||
*/
|
||||
0: {
|
||||
'none': '未知错误!'
|
||||
},
|
||||
1: {
|
||||
'invalid password': '密码错误!',
|
||||
'login failed': '登陆失败!',
|
||||
'user not found': '该用户不存在!',
|
||||
},
|
||||
17: {
|
||||
'duplicate_unique_property_exists': 'id已存在!',
|
||||
'resource_limited': '注册已达上限请开通企业版!',
|
||||
'unauthorized': '未开放授权注册!',
|
||||
'resource_not_found': '账号不存在!',
|
||||
},
|
||||
28: {
|
||||
'appkey or token error': '未登录!',
|
||||
},
|
||||
101: {
|
||||
'file exceeding maximum limit': '文件大小超出限制(默认10M)!',
|
||||
'none': '文件相关未知错误!'
|
||||
},
|
||||
217: {
|
||||
'the user was kicked by other device': '其他端踢出了该账号!',
|
||||
},
|
||||
/* 群组相关 */
|
||||
602: {
|
||||
'not in group or chatroom': '已不再该群组中!',
|
||||
},
|
||||
605: {
|
||||
'The chat room dose not exist.': '此群不存在!',
|
||||
},
|
||||
/* 消息相关 */
|
||||
221: {
|
||||
'not contact': '非好友关系,不可发送消息!',
|
||||
},
|
||||
400: {
|
||||
'UserId password error.': '用户密码错误!',
|
||||
'Please wait a moment while trying to send.': '验证码在有效期内,请勿重复发送!',
|
||||
'Image verification code error.': '图片验证码错误,请更换验证码或重新输入!',
|
||||
'Image code id cannot be empty.': '请填入图片验证码!',
|
||||
'Phone number cannot be empty.': '获取图片验证码请填入手机号!',
|
||||
'UserId hfp already exists.': '用户已注册!',
|
||||
'phone number illegal': '手机号不合法!',
|
||||
'Please send SMS to get mobile phone verification code.': '请发送短信获取手机验证码!',
|
||||
'SMS verification code error.': '验证码错误!'
|
||||
},
|
||||
603: {
|
||||
'blocked': '对方已将您加入黑名单!',
|
||||
'blacklist': '已在该群黑名单当中!无法加入该群。',
|
||||
'already': '已加入该群!'
|
||||
},
|
||||
504: {
|
||||
'exceed recall time limit': '消息超过可撤回时间!',
|
||||
},
|
||||
507: {
|
||||
'muted': '已被禁言!'
|
||||
},
|
||||
508: {
|
||||
'moderation': '内容审核不通过!请检查发送内容。'
|
||||
}
|
||||
// e.type === '603' 被拉黑
|
||||
// e.type === '605' 群组不存在
|
||||
// e.type === '602' 不在群组或聊天室中
|
||||
// e.type === '504' 撤回消息时超出撤回时间
|
||||
// e.type === '505' 未开通消息撤回
|
||||
// e.type === '506' 没有在群组或聊天室白名单
|
||||
// e.type === '501' 消息包含敏感词
|
||||
// e.type === '502' 被设置的自定义拦截捕获
|
||||
// e.type === '503' 未知错误
|
||||
}
|
14
src/constant/index.js
Normal file
14
src/constant/index.js
Normal file
@ -0,0 +1,14 @@
|
||||
import errorCode from './errorCode'
|
||||
import onLineStatus from './onLineStatus'
|
||||
import messageType from './messageType'
|
||||
import informType from './informType'
|
||||
import emojis from './emojis'
|
||||
import warningText from './warningText'
|
||||
export {
|
||||
errorCode,
|
||||
onLineStatus,
|
||||
messageType,
|
||||
informType,
|
||||
emojis,
|
||||
warningText,
|
||||
}
|
52
src/constant/informType.js
Normal file
52
src/constant/informType.js
Normal file
@ -0,0 +1,52 @@
|
||||
const INFORM_NAME = {
|
||||
FRIEND_INVITE: '好友申请',
|
||||
FRIEND_BUILD: '已成为好友',
|
||||
FRIEND_DELETED: '好友关系解除',
|
||||
FRIEND_APPLY_REFUSE: '好友申请被拒绝',
|
||||
FRIEND_APPLY_AGREE: '好友申请已通过',
|
||||
GROUP_JOIN_SUCCESS: '成员入群成功',
|
||||
GROUP_QUIT_SUCCESS: '成员退出群组成功',
|
||||
GROUP_INVITE_JOIN: '邀请加入群组',
|
||||
GROUP_REQUESTTOJOIN: '申请加入群组',
|
||||
GROUP_REMOVE_MEMBER: '移出了群成员',
|
||||
GROUP_DIRECT_MEMBER: '被直接拉入群组',
|
||||
GROUP_UPDATE_ANNOUNCEMENT: '更新了群组公告',
|
||||
GROUP_SET_ADMIN: '设定为管理员',
|
||||
GROUP_REMOVE_ADMIN: '移除管理员',
|
||||
GROUP_MUTE_MEMBER: '禁言成员',
|
||||
GROUP_UNMUTE_MEMBER: '移除成员禁言',
|
||||
GROUP_DESTORY: '解散群组',
|
||||
GROUP_ACCEPTREQUEST: '同意入群申请',
|
||||
GROUP_UPDATE_INFO: '更新群组信息',
|
||||
GROUP_UPDATE_MEMBER_ATTRIBUTES: '群组成员属性更新'
|
||||
}
|
||||
const INFORM_TYPE = {
|
||||
subscribe: INFORM_NAME.FRIEND_INVITE,
|
||||
subscribed: INFORM_NAME.FRIEND_BUILD,
|
||||
unsubscribed: INFORM_NAME.FRIEND_DELETED,
|
||||
other_person_refuse: INFORM_NAME.FRIEND_APPLY_REFUSE,
|
||||
other_person_agree: INFORM_NAME.FRIEND_APPLY_AGREE,
|
||||
memberPresence: INFORM_NAME.GROUP_JOIN_SUCCESS,
|
||||
memberAbsence: INFORM_NAME.GROUP_QUIT_SUCCESS,
|
||||
inviteToJoin: INFORM_NAME.GROUP_INVITE_JOIN,
|
||||
removeMember: INFORM_NAME.GROUP_REMOVE_MEMBER,
|
||||
directJoined: INFORM_NAME.GROUP_DIRECT_MEMBER,
|
||||
updateAnnouncement: INFORM_NAME.GROUP_UPDATE_ANNOUNCEMENT,
|
||||
setAdmin: INFORM_NAME.GROUP_SET_ADMIN,
|
||||
removeAdmin: INFORM_NAME.GROUP_REMOVE_ADMIN,
|
||||
muteMember: INFORM_NAME.GROUP_MUTE_MEMBER,
|
||||
unmuteMember: INFORM_NAME.GROUP_UNMUTE_MEMBER,
|
||||
destroy: INFORM_NAME.GROUP_DESTORY,
|
||||
requestToJoin: INFORM_NAME.GROUP_REQUESTTOJOIN,
|
||||
acceptRequest: INFORM_NAME.GROUP_ACCEPTREQUEST,
|
||||
updateInfo: INFORM_NAME.GROUP_UPDATE_INFO,
|
||||
memberAttributesUpdate: INFORM_NAME.GROUP_UPDATE_MEMBER_ATTRIBUTES
|
||||
}
|
||||
const INFORM_FROM = {
|
||||
FRIEND: 'friend',
|
||||
GROUP: 'group'
|
||||
}
|
||||
export default {
|
||||
INFORM_TYPE,
|
||||
INFORM_FROM
|
||||
}
|
43
src/constant/messageType.js
Normal file
43
src/constant/messageType.js
Normal file
@ -0,0 +1,43 @@
|
||||
const SESSION_MESSAGE_TYPE = {
|
||||
img: '[图片]',
|
||||
file: '[文件]',
|
||||
audio: '[语音]',
|
||||
loc: '[位置]'
|
||||
}
|
||||
|
||||
const CUSTOM_TYPE = {
|
||||
userCard: '个人名片'
|
||||
}
|
||||
const ALL_MESSAGE_TYPE = {
|
||||
TEXT: 'txt',
|
||||
IMAGE: 'img',
|
||||
AUDIO: 'audio',
|
||||
LOCAL: 'loc',
|
||||
VIDEO: 'video',
|
||||
FILE: 'file',
|
||||
CUSTOM: 'custom',
|
||||
CMD: 'cmd',
|
||||
INFORM: 'inform' //这个类型不在环信消息类型内,属于自己定义的一种系统通知类的消息。
|
||||
}
|
||||
const CHAT_TYPE = {
|
||||
SINGLE: 'singleChat',
|
||||
GROUP: 'groupChat'
|
||||
}
|
||||
|
||||
const MENTION_ALL = {
|
||||
TEXT: '所有人',
|
||||
VALUE: 'ALL'
|
||||
}
|
||||
const CHANGE_MESSAGE_BODAY_TYPE = {
|
||||
RECALL: 0,
|
||||
DELETE: 1,
|
||||
MODIFY: 2
|
||||
}
|
||||
export default {
|
||||
SESSION_MESSAGE_TYPE,
|
||||
CUSTOM_TYPE,
|
||||
ALL_MESSAGE_TYPE,
|
||||
CHAT_TYPE,
|
||||
MENTION_ALL,
|
||||
CHANGE_MESSAGE_BODAY_TYPE
|
||||
}
|
11
src/constant/onLineStatus.js
Normal file
11
src/constant/onLineStatus.js
Normal file
@ -0,0 +1,11 @@
|
||||
const onLineStatus = {
|
||||
Online: { label: '在线', style: 'background-color:#49FD1D' },
|
||||
Leave: { label: '离开', style: 'background-color:#4E4239' },
|
||||
Cloaking: {
|
||||
label: '勿扰',
|
||||
style: 'background-color:#F27014',
|
||||
},
|
||||
Offline: { label: '离线', style: 'background-color:#BEC1BD' },
|
||||
}
|
||||
|
||||
export default onLineStatus
|
39
src/constant/warningText.js
Normal file
39
src/constant/warningText.js
Normal file
@ -0,0 +1,39 @@
|
||||
const SWINDLER_GO_DIE = [
|
||||
'时刻绷紧防范之弦,谨防新型电信诈骗。',
|
||||
'号码陌⽣勿轻接,虚拟电话设陷阱。',
|
||||
'飞来⼤奖莫惊喜,让您掏钱洞⽆底。',
|
||||
'不存贪婪⼼,诈骗难得逞。',
|
||||
'提⾼防骗意识,增强防范能⼒,构筑电信诈骗“防⽕墙。',
|
||||
'骗⼈之⼼不可有,防骗之⼼不可⽆。',
|
||||
'⽹上汇款需警惕,电话核实莫⼤意。',
|
||||
'执法办案有规范,怎会汇款到个⼈。',
|
||||
'不明电话及时挂,可疑短信不要回。',
|
||||
'⽹络购物便利多,⽀付流程要仔细。',
|
||||
'投资理财和股票,多是骗⼦设的套。',
|
||||
'不信陌⽣短信,拒接陌⽣来电,让骗⼦⽆从下⼿。',
|
||||
'⼀不贪⼆不占,诈骗再诡玩不转。',
|
||||
'遇到恐吓要淡定,说你违法莫慌张,⼀旦难分真与假,警方电话110。',
|
||||
'陌⽣来电要提防,多⽅确认防上当。',
|
||||
'致富⼗年功,诈骗⼀场空。',
|
||||
'积极加强⾃我防范意识,共同提⾼识骗防骗能⼒。',
|
||||
'防范⽹络的骗术,不贪便宜要记住。',
|
||||
' 和谐⽹络你我共享,电信诈骗⼤家共防。',
|
||||
'真假⽹店难分辨,购物不慎就被骗。',
|
||||
'个⼈信息顶重要,密码账号保管好。',
|
||||
'飞来⼤奖莫惊喜,让你掏钱洞⽆底。',
|
||||
'安全账户⼦虚有,⼤额汇款要三思。',
|
||||
'异地刷卡消费现,不要着急忙给钱。',
|
||||
'电话通知接传票,实为骗钱设圈套。',
|
||||
'刷卡消费莫离眼,防⽌盗刷盯着点。',
|
||||
'⼼中⽆贪念,骗局远⾝边。',
|
||||
'转账汇款须谨慎,万元以上到柜⾯。',
|
||||
'陌⽣电话勿轻信,对⽅⾝份要核清。',
|
||||
'电信诈骗不难防,不给不要不上当。',
|
||||
'陌⽣信息不要理,以防害⼈⼜害⼰。',
|
||||
]
|
||||
|
||||
const EASEIM_HINT =
|
||||
'【安全提示】本应用仅用于环信产品功能开发测试,请勿用于非法用途。任何涉及转账、汇款、裸聊、网恋、网购退款、投资理财等统统都是诈骗,请勿相信!'
|
||||
|
||||
const WARM_TIP = '【温馨提示】该群仅供试用,72小时后将被删除!'
|
||||
export default { SWINDLER_GO_DIE, EASEIM_HINT, WARM_TIP }
|
@ -593,7 +593,7 @@ const remainingRouter: AppRouteRecordRaw[] = [
|
||||
children: [
|
||||
{
|
||||
// 会话详情
|
||||
path: 'informdetails',
|
||||
path: 'informDetails',
|
||||
name: 'InformDetails',
|
||||
meta: {
|
||||
title: '通知详情',
|
||||
|
539
src/styles/iconfont/demo.css
Normal file
539
src/styles/iconfont/demo.css
Normal file
@ -0,0 +1,539 @@
|
||||
/* Logo 字体 */
|
||||
@font-face {
|
||||
font-family: "iconfont logo";
|
||||
src: url('https://at.alicdn.com/t/font_985780_km7mi63cihi.eot?t=1545807318834');
|
||||
src: url('https://at.alicdn.com/t/font_985780_km7mi63cihi.eot?t=1545807318834#iefix') format('embedded-opentype'),
|
||||
url('https://at.alicdn.com/t/font_985780_km7mi63cihi.woff?t=1545807318834') format('woff'),
|
||||
url('https://at.alicdn.com/t/font_985780_km7mi63cihi.ttf?t=1545807318834') format('truetype'),
|
||||
url('https://at.alicdn.com/t/font_985780_km7mi63cihi.svg?t=1545807318834#iconfont') format('svg');
|
||||
}
|
||||
|
||||
.logo {
|
||||
font-family: "iconfont logo";
|
||||
font-size: 160px;
|
||||
font-style: normal;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
/* tabs */
|
||||
.nav-tabs {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.nav-tabs .nav-more {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
height: 42px;
|
||||
line-height: 42px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
#tabs {
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
#tabs li {
|
||||
cursor: pointer;
|
||||
width: 100px;
|
||||
height: 40px;
|
||||
line-height: 40px;
|
||||
text-align: center;
|
||||
font-size: 16px;
|
||||
border-bottom: 2px solid transparent;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
margin-bottom: -1px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
|
||||
#tabs .active {
|
||||
border-bottom-color: #f00;
|
||||
color: #222;
|
||||
}
|
||||
|
||||
.tab-container .content {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* 页面布局 */
|
||||
.main {
|
||||
padding: 30px 100px;
|
||||
width: 960px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.main .logo {
|
||||
color: #333;
|
||||
text-align: left;
|
||||
margin-bottom: 30px;
|
||||
line-height: 1;
|
||||
height: 110px;
|
||||
margin-top: -50px;
|
||||
overflow: hidden;
|
||||
*zoom: 1;
|
||||
}
|
||||
|
||||
.main .logo a {
|
||||
font-size: 160px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.helps {
|
||||
margin-top: 40px;
|
||||
}
|
||||
|
||||
.helps pre {
|
||||
padding: 20px;
|
||||
margin: 10px 0;
|
||||
border: solid 1px #e7e1cd;
|
||||
background-color: #fffdef;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.icon_lists {
|
||||
width: 100% !important;
|
||||
overflow: hidden;
|
||||
*zoom: 1;
|
||||
}
|
||||
|
||||
.icon_lists li {
|
||||
width: 100px;
|
||||
margin-bottom: 10px;
|
||||
margin-right: 20px;
|
||||
text-align: center;
|
||||
list-style: none !important;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.icon_lists li .code-name {
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.icon_lists .icon {
|
||||
display: block;
|
||||
height: 100px;
|
||||
line-height: 100px;
|
||||
font-size: 42px;
|
||||
margin: 10px auto;
|
||||
color: #333;
|
||||
-webkit-transition: font-size 0.25s linear, width 0.25s linear;
|
||||
-moz-transition: font-size 0.25s linear, width 0.25s linear;
|
||||
transition: font-size 0.25s linear, width 0.25s linear;
|
||||
}
|
||||
|
||||
.icon_lists .icon:hover {
|
||||
font-size: 100px;
|
||||
}
|
||||
|
||||
.icon_lists .svg-icon {
|
||||
/* 通过设置 font-size 来改变图标大小 */
|
||||
width: 1em;
|
||||
/* 图标和文字相邻时,垂直对齐 */
|
||||
vertical-align: -0.15em;
|
||||
/* 通过设置 color 来改变 SVG 的颜色/fill */
|
||||
fill: currentColor;
|
||||
/* path 和 stroke 溢出 viewBox 部分在 IE 下会显示
|
||||
normalize.css 中也包含这行 */
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.icon_lists li .name,
|
||||
.icon_lists li .code-name {
|
||||
color: #666;
|
||||
}
|
||||
|
||||
/* markdown 样式 */
|
||||
.markdown {
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
line-height: 1.8;
|
||||
}
|
||||
|
||||
.highlight {
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.markdown img {
|
||||
vertical-align: middle;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.markdown h1 {
|
||||
color: #404040;
|
||||
font-weight: 500;
|
||||
line-height: 40px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.markdown h2,
|
||||
.markdown h3,
|
||||
.markdown h4,
|
||||
.markdown h5,
|
||||
.markdown h6 {
|
||||
color: #404040;
|
||||
margin: 1.6em 0 0.6em 0;
|
||||
font-weight: 500;
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.markdown h1 {
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
.markdown h2 {
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
.markdown h3 {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.markdown h4 {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.markdown h5 {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.markdown h6 {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.markdown hr {
|
||||
height: 1px;
|
||||
border: 0;
|
||||
background: #e9e9e9;
|
||||
margin: 16px 0;
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.markdown p {
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
.markdown>p,
|
||||
.markdown>blockquote,
|
||||
.markdown>.highlight,
|
||||
.markdown>ol,
|
||||
.markdown>ul {
|
||||
width: 80%;
|
||||
}
|
||||
|
||||
.markdown ul>li {
|
||||
list-style: circle;
|
||||
}
|
||||
|
||||
.markdown>ul li,
|
||||
.markdown blockquote ul>li {
|
||||
margin-left: 20px;
|
||||
padding-left: 4px;
|
||||
}
|
||||
|
||||
.markdown>ul li p,
|
||||
.markdown>ol li p {
|
||||
margin: 0.6em 0;
|
||||
}
|
||||
|
||||
.markdown ol>li {
|
||||
list-style: decimal;
|
||||
}
|
||||
|
||||
.markdown>ol li,
|
||||
.markdown blockquote ol>li {
|
||||
margin-left: 20px;
|
||||
padding-left: 4px;
|
||||
}
|
||||
|
||||
.markdown code {
|
||||
margin: 0 3px;
|
||||
padding: 0 5px;
|
||||
background: #eee;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.markdown strong,
|
||||
.markdown b {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.markdown>table {
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0px;
|
||||
empty-cells: show;
|
||||
border: 1px solid #e9e9e9;
|
||||
width: 95%;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.markdown>table th {
|
||||
white-space: nowrap;
|
||||
color: #333;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.markdown>table th,
|
||||
.markdown>table td {
|
||||
border: 1px solid #e9e9e9;
|
||||
padding: 8px 16px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.markdown>table th {
|
||||
background: #F7F7F7;
|
||||
}
|
||||
|
||||
.markdown blockquote {
|
||||
font-size: 90%;
|
||||
color: #999;
|
||||
border-left: 4px solid #e9e9e9;
|
||||
padding-left: 0.8em;
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
.markdown blockquote p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.markdown .anchor {
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s ease;
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.markdown .waiting {
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.markdown h1:hover .anchor,
|
||||
.markdown h2:hover .anchor,
|
||||
.markdown h3:hover .anchor,
|
||||
.markdown h4:hover .anchor,
|
||||
.markdown h5:hover .anchor,
|
||||
.markdown h6:hover .anchor {
|
||||
opacity: 1;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.markdown>br,
|
||||
.markdown>p>br {
|
||||
clear: both;
|
||||
}
|
||||
|
||||
|
||||
.hljs {
|
||||
display: block;
|
||||
background: white;
|
||||
padding: 0.5em;
|
||||
color: #333333;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.hljs-comment,
|
||||
.hljs-meta {
|
||||
color: #969896;
|
||||
}
|
||||
|
||||
.hljs-string,
|
||||
.hljs-variable,
|
||||
.hljs-template-variable,
|
||||
.hljs-strong,
|
||||
.hljs-emphasis,
|
||||
.hljs-quote {
|
||||
color: #df5000;
|
||||
}
|
||||
|
||||
.hljs-keyword,
|
||||
.hljs-selector-tag,
|
||||
.hljs-type {
|
||||
color: #a71d5d;
|
||||
}
|
||||
|
||||
.hljs-literal,
|
||||
.hljs-symbol,
|
||||
.hljs-bullet,
|
||||
.hljs-attribute {
|
||||
color: #0086b3;
|
||||
}
|
||||
|
||||
.hljs-section,
|
||||
.hljs-name {
|
||||
color: #63a35c;
|
||||
}
|
||||
|
||||
.hljs-tag {
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.hljs-title,
|
||||
.hljs-attr,
|
||||
.hljs-selector-id,
|
||||
.hljs-selector-class,
|
||||
.hljs-selector-attr,
|
||||
.hljs-selector-pseudo {
|
||||
color: #795da3;
|
||||
}
|
||||
|
||||
.hljs-addition {
|
||||
color: #55a532;
|
||||
background-color: #eaffea;
|
||||
}
|
||||
|
||||
.hljs-deletion {
|
||||
color: #bd2c00;
|
||||
background-color: #ffecec;
|
||||
}
|
||||
|
||||
.hljs-link {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* 代码高亮 */
|
||||
/* PrismJS 1.15.0
|
||||
https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript */
|
||||
/**
|
||||
* prism.js default theme for JavaScript, CSS and HTML
|
||||
* Based on dabblet (http://dabblet.com)
|
||||
* @author Lea Verou
|
||||
*/
|
||||
code[class*="language-"],
|
||||
pre[class*="language-"] {
|
||||
color: black;
|
||||
background: none;
|
||||
text-shadow: 0 1px white;
|
||||
font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
|
||||
text-align: left;
|
||||
white-space: pre;
|
||||
word-spacing: normal;
|
||||
word-break: normal;
|
||||
word-wrap: normal;
|
||||
line-height: 1.5;
|
||||
|
||||
-moz-tab-size: 4;
|
||||
-o-tab-size: 4;
|
||||
tab-size: 4;
|
||||
|
||||
-webkit-hyphens: none;
|
||||
-moz-hyphens: none;
|
||||
-ms-hyphens: none;
|
||||
hyphens: none;
|
||||
}
|
||||
|
||||
pre[class*="language-"]::-moz-selection,
|
||||
pre[class*="language-"] ::-moz-selection,
|
||||
code[class*="language-"]::-moz-selection,
|
||||
code[class*="language-"] ::-moz-selection {
|
||||
text-shadow: none;
|
||||
background: #b3d4fc;
|
||||
}
|
||||
|
||||
pre[class*="language-"]::selection,
|
||||
pre[class*="language-"] ::selection,
|
||||
code[class*="language-"]::selection,
|
||||
code[class*="language-"] ::selection {
|
||||
text-shadow: none;
|
||||
background: #b3d4fc;
|
||||
}
|
||||
|
||||
@media print {
|
||||
|
||||
code[class*="language-"],
|
||||
pre[class*="language-"] {
|
||||
text-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* Code blocks */
|
||||
pre[class*="language-"] {
|
||||
padding: 1em;
|
||||
margin: .5em 0;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
:not(pre)>code[class*="language-"],
|
||||
pre[class*="language-"] {
|
||||
background: #f5f2f0;
|
||||
}
|
||||
|
||||
/* Inline code */
|
||||
:not(pre)>code[class*="language-"] {
|
||||
padding: .1em;
|
||||
border-radius: .3em;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.token.comment,
|
||||
.token.prolog,
|
||||
.token.doctype,
|
||||
.token.cdata {
|
||||
color: slategray;
|
||||
}
|
||||
|
||||
.token.punctuation {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.namespace {
|
||||
opacity: .7;
|
||||
}
|
||||
|
||||
.token.property,
|
||||
.token.tag,
|
||||
.token.boolean,
|
||||
.token.number,
|
||||
.token.constant,
|
||||
.token.symbol,
|
||||
.token.deleted {
|
||||
color: #905;
|
||||
}
|
||||
|
||||
.token.selector,
|
||||
.token.attr-name,
|
||||
.token.string,
|
||||
.token.char,
|
||||
.token.builtin,
|
||||
.token.inserted {
|
||||
color: #690;
|
||||
}
|
||||
|
||||
.token.operator,
|
||||
.token.entity,
|
||||
.token.url,
|
||||
.language-css .token.string,
|
||||
.style .token.string {
|
||||
color: #9a6e3a;
|
||||
background: hsla(0, 0%, 100%, .5);
|
||||
}
|
||||
|
||||
.token.atrule,
|
||||
.token.attr-value,
|
||||
.token.keyword {
|
||||
color: #07a;
|
||||
}
|
||||
|
||||
.token.function,
|
||||
.token.class-name {
|
||||
color: #DD4A68;
|
||||
}
|
||||
|
||||
.token.regex,
|
||||
.token.important,
|
||||
.token.variable {
|
||||
color: #e90;
|
||||
}
|
||||
|
||||
.token.important,
|
||||
.token.bold {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.token.italic {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.token.entity {
|
||||
cursor: help;
|
||||
}
|
345
src/styles/iconfont/demo_index.html
Normal file
345
src/styles/iconfont/demo_index.html
Normal file
@ -0,0 +1,345 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<title>iconfont Demo</title>
|
||||
<link rel="shortcut icon" href="//img.alicdn.com/imgextra/i2/O1CN01ZyAlrn1MwaMhqz36G_!!6000000001499-73-tps-64-64.ico" type="image/x-icon"/>
|
||||
<link rel="icon" type="image/svg+xml" href="//img.alicdn.com/imgextra/i4/O1CN01EYTRnJ297D6vehehJ_!!6000000008020-55-tps-64-64.svg"/>
|
||||
<link rel="stylesheet" href="https://g.alicdn.com/thx/cube/1.3.2/cube.min.css">
|
||||
<link rel="stylesheet" href="demo.css">
|
||||
<link rel="stylesheet" href="iconfont.css">
|
||||
<script src="iconfont.js"></script>
|
||||
<!-- jQuery -->
|
||||
<script src="https://a1.alicdn.com/oss/uploads/2018/12/26/7bfddb60-08e8-11e9-9b04-53e73bb6408b.js"></script>
|
||||
<!-- 代码高亮 -->
|
||||
<script src="https://a1.alicdn.com/oss/uploads/2018/12/26/a3f714d0-08e6-11e9-8a15-ebf944d7534c.js"></script>
|
||||
<style>
|
||||
.main .logo {
|
||||
margin-top: 0;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.main .logo a {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.main .logo .sub-title {
|
||||
margin-left: 0.5em;
|
||||
font-size: 22px;
|
||||
color: #fff;
|
||||
background: linear-gradient(-45deg, #3967FF, #B500FE);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="main">
|
||||
<h1 class="logo"><a href="https://www.iconfont.cn/" title="iconfont 首页" target="_blank">
|
||||
<img width="200" src="https://img.alicdn.com/imgextra/i3/O1CN01Mn65HV1FfSEzR6DKv_!!6000000000514-55-tps-228-59.svg">
|
||||
|
||||
</a></h1>
|
||||
<div class="nav-tabs">
|
||||
<ul id="tabs" class="dib-box">
|
||||
<li class="dib active"><span>Unicode</span></li>
|
||||
<li class="dib"><span>Font class</span></li>
|
||||
<li class="dib"><span>Symbol</span></li>
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
<div class="tab-container">
|
||||
<div class="content unicode" style="display: block;">
|
||||
<ul class="icon_lists dib-box">
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">emoji</div>
|
||||
<div class="code-name">&#xe64a;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont">󰈃</span>
|
||||
<div class="name">3.1电话</div>
|
||||
<div class="code-name">&#xf0203;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">语音</div>
|
||||
<div class="code-name">&#xe610;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">视频</div>
|
||||
<div class="code-name">&#xe61f;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">垃圾桶</div>
|
||||
<div class="code-name">&#xe615;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">文件</div>
|
||||
<div class="code-name">&#xe69f;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">图库</div>
|
||||
<div class="code-name">&#xe712;</div>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
<div class="article markdown">
|
||||
<h2 id="unicode-">Unicode 引用</h2>
|
||||
<hr>
|
||||
|
||||
<p>Unicode 是字体在网页端最原始的应用方式,特点是:</p>
|
||||
<ul>
|
||||
<li>支持按字体的方式去动态调整图标大小,颜色等等。</li>
|
||||
<li>默认情况下不支持多色,直接添加多色图标会自动去色。</li>
|
||||
</ul>
|
||||
<blockquote>
|
||||
<p>注意:新版 iconfont 支持两种方式引用多色图标:SVG symbol 引用方式和彩色字体图标模式。(使用彩色字体图标需要在「编辑项目」中开启「彩色」选项后并重新生成。)</p>
|
||||
</blockquote>
|
||||
<p>Unicode 使用步骤如下:</p>
|
||||
<h3 id="-font-face">第一步:拷贝项目下面生成的 <code>@font-face</code></h3>
|
||||
<pre><code class="language-css"
|
||||
>@font-face {
|
||||
font-family: 'iconfont';
|
||||
src: url('iconfont.ttf?t=1654496599109') format('truetype');
|
||||
}
|
||||
</code></pre>
|
||||
<h3 id="-iconfont-">第二步:定义使用 iconfont 的样式</h3>
|
||||
<pre><code class="language-css"
|
||||
>.iconfont {
|
||||
font-family: "iconfont" !important;
|
||||
font-size: 16px;
|
||||
font-style: normal;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
</code></pre>
|
||||
<h3 id="-">第三步:挑选相应图标并获取字体编码,应用于页面</h3>
|
||||
<pre>
|
||||
<code class="language-html"
|
||||
><span class="iconfont">&#x33;</span>
|
||||
</code></pre>
|
||||
<blockquote>
|
||||
<p>"iconfont" 是你项目下的 font-family。可以通过编辑项目查看,默认是 "iconfont"。</p>
|
||||
</blockquote>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content font-class">
|
||||
<ul class="icon_lists dib-box">
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont icon-emoji"></span>
|
||||
<div class="name">
|
||||
emoji
|
||||
</div>
|
||||
<div class="code-name">.icon-emoji
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont icon-31dianhua"></span>
|
||||
<div class="name">
|
||||
3.1电话
|
||||
</div>
|
||||
<div class="code-name">.icon-31dianhua
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont icon-01"></span>
|
||||
<div class="name">
|
||||
语音
|
||||
</div>
|
||||
<div class="code-name">.icon-01
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont icon-video"></span>
|
||||
<div class="name">
|
||||
视频
|
||||
</div>
|
||||
<div class="code-name">.icon-video
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont icon-lajitong"></span>
|
||||
<div class="name">
|
||||
垃圾桶
|
||||
</div>
|
||||
<div class="code-name">.icon-lajitong
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont icon-wenjian"></span>
|
||||
<div class="name">
|
||||
文件
|
||||
</div>
|
||||
<div class="code-name">.icon-wenjian
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont icon-tuku"></span>
|
||||
<div class="name">
|
||||
图库
|
||||
</div>
|
||||
<div class="code-name">.icon-tuku
|
||||
</div>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
<div class="article markdown">
|
||||
<h2 id="font-class-">font-class 引用</h2>
|
||||
<hr>
|
||||
|
||||
<p>font-class 是 Unicode 使用方式的一种变种,主要是解决 Unicode 书写不直观,语意不明确的问题。</p>
|
||||
<p>与 Unicode 使用方式相比,具有如下特点:</p>
|
||||
<ul>
|
||||
<li>相比于 Unicode 语意明确,书写更直观。可以很容易分辨这个 icon 是什么。</li>
|
||||
<li>因为使用 class 来定义图标,所以当要替换图标时,只需要修改 class 里面的 Unicode 引用。</li>
|
||||
</ul>
|
||||
<p>使用步骤如下:</p>
|
||||
<h3 id="-fontclass-">第一步:引入项目下面生成的 fontclass 代码:</h3>
|
||||
<pre><code class="language-html"><link rel="stylesheet" href="./iconfont.css">
|
||||
</code></pre>
|
||||
<h3 id="-">第二步:挑选相应图标并获取类名,应用于页面:</h3>
|
||||
<pre><code class="language-html"><span class="iconfont icon-xxx"></span>
|
||||
</code></pre>
|
||||
<blockquote>
|
||||
<p>"
|
||||
iconfont" 是你项目下的 font-family。可以通过编辑项目查看,默认是 "iconfont"。</p>
|
||||
</blockquote>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content symbol">
|
||||
<ul class="icon_lists dib-box">
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#icon-emoji"></use>
|
||||
</svg>
|
||||
<div class="name">emoji</div>
|
||||
<div class="code-name">#icon-emoji</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#icon-31dianhua"></use>
|
||||
</svg>
|
||||
<div class="name">3.1电话</div>
|
||||
<div class="code-name">#icon-31dianhua</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#icon-01"></use>
|
||||
</svg>
|
||||
<div class="name">语音</div>
|
||||
<div class="code-name">#icon-01</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#icon-video"></use>
|
||||
</svg>
|
||||
<div class="name">视频</div>
|
||||
<div class="code-name">#icon-video</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#icon-lajitong"></use>
|
||||
</svg>
|
||||
<div class="name">垃圾桶</div>
|
||||
<div class="code-name">#icon-lajitong</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#icon-wenjian"></use>
|
||||
</svg>
|
||||
<div class="name">文件</div>
|
||||
<div class="code-name">#icon-wenjian</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#icon-tuku"></use>
|
||||
</svg>
|
||||
<div class="name">图库</div>
|
||||
<div class="code-name">#icon-tuku</div>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
<div class="article markdown">
|
||||
<h2 id="symbol-">Symbol 引用</h2>
|
||||
<hr>
|
||||
|
||||
<p>这是一种全新的使用方式,应该说这才是未来的主流,也是平台目前推荐的用法。相关介绍可以参考这篇<a href="">文章</a>
|
||||
这种用法其实是做了一个 SVG 的集合,与另外两种相比具有如下特点:</p>
|
||||
<ul>
|
||||
<li>支持多色图标了,不再受单色限制。</li>
|
||||
<li>通过一些技巧,支持像字体那样,通过 <code>font-size</code>, <code>color</code> 来调整样式。</li>
|
||||
<li>兼容性较差,支持 IE9+,及现代浏览器。</li>
|
||||
<li>浏览器渲染 SVG 的性能一般,还不如 png。</li>
|
||||
</ul>
|
||||
<p>使用步骤如下:</p>
|
||||
<h3 id="-symbol-">第一步:引入项目下面生成的 symbol 代码:</h3>
|
||||
<pre><code class="language-html"><script src="./iconfont.js"></script>
|
||||
</code></pre>
|
||||
<h3 id="-css-">第二步:加入通用 CSS 代码(引入一次就行):</h3>
|
||||
<pre><code class="language-html"><style>
|
||||
.icon {
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
vertical-align: -0.15em;
|
||||
fill: currentColor;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
</code></pre>
|
||||
<h3 id="-">第三步:挑选相应图标并获取类名,应用于页面:</h3>
|
||||
<pre><code class="language-html"><svg class="icon" aria-hidden="true">
|
||||
<use xlink:href="#icon-xxx"></use>
|
||||
</svg>
|
||||
</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
$('.tab-container .content:first').show()
|
||||
|
||||
$('#tabs li').click(function (e) {
|
||||
var tabContent = $('.tab-container .content')
|
||||
var index = $(this).index()
|
||||
|
||||
if ($(this).hasClass('active')) {
|
||||
return
|
||||
} else {
|
||||
$('#tabs li').removeClass('active')
|
||||
$(this).addClass('active')
|
||||
|
||||
tabContent.hide().eq(index).fadeIn()
|
||||
}
|
||||
})
|
||||
})
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
41
src/styles/iconfont/iconfont.css
Normal file
41
src/styles/iconfont/iconfont.css
Normal file
@ -0,0 +1,41 @@
|
||||
@font-face {
|
||||
font-family: "iconfont"; /* Project id */
|
||||
src: url('iconfont.ttf?t=1654496599109') format('truetype');
|
||||
}
|
||||
|
||||
.iconfont {
|
||||
font-family: "iconfont" !important;
|
||||
font-size: 16px;
|
||||
font-style: normal;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.icon-emoji:before {
|
||||
content: "\e64a";
|
||||
}
|
||||
|
||||
.icon-31dianhua:before {
|
||||
content: "\f0203";
|
||||
}
|
||||
|
||||
.icon-01:before {
|
||||
content: "\e610";
|
||||
}
|
||||
|
||||
.icon-video:before {
|
||||
content: "\e61f";
|
||||
}
|
||||
|
||||
.icon-lajitong:before {
|
||||
content: "\e615";
|
||||
}
|
||||
|
||||
.icon-wenjian:before {
|
||||
content: "\e69f";
|
||||
}
|
||||
|
||||
.icon-tuku:before {
|
||||
content: "\e712";
|
||||
}
|
||||
|
73
src/styles/iconfont/iconfont.js
Normal file
73
src/styles/iconfont/iconfont.js
Normal file
File diff suppressed because one or more lines are too long
58
src/styles/iconfont/iconfont.json
Normal file
58
src/styles/iconfont/iconfont.json
Normal file
@ -0,0 +1,58 @@
|
||||
{
|
||||
"id": "",
|
||||
"name": "",
|
||||
"font_family": "iconfont",
|
||||
"css_prefix_text": "icon-",
|
||||
"description": "",
|
||||
"glyphs": [
|
||||
{
|
||||
"icon_id": "29929",
|
||||
"name": "emoji",
|
||||
"font_class": "emoji",
|
||||
"unicode": "e64a",
|
||||
"unicode_decimal": 58954
|
||||
},
|
||||
{
|
||||
"icon_id": "201577",
|
||||
"name": "3.1电话",
|
||||
"font_class": "31dianhua",
|
||||
"unicode": "f0203",
|
||||
"unicode_decimal": 983555
|
||||
},
|
||||
{
|
||||
"icon_id": "1236846",
|
||||
"name": "语音",
|
||||
"font_class": "01",
|
||||
"unicode": "e610",
|
||||
"unicode_decimal": 58896
|
||||
},
|
||||
{
|
||||
"icon_id": "3878694",
|
||||
"name": "视频",
|
||||
"font_class": "video",
|
||||
"unicode": "e61f",
|
||||
"unicode_decimal": 58911
|
||||
},
|
||||
{
|
||||
"icon_id": "7587956",
|
||||
"name": "垃圾桶",
|
||||
"font_class": "lajitong",
|
||||
"unicode": "e615",
|
||||
"unicode_decimal": 58901
|
||||
},
|
||||
{
|
||||
"icon_id": "20710439",
|
||||
"name": "文件",
|
||||
"font_class": "wenjian",
|
||||
"unicode": "e69f",
|
||||
"unicode_decimal": 59039
|
||||
},
|
||||
{
|
||||
"icon_id": "27334037",
|
||||
"name": "图库",
|
||||
"font_class": "tuku",
|
||||
"unicode": "e712",
|
||||
"unicode_decimal": 59154
|
||||
}
|
||||
]
|
||||
}
|
BIN
src/styles/iconfont/iconfont.ttf
Normal file
BIN
src/styles/iconfont/iconfont.ttf
Normal file
Binary file not shown.
@ -1,31 +1,49 @@
|
||||
<script setup lang="ts">
|
||||
import { formatDate } from '@/utils/formatTime'
|
||||
import { reactive } from 'vue'
|
||||
//取好友列表(主要使用好友下的用户属性相关)
|
||||
const friendList = reactive([
|
||||
{
|
||||
friendKey: {
|
||||
avatarurl: '',
|
||||
nickName: ''
|
||||
/* 头像相关 */
|
||||
import informIcon from '@/assets/imgs/avatar/inform.png'
|
||||
/* route */
|
||||
const route = useRoute()
|
||||
/* router */
|
||||
const router = useRouter()
|
||||
//取系统通知数据
|
||||
const informDetail = computed(() => {
|
||||
const informDetailArr = reactive([
|
||||
{
|
||||
from: '系统通知',
|
||||
desc: '您有一条新的通知',
|
||||
time: new Date(),
|
||||
untreated: 1
|
||||
}
|
||||
}
|
||||
])
|
||||
//取会话数据
|
||||
const conversationList = reactive({
|
||||
conversationKey: {
|
||||
conversationInfo: {
|
||||
avatarUrl: '',
|
||||
name: '',
|
||||
conversationType: 0
|
||||
},
|
||||
latestMessage: {
|
||||
msg: ''
|
||||
},
|
||||
latestSendTime: 0,
|
||||
unreadMessageNum: 0,
|
||||
isMention: false
|
||||
])
|
||||
const lastInformDeatail = informDetailArr[0] || {}
|
||||
const untreated = 1
|
||||
return { untreated, lastInformDeatail }
|
||||
})
|
||||
|
||||
//取好友列表(主要使用好友下的用户属性相关)
|
||||
const friendList = reactive({
|
||||
1: {
|
||||
avatarurl: 'https://img.yzcdn.cn/vant/cat.jpeg'
|
||||
}
|
||||
})
|
||||
//取会话数据
|
||||
const conversationList = reactive([
|
||||
{
|
||||
conversationKey: 1,
|
||||
conversationInfo: { avatarUrl: 'https://img.yzcdn.cn/vant/cat.jpeg' },
|
||||
|
||||
name: '好友1',
|
||||
conversationType: 2,
|
||||
latestMessage: {
|
||||
msg: 'hello word!'
|
||||
},
|
||||
latestSendTime: new Date(),
|
||||
unreadMessageNum: 5,
|
||||
isMention: false
|
||||
}
|
||||
])
|
||||
|
||||
//处理会话name
|
||||
const handleConversationName = computed(() => {
|
||||
@ -35,74 +53,112 @@ const handleConversationName = computed(() => {
|
||||
const handleLastMsgNickName = computed(() => {
|
||||
return ''
|
||||
})
|
||||
const emit = defineEmits(['toInformDetails', 'toChatMessage'])
|
||||
//普通会话
|
||||
const checkedConverItemIndex = ref(null)
|
||||
const toChatMessage = (item, itemKey, index) => {
|
||||
checkedConverItemIndex.value = index
|
||||
console.log('选中的会话key', itemKey)
|
||||
//跳转至对应的消息界面
|
||||
emit('toChatMessage', itemKey, item.conversationType)
|
||||
}
|
||||
//删除某条会话
|
||||
const deleteConversation = (itemKey) => {}
|
||||
const deleteConversation = (itemKey) => {
|
||||
console.log('选中的会话key', itemKey)
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<!-- 普通会话 -->
|
||||
<template v-if="Object.keys(conversationList).length > 0">
|
||||
<el-scrollbar class="session_list" style="overflow: auto" tag="ul">
|
||||
<!-- 系统通知会话 -->
|
||||
<li
|
||||
v-for="(item, itemKey, index) in conversationList"
|
||||
:key="itemKey"
|
||||
@click="toChatMessage(item, itemKey, index)"
|
||||
:style="{
|
||||
background: checkedConverItemIndex === index ? '#E5E5E5' : ''
|
||||
}"
|
||||
v-if="JSON.stringify(informDetail.lastInformDeatail) !== '{}' && informDetail.untreated >= 1"
|
||||
class="session_list_item"
|
||||
@click="$emit('toInformDetails')"
|
||||
>
|
||||
<el-popover
|
||||
popper-class="conversation_popover"
|
||||
placement="right-end"
|
||||
trigger="contextmenu"
|
||||
:show-arrow="false"
|
||||
:offset="-10"
|
||||
>
|
||||
<template #reference>
|
||||
<div class="session_list_item">
|
||||
<div class="item_body item_left">
|
||||
<div class="session_other_avatar">
|
||||
<el-avatar
|
||||
:size="34"
|
||||
:src="
|
||||
friendList[item.conversationKey] && friendList[item.conversationKey].avatarurl
|
||||
? friendList[item.conversationKey].avatarurl
|
||||
: item.conversationInfo.avatarUrl
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item_body item_main">
|
||||
<div class="name"> 好友 </div>
|
||||
<div class="last_msg_body">
|
||||
<span class="last_msg_body_mention" v-if="item.isMention">[有人@我]</span>
|
||||
<span v-show="item.conversationType === 2">好友</span>
|
||||
{{ item.latestMessage.msg }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="item_body item_right">
|
||||
<span class="time">{{ formatDate(item.latestSendTime, 'MM/DD/HH:mm') }}</span>
|
||||
<span class="unReadNum_box" v-if="item.unreadMessageNum >= 1">
|
||||
<sup
|
||||
class="unReadNum_count"
|
||||
v-text="item.unreadMessageNum >= 99 ? '99+' : item.unreadMessageNum"
|
||||
></sup>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #default>
|
||||
<div class="session_list_delete" @click="deleteConversation(itemKey)"> 删除会话 </div>
|
||||
</template>
|
||||
</el-popover>
|
||||
<div class="item_body item_left">
|
||||
<!-- 通知头像 -->
|
||||
<div class="session_other_avatar">
|
||||
<el-avatar :size="34" :src="informIcon" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="item_body item_main">
|
||||
<div class="name">系统通知</div>
|
||||
<div class="last_msg_body">
|
||||
{{ informDetail.lastInformDeatail.from }}:{{ informDetail.lastInformDeatail.desc }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="item_body item_right">
|
||||
<span class="time">{{
|
||||
formatDate(informDetail.lastInformDeatail.time, 'MM/DD/HH:mm')
|
||||
}}</span>
|
||||
<span class="unReadNum_box" v-if="informDetail.untreated >= 1">
|
||||
<sup
|
||||
class="unReadNum_count"
|
||||
v-text="informDetail.untreated >= 99 ? '99+' : informDetail.untreated"
|
||||
></sup>
|
||||
</span>
|
||||
</div>
|
||||
</li>
|
||||
</template>
|
||||
<template v-else>
|
||||
<el-empty description="暂无最近会话" />
|
||||
</template>
|
||||
<!-- 普通会话 -->
|
||||
<template v-if="Object.keys(conversationList).length > 0">
|
||||
<li
|
||||
v-for="(item, itemKey, index) in conversationList"
|
||||
:key="itemKey"
|
||||
@click="toChatMessage(item, itemKey, index)"
|
||||
:style="{
|
||||
background: checkedConverItemIndex === index ? '#E5E5E5' : ''
|
||||
}"
|
||||
>
|
||||
<el-popover
|
||||
popper-class="conversation_popover"
|
||||
placement="right-end"
|
||||
trigger="contextmenu"
|
||||
:show-arrow="false"
|
||||
:offset="-10"
|
||||
>
|
||||
<template #reference>
|
||||
<div class="session_list_item">
|
||||
<div class="item_body item_left">
|
||||
<div class="session_other_avatar">
|
||||
<el-avatar
|
||||
:size="34"
|
||||
:src="
|
||||
friendList[item.conversationKey] && friendList[item.conversationKey].avatarurl
|
||||
? friendList[item.conversationKey].avatarurl
|
||||
: item.conversationInfo.avatarUrl
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item_body item_main">
|
||||
<div class="name"> 好友 </div>
|
||||
<div class="last_msg_body">
|
||||
<span class="last_msg_body_mention" v-if="item.isMention">[有人@我]</span>
|
||||
<span v-show="item.conversationType === 2">好友</span>
|
||||
{{ item.latestMessage.msg }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="item_body item_right">
|
||||
<span class="time">{{ formatDate(item.latestSendTime, 'MM/DD/HH:mm') }}</span>
|
||||
<span class="unReadNum_box" v-if="item.unreadMessageNum >= 1">
|
||||
<sup
|
||||
class="unReadNum_count"
|
||||
v-text="item.unreadMessageNum >= 99 ? '99+' : item.unreadMessageNum"
|
||||
></sup>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #default>
|
||||
<div class="session_list_delete" @click="deleteConversation(itemKey)"> 删除会话 </div>
|
||||
</template>
|
||||
</el-popover>
|
||||
</li>
|
||||
</template>
|
||||
<template v-else>
|
||||
<el-empty description="暂无最近会话" />
|
||||
</template>
|
||||
</el-scrollbar>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
@ -4,6 +4,24 @@ import SearchInput from '@/components/SearchInput/index.vue'
|
||||
/* 欢迎页 */
|
||||
import Welcome from '@/components/Welcome/index.vue'
|
||||
import ConversationList from '../Conversation/components/ConversationList.vue'
|
||||
import router from '@/router'
|
||||
|
||||
//路由跳转-系统通知
|
||||
const toInformDetails = () => {
|
||||
router.push('/im/conversation/informDetails')
|
||||
}
|
||||
|
||||
//路由跳转-对应好友会话
|
||||
const toChatMessage = (id, chatType) => {
|
||||
console.log('>>>>>>>id', id)
|
||||
router.push({
|
||||
path: '/im/conversation/message',
|
||||
query: {
|
||||
id,
|
||||
chatType
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<el-container style="height: 100%">
|
||||
@ -11,7 +29,7 @@ import ConversationList from '../Conversation/components/ConversationList.vue'
|
||||
<!-- 搜索组件 -->
|
||||
<SearchInput :searchType="'conversation'" />
|
||||
<div class="chat_conversation_list">
|
||||
<ConversationList />
|
||||
<ConversationList @toInformDetails="toInformDetails" @toChatMessage="toChatMessage" />
|
||||
</div>
|
||||
</el-aside>
|
||||
<el-main class="chat_conversation_main_box">
|
||||
|
@ -1,11 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
</script>
|
||||
<script setup lang="ts"></script>
|
||||
|
||||
<template>
|
||||
|
||||
<h2>系统通知</h2>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
</style>
|
||||
<style scoped lang="scss"></style>
|
||||
|
107
src/views/im/Message/components/inputBox/index.scss
Normal file
107
src/views/im/Message/components/inputBox/index.scss
Normal file
@ -0,0 +1,107 @@
|
||||
.chat_func_box {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 42px;
|
||||
width: 100%;
|
||||
background-color: #f7f7f7;
|
||||
border-top: 1px solid #e6e6e6;
|
||||
border-bottom: 1px solid #e6e6e6;
|
||||
line-height: 12px;
|
||||
|
||||
.chat_func_icon {
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
}
|
||||
|
||||
.emojis_box {
|
||||
position: absolute;
|
||||
left: 15px;
|
||||
top: -180px;
|
||||
width: 330px;
|
||||
height: 150px;
|
||||
border-radius: 5px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
background: #fff;
|
||||
padding: 15px 5px;
|
||||
|
||||
.emoji {
|
||||
display: inline-block;
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
text-align: center;
|
||||
line-height: 25px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:hover {
|
||||
transform: scale(1.2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.loading_box {
|
||||
position: absolute;
|
||||
right: 5px;
|
||||
top: 0;
|
||||
width: 50px;
|
||||
height: 100%;
|
||||
font-size: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
/* loading svg大小调整 */
|
||||
::v-deep .circular {
|
||||
margin-top: 8px;
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
}
|
||||
|
||||
.chat_content_editable {
|
||||
font-family: 'PingFang SC';
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
min-height: 100px;
|
||||
border: none;
|
||||
background: none;
|
||||
letter-spacing: 0.5px;
|
||||
resize: none;
|
||||
padding: 10px 20px;
|
||||
font-size: 14px;
|
||||
line-height: 10px;
|
||||
}
|
||||
|
||||
.no_content_send_btn {
|
||||
position: absolute;
|
||||
bottom: 20px;
|
||||
right: 20px;
|
||||
width: 80px;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.chat_send_btn {
|
||||
position: absolute;
|
||||
bottom: 20px;
|
||||
right: 20px;
|
||||
width: 80px;
|
||||
}
|
||||
|
||||
.iconfont {
|
||||
margin-right: 12px;
|
||||
transition: all 0.3s ease;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
transform: scale(1.2);
|
||||
color: #1b83f9;
|
||||
}
|
||||
}
|
||||
|
||||
.record_box {
|
||||
width: 250px;
|
||||
height: 180px;
|
||||
}
|
453
src/views/im/Message/components/inputBox/index.vue
Normal file
453
src/views/im/Message/components/inputBox/index.vue
Normal file
@ -0,0 +1,453 @@
|
||||
<script setup>
|
||||
import { ElLoading, ElMessageBox } from 'element-plus'
|
||||
import { emojis } from '@/constant'
|
||||
import { messageType } from '@/constant'
|
||||
import _ from 'lodash'
|
||||
/* 组件 */
|
||||
import PreviewSendImg from '../suit/previewSendImg.vue'
|
||||
import VueAt from 'vue-at/dist/vue-at-textarea' // for textarea
|
||||
const { ALL_MESSAGE_TYPE, CHAT_TYPE, MENTION_ALL } = messageType
|
||||
const nowPickInfo = ref({
|
||||
id: '',
|
||||
chatType: ''
|
||||
})
|
||||
const atMembersList = ref([])
|
||||
//附件类上传加载状态
|
||||
const loadingBox = ref(null)
|
||||
const isAtAll = ref(false)
|
||||
const atMembers = ref([])
|
||||
//输入框插入@事件
|
||||
const onInsert = (target) => {
|
||||
// if (!) return false
|
||||
console.log('onInset', target)
|
||||
if (_.map(atMembers.value, 'value').includes(target.value)) return false
|
||||
if (target.value === MENTION_ALL.VALUE) {
|
||||
return (isAtAll.value = true)
|
||||
} else {
|
||||
atMembers.value.push({ ...target })
|
||||
}
|
||||
}
|
||||
//校验消息内容中是否包含要@的成员
|
||||
const checkAtMembers = (text) => {
|
||||
if (!text) {
|
||||
return false
|
||||
}
|
||||
//判断是否文本中是否有@ALL,没有则直接设置为false
|
||||
const patternAtAll = new RegExp(`@${MENTION_ALL.TEXT}`)
|
||||
console.log('patternAtAll', patternAtAll)
|
||||
if (isAtAll.value && !patternAtAll.test(text)) {
|
||||
isAtAll.value = false
|
||||
}
|
||||
if (atMembers.value.length !== 0) {
|
||||
//循环AT成员数组通过匹配文本内容判断是否存在已经移除@成员
|
||||
_.map(atMembers.value, 'text').forEach((item, index) => {
|
||||
console.log('atMembers item', item, index)
|
||||
const pattern = new RegExp(`@${item}`)
|
||||
const result = pattern.test(text)
|
||||
if (!result) {
|
||||
console.log('文本中不满足条件')
|
||||
//不包含则从@列表中移除该成员
|
||||
atMembers.value.splice(index, 1)
|
||||
console.log('>>>>>已删除', atMembers.value)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
//emojis框展开
|
||||
const isShowEmojisBox = ref(false)
|
||||
const emojisBox = ref(null)
|
||||
const showEmojisBox = () => {
|
||||
console.log('>>>>>展开模态框')
|
||||
isShowEmojisBox.value = true
|
||||
}
|
||||
//新增一个emoji
|
||||
const addOneEmoji = (emoji) => {
|
||||
console.log('>>>>>>emoji', emoji)
|
||||
textContent.value = textContent.value + emoji
|
||||
}
|
||||
//消息引用
|
||||
const messageQuoteRef = ref(null)
|
||||
const handleQuoteMessage = (msgBody) => {
|
||||
messageQuoteRef.value && messageQuoteRef.value.setQuoteContent(msgBody)
|
||||
}
|
||||
|
||||
//换行操作
|
||||
const insertNewLine = () => (textContent.value += '\n')
|
||||
//发送文本内容
|
||||
const textContent = ref('')
|
||||
const sendTextMessage = _.debounce(async () => {
|
||||
//如果输入框全部为空格同样拒绝发送
|
||||
if (textContent.value.match(/^\s*$/)) return
|
||||
console.log('atMembers.value', atMembers.value)
|
||||
checkAtMembers(textContent.value)
|
||||
const msgOptions = {
|
||||
id: nowPickInfo.value.id,
|
||||
chatType: nowPickInfo.value.chatType,
|
||||
msg: textContent.value,
|
||||
ext: {
|
||||
em_at_list: isAtAll.value ? MENTION_ALL.VALUE : _.map(atMembers.value, 'value')
|
||||
}
|
||||
}
|
||||
//关闭引用框
|
||||
if (messageQuoteRef.value?.isShowQuoteMsgBox) {
|
||||
}
|
||||
textContent.value = ''
|
||||
messageQuoteRef.value?.clearQuoteContent()
|
||||
try {
|
||||
console.log('msgOptions', msgOptions)
|
||||
// await store.dispatch('sendShowTypeMessage', {
|
||||
// msgType: ALL_MESSAGE_TYPE.TEXT,
|
||||
// msgOptions
|
||||
// })
|
||||
} catch (error) {
|
||||
//handleSDKErrorNotifi(error.type, error.message)
|
||||
console.log('>>>>>>>发送失败+++++++', error)
|
||||
} finally {
|
||||
isAtAll.value = false
|
||||
atMembers.value = []
|
||||
}
|
||||
}, 50)
|
||||
//监听键盘按下事件,如果为enter键则发送文本内容,shift+enter则换行。
|
||||
const onTextInputKeyDown = (event) => {
|
||||
if (event.keyCode === 13 && !event.shiftKey) {
|
||||
event.preventDefault()
|
||||
// 执行发送操作
|
||||
sendTextMessage()
|
||||
} else if (event.keyCode === 13 && event.shiftKey) {
|
||||
// 换行操作
|
||||
insertNewLine()
|
||||
}
|
||||
}
|
||||
/* 图片消息相关 */
|
||||
//选择图片
|
||||
const uploadImgs = ref(null)
|
||||
const chooseImages = () => {
|
||||
uploadImgs.value.click()
|
||||
console.log('uploadImgs')
|
||||
}
|
||||
//发送图片
|
||||
const sendImagesMessage = async (type) => {
|
||||
const file = {
|
||||
data: null, // file 对象。
|
||||
filename: '', //文件名称。
|
||||
filetype: '' //文件类型。
|
||||
}
|
||||
const url = window.URL || window.webkitURL
|
||||
const img = new Image() //手动创建一个Image对象
|
||||
const msgOptions = {
|
||||
id: nowPickInfo.value.id,
|
||||
chatType: nowPickInfo.value.chatType,
|
||||
file: file,
|
||||
width: 0,
|
||||
height: 0
|
||||
}
|
||||
if (type === 'common') {
|
||||
//读取图片的宽高
|
||||
const imgFile = uploadImgs.value.files[0]
|
||||
file.data = imgFile
|
||||
file.filename = imgFile.name
|
||||
file.filetype = imgFile.type
|
||||
console.log('imgFile', file)
|
||||
img.src = url.createObjectURL(imgFile) //创建Image的对象的url
|
||||
img.onload = async () => {
|
||||
const loadingInstance = ElLoading.service({
|
||||
target: loadingBox.value,
|
||||
background: '#f7f7f7'
|
||||
})
|
||||
msgOptions.width = img.width
|
||||
msgOptions.height = img.height
|
||||
console.log('height:' + img.height + '----' + img.width)
|
||||
try {
|
||||
// await store.dispatch('sendShowTypeMessage', {
|
||||
// msgType: ALL_MESSAGE_TYPE.IMAGE,
|
||||
// msgOptions: _.cloneDeep(msgOptions)
|
||||
// })
|
||||
loadingInstance.close()
|
||||
uploadImgs.value.value = null
|
||||
} catch (error) {
|
||||
console.log('>>>>>发送失败', error)
|
||||
if (error.type && error?.data) {
|
||||
handleSDKErrorNotifi(error.type, error.data.error || 'none')
|
||||
} else {
|
||||
handleSDKErrorNotifi(0, 'none')
|
||||
}
|
||||
loadingInstance.close()
|
||||
uploadImgs.value.value = null
|
||||
}
|
||||
}
|
||||
} else if (type === 'other') {
|
||||
console.log('fileObjfileObjfileObj', fileObj)
|
||||
const imgFile = fileObj
|
||||
file.data = imgFile
|
||||
file.filename = imgFile.name
|
||||
file.filetype = imgFile.type
|
||||
console.log('imgFile', file)
|
||||
img.src = url.createObjectURL(imgFile) //创建Image的对象的url
|
||||
img.onload = async () => {
|
||||
const loadingInstance = ElLoading.service({
|
||||
target: loadingBox.value,
|
||||
background: '#f7f7f7'
|
||||
})
|
||||
msgOptions.width = img.width
|
||||
msgOptions.height = img.height
|
||||
console.log('height:' + img.height + '----' + img.width)
|
||||
try {
|
||||
await store.dispatch('sendShowTypeMessage', {
|
||||
msgType: ALL_MESSAGE_TYPE.IMAGE,
|
||||
msgOptions: _.cloneDeep(msgOptions)
|
||||
})
|
||||
loadingInstance.close()
|
||||
uploadImgs.value.value = null
|
||||
} catch (error) {
|
||||
console.log('>>>>>发送失败', error)
|
||||
if (error.type && error?.data) {
|
||||
handleSDKErrorNotifi(error.type, error.data.error || 'none')
|
||||
} else {
|
||||
handleSDKErrorNotifi(0, 'none')
|
||||
}
|
||||
loadingInstance.close()
|
||||
uploadImgs.value.value = null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
//贴图发送
|
||||
const previewSendImg = ref(null)
|
||||
const onPasteImage = (event) => {
|
||||
console.log('>>>>>>监听粘贴事件', event)
|
||||
const data = event.clipboardData || window.clipboardData
|
||||
//获取图片内容
|
||||
const imgContent = data.items[0].getAsFile()
|
||||
//判断是不是图片,最好通过文件类型判断
|
||||
const isImg = (imgContent && 1) || -1
|
||||
const reader = new FileReader()
|
||||
if (isImg >= 0) {
|
||||
//将文件读取为 DataURL
|
||||
reader.readAsDataURL(imgContent)
|
||||
}
|
||||
//文件读取完成时触发
|
||||
reader.onload = (event) => {
|
||||
//获取base64流
|
||||
const base64_str = event.target.result
|
||||
const imgInfo = {
|
||||
imgFile: imgContent,
|
||||
tempFilePath: base64_str
|
||||
}
|
||||
previewSendImg.value.showPreviewImgModal({ ...imgInfo })
|
||||
console.log('>>>>>获取到粘贴到的文本', imgInfo)
|
||||
}
|
||||
}
|
||||
/* 文件消息相关 */
|
||||
//选择文件
|
||||
const uploadFiles = ref(null)
|
||||
const chooseFiles = () => {
|
||||
uploadFiles.value.click()
|
||||
}
|
||||
//发送文件
|
||||
const sendFilesMessages = async () => {
|
||||
const commonFile = uploadFiles.value.files[0]
|
||||
const file = {
|
||||
data: commonFile, // file 对象。
|
||||
filename: commonFile.name, //文件名称。
|
||||
filetype: commonFile.type, //文件类型。
|
||||
size: commonFile.size
|
||||
}
|
||||
console.log('>>>>>调用发送文件', file)
|
||||
const msgOptions = {
|
||||
id: nowPickInfo.value.id,
|
||||
chatType: nowPickInfo.value.chatType,
|
||||
file: file
|
||||
}
|
||||
const loadingInstance = ElLoading.service({
|
||||
target: loadingBox.value,
|
||||
background: '#f7f7f7'
|
||||
})
|
||||
try {
|
||||
// await store.dispatch('sendShowTypeMessage', {
|
||||
// msgType: ALL_MESSAGE_TYPE.FILE,
|
||||
// msgOptions: _.cloneDeep(msgOptions)
|
||||
// })
|
||||
loadingInstance.close()
|
||||
uploadFiles.value.value = null
|
||||
} catch (error) {
|
||||
console.log('>>>>file error', error)
|
||||
if (error.type && error?.data) {
|
||||
handleSDKErrorNotifi(error.type, error.data.error || 'none')
|
||||
} else {
|
||||
handleSDKErrorNotifi(0, 'none')
|
||||
}
|
||||
|
||||
uploadFiles.value.value = null
|
||||
loadingInstance.close()
|
||||
}
|
||||
}
|
||||
/* 语音消息相关 */
|
||||
//展示录音对话框
|
||||
const isHttps = window.location.protocol === 'https:' || window.location.hostname === 'localhost'
|
||||
const isShowRecordBox = ref(false)
|
||||
const recordBox = ref(null)
|
||||
const showRecordBox = () => {
|
||||
isShowRecordBox.value = true
|
||||
}
|
||||
const sendAudioMessages = async (audioData) => {
|
||||
const file = {
|
||||
// url: EaseChatSDK.utils.parseDownloadResponse(audioData.src),
|
||||
filename: '录音',
|
||||
filetype: '.amr',
|
||||
data: audioData.src
|
||||
}
|
||||
console.log('>>>>>audioData', audioData, file)
|
||||
const msgOptions = {
|
||||
id: nowPickInfo.value.id,
|
||||
chatType: nowPickInfo.value.chatType,
|
||||
file: file,
|
||||
length: audioData.length
|
||||
}
|
||||
try {
|
||||
// await store.dispatch('sendShowTypeMessage', {
|
||||
// msgType: ALL_MESSAGE_TYPE.AUDIO,
|
||||
// msgOptions: _.cloneDeep(msgOptions)
|
||||
// })
|
||||
isShowRecordBox.value = false
|
||||
} catch (error) {
|
||||
// if (error.type && error?.data) {
|
||||
// handleSDKErrorNotifi(error.type, error.data.error || 'none')
|
||||
// } else {
|
||||
// handleSDKErrorNotifi(0, 'none')
|
||||
// }
|
||||
isShowRecordBox.value = false
|
||||
}
|
||||
}
|
||||
/*清除屏幕*/
|
||||
const clearScreen = () => {
|
||||
ElMessageBox.confirm('确认清空当前消息内容?', '消息清屏', {
|
||||
confirmButtonText: '确认',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
})
|
||||
.then(() => {
|
||||
const key = nowPickInfo.value.id
|
||||
// store.commit('CLEAR_SOMEONE_MESSAGE', key)
|
||||
})
|
||||
.catch(() => {
|
||||
return false
|
||||
})
|
||||
}
|
||||
//func 对应事件 icon class样式等
|
||||
const all_func = [
|
||||
{
|
||||
className: 'icon-emoji',
|
||||
style: 'font-size:20px;margin-left: 20px;',
|
||||
title: '选择表情',
|
||||
methodName: showEmojisBox
|
||||
},
|
||||
{
|
||||
className: 'icon-tuku',
|
||||
style: 'font-size: 26px;',
|
||||
title: '发送图片',
|
||||
methodName: chooseImages
|
||||
},
|
||||
{
|
||||
className: 'icon-wenjian',
|
||||
style: 'font-size: 20px;',
|
||||
title: '发送文件',
|
||||
methodName: chooseFiles
|
||||
},
|
||||
{
|
||||
className: 'icon-01',
|
||||
style: 'font-size: 20px;',
|
||||
title: '发送语音',
|
||||
methodName: showRecordBox
|
||||
},
|
||||
{
|
||||
className: 'icon-lajitong',
|
||||
style: 'font-size: 23px;',
|
||||
title: '清屏',
|
||||
methodName: clearScreen
|
||||
}
|
||||
]
|
||||
defineExpose({
|
||||
textContent,
|
||||
handleQuoteMessage
|
||||
})
|
||||
</script>
|
||||
<template>
|
||||
<div class="chat_func_box">
|
||||
<span
|
||||
v-for="iconItem in all_func"
|
||||
:class="['iconfont', iconItem.className]"
|
||||
:key="iconItem.className"
|
||||
:style="iconItem.style"
|
||||
:title="iconItem.title"
|
||||
@click.stop="iconItem.methodName"
|
||||
></span>
|
||||
<!-- 表情框 -->
|
||||
<el-scrollbar ref="emojisBox" v-if="isShowEmojisBox" class="emojis_box" tag="div">
|
||||
<span
|
||||
class="emoji"
|
||||
v-for="(emoji, index) in emojis"
|
||||
:key="index"
|
||||
@click="addOneEmoji(emoji)"
|
||||
>{{ emoji }}</span
|
||||
>
|
||||
</el-scrollbar>
|
||||
<!-- 图片附件choose -->
|
||||
<input
|
||||
ref="uploadImgs"
|
||||
type="file"
|
||||
style="display: none"
|
||||
@change="sendImagesMessage('common')"
|
||||
accept="image/*"
|
||||
/>
|
||||
<!-- 文件附件choose -->
|
||||
<input ref="uploadFiles" type="file" style="display: none" @change="sendFilesMessages" />
|
||||
<!-- 录音采集框 -->
|
||||
<el-card ref="recordBox" v-if="isShowRecordBox" class="record_box" shadow="always">
|
||||
<p v-if="!isHttps"> 由于浏览器限制,录音功能必须为https环境或者为localhost环境下使用! </p>
|
||||
<!-- <CollectAudio v-else @sendAudioMessages="sendAudioMessages" />-->
|
||||
</el-card>
|
||||
<!-- 附件上传加载容器 -->
|
||||
<div ref="loadingBox" class="loading_box"></div>
|
||||
</div>
|
||||
<template v-if="nowPickInfo.chatType === CHAT_TYPE.SINGLE">
|
||||
<textarea
|
||||
ref="editable"
|
||||
v-model="textContent"
|
||||
class="chat_content_editable"
|
||||
spellcheck="false"
|
||||
contenteditable="true"
|
||||
placeholder="请输入消息内容..."
|
||||
@keydown="onTextInputKeyDown"
|
||||
@paste="onPasteImage"
|
||||
>
|
||||
</textarea>
|
||||
</template>
|
||||
<template v-else-if="nowPickInfo.chatType === CHAT_TYPE.GROUP">
|
||||
<vue-at :members="atMembersList" name-key="text" @insert="onInsert">
|
||||
<textarea
|
||||
ref="editable"
|
||||
v-model="textContent"
|
||||
class="chat_content_editable"
|
||||
spellcheck="false"
|
||||
contenteditable="true"
|
||||
placeholder="请输入消息内容..."
|
||||
@keydown="onTextInputKeyDown"
|
||||
@paste="onPasteImage"
|
||||
>
|
||||
</textarea>
|
||||
</vue-at>
|
||||
</template>
|
||||
|
||||
<el-button
|
||||
:class="[textContent === '' ? 'no_content_send_btn' : 'chat_send_btn']"
|
||||
type="primary"
|
||||
@click="sendTextMessage"
|
||||
>发送</el-button
|
||||
>
|
||||
<PreviewSendImg ref="previewSendImg" @send-images-message="sendImagesMessage" />
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import './index.scss';
|
||||
@import '@/styles/iconfont/iconfont.css';
|
||||
</style>
|
303
src/views/im/Message/components/messageList/index.scss
Normal file
303
src/views/im/Message/components/messageList/index.scss
Normal file
@ -0,0 +1,303 @@
|
||||
.messageList_box {
|
||||
width: 100%;
|
||||
|
||||
.message_box_item {
|
||||
position: relative;
|
||||
display: flex;
|
||||
margin: 32px auto;
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
letter-spacing: 0.4px;
|
||||
color: #333333;
|
||||
|
||||
.message_item_time {
|
||||
position: absolute;
|
||||
top: -25px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
margin: auto;
|
||||
width: 74px;
|
||||
height: 20px;
|
||||
color: #adadad;
|
||||
font-weight: 400;
|
||||
font-size: 10px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.message_item_avator {
|
||||
width: 38px;
|
||||
height: 38px;
|
||||
}
|
||||
.message_box_card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-width: 50%;
|
||||
min-height: 34px;
|
||||
}
|
||||
.message_box_nickname {
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
letter-spacing: 0.4px;
|
||||
color: #9a9a9a;
|
||||
margin: 0 10px;
|
||||
}
|
||||
.message_box_content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 0 6px;
|
||||
word-break: break-all;
|
||||
/* 通用音频播放样式 */
|
||||
.message_box_content_audio {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
max-width: 250px;
|
||||
min-width: 80px;
|
||||
font-size: 12px;
|
||||
|
||||
.audio_length_text {
|
||||
font-family: 'Avenir';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
/* 对方音频播放样式 */
|
||||
.message_box_content_audio_other {
|
||||
flex-direction: row;
|
||||
|
||||
@keyframes other_play_icon {
|
||||
0% {
|
||||
background: url('@/assets/images/playAudio/msg_recv_audio02@3x.png')
|
||||
no-repeat;
|
||||
|
||||
background-size: 100% 100%;
|
||||
}
|
||||
|
||||
50% {
|
||||
background: url('@/assets/images/playAudio/msg_recv_audio01@3x.png')
|
||||
no-repeat;
|
||||
|
||||
background-size: 100% 100%;
|
||||
}
|
||||
|
||||
100% {
|
||||
background: url('@/assets/images/playAudio/msg_recv_audio@3x.png')
|
||||
no-repeat;
|
||||
background-size: 100% 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.play_audio_icon_other {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
background: url('@/assets/images/playAudio/msg_recv_audio@3x.png')
|
||||
no-repeat;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.start_play_audio {
|
||||
animation: other_play_icon 2s;
|
||||
animation-iteration-count: infinite;
|
||||
}
|
||||
}
|
||||
|
||||
/* 己方音频播放样式 */
|
||||
.message_box_content_audio_mine {
|
||||
flex-direction: row-reverse;
|
||||
|
||||
@keyframes mine_play_icon {
|
||||
0% {
|
||||
background: url('@/assets/images/playAudio/msg_send_audio02@3x.png')
|
||||
no-repeat;
|
||||
|
||||
background-size: 100% 100%;
|
||||
}
|
||||
|
||||
50% {
|
||||
background: url('@/assets/images/playAudio/msg_send_audio01@3x.png')
|
||||
no-repeat;
|
||||
|
||||
background-size: 100% 100%;
|
||||
}
|
||||
|
||||
100% {
|
||||
background: url('@/assets/images/playAudio/msg_send_audio@3x.png')
|
||||
no-repeat;
|
||||
background-size: 100% 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.play_audio_icon_mine {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
background-size: 100% 100%;
|
||||
background: url('@/assets/images/playAudio/msg_send_audio@3x.png')
|
||||
no-repeat;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.start_play_audio {
|
||||
animation: mine_play_icon 2s;
|
||||
animation-iteration-count: infinite;
|
||||
}
|
||||
}
|
||||
|
||||
/* 文件消息样式 */
|
||||
.message_box_content_file {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
width: 200px;
|
||||
min-height: 60px;
|
||||
max-height: 120px;
|
||||
padding: 10px;
|
||||
|
||||
.file_text_box {
|
||||
width: 75%;
|
||||
height: 80%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
|
||||
.file_name {
|
||||
width: 120px;
|
||||
white-space: wrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
font-size: 15px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.file_size {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.file_download {
|
||||
width: 100%;
|
||||
color: #333333;
|
||||
font-size: 13px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:hover {
|
||||
transform: scale(0.9);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.icon-wenjian {
|
||||
font-size: 50px;
|
||||
color: #8d8a8a;
|
||||
}
|
||||
}
|
||||
|
||||
/* 自定义消息 */
|
||||
.message_box_content_custom {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
width: 200px;
|
||||
min-height: 60px;
|
||||
max-height: 120px;
|
||||
padding: 10px;
|
||||
overflow: hidden;
|
||||
|
||||
.user_card_main {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
color: #333333;
|
||||
font-size: 17px;
|
||||
|
||||
.nickname {
|
||||
display: inline-block;
|
||||
// width: 100%;
|
||||
margin-left: 10px;
|
||||
height: 35px;
|
||||
line-height: 35px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 个人名片 */
|
||||
}
|
||||
.quote_msg_avtive {
|
||||
animation: twinkle 0.4s infinite alternate;
|
||||
}
|
||||
.quote_msg_avtive ::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 5px;
|
||||
background-color: rgba(247, 169, 35, 0.5);
|
||||
}
|
||||
@keyframes twinkle {
|
||||
0% {
|
||||
opacity: 0.3;
|
||||
}
|
||||
50% {
|
||||
opacity: 0.6;
|
||||
}
|
||||
100% {
|
||||
opacity: 0.9;
|
||||
}
|
||||
}
|
||||
.message_box_content_other {
|
||||
background: #fff;
|
||||
border-radius: 8px 8px 8px 0px;
|
||||
}
|
||||
|
||||
.message_box_content_mine {
|
||||
background: #c1e3fc;
|
||||
border-radius: 8px 0px 8px 8px;
|
||||
}
|
||||
}
|
||||
|
||||
/* 撤回或者系统通知类消息 */
|
||||
.recall_style,
|
||||
.inform_style {
|
||||
height: 60px;
|
||||
text-align: center;
|
||||
color: #aaaaaa;
|
||||
font-size: 10px;
|
||||
margin: 5px 0;
|
||||
|
||||
.reEdit {
|
||||
color: #3e91fa;
|
||||
margin-left: 3px;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
.message_quote_box {
|
||||
padding: 5px 10px;
|
||||
font-size: 7px;
|
||||
background-color: #e7e7e7;
|
||||
border-radius: 5px;
|
||||
margin-top: 5px;
|
||||
color: #a0a0a0;
|
||||
cursor: pointer;
|
||||
p {
|
||||
word-break: break-all;
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
}
|
||||
:deep(.el-input__wrapper) {
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
:deep(.el-dialog__header) {
|
||||
background: #f2f2f2;
|
||||
margin: 0;
|
||||
}
|
303
src/views/im/Message/components/messageList/index.vue
Normal file
303
src/views/im/Message/components/messageList/index.vue
Normal file
@ -0,0 +1,303 @@
|
||||
<script setup>
|
||||
import { formatDate } from '@/utils/formatTime'
|
||||
/* 默认头像 */
|
||||
import defaultAvatar from '@/assets/imgs/avatar.gif'
|
||||
|
||||
const messageData = ref([
|
||||
{
|
||||
id: 1,
|
||||
type: 'text',
|
||||
isRecall: false,
|
||||
time: '2024-04-01 12:00:00',
|
||||
from: '1',
|
||||
msg: 'Hello, world!',
|
||||
modifiedInfo: {
|
||||
operationCount: 1
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
type: 'text',
|
||||
isRecall: false,
|
||||
time: '2024-04-01 12:00:01',
|
||||
from: '2',
|
||||
msg: 'Hi, there!',
|
||||
modifiedInfo: {
|
||||
operationCount: 0
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
type: 'text',
|
||||
isRecall: true,
|
||||
time: '2024-04-01 12:00:02',
|
||||
from: '1',
|
||||
msg: 'Hello, world!',
|
||||
modifiedInfo: {
|
||||
operationCount: 0
|
||||
}
|
||||
}
|
||||
])
|
||||
const ALL_MESSAGE_TYPE = {
|
||||
TEXT: 'txt',
|
||||
IMAGE: 'img',
|
||||
AUDIO: 'audio',
|
||||
LOCAL: 'loc',
|
||||
VIDEO: 'video',
|
||||
FILE: 'file',
|
||||
CUSTOM: 'custom',
|
||||
CMD: 'cmd',
|
||||
INFORM: 'inform' //这个类型不在环信消息类型内,属于自己定义的一种系统通知类的消息。
|
||||
}
|
||||
/* 处理时间显示间隔 */
|
||||
const handleMsgTimeShow = (time, index) => {
|
||||
const msgList = Array.from(messageData.value)
|
||||
if (index !== 0) {
|
||||
const lastTime = msgList[index - 1].time
|
||||
return time - lastTime > 50000 ? formatDate(time, 'MM/DD/HH:mm') : false
|
||||
} else {
|
||||
return formatDate(time, 'MM/DD/HH:mm')
|
||||
}
|
||||
}
|
||||
/* computed-- 消息来源是否为自己 */
|
||||
const isMyself = (msgBody) => {
|
||||
return msgBody.from === '1'
|
||||
}
|
||||
/* 获取自己的用户信息 */
|
||||
const loginUserInfo = {
|
||||
avatarurl: 'https://avatars.githubusercontent.com/u/1?v=4'
|
||||
}
|
||||
/* 获取他人的用户信息 */
|
||||
const otherUserInfo = (from) => {
|
||||
return {
|
||||
avatarurl: 'https://avatars.githubusercontent.com/u/2?v=4'
|
||||
}
|
||||
}
|
||||
//处理聊天对方昵称展示
|
||||
const handleNickName = (from) => {
|
||||
return from === '1' ? '我' : '对方'
|
||||
}
|
||||
//引用消息
|
||||
let clickQuoteMsgId = ref('')
|
||||
//音频播放状态
|
||||
const audioPlayStatus = reactive({
|
||||
isPlaying: false, //是否在播放中
|
||||
playMsgId: '' //在播放的音频消息id,
|
||||
})
|
||||
//开始播放
|
||||
const startplayAudio = (msgBody) => {
|
||||
const armRec = new BenzAMRRecorder()
|
||||
const src = msgBody.url
|
||||
audioPlayStatus.playMsgId = msgBody.id
|
||||
console.log('>>>>>开始播放音频', msgBody.url)
|
||||
//初始化音频源并调用播放
|
||||
armRec.initWithUrl(src).then(() => {
|
||||
if (!audioPlayStatus.isPlaying) {
|
||||
armRec.play()
|
||||
}
|
||||
})
|
||||
//播放开始监听
|
||||
armRec.onPlay(() => {
|
||||
audioPlayStatus.isPlaying = true
|
||||
audioPlayStatus.playMsgId = msgBody.id
|
||||
})
|
||||
//播放结束监听
|
||||
armRec.onStop(() => {
|
||||
audioPlayStatus.isPlaying = false
|
||||
audioPlayStatus.playMsgId = ''
|
||||
})
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<div>
|
||||
<div
|
||||
class="messageList_box"
|
||||
v-for="(msgBody, index) in messageData"
|
||||
:key="msgBody.id"
|
||||
:data-mid="msgBody.id"
|
||||
>
|
||||
<!-- 普通消息气泡 -->
|
||||
<div
|
||||
v-if="!msgBody.isRecall && msgBody.type !== ALL_MESSAGE_TYPE.INFORM"
|
||||
class="message_box_item"
|
||||
:style="{
|
||||
flexDirection: isMyself(msgBody) ? 'row-reverse' : 'row'
|
||||
}"
|
||||
>
|
||||
<div class="message_item_time">
|
||||
{{ handleMsgTimeShow(msgBody.time, index) || '' }}
|
||||
</div>
|
||||
<el-avatar
|
||||
class="message_item_avator"
|
||||
:src="
|
||||
isMyself(msgBody)
|
||||
? loginUserInfo.avatarurl
|
||||
: otherUserInfo(msgBody.from).avatarurl || defaultAvatar
|
||||
"
|
||||
/>
|
||||
<!-- 普通消息内容 -->
|
||||
<div class="message_box_card">
|
||||
<span v-show="!isMyself(msgBody)" class="message_box_nickname">{{
|
||||
handleNickName(msgBody.from)
|
||||
}}</span>
|
||||
<el-dropdown
|
||||
class="message_box_content"
|
||||
:class="[
|
||||
isMyself(msgBody) ? 'message_box_content_mine' : 'message_box_content_other',
|
||||
clickQuoteMsgId === msgBody.id && 'quote_msg_avtive'
|
||||
]"
|
||||
trigger="contextmenu"
|
||||
placement="bottom-end"
|
||||
>
|
||||
<!-- 文本类型消息 -->
|
||||
<p
|
||||
style="padding: 10px; line-height: 20px"
|
||||
v-if="msgBody.type === ALL_MESSAGE_TYPE.TEXT"
|
||||
>
|
||||
<template v-if="!isLink(msgBody.msg)">
|
||||
{{ msgBody.msg }}
|
||||
<!-- 已编辑 -->
|
||||
<sup
|
||||
style="font-size: 7px; color: #707784"
|
||||
v-show="msgBody?.modifiedInfo?.operationCount"
|
||||
>(已编辑)</sup
|
||||
>
|
||||
</template>
|
||||
<template v-else> <span v-html="paseLink(msgBody.msg).msg"> </span></template>
|
||||
</p>
|
||||
<!-- 图片类型消息 -->
|
||||
<!-- <div> -->
|
||||
<el-image
|
||||
v-if="msgBody.type === ALL_MESSAGE_TYPE.IMAGE"
|
||||
style="border-radius: 5px"
|
||||
:src="msgBody.thumb"
|
||||
:preview-src-list="[msgBody.url]"
|
||||
:initial-index="1"
|
||||
fit="cover"
|
||||
/>
|
||||
<!-- </div> -->
|
||||
<!-- 语音类型消息 -->
|
||||
<div
|
||||
:class="[
|
||||
'message_box_content_audio',
|
||||
isMyself(msgBody)
|
||||
? 'message_box_content_audio_mine'
|
||||
: 'message_box_content_audio_other'
|
||||
]"
|
||||
v-if="msgBody.type === ALL_MESSAGE_TYPE.AUDIO"
|
||||
@click="startplayAudio(msgBody)"
|
||||
:style="`width:${msgBody.length * 10}px`"
|
||||
>
|
||||
<span class="audio_length_text"> {{ msgBody.length }}′′ </span>
|
||||
<div
|
||||
:class="[
|
||||
isMyself(msgBody) ? 'play_audio_icon_mine' : 'play_audio_icon_other',
|
||||
audioPlayStatus.playMsgId === msgBody.id && 'start_play_audio'
|
||||
]"
|
||||
style="background-size: 100% 100%"
|
||||
></div>
|
||||
</div>
|
||||
<div v-if="msgBody.type === ALL_MESSAGE_TYPE.LOCAL">
|
||||
<p style="padding: 10px">[暂不支持位置消息展示]</p>
|
||||
</div>
|
||||
<!-- 文件类型消息 -->
|
||||
<div v-if="msgBody.type === ALL_MESSAGE_TYPE.FILE" class="message_box_content_file">
|
||||
<div class="file_text_box">
|
||||
<div class="file_name">
|
||||
{{ msgBody.filename }}
|
||||
</div>
|
||||
<div class="file_size">
|
||||
{{ fileSizeFormat(msgBody.file_length) }}
|
||||
</div>
|
||||
<a class="file_download" :href="msgBody.url" download>点击下载</a>
|
||||
</div>
|
||||
<span class="iconfont icon-wenjian"></span>
|
||||
</div>
|
||||
<!-- 自定义类型消息 -->
|
||||
<div v-if="msgBody.type === ALL_MESSAGE_TYPE.CUSTOM" class="message_box_content_custom">
|
||||
<template v-if="msgBody.customEvent && CUSTOM_TYPE[msgBody.customEvent]">
|
||||
<div class="user_card">
|
||||
<div class="user_card_main">
|
||||
<!-- 头像 -->
|
||||
<el-avatar
|
||||
shape="circle"
|
||||
:size="50"
|
||||
:src="
|
||||
(msgBody.customExts && msgBody.customExts.avatarurl) ||
|
||||
msgBody.customExts.avatar ||
|
||||
defaultAvatar
|
||||
"
|
||||
fit="cover"
|
||||
/>
|
||||
<!-- 昵称 -->
|
||||
<span class="nickname">{{
|
||||
(msgBody.customExts && msgBody.customExts.nickname) || msgBody.customExts.uid
|
||||
}}</span>
|
||||
</div>
|
||||
<el-divider style="margin: 5px 0; border-top: 1px solid black" />
|
||||
<p style="font-size: 8px">个人名片</p>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<!-- 右键点击弹起更多功能栏 -->
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item
|
||||
v-if="msgBody.type === ALL_MESSAGE_TYPE.TEXT && isSupported"
|
||||
@click="copyTextMessages(msgBody.msg)"
|
||||
>
|
||||
复制
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item v-if="isMyself(msgBody)" @click="recallMessage(msgBody)">
|
||||
撤回
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item
|
||||
v-if="msgBody.type === ALL_MESSAGE_TYPE.TEXT && isMyself(msgBody)"
|
||||
@click="showModifyMsgModal(msgBody)"
|
||||
>
|
||||
编辑
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item @click="onMsgQuote(msgBody)"> 引用 </el-dropdown-item>
|
||||
<el-dropdown-item @click="deleteMessage(msgBody)"> 删除 </el-dropdown-item>
|
||||
<el-dropdown-item v-if="!isMyself(msgBody)" @click="informOnMessage(msgBody)">
|
||||
举报
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
<!-- 引用消息展示框 -->
|
||||
<div
|
||||
class="message_quote_box"
|
||||
v-if="msgBody?.ext?.msgQuote"
|
||||
@click="clickQuoteMessage(msgBody.ext.msgQuote)"
|
||||
>
|
||||
<p>
|
||||
{{ msgBody?.ext?.msgQuote?.msgSender }}:{{ msgBody?.ext?.msgQuote?.msgPreview }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 撤回消息通知通知 -->
|
||||
<div v-if="msgBody.isRecall" class="recall_style">
|
||||
{{ isMyself(msgBody) ? '你' : `${msgBody.from}` }}撤回了一条消息<span
|
||||
class="reEdit"
|
||||
v-show="isMyself(msgBody) && msgBody.type === ALL_MESSAGE_TYPE.TEXT"
|
||||
@click="reEdit(msgBody.msg)"
|
||||
>重新编辑</span
|
||||
>
|
||||
</div>
|
||||
<!-- 灰色系统通知 -->
|
||||
<div v-if="msgBody.type === ALL_MESSAGE_TYPE.INFORM" class="inform_style">
|
||||
<p>
|
||||
{{ msgBody.msg }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<ReportMessage ref="reportMessage" />
|
||||
<ModifyMessage ref="modifyMessageRef" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import './index.scss';
|
||||
</style>
|
237
src/views/im/Message/components/suit/audio.vue
Normal file
237
src/views/im/Message/components/suit/audio.vue
Normal file
@ -0,0 +1,237 @@
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import BenzAMRRecorder from 'benz-amr-recorder'
|
||||
import { ElNotification } from 'element-plus'
|
||||
const voice = ref({
|
||||
interval: null, // 录音定时器
|
||||
type: 0, // 0未录音 1录音中 2录音完毕 3回放录音
|
||||
length: 0, // 录音长度
|
||||
src: null // 录音资源
|
||||
})
|
||||
const amrRec = ref(null)
|
||||
const timer = ref({
|
||||
interval: null,
|
||||
tim: 60
|
||||
}) //倒计时
|
||||
const showCountDown = ref(false)
|
||||
const benginTimer = () => {
|
||||
showCountDown.value = false
|
||||
timer.value.interval = setInterval(() => {
|
||||
if (timer.value.tim === 0) {
|
||||
clearInterval(timer.value.interval)
|
||||
clearInterval(voice.value.interval)
|
||||
// 自动发送
|
||||
recordOver()
|
||||
return
|
||||
}
|
||||
else if (false) {
|
||||
// 弹层关闭 清空倒计时
|
||||
timer.value.tim = 60
|
||||
return
|
||||
} else {
|
||||
timer.value.tim--
|
||||
}
|
||||
if (timer.value.tim < 11) {
|
||||
showCountDown.value = true
|
||||
}
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
const startRecord = () => {
|
||||
if (voice.value.type === 0) {
|
||||
amrRec.value = new BenzAMRRecorder()
|
||||
console.log(amrRec)
|
||||
amrRec.value
|
||||
.initWithRecord()
|
||||
.then(() => {
|
||||
amrRec.value.startRecord() //开始录音
|
||||
voice.value.type = 1
|
||||
benginTimer()
|
||||
//开启录音时长定时器
|
||||
voice.value.interval = setInterval(() => {
|
||||
voice.value.length++
|
||||
}, 1000)
|
||||
})
|
||||
.catch(e => {
|
||||
console.log(e)
|
||||
voice.value.type = 0
|
||||
ElNotification({
|
||||
title: '',
|
||||
message: '录音失败,请检查相关权限和设备',
|
||||
type: 'error',
|
||||
})
|
||||
// $Toast("录音失败,请检查相关权限和设备");
|
||||
})
|
||||
}
|
||||
}
|
||||
const emit = defineEmits(['sendAudioMessages'])
|
||||
const recordOver = () => {
|
||||
amrRec.value
|
||||
.finishRecord()
|
||||
.then(() => {
|
||||
if (voice.value.length <= 1) {
|
||||
clearInterval(timer.value.interval)
|
||||
clearInterval(voice.value.interval)
|
||||
initVocie()
|
||||
// 放弃录音
|
||||
amrRec.value.cancelRecord()
|
||||
ElNotification({
|
||||
title: '',
|
||||
message: '录音时间较短',
|
||||
type: 'warning',
|
||||
})
|
||||
} else {
|
||||
voice.value.length = Math.ceil(amrRec.value.getDuration())
|
||||
// 获取音频文件
|
||||
voice.value.src = amrRec.value.getBlob()
|
||||
emit('sendAudioMessages', {
|
||||
src: voice.value.src,
|
||||
length: voice.value.length > 60 ? 60 : voice.value.length //一般计时器开始时间都较为提前一秒 减去误差值
|
||||
})
|
||||
|
||||
clearInterval(timer.value.interval)
|
||||
clearInterval(voice.value.interval)
|
||||
initVocie()
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
ElNotification({
|
||||
title: '',
|
||||
message: '录音失败,请检查相关权限',
|
||||
type: 'error',
|
||||
})
|
||||
})
|
||||
}
|
||||
const initVocie = () => {
|
||||
voice.value.interval = null
|
||||
voice.value.length = 0
|
||||
voice.value.type = 0
|
||||
timer.value.tim = 60
|
||||
showCountDown.value = false
|
||||
timer.value.interval = null
|
||||
}
|
||||
// const filterRecordVoicTime = (len) => {
|
||||
// let min = Math.floor(len / 60),
|
||||
// sec = len % 60;
|
||||
// ("1:30");
|
||||
// return min + ":" + (sec < 10 ? "0" + sec : sec);
|
||||
// }
|
||||
// const cancelRecord = (e) => {
|
||||
// amrRec.value.cancelRecord();
|
||||
// clearInterval(timer.value.interval);
|
||||
// clearInterval(voice.value.interval);
|
||||
// initVocie();
|
||||
// ElNotification({
|
||||
// title: '',
|
||||
// message: '取消录音',
|
||||
// type: 'success',
|
||||
// });
|
||||
// }
|
||||
const closeDialog = () => {
|
||||
console.log('关闭子组件的定时器')
|
||||
clearInterval(timer.value.interval)
|
||||
clearInterval(voice.value.interval)
|
||||
initVocie()
|
||||
}
|
||||
defineExpose({ closeDialog })
|
||||
|
||||
|
||||
</script>
|
||||
<template>
|
||||
<div>
|
||||
<div class="collect_box">
|
||||
<div v-show="!voice.type" class="start">
|
||||
<span class="title">单击开始录音,最长可录制60秒</span>
|
||||
<svg @click="startRecord()" width="58" height="58" viewBox="0 0 58 58" fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M29 57.2363C44.6152 57.2363 57.3887 44.4629 57.3887 28.8477C57.3887 13.2324 44.6152 0.458984 29 0.458984C13.3848 0.458984 0.611328 13.2324 0.611328 28.8477C0.611328 44.4629 13.3848 57.2363 29 57.2363ZM29 55.8008C14.1465 55.8008 2.04688 43.7012 2.04688 28.8477C2.04688 13.9648 14.1465 1.89453 29 1.89453C43.8828 1.89453 55.9531 13.9648 55.9531 28.8477C55.9531 43.7012 43.8828 55.8008 29 55.8008ZM29 34.5312C31.9883 34.5312 33.8926 32.2168 33.8926 29.1699V16.1621C33.8926 13.1152 31.9883 10.8008 29 10.8008C26.0117 10.8008 24.1074 13.1152 24.1074 16.1621V29.1699C24.1074 32.2168 26.0117 34.5312 29 34.5312ZM22.1445 46.9531H35.8848C36.2949 46.9531 36.6172 46.6309 36.6172 46.2207C36.6172 45.8105 36.2656 45.4883 35.8848 45.4883H29.7324V40.0098C35.5332 39.6875 39.6055 35.4688 39.6055 29.9316V27.0312C39.6055 26.6211 39.2832 26.2988 38.873 26.2988C38.4922 26.2988 38.1406 26.6211 38.1406 27.0312V29.9316C38.1406 35.2344 34.7422 38.5742 29 38.5742C23.2578 38.5742 19.918 35.2344 19.918 29.9316V27.0312C19.918 26.6211 19.5664 26.2988 19.1855 26.2988C18.7754 26.2988 18.4238 26.6211 18.4238 27.0312V29.9316C18.4238 35.4688 22.4668 39.6875 28.2676 40.0098V45.4883H22.1445C21.7344 45.4883 21.4121 45.8105 21.4121 46.2207C21.4121 46.6309 21.7344 46.9531 22.1445 46.9531Z"
|
||||
fill="#04D0A4" />
|
||||
</svg>
|
||||
<span>{{ timer.tim }}″</span>
|
||||
</div>
|
||||
<div @click="recordOver()" v-show="voice.type && !showCountDown" class="send">
|
||||
<span class="title">再次单击发送,点空白处取消</span>
|
||||
<svg width="58" height="58" viewBox="0 0 58 58" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M29 57.2363C44.6152 57.2363 57.3887 44.4629 57.3887 28.8477C57.3887 13.2324 44.6152 0.458984 29 0.458984C13.3848 0.458984 0.611328 13.2324 0.611328 28.8477C0.611328 44.4629 13.3848 57.2363 29 57.2363ZM29 55.8008C14.1465 55.8008 2.04688 43.7012 2.04688 28.8477C2.04688 13.9648 14.1465 1.89453 29 1.89453C43.8828 1.89453 55.9531 13.9648 55.9531 28.8477C55.9531 43.7012 43.8828 55.8008 29 55.8008ZM20.8848 39.2773H37.1152C38.5508 39.2773 39.4297 38.3691 39.4297 36.9629V20.7324C39.4297 19.2969 38.5508 18.418 37.1152 18.418H20.8848C19.4785 18.418 18.5703 19.2969 18.5703 20.7324V36.9629C18.5703 38.3691 19.4785 39.2773 20.8848 39.2773Z"
|
||||
fill="#7F7F7F" />
|
||||
</svg>
|
||||
<span>{{ `${voice.length}″` }}</span>
|
||||
</div>
|
||||
<div v-show="showCountDown" class="send">
|
||||
<span class="title">{{ timer.tim }}秒后自动发送</span>
|
||||
<svg width="58" height="58" viewBox="0 0 58 58" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M29 57.2363C44.6152 57.2363 57.3887 44.4629 57.3887 28.8477C57.3887 13.2324 44.6152 0.458984 29 0.458984C13.3848 0.458984 0.611328 13.2324 0.611328 28.8477C0.611328 44.4629 13.3848 57.2363 29 57.2363ZM29 55.8008C14.1465 55.8008 2.04688 43.7012 2.04688 28.8477C2.04688 13.9648 14.1465 1.89453 29 1.89453C43.8828 1.89453 55.9531 13.9648 55.9531 28.8477C55.9531 43.7012 43.8828 55.8008 29 55.8008ZM20.8848 39.2773H37.1152C38.5508 39.2773 39.4297 38.3691 39.4297 36.9629V20.7324C39.4297 19.2969 38.5508 18.418 37.1152 18.418H20.8848C19.4785 18.418 18.5703 19.2969 18.5703 20.7324V36.9629C18.5703 38.3691 19.4785 39.2773 20.8848 39.2773Z"
|
||||
fill="#FF4D4F" />
|
||||
</svg>
|
||||
<span>{{ `${voice.length}″` }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.collect_box {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: space-around;
|
||||
|
||||
.start,
|
||||
.send {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
|
||||
span {
|
||||
margin-bottom: 22px;
|
||||
}
|
||||
|
||||
svg {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
}
|
||||
.title{
|
||||
margin-top: 10px;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
.collect_box>h3 {
|
||||
user-select: none;
|
||||
margin-bottom: .03rem;
|
||||
}
|
||||
|
||||
.recordTime {
|
||||
margin: .09rem 0;
|
||||
}
|
||||
|
||||
.collect_btn {
|
||||
user-select: none;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: .8rem;
|
||||
height: .8rem;
|
||||
border: .03rem solid #474747;
|
||||
border-radius: 50%;
|
||||
|
||||
}
|
||||
|
||||
.collect_btn>img {
|
||||
pointer-events: none;
|
||||
/* 禁止长按图片保存 */
|
||||
width: .6rem;
|
||||
height: .6rem;
|
||||
}
|
||||
|
||||
.reacordingStyle {
|
||||
width: .5rem;
|
||||
height: .5rem;
|
||||
background: red;
|
||||
border-radius: 50%;
|
||||
}
|
||||
</style>
|
120
src/views/im/Message/components/suit/modifyMessage.vue
Normal file
120
src/views/im/Message/components/suit/modifyMessage.vue
Normal file
@ -0,0 +1,120 @@
|
||||
<template>
|
||||
<el-dialog v-model="dialogVisible" title="编辑消息" width="30%">
|
||||
<el-input
|
||||
class="modifymessage_input"
|
||||
v-model="editMessageContent.msg"
|
||||
:autosize="{ minRows: 2, maxRows: 4 }"
|
||||
type="textarea"
|
||||
/>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="dialogVisible = false" :icon="Close"
|
||||
>取消</el-button
|
||||
>
|
||||
<el-button
|
||||
type="primary"
|
||||
:loading="loading"
|
||||
@click="saveEditedMessage"
|
||||
:icon="Check"
|
||||
>
|
||||
{{ loading ? '更新中' : '保存' }}
|
||||
</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, nextTick } from 'vue'
|
||||
import { useStore } from 'vuex'
|
||||
import { messageType } from '@/constant'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { Check, Close } from '@element-plus/icons-vue'
|
||||
const { CHAT_TYPE } = messageType
|
||||
const store = useStore()
|
||||
const dialogVisible = ref(false)
|
||||
const editMessageContent = reactive({
|
||||
msg: '',
|
||||
to: '',
|
||||
id: '',
|
||||
chatType: CHAT_TYPE.SINGLE
|
||||
})
|
||||
const loading = ref(false)
|
||||
const saveEditedMessage = async () => {
|
||||
loading.value = true
|
||||
if (!editMessageContent.msg) {
|
||||
ElMessage.warning('消息内容不能为空')
|
||||
}
|
||||
try {
|
||||
await store.dispatch('modifyMessage', { ...editMessageContent })
|
||||
} catch (error) {
|
||||
if (error?.type === 50) {
|
||||
ElMessage({
|
||||
type: 'error',
|
||||
message: '该消息可编辑次数已达上限',
|
||||
center: true
|
||||
})
|
||||
} else {
|
||||
ElMessage({
|
||||
type: 'error',
|
||||
message: '消息编辑失败请稍后重试',
|
||||
center: true
|
||||
})
|
||||
}
|
||||
} finally {
|
||||
initModifyMessage()
|
||||
loading.value = false
|
||||
dialogVisible.value = false
|
||||
}
|
||||
}
|
||||
const initModifyMessage = (msgBody) => {
|
||||
//initModifyMessage 第二个形参传true,置空待编辑消息内容。
|
||||
dialogVisible.value = true
|
||||
nextTick(() => {
|
||||
if (msgBody) {
|
||||
const { id, msg, to, chatType } = msgBody
|
||||
// console.log('>>>>>>', id, msg, to, chatType)
|
||||
editMessageContent.msg = msg
|
||||
editMessageContent.to = to
|
||||
editMessageContent.id = id
|
||||
editMessageContent.chatType = chatType
|
||||
} else {
|
||||
editMessageContent.msg = ''
|
||||
editMessageContent.to = ''
|
||||
editMessageContent.id = ''
|
||||
editMessageContent.chatType = CHAT_TYPE.SINGLE
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
initModifyMessage
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.modify_input_container {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
padding: 10px 15px;
|
||||
}
|
||||
.modify_input_btn_container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
margin-top: 5px;
|
||||
}
|
||||
.modify_input_btn {
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.modify_input_btn:hover {
|
||||
transform: scale(1.2);
|
||||
}
|
||||
:deep(.el-textarea__inner) {
|
||||
border-radius: 5px;
|
||||
resize: none;
|
||||
}
|
||||
</style>
|
155
src/views/im/Message/components/suit/msgQuote.vue
Normal file
155
src/views/im/Message/components/suit/msgQuote.vue
Normal file
@ -0,0 +1,155 @@
|
||||
<template>
|
||||
<div v-if="isShowQuoteMsgBox" class="message_quote_container">
|
||||
<span> {{ msgQuote.msgSender || '' }}:</span>
|
||||
<div class="quote_from_content">
|
||||
<template v-if="msgQuote.msgType === ALL_MESSAGE_TYPE.IMAGE">
|
||||
<el-image
|
||||
v-show="quoteImageUrl.imageUrl"
|
||||
style="width: 35px; height: 35px"
|
||||
:src="quoteImageUrl.thumb || quoteImageUrl.imageUrl"
|
||||
:preview-src-list="[quoteImageUrl.imageUrl]"
|
||||
/>
|
||||
<p v-show="!quoteImageUrl">{{ msgQuote.msgPreview }}</p>
|
||||
</template>
|
||||
<template v-else>
|
||||
<p class="quote_text" :title="msgQuote.msg">
|
||||
{{ msgQuote.msgPreview || '' }}
|
||||
</p>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<div class="quote_close_icon" @click="clearQuoteContent">
|
||||
<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" data-v-ea893728="">
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="m466.752 512-90.496-90.496a32 32 0 0 1 45.248-45.248L512 466.752l90.496-90.496a32 32 0 1 1 45.248 45.248L557.248 512l90.496 90.496a32 32 0 1 1-45.248 45.248L512 557.248l-90.496 90.496a32 32 0 0 1-45.248-45.248L466.752 512z"
|
||||
/>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M512 896a384 384 0 1 0 0-768 384 384 0 0 0 0 768zm0 64a448 448 0 1 1 0-896 448 448 0 0 1 0 896z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, computed } from 'vue'
|
||||
import messageType from '@/constant/messageType'
|
||||
|
||||
const { ALL_MESSAGE_TYPE, SESSION_MESSAGE_TYPE, CHAT_TYPE } = messageType
|
||||
/* stores */
|
||||
|
||||
console.log(store.state)
|
||||
const loginUserInfo = computed(() => {
|
||||
return store.state.loginUserInfo
|
||||
})
|
||||
//是否开启引用展示框
|
||||
const isShowQuoteMsgBox = ref(false)
|
||||
//引用消息必传参数
|
||||
let msgQuote = reactive({
|
||||
msgID: '', //引用消息id
|
||||
msgPreview: '', //引用消息预览
|
||||
msgSender: '', //引用消息发送人
|
||||
msgType: '' //引用消息类型
|
||||
})
|
||||
//引用图片消息预览图片
|
||||
const quoteImageUrl = reactive({
|
||||
thumb: '',
|
||||
imageUrl: ''
|
||||
})
|
||||
//提取原始消息内容至待发送的引用参数内
|
||||
const extractMessageBodyValue = (sourceMsg) => {
|
||||
const { type, msg: msgContent, id: mid, from } = sourceMsg
|
||||
msgQuote.msgID = mid
|
||||
msgQuote.msgType = type
|
||||
if (from === loginUserInfo.value.hxId) {
|
||||
msgQuote.msgSender = getLoginNickNameById()
|
||||
} else {
|
||||
//判断消息引用来源是否为群组,如果是群组,则从群组中获取群组属性。
|
||||
const groupId = sourceMsg.chatType === CHAT_TYPE.GROUP ? sourceMsg.to : ''
|
||||
msgQuote.msgSender = getTheGroupNickNameById(groupId, from)
|
||||
}
|
||||
if (type === ALL_MESSAGE_TYPE.IMAGE) {
|
||||
quoteImageUrl.thumb = sourceMsg.thumb
|
||||
quoteImageUrl.imageUrl = sourceMsg.url
|
||||
}
|
||||
if (type === ALL_MESSAGE_TYPE.TEXT) {
|
||||
msgQuote.msgPreview = msgContent
|
||||
} else {
|
||||
msgQuote.msgPreview = SESSION_MESSAGE_TYPE[type]
|
||||
}
|
||||
}
|
||||
const setQuoteContent = (msg) => {
|
||||
msg && extractMessageBodyValue(msg)
|
||||
isShowQuoteMsgBox.value = true
|
||||
}
|
||||
//清除引用消息状态内容。
|
||||
const clearQuoteContent = () => {
|
||||
msgQuote.msgID = ''
|
||||
msgQuote.msgPreview = ''
|
||||
msgQuote.msgType = ''
|
||||
msgQuote.msgSender = ''
|
||||
quoteImageUrl.imageUrl = ''
|
||||
quoteImageUrl.thumb = ''
|
||||
isShowQuoteMsgBox.value = false
|
||||
}
|
||||
defineExpose({
|
||||
isShowQuoteMsgBox,
|
||||
msgQuote,
|
||||
setQuoteContent,
|
||||
clearQuoteContent
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.message_quote_container {
|
||||
position: absolute;
|
||||
left: 15px;
|
||||
bottom: 10px;
|
||||
min-width: 20%;
|
||||
max-width: 45%;
|
||||
height: 20%;
|
||||
border-radius: 3px;
|
||||
background: #e7e7e690;
|
||||
color: #8e8e8e;
|
||||
padding: 5px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
font-size: 13px;
|
||||
}
|
||||
.quote_file_box {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
}
|
||||
.quote_close_icon {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
right: -8%;
|
||||
margin: auto;
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.quote_close_icon:hover {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
.quote_text {
|
||||
width: 100%;
|
||||
word-break: break-all;
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
line-height: 17px;
|
||||
}
|
||||
|
||||
.quote_file_icon {
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
}
|
||||
</style>
|
68
src/views/im/Message/components/suit/previewSendImg.vue
Normal file
68
src/views/im/Message/components/suit/previewSendImg.vue
Normal file
@ -0,0 +1,68 @@
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
// import fileSizeFormat from '@/utils/fileSizeFormat'
|
||||
const emits = defineEmits(['sendImagesMessage'])
|
||||
let fileObj = null
|
||||
const imgPaths = ref('')
|
||||
const imgName = ref('')
|
||||
const imgSize = ref('')
|
||||
const dialogTableVisible = ref(false)
|
||||
const showPreviewImgModal = (imgObj) => {
|
||||
imgPaths.value = imgObj.tempFilePath
|
||||
imgName.value = imgObj.imgFile.name
|
||||
imgSize.value = imgObj.imgFile.size
|
||||
fileObj = imgObj.imgFile
|
||||
dialogTableVisible.value = true
|
||||
}
|
||||
const sendTheImg = () => {
|
||||
emits('sendImagesMessage', 'other', fileObj)
|
||||
dialogTableVisible.value = false
|
||||
}
|
||||
defineExpose({
|
||||
showPreviewImgModal
|
||||
})
|
||||
</script>
|
||||
<template>
|
||||
<el-dialog v-model="dialogTableVisible" title="发送图片" width="300px">
|
||||
<el-image class="img_box" :src="imgPaths">
|
||||
<template #placeholder>
|
||||
<div class="image-slot">Loading<span class="dot">...</span></div>
|
||||
</template>
|
||||
</el-image>
|
||||
<div class="img_infos">
|
||||
<span class="img_name">{{ imgName }}</span>
|
||||
<!-- <span class="img_size">{{ fileSizeFormat(imgSize) }}</span>-->
|
||||
</div>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="dialogTableVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="sendTheImg"> 发送 </el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.img_box {
|
||||
max-width: 500px;
|
||||
}
|
||||
|
||||
.img_infos {
|
||||
margin: 7px;
|
||||
line-height: 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
.img_name {
|
||||
font-size: 17px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.img_size {
|
||||
font-size: 13px;
|
||||
font-weight: 400;
|
||||
}
|
||||
}
|
||||
</style>
|
145
src/views/im/Message/components/suit/reportMessage.vue
Normal file
145
src/views/im/Message/components/suit/reportMessage.vue
Normal file
@ -0,0 +1,145 @@
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onBeforeUnmount } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { EaseChatClient } from '@/IM/initwebsdk'
|
||||
|
||||
const ReportTypeOptions = [
|
||||
{
|
||||
key: '1',
|
||||
value: '涉政'
|
||||
},
|
||||
{
|
||||
key: '2',
|
||||
value: '涉黄'
|
||||
},
|
||||
{
|
||||
key: '3',
|
||||
value: '广告'
|
||||
},
|
||||
{
|
||||
key: '4',
|
||||
value: '辱骂'
|
||||
},
|
||||
{
|
||||
key: '5',
|
||||
value: '暴恐'
|
||||
},
|
||||
{
|
||||
key: '6',
|
||||
value: '违禁'
|
||||
},
|
||||
{
|
||||
key: '7',
|
||||
value: '其他'
|
||||
}
|
||||
]
|
||||
const dialogVisible = ref(false)
|
||||
const reportMessageForm = reactive({
|
||||
mid: '',
|
||||
reportType: '涉政',
|
||||
reportReason: ''
|
||||
})
|
||||
const rules = reactive({
|
||||
reportReason: [
|
||||
{ required: true, message: '请描述举报原因!', trigger: 'blur' },
|
||||
]
|
||||
})
|
||||
const alertReportMsgModal = (msgBody) => {
|
||||
const msg = Object.assign({}, msgBody)
|
||||
console.log('>>>>调用弹出', msgBody)
|
||||
if (msg.id) {
|
||||
reportMessageForm.mid = msg.id
|
||||
console.log('reportMessageForm.mid', reportMessageForm.mid)
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
}
|
||||
const reportMsgForm = ref(null)
|
||||
const confimReportMessage = (formEl) => {
|
||||
console.log('formEl', formEl)
|
||||
if (!formEl) return
|
||||
formEl.validate(async (valid) => {
|
||||
if (valid) {
|
||||
console.log('submit!')
|
||||
try {
|
||||
console.log('confimReportMessage', reportMessageForm.mid)
|
||||
const params = {
|
||||
reportType: reportMessageForm.reportType, // 举报类型。
|
||||
reportReason: reportMessageForm.reportReason, // 举报原因。
|
||||
messageId: reportMessageForm.mid.toString() // 消息 ID
|
||||
}
|
||||
console.log('>>>>>>要传入的举报参数', params)
|
||||
await EaseChatClient.reportMessage({ ...params })
|
||||
cannelReport(formEl)
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: '已收到您的举报申请!',
|
||||
center: true
|
||||
})
|
||||
|
||||
} catch (error) {
|
||||
console.log('举报error', error)
|
||||
ElMessage({
|
||||
type: 'error',
|
||||
message: '举报失败!',
|
||||
center: true
|
||||
})
|
||||
}
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
const cannelReport = (formEl) => {
|
||||
if (!formEl) return
|
||||
formEl.resetFields()
|
||||
dialogVisible.value = false
|
||||
}
|
||||
defineExpose({
|
||||
alertReportMsgModal
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-dialog v-model="dialogVisible" title="消息举报" width="500px" :show-close="false" :close-on-press-escape="false"
|
||||
:close-on-click-modal="false">
|
||||
<el-form ref="reportMsgForm" :model="reportMessageForm" :rules="rules" label-position="top" label-width="100px">
|
||||
<el-form-item label="举报类别:">
|
||||
<el-select v-model="reportMessageForm.reportType">
|
||||
<el-option v-for="item in ReportTypeOptions" :key="item.key" :label="item.value"
|
||||
:value="item.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="举报原因:" prop="reportReason">
|
||||
<el-input v-model="reportMessageForm.reportReason" maxlength="150" placeholder="请描述举报原因..."
|
||||
show-word-limit type="textarea" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="cannelReport(reportMsgForm)">取消</el-button>
|
||||
<el-button type="primary" @click="confimReportMessage(reportMsgForm)">
|
||||
确认
|
||||
</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.dialog-footer button:first-child {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
:deep(.el-textarea__inner) {
|
||||
border-radius: 5px;
|
||||
resize: none;
|
||||
}
|
||||
|
||||
:deep(.el-input) {
|
||||
height: 40px;
|
||||
}
|
||||
</style>
|
129
src/views/im/Message/index.scss
Normal file
129
src/views/im/Message/index.scss
Normal file
@ -0,0 +1,129 @@
|
||||
.app_container {
|
||||
height: 100%;
|
||||
border-left: 1px solid #e6e6e6;
|
||||
}
|
||||
|
||||
.chat_message_header {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
height: 61px;
|
||||
background: #f9f9f9;
|
||||
border-radius: 0 3px 0 0;
|
||||
border-bottom: 1px solid #e6e6e6;
|
||||
|
||||
.chat_user_box {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
height: 20px;
|
||||
max-width: 80%;
|
||||
|
||||
.chat_user_name {
|
||||
font-family: 'PingFang SC';
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-size: 17px;
|
||||
line-height: 20px;
|
||||
letter-spacing: 0.3px;
|
||||
color: #333333;
|
||||
}
|
||||
}
|
||||
|
||||
.more {
|
||||
display: flex;
|
||||
width: 35px;
|
||||
height: 100%;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 20px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
|
||||
&:hover {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.easeim_safe_tips {
|
||||
position: relative;
|
||||
padding: 12px 20px;
|
||||
background-color: #fff4e6;
|
||||
color: #ff8c39;
|
||||
line-height: 18px;
|
||||
font-family: PingFang SC;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
text-align: justify;
|
||||
font-size: 12px;
|
||||
border: none;
|
||||
|
||||
.easeim_close_tips {
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
top: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.chat_message_main {
|
||||
padding: 0;
|
||||
background: #f9f9f9;
|
||||
|
||||
.main_container {
|
||||
padding: 0 20px;
|
||||
height: 100%;
|
||||
// overflow-y: scroll;
|
||||
|
||||
.chat_message_tips {
|
||||
margin-top: 5px;
|
||||
width: 100%;
|
||||
height: 30px;
|
||||
text-align: center;
|
||||
line-height: 30px;
|
||||
|
||||
.load_more_msg {
|
||||
width: 200px;
|
||||
height: 30px;
|
||||
border-radius: 20px;
|
||||
margin: 0 auto;
|
||||
background: rgba(114, 112, 112, 0.143);
|
||||
font-size: 13px;
|
||||
letter-spacing: 0.5px;
|
||||
// box-shadow: 1px 1px 1px 1px rgba(128, 128, 128, 0.193);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.chat_message_inputbar {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 25%;
|
||||
padding: 0;
|
||||
background-color: #f9f9f9;
|
||||
border-radius: 0 0 3px 0;
|
||||
}
|
||||
|
||||
::v-deep .el-drawer {
|
||||
margin-top: 60px;
|
||||
width: 150px;
|
||||
height: calc(100% - 60px);
|
||||
border-radius: 5px 0 0 5px;
|
||||
|
||||
.el-drawer__header {
|
||||
margin-bottom: 0;
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.el-drawer__body {
|
||||
padding: 0;
|
||||
// padding-left: 16px;
|
||||
}
|
||||
}
|
@ -1,11 +1,182 @@
|
||||
<script setup lang="ts">
|
||||
import UserStatus from '@/components/UserStatus/index.vue'
|
||||
import MessageList from './components/messageList/index.vue'
|
||||
import InputBox from './components/inputBox/index.vue'
|
||||
/* header 操作 */
|
||||
const drawer = ref(false) //抽屉显隐
|
||||
const handleDrawer = () => {
|
||||
drawer.value = !drawer.value
|
||||
}
|
||||
//删除好友
|
||||
const delTheFriend = () => {
|
||||
console.log(nowPickInfo.value)
|
||||
if (nowPickInfo.value?.id) {
|
||||
const targetId = nowPickInfo.value.id
|
||||
// EaseChatClient.deleteContact(targetId)
|
||||
// ElMessage({ type: 'success', center: true, message: '好友已删除~' })
|
||||
}
|
||||
}
|
||||
// 当前聊天对象信息
|
||||
const nowPickInfo = ref({
|
||||
id: '1',
|
||||
chatType: 1,
|
||||
userInfo: {
|
||||
nickname: '好友1',
|
||||
userStatus: '1'
|
||||
},
|
||||
groupDetail: {
|
||||
name: '',
|
||||
affiliations_count: '',
|
||||
custom: ''
|
||||
}
|
||||
})
|
||||
|
||||
//获取群组详情
|
||||
const groupDetail = computed(() => {
|
||||
return nowPickInfo.value.groupDetail
|
||||
})
|
||||
//获取其id对应的消息内容
|
||||
const messageData = computed(() => [
|
||||
{
|
||||
type: 'text'
|
||||
}
|
||||
])
|
||||
/* 消息相关 */
|
||||
const loadingHistoryMsg = ref(false) //是否正在加载中
|
||||
const isMoreHistoryMsg = ref(true) //加载文案展示为加载更多还是已无更多。
|
||||
const notScrollBottom = ref(false) //是否滚动置底
|
||||
//获取历史记录
|
||||
const fechHistoryMessage = (loadType) => {
|
||||
console.log(loadType)
|
||||
console.log('加载更多')
|
||||
}
|
||||
//控制消息滚动
|
||||
const scrollMessageList = (direction) => {
|
||||
console.log(direction)
|
||||
}
|
||||
//消息重新编辑
|
||||
const inputBox = ref(null)
|
||||
const reEditMessage = (msg) => (inputBox.value.textContent = msg)
|
||||
//消息引用
|
||||
const messageQuote = (msg) => inputBox.value.handleQuoteMessage(msg)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
<el-container class="app_container">
|
||||
<el-header class="chat_message_header">
|
||||
<template v-if="nowPickInfo.chatType === 1">
|
||||
<div v-if="nowPickInfo.userInfo" class="chat_user_box">
|
||||
<span class="chat_user_name"> {{ nowPickInfo.userInfo.nickname || nowPickInfo.id }}</span>
|
||||
<UserStatus :userStatus="nowPickInfo.userInfo.userStatus" />
|
||||
</div>
|
||||
<div v-else> {{ nowPickInfo.id }}<span style="font-size: 10px">(非好友)</span> </div>
|
||||
</template>
|
||||
<template v-if="nowPickInfo.chatType === 2">
|
||||
<div v-if="nowPickInfo.groupDetail" class="chat_user_box">
|
||||
<span class="chat_user_name">
|
||||
{{ groupDetail.name || '' }}
|
||||
{{ `(${groupDetail?.affiliations_count || ''})` }}
|
||||
</span>
|
||||
</div>
|
||||
<div v-else class="chat_user_box">
|
||||
<span class="chat_user_name">
|
||||
{{ groupDetail.name || nowPickInfo.id }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
<!-- 群组展示抽屉 -->
|
||||
<span
|
||||
class="more"
|
||||
v-if="nowPickInfo.groupDetail && nowPickInfo.chatType === 2"
|
||||
@click="handleDrawer"
|
||||
>
|
||||
<svg
|
||||
width="18"
|
||||
height="4"
|
||||
viewBox="0 0 18 4"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<circle cx="2" cy="2" r="2" fill="#333333" />
|
||||
<circle cx="9" cy="2" r="2" fill="#333333" />
|
||||
<circle cx="16" cy="2" r="2" fill="#333333" />
|
||||
</svg>
|
||||
</span>
|
||||
<!-- 单人展示删除拉黑 -->
|
||||
<span class="more" v-if="nowPickInfo.chatType === 1">
|
||||
<el-dropdown placement="bottom-end" trigger="click">
|
||||
<svg
|
||||
width="18"
|
||||
height="4"
|
||||
viewBox="0 0 18 4"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<circle cx="2" cy="2" r="2" fill="#333333" />
|
||||
<circle cx="9" cy="2" r="2" fill="#333333" />
|
||||
<circle cx="16" cy="2" r="2" fill="#333333" />
|
||||
</svg>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item @click="delTheFriend"> 删除好友 </el-dropdown-item>
|
||||
<!-- <el-dropdown-item @click="addFriendToBlackList">
|
||||
加入黑名单
|
||||
</el-dropdown-item> -->
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</span>
|
||||
</el-header>
|
||||
<el-main class="chat_message_main">
|
||||
<el-scrollbar class="main_container" ref="messageContainer">
|
||||
<div class="innerRef">
|
||||
<div v-show="isMoreHistoryMsg" class="chat_message_tips">
|
||||
<div
|
||||
v-show="messageData?.length && messageData[0].type !== 'inform'"
|
||||
class="load_more_msg"
|
||||
>
|
||||
<el-link
|
||||
v-show="!loadingHistoryMsg"
|
||||
:disabled="!isMoreHistoryMsg"
|
||||
:underline="false"
|
||||
@click="fechHistoryMessage('loadFirst')"
|
||||
>
|
||||
加载更多
|
||||
</el-link>
|
||||
<el-link v-show="loadingHistoryMsg" disabled>消息加载中...</el-link>
|
||||
</div>
|
||||
</div>
|
||||
<MessageList
|
||||
:nowPickInfo="nowPickInfo"
|
||||
:messageData="messageData"
|
||||
@scrollMessageList="scrollMessageList"
|
||||
@reEditMessage="reEditMessage"
|
||||
@messageQuote="messageQuote"
|
||||
/>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
</el-main>
|
||||
<el-footer class="chat_message_inputbar">
|
||||
<InputBox ref="inputBox" :nowPickInfo="nowPickInfo" />
|
||||
</el-footer>
|
||||
<el-drawer
|
||||
v-if="nowPickInfo.chatType === 2"
|
||||
v-model="drawer"
|
||||
:show-close="false"
|
||||
:close-on-click-modal="true"
|
||||
direction="rtl"
|
||||
:modal="true"
|
||||
size="280px"
|
||||
>
|
||||
<GroupsDetails
|
||||
:nowGroupId="nowPickInfo.id"
|
||||
:groupDetail="groupDetail"
|
||||
@handleDrawer="handleDrawer"
|
||||
/>
|
||||
</el-drawer>
|
||||
</el-container>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
@import './index.scss';
|
||||
</style>
|
||||
|
Loading…
Reference in New Issue
Block a user