Merge branch 'xingyy'
# Conflicts: # env/.env.test resolved by xingyy version
This commit is contained in:
commit
e3d61107cb
@ -1,6 +1,6 @@
|
|||||||
# LumenIM - 在线即时通讯应用
|
# IM - 在线即时通讯应用
|
||||||
|
|
||||||
LumenIM 是一个基于 Vue 3 开发的现代化在线即时通讯应用,提供实时聊天、消息管理、笔记等功能。
|
IM 是一个基于 Vue 3 开发的现代化在线即时通讯应用,提供实时聊天、消息管理、笔记等功能。
|
||||||
|
|
||||||
## 功能特性
|
## 功能特性
|
||||||
|
|
||||||
@ -101,4 +101,4 @@ src/
|
|||||||
|
|
||||||
## 许可证
|
## 许可证
|
||||||
|
|
||||||
Copyright © 2023 LumenIM
|
Copyright © 2023 IM
|
||||||
|
2
components.d.ts
vendored
2
components.d.ts
vendored
@ -52,6 +52,7 @@ declare module 'vue' {
|
|||||||
NAvatar: typeof import('naive-ui')['NAvatar']
|
NAvatar: typeof import('naive-ui')['NAvatar']
|
||||||
NButton: typeof import('naive-ui')['NButton']
|
NButton: typeof import('naive-ui')['NButton']
|
||||||
NCheckbox: typeof import('naive-ui')['NCheckbox']
|
NCheckbox: typeof import('naive-ui')['NCheckbox']
|
||||||
|
NDropdown: typeof import('naive-ui')['NDropdown']
|
||||||
NEmpty: typeof import('naive-ui')['NEmpty']
|
NEmpty: typeof import('naive-ui')['NEmpty']
|
||||||
NIcon: typeof import('naive-ui')['NIcon']
|
NIcon: typeof import('naive-ui')['NIcon']
|
||||||
NImage: typeof import('naive-ui')['NImage']
|
NImage: typeof import('naive-ui')['NImage']
|
||||||
@ -68,7 +69,6 @@ declare module 'vue' {
|
|||||||
RevokeMessage: typeof import('./src/components/talk/message/RevokeMessage.vue')['default']
|
RevokeMessage: typeof import('./src/components/talk/message/RevokeMessage.vue')['default']
|
||||||
RouterLink: typeof import('vue-router')['RouterLink']
|
RouterLink: typeof import('vue-router')['RouterLink']
|
||||||
RouterView: typeof import('vue-router')['RouterView']
|
RouterView: typeof import('vue-router')['RouterView']
|
||||||
SearchByCondition: typeof import('./src/components/search/searchByCondition.vue')['default']
|
|
||||||
SearchItem: typeof import('./src/components/search/searchItem.vue')['default']
|
SearchItem: typeof import('./src/components/search/searchItem.vue')['default']
|
||||||
SearchList: typeof import('./src/components/search/searchList.vue')['default']
|
SearchList: typeof import('./src/components/search/searchList.vue')['default']
|
||||||
SysGroupAdminMessage: typeof import('./src/components/talk/message/system/SysGroupAdminMessage.vue')['default']
|
SysGroupAdminMessage: typeof import('./src/components/talk/message/system/SysGroupAdminMessage.vue')['default']
|
||||||
|
8
env/.env.test
vendored
8
env/.env.test
vendored
@ -2,9 +2,7 @@ ENV = 'development'
|
|||||||
|
|
||||||
VITE_BASE=/
|
VITE_BASE=/
|
||||||
VUE_APP_PREVIEW=false
|
VUE_APP_PREVIEW=false
|
||||||
VITE_BASE_API=http://172.16.100.93:8503
|
VITE_BASE_API=http://114.218.158.24:8503
|
||||||
# VITE_BASE_API=http://192.168.88.21:9503
|
|
||||||
VITE_EPR_BASEURL=http://114.218.158.24:9020
|
VITE_EPR_BASEURL=http://114.218.158.24:9020
|
||||||
VITE_SOCKET_API=ws://172.16.100.93:8504
|
VITE_SOCKET_API=ws://114.218.158.24:8504
|
||||||
# VITE_SOCKET_API=ws://192.168.88.21:9504
|
VUE_APP_WEBSITE_NAME=""
|
||||||
VUE_APP_WEBSITE_NAME="Lumen IM"
|
|
@ -5,7 +5,7 @@
|
|||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" href="./src/assets/image/favicon.png" />
|
<link rel="icon" href="./src/assets/image/favicon.png" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Lumen IM 在线聊天</title>
|
<title> 在线聊天</title>
|
||||||
<style>
|
<style>
|
||||||
.outer,
|
.outer,
|
||||||
.middle,
|
.middle,
|
||||||
|
4
package-lock.json
generated
4
package-lock.json
generated
@ -1,11 +1,11 @@
|
|||||||
{
|
{
|
||||||
"name": "LumenIM",
|
"name": "IM",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "LumenIM",
|
"name": "IM",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@highlightjs/vue-plugin": "^2.1.0",
|
"@highlightjs/vue-plugin": "^2.1.0",
|
||||||
|
20
package.json
20
package.json
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "LumenIM",
|
"name": "IM",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
@ -18,6 +18,8 @@
|
|||||||
"@highlightjs/vue-plugin": "^2.1.0",
|
"@highlightjs/vue-plugin": "^2.1.0",
|
||||||
"@iconify-json/ion": "^1.2.3",
|
"@iconify-json/ion": "^1.2.3",
|
||||||
"@kangc/v-md-editor": "^2.3.18",
|
"@kangc/v-md-editor": "^2.3.18",
|
||||||
|
"@onlyoffice/document-editor-vue": "^1.5.0",
|
||||||
|
"@vicons/fluent": "^0.13.0",
|
||||||
"@vicons/ionicons5": "^0.13.0",
|
"@vicons/ionicons5": "^0.13.0",
|
||||||
"@vueup/vue-quill": "^1.2.0",
|
"@vueup/vue-quill": "^1.2.0",
|
||||||
"@vueuse/core": "^10.7.0",
|
"@vueuse/core": "^10.7.0",
|
||||||
@ -69,15 +71,15 @@
|
|||||||
"wait-on": "^6.0.1"
|
"wait-on": "^6.0.1"
|
||||||
},
|
},
|
||||||
"build": {
|
"build": {
|
||||||
"appId": "com.gzydong.lumenim",
|
"appId": "com.gzydong.im",
|
||||||
"productName": "LumenIM",
|
"productName": "IM",
|
||||||
"copyright": "Copyright © 2023 LumenIM",
|
"copyright": "Copyright © 2023 IM",
|
||||||
"mac": {
|
"mac": {
|
||||||
"category": "public.app-category.utilities",
|
"category": "public.app-category.utilities",
|
||||||
"icon": "build/icons/lumen-im-mac.png"
|
"icon": "build/icons/-im-mac.png"
|
||||||
},
|
},
|
||||||
"win": {
|
"win": {
|
||||||
"icon": "build/icons/lumen-im-mac.png",
|
"icon": "build/icons/-im-mac.png",
|
||||||
"target": [
|
"target": [
|
||||||
{
|
{
|
||||||
"target": "nsis"
|
"target": "nsis"
|
||||||
@ -87,9 +89,9 @@
|
|||||||
"nsis": {
|
"nsis": {
|
||||||
"oneClick": false,
|
"oneClick": false,
|
||||||
"allowToChangeInstallationDirectory": true,
|
"allowToChangeInstallationDirectory": true,
|
||||||
"installerIcon": "build/icons/lumen-im-win.ico",
|
"installerIcon": "build/icons/-im-win.ico",
|
||||||
"uninstallerIcon": "build/icons/lumen-im-win.ico",
|
"uninstallerIcon": "build/icons/-im-win.ico",
|
||||||
"installerHeaderIcon": "build/icons/lumen-im-win.ico",
|
"installerHeaderIcon": "build/icons/-im-win.ico",
|
||||||
"createDesktopShortcut": true,
|
"createDesktopShortcut": true,
|
||||||
"createStartMenuShortcut": true,
|
"createStartMenuShortcut": true,
|
||||||
"shortcutName": "lumeim-icon"
|
"shortcutName": "lumeim-icon"
|
||||||
|
@ -20,6 +20,12 @@ importers:
|
|||||||
'@kangc/v-md-editor':
|
'@kangc/v-md-editor':
|
||||||
specifier: ^2.3.18
|
specifier: ^2.3.18
|
||||||
version: 2.3.18(@vue/compiler-sfc@3.5.13)(vue@3.5.13(typescript@5.2.2))
|
version: 2.3.18(@vue/compiler-sfc@3.5.13)(vue@3.5.13(typescript@5.2.2))
|
||||||
|
'@onlyoffice/document-editor-vue':
|
||||||
|
specifier: ^1.5.0
|
||||||
|
version: 1.5.0(vue@3.5.13(typescript@5.2.2))
|
||||||
|
'@vicons/fluent':
|
||||||
|
specifier: ^0.13.0
|
||||||
|
version: 0.13.0
|
||||||
'@vicons/ionicons5':
|
'@vicons/ionicons5':
|
||||||
specifier: ^0.13.0
|
specifier: ^0.13.0
|
||||||
version: 0.13.0
|
version: 0.13.0
|
||||||
@ -594,6 +600,11 @@ packages:
|
|||||||
resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
|
resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
|
||||||
engines: {node: '>= 8'}
|
engines: {node: '>= 8'}
|
||||||
|
|
||||||
|
'@onlyoffice/document-editor-vue@1.5.0':
|
||||||
|
resolution: {integrity: sha512-HZEebUhBloP4LomspI5BddgoQdhtPq91h57yA9K/Lk70MMc1vgOTQ4Wq+N5TZYXNxdDTv+TSsEVFLnBCl1Y71A==}
|
||||||
|
peerDependencies:
|
||||||
|
vue: ^3.0.0
|
||||||
|
|
||||||
'@parcel/watcher-android-arm64@2.5.1':
|
'@parcel/watcher-android-arm64@2.5.1':
|
||||||
resolution: {integrity: sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==}
|
resolution: {integrity: sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==}
|
||||||
engines: {node: '>= 10.0.0'}
|
engines: {node: '>= 10.0.0'}
|
||||||
@ -988,6 +999,9 @@ packages:
|
|||||||
peerDependencies:
|
peerDependencies:
|
||||||
vue: ^3.0.0
|
vue: ^3.0.0
|
||||||
|
|
||||||
|
'@vicons/fluent@0.13.0':
|
||||||
|
resolution: {integrity: sha512-bYGZsOE3qzvm3Cm43e7tybgGlr5ZUpYqtRZq0g0Tfupe8jIzLolpvQLNUt1zS8Mgt6goTbUk5YH7Fkv16jkykg==}
|
||||||
|
|
||||||
'@vicons/ionicons5@0.13.0':
|
'@vicons/ionicons5@0.13.0':
|
||||||
resolution: {integrity: sha512-zvZKBPjEXKN7AXNo2Na2uy+nvuv6SP4KAMQxpKL2vfHMj0fSvuw7JZcOPCjQC3e7ayssKnaoFVAhbYcW6v41qQ==}
|
resolution: {integrity: sha512-zvZKBPjEXKN7AXNo2Na2uy+nvuv6SP4KAMQxpKL2vfHMj0fSvuw7JZcOPCjQC3e7ayssKnaoFVAhbYcW6v41qQ==}
|
||||||
|
|
||||||
@ -4012,6 +4026,11 @@ snapshots:
|
|||||||
'@nodelib/fs.scandir': 2.1.5
|
'@nodelib/fs.scandir': 2.1.5
|
||||||
fastq: 1.19.1
|
fastq: 1.19.1
|
||||||
|
|
||||||
|
'@onlyoffice/document-editor-vue@1.5.0(vue@3.5.13(typescript@5.2.2))':
|
||||||
|
dependencies:
|
||||||
|
lodash: 4.17.21
|
||||||
|
vue: 3.5.13(typescript@5.2.2)
|
||||||
|
|
||||||
'@parcel/watcher-android-arm64@2.5.1':
|
'@parcel/watcher-android-arm64@2.5.1':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
@ -4390,6 +4409,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
vue: 3.5.13(typescript@5.2.2)
|
vue: 3.5.13(typescript@5.2.2)
|
||||||
|
|
||||||
|
'@vicons/fluent@0.13.0': {}
|
||||||
|
|
||||||
'@vicons/ionicons5@0.13.0': {}
|
'@vicons/ionicons5@0.13.0': {}
|
||||||
|
|
||||||
'@vitejs/plugin-vue-jsx@3.1.0(vite@6.3.5(@types/node@18.19.99)(jiti@1.21.7)(less@4.3.0)(sass@1.88.0)(terser@5.39.2))(vue@3.5.13(typescript@5.2.2))':
|
'@vitejs/plugin-vue-jsx@3.1.0(vite@6.3.5(@types/node@18.19.99)(jiti@1.21.7)(less@4.3.0)(sass@1.88.0)(terser@5.39.2))(vue@3.5.13(typescript@5.2.2))':
|
||||||
|
@ -206,7 +206,7 @@ textarea {
|
|||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
cursor: default;
|
cursor: default;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
background-color: #dee0e3;
|
|
||||||
transform: scale(0.84);
|
transform: scale(0.84);
|
||||||
transform-origin: left;
|
transform-origin: left;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
@ -21,7 +21,7 @@ html {
|
|||||||
// message
|
// message
|
||||||
--im-message-bg-color: #f7f7f7;
|
--im-message-bg-color: #f7f7f7;
|
||||||
--im-message-border-color: #efeff5;
|
--im-message-border-color: #efeff5;
|
||||||
--im-message-left-bg-color: #F4F4FC;
|
--im-message-left-bg-color: #fff;
|
||||||
--im-message-left-text-color: #333;
|
--im-message-left-text-color: #333;
|
||||||
--im-message-right-bg-color: #46299D;
|
--im-message-right-bg-color: #46299D;
|
||||||
--im-message-right-text-color: #fff;
|
--im-message-right-text-color: #fff;
|
||||||
|
@ -4,6 +4,9 @@
|
|||||||
color: #fff!important;
|
color: #fff!important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.n-checkbox-box-wrapper .n-checkbox-box{
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
/*表格头多选框颜色调整避免和表头颜色冲突*/
|
/*表格头多选框颜色调整避免和表头颜色冲突*/
|
||||||
.n-data-table-thead .n-data-table-tr .n-checkbox-box{
|
.n-data-table-thead .n-data-table-tr .n-checkbox-box{
|
||||||
background: #fff;
|
background: #fff;
|
||||||
|
BIN
src/assets/image/dofd.png
Normal file
BIN
src/assets/image/dofd.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 396 B |
BIN
src/assets/image/xxxx@2x.png
Normal file
BIN
src/assets/image/xxxx@2x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 684 B |
BIN
src/assets/image/zu6146@2x.png
Normal file
BIN
src/assets/image/zu6146@2x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 26 KiB |
@ -577,7 +577,6 @@ function hideMentionDom() {
|
|||||||
* @param data 消息数据
|
* @param data 消息数据
|
||||||
*/
|
*/
|
||||||
function onSubscribeEdit(data: any) {
|
function onSubscribeEdit(data: any) {
|
||||||
console.log('data', data)
|
|
||||||
const quill = getQuill()
|
const quill = getQuill()
|
||||||
if (!quill) return
|
if (!quill) return
|
||||||
|
|
||||||
|
@ -5,22 +5,21 @@ import { ServeGetForwardRecords } from '@/api/chat'
|
|||||||
import { MessageComponents } from '@/constant/message'
|
import { MessageComponents } from '@/constant/message'
|
||||||
import { ITalkRecord } from '@/types/chat'
|
import { ITalkRecord } from '@/types/chat'
|
||||||
import { useInject } from '@/hooks'
|
import { useInject } from '@/hooks'
|
||||||
|
import customModal from '@/components/common/customModal.vue'
|
||||||
const emit = defineEmits(['close'])
|
import { voiceToText } from '@/api/chat.js'
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
msgId: {
|
msgId: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true
|
required: true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
const isShow=defineModel<boolean>('show')
|
||||||
const { showUserInfoModal } = useInject()
|
const { showUserInfoModal } = useInject()
|
||||||
const isShow = ref(true)
|
|
||||||
const items = ref<ITalkRecord[]>([])
|
const items = ref<ITalkRecord[]>([])
|
||||||
const title = ref('会话记录')
|
const title = ref('会话记录')
|
||||||
|
|
||||||
const onMaskClick = () => {
|
const onMaskClick = () => {
|
||||||
emit('close')
|
isShow.value=false
|
||||||
}
|
}
|
||||||
|
|
||||||
const onLoadData = () => {
|
const onLoadData = () => {
|
||||||
@ -30,18 +29,92 @@ const onLoadData = () => {
|
|||||||
if (res.code == 200) {
|
if (res.code == 200) {
|
||||||
items.value = res.data.items || []
|
items.value = res.data.items || []
|
||||||
|
|
||||||
title.value = `会话记录(${items.value.length})`
|
// title.value = `会话记录(${items.value.length})`
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
const dropdown=ref({
|
||||||
|
show:false,
|
||||||
|
x:'',
|
||||||
|
y:'',
|
||||||
|
options:[] as any,
|
||||||
|
item:{} as ITalkRecord,
|
||||||
|
})
|
||||||
|
const onConvertText =async (data: ITalkRecord) => {
|
||||||
|
data.is_convert_text = 1
|
||||||
|
const res = await voiceToText({msgId:data.msg_id,voiceUrl:data.extra.url})
|
||||||
|
if(res.code == 200){
|
||||||
|
data.extra.content = res.data.convText
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const onloseConvertText=(data: ITalkRecord)=>{
|
||||||
|
data.is_convert_text = 0
|
||||||
|
}
|
||||||
|
const evnets = {
|
||||||
|
convertText: onConvertText,
|
||||||
|
closeConvertText:onloseConvertText
|
||||||
|
}
|
||||||
|
|
||||||
|
const onContextMenuHandle=(key:string)=>{
|
||||||
|
evnets[key] && evnets[key](dropdown.value.item)
|
||||||
|
closeDropdownMenu()
|
||||||
|
}
|
||||||
|
const closeDropdownMenu=()=>{
|
||||||
|
dropdown.value.show=false
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
onLoadData()
|
onLoadData()
|
||||||
})
|
})
|
||||||
|
const onContextMenu = (e:any,item: ITalkRecord) => {
|
||||||
|
dropdown.value.show=true
|
||||||
|
|
||||||
|
dropdown.value.x=e.clientX
|
||||||
|
dropdown.value.y=e.clientY
|
||||||
|
if(item.is_convert_text === 1){
|
||||||
|
dropdown.value.options=[{ label: '关闭转文字', key: 'closeConvertText' }]
|
||||||
|
}else{
|
||||||
|
dropdown.value.options=[{ label: '转文字', key: 'convertText' }]
|
||||||
|
}
|
||||||
|
|
||||||
|
dropdown.value.item=item
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<n-modal
|
<customModal :closable="false" customCloseBtn v-model:show="isShow" :title="title" style="width: 997px;background-color: #F9F9FD;" :on-after-leave="onMaskClick">
|
||||||
|
<template #content>
|
||||||
|
<div class="main-box bg-#fff me-scrollbar me-scrollbar-thumb">
|
||||||
|
<Loading v-if="items.length === 0" />
|
||||||
|
|
||||||
|
<div v-for="item in items" :key="item.msg_id" class="message-item">
|
||||||
|
<div class="left-box pointer" @click="showUserInfoModal(item.user_id)">
|
||||||
|
<im-avatar :src="item.avatar" :size="38" :username="item.nickname" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="right-box">
|
||||||
|
<div class="msg-header">
|
||||||
|
<span class="name">{{ item.nickname }}</span>
|
||||||
|
<span class="time"> {{ item.created_at }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<component
|
||||||
|
@contextmenu.prevent="onContextMenu($event,item)"
|
||||||
|
:is="MessageComponents[item.msg_type] || 'unknown-message'"
|
||||||
|
:extra="item.extra"
|
||||||
|
:data="item"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- 右键菜单 -->
|
||||||
|
<n-dropdown :show="dropdown.show" :x="dropdown.x" :y="dropdown.y" style="width: 142px;" :options="dropdown.options"
|
||||||
|
@select="onContextMenuHandle" @clickoutside="closeDropdownMenu" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
</customModal>
|
||||||
|
<!-- <n-modal
|
||||||
v-model:show="isShow"
|
v-model:show="isShow"
|
||||||
preset="card"
|
preset="card"
|
||||||
:title="title"
|
:title="title"
|
||||||
@ -80,7 +153,7 @@ onMounted(() => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</n-modal>
|
</n-modal> -->
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
@ -94,10 +167,12 @@ onMounted(() => {
|
|||||||
min-height: 38px;
|
min-height: 38px;
|
||||||
display: flex;
|
display: flex;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
padding: 5px 15px;
|
padding: 24px 42px;
|
||||||
|
.im-message-text{
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
.left-box {
|
.left-box {
|
||||||
width: 30px;
|
width: 38px;
|
||||||
display: flex;
|
display: flex;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
padding-top: 8px;
|
padding-top: 8px;
|
||||||
|
@ -7,7 +7,8 @@ import excelText from '@/assets/image/excel-text.png'
|
|||||||
import wordText from '@/assets/image/word-text.png'
|
import wordText from '@/assets/image/word-text.png'
|
||||||
import pdfText from '@/assets/image/pdf-text.png'
|
import pdfText from '@/assets/image/pdf-text.png'
|
||||||
import fileText from '@/assets/image/file-text.png'
|
import fileText from '@/assets/image/file-text.png'
|
||||||
|
import { ArrowDownload16Filled } from '@vicons/fluent'
|
||||||
|
import { download } from '@/utils/functions.js'
|
||||||
// 定义组件属性
|
// 定义组件属性
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
// 文件的额外信息
|
// 文件的额外信息
|
||||||
@ -83,10 +84,36 @@ const circumference = computed(() => 2 * Math.PI * radius)
|
|||||||
const strokeDashoffset = computed(() =>
|
const strokeDashoffset = computed(() =>
|
||||||
circumference.value * (1 - (props.extra.percentage || 0) / 100)
|
circumference.value * (1 - (props.extra.percentage || 0) / 100)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// 处理文件点击事件
|
||||||
|
const handleClick = () => {
|
||||||
|
console.log('handleClick')
|
||||||
|
window.open(
|
||||||
|
`${window.location.origin}/office?url=${props.extra.path}`,
|
||||||
|
'_blank',
|
||||||
|
'width=1200,height=900,left=200,top=200,toolbar=no,menubar=no,scrollbars=yes,resizable=yes,location=no,status=no'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function downloadFileWithProgress(resourceUrl, filename) {
|
||||||
|
const iframe = document.createElement('iframe');
|
||||||
|
iframe.style.display = 'none';
|
||||||
|
iframe.src = resourceUrl;
|
||||||
|
document.body.appendChild(iframe);
|
||||||
|
setTimeout(() => {
|
||||||
|
document.body.removeChild(iframe);
|
||||||
|
}, 60000);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 处理下载事件
|
||||||
|
const handleDownload = () => {
|
||||||
|
downloadFileWithProgress(props.extra.path,props.extra.name)
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="file-message">
|
<div class="file-message" @click="handleClick">
|
||||||
<!-- 文件头部信息 -->
|
<!-- 文件头部信息 -->
|
||||||
<div class="file-header">
|
<div class="file-header">
|
||||||
<!-- 文件名 -->
|
<!-- 文件名 -->
|
||||||
@ -136,7 +163,14 @@ const strokeDashoffset = computed(() =>
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- 文件大小信息 -->
|
<!-- 文件大小信息 -->
|
||||||
|
<div class="flex justify-between items-center">
|
||||||
<div class="file-size">{{ fileFormatSize(extra.size) }}</div>
|
<div class="file-size">{{ fileFormatSize(extra.size) }}</div>
|
||||||
|
<div class="flex items-center" v-if="!extra.is_uploading">
|
||||||
|
<div class="flex items-center" @click.stop="handleDownload"> <img class="w-11.7px h-11.74px mr-7px" src="@/assets/image/dofd.png" alt=""> <span class="text-12px text-#46299D">下载</span></div>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -33,7 +33,7 @@ const onClick = () => {
|
|||||||
<span>转发:聊天会话记录 ({{ extra.msg_ids.length }}条)</span>
|
<span>转发:聊天会话记录 ({{ extra.msg_ids.length }}条)</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ForwardRecord v-if="isShowRecord" :msg-id="data.msg_id" @close="isShowRecord = false" />
|
<ForwardRecord v-model:show="isShowRecord" :msg-id="data.msg_id" @close="isShowRecord = false" />
|
||||||
</section>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -43,7 +43,7 @@ textContent = textReplaceEmoji(textContent)
|
|||||||
min-height: 30px;
|
min-height: 30px;
|
||||||
padding: 3px;
|
padding: 3px;
|
||||||
color: var(--im-message-left-text-color);
|
color: var(--im-message-left-text-color);
|
||||||
background: var(--im-message-left-bg-color);
|
background: #F4F4FC;
|
||||||
border-radius: 0px 10px 10px 10px;
|
border-radius: 0px 10px 10px 10px;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
&.right {
|
&.right {
|
||||||
|
@ -15,8 +15,7 @@ const { showUserInfoModal } = useInject()
|
|||||||
<div class="sys-text">
|
<div class="sys-text">
|
||||||
|
|
||||||
<template v-for="(user, index) in extra.members" :key="index">
|
<template v-for="(user, index) in extra.members" :key="index">
|
||||||
{{ data }}
|
<a @click="showUserInfoModal(user.erp_user_id,user.user_id)">{{ user.nickname }}</a>
|
||||||
<a @click="showUserInfoModal(user.user_id)">{{ user.nickname }}</a>
|
|
||||||
<em v-show="index < extra.members.length - 1">、</em>
|
<em v-show="index < extra.members.length - 1">、</em>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -13,6 +13,7 @@ const { showUserInfoModal } = useInject()
|
|||||||
<template>
|
<template>
|
||||||
<div class="im-message-sys-text">
|
<div class="im-message-sys-text">
|
||||||
<div class="sys-text">
|
<div class="sys-text">
|
||||||
|
|
||||||
<a @click="showUserInfoModal(extra.owner_id)">
|
<a @click="showUserInfoModal(extra.owner_id)">
|
||||||
{{ extra.owner_name }}
|
{{ extra.owner_name }}
|
||||||
</a>
|
</a>
|
||||||
|
@ -127,11 +127,12 @@ const onCancel = () => {
|
|||||||
const onSubmit = () => {
|
const onSubmit = () => {
|
||||||
let data = checkedFilter.value.map((item: any) => {
|
let data = checkedFilter.value.map((item: any) => {
|
||||||
return {
|
return {
|
||||||
id: item.id,
|
receiver_id: item.receiver_id,
|
||||||
type: item.type
|
talk_type: item.talk_type
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
console.log('data', data);
|
||||||
|
console.log('checkedFilter.value', checkedFilter.value);
|
||||||
emit('on-submit', data)
|
emit('on-submit', data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -170,10 +170,25 @@ const onToTalk = () => {
|
|||||||
const onAfterEnter = () => {
|
const onAfterEnter = () => {
|
||||||
onLoadData()
|
onLoadData()
|
||||||
}
|
}
|
||||||
|
const onAfterLeave = () => {
|
||||||
|
// loading.value = true
|
||||||
|
userInfo.value = {
|
||||||
|
id: 0,
|
||||||
|
avatar: '',
|
||||||
|
gender: 0,
|
||||||
|
mobile: '',
|
||||||
|
motto: '',
|
||||||
|
nickname: '',
|
||||||
|
remark: '',
|
||||||
|
email: '',
|
||||||
|
status: 1,
|
||||||
|
text: ''
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<x-n-modal content-style="padding:0;" :closable="false" class="w-311px min-h-445px" style="border-radius: 10px;overflow:hidden;" :show="show" :on-after-enter="onAfterEnter">
|
<x-n-modal content-style="padding:0;" :closable="false" class="w-311px min-h-445px" style="border-radius: 10px;overflow:hidden;" :show="show" :on-after-leave="onAfterLeave" :on-after-enter="onAfterEnter">
|
||||||
<div class="section relative px-7px pt-82px pb-20px">
|
<div class="section relative px-7px pt-82px pb-20px">
|
||||||
<div class="absolute top-9px right-7px pointer z-10" @click="emit('update:show', false)">
|
<div class="absolute top-9px right-7px pointer z-10" @click="emit('update:show', false)">
|
||||||
<img class="w-20px h-20px" src="@/assets/image/close.png" alt="">
|
<img class="w-20px h-20px" src="@/assets/image/close.png" alt="">
|
||||||
@ -181,8 +196,8 @@ const onAfterEnter = () => {
|
|||||||
|
|
||||||
<template v-if="loading">
|
<template v-if="loading">
|
||||||
<div class="flex py-10px bg-#fff px-16px rounded-4px items-center mb-10px">
|
<div class="flex py-10px bg-#fff px-16px rounded-4px items-center mb-10px">
|
||||||
<div class="w-59px h-59px rounded-8px mr-12px overflow-hidden">
|
<div class="w-59px h-59px rounded-8px mr-12px">
|
||||||
<n-skeleton circle height="59px" width="59px" />
|
<n-skeleton height="59px" width="59px" />
|
||||||
</div>
|
</div>
|
||||||
<div class="w-full">
|
<div class="w-full">
|
||||||
<n-skeleton text style="width: 80%; margin-bottom: 5px;" />
|
<n-skeleton text style="width: 80%; margin-bottom: 5px;" />
|
||||||
|
@ -118,7 +118,7 @@ class Talk extends Base {
|
|||||||
*/
|
*/
|
||||||
showMessageNocice() {
|
showMessageNocice() {
|
||||||
if (useSettingsStore().isLeaveWeb) {
|
if (useSettingsStore().isLeaveWeb) {
|
||||||
const notification = new Notification('LumenIM 在线聊天', {
|
const notification = new Notification('IM 在线聊天', {
|
||||||
dir: 'auto',
|
dir: 'auto',
|
||||||
lang: 'zh-CN',
|
lang: 'zh-CN',
|
||||||
body: '您有新的消息请注意查收'
|
body: '您有新的消息请注意查收'
|
||||||
|
@ -1,17 +1,5 @@
|
|||||||
import { reactive, nextTick, computed, h, inject } from 'vue'
|
import { reactive, nextTick, computed, h, inject } from 'vue'
|
||||||
import { ISession } from '@/types/chat'
|
import { ISession } from '@/types/chat'
|
||||||
import { renderIcon } from '@/utils/util'
|
|
||||||
import {
|
|
||||||
ArrowUp,
|
|
||||||
ArrowDown,
|
|
||||||
Logout,
|
|
||||||
Delete,
|
|
||||||
Clear,
|
|
||||||
Remind,
|
|
||||||
CloseRemind,
|
|
||||||
EditTwo,
|
|
||||||
IdCard
|
|
||||||
} from '@icon-park/vue-next'
|
|
||||||
import { ServeTopTalkList, ServeDeleteTalkList, ServeSetNotDisturb } from '@/api/chat'
|
import { ServeTopTalkList, ServeDeleteTalkList, ServeSetNotDisturb } from '@/api/chat'
|
||||||
import { useDialogueStore, useTalkStore } from '@/store'
|
import { useDialogueStore, useTalkStore } from '@/store'
|
||||||
import { ServeSecedeGroup } from '@/api/group'
|
import { ServeSecedeGroup } from '@/api/group'
|
||||||
@ -52,45 +40,45 @@ export function useSessionMenu() {
|
|||||||
|
|
||||||
if (item.talk_type == 1) {
|
if (item.talk_type == 1) {
|
||||||
options.push({
|
options.push({
|
||||||
icon: renderIcon(IdCard),
|
|
||||||
label: '好友信息',
|
label: '好友信息',
|
||||||
key: 'info'
|
key: 'info'
|
||||||
})
|
})
|
||||||
|
|
||||||
options.push({
|
options.push({
|
||||||
icon: renderIcon(EditTwo),
|
|
||||||
label: '修改备注',
|
label: '修改备注',
|
||||||
key: 'remark'
|
key: 'remark'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
options.push({
|
options.push({
|
||||||
icon: renderIcon(item.is_top ? ArrowDown : ArrowUp),
|
|
||||||
label: item.is_top ? '取消置顶' : '会话置顶',
|
label: item.is_top ? '取消置顶' : '会话置顶',
|
||||||
key: 'top'
|
key: 'top'
|
||||||
})
|
})
|
||||||
|
|
||||||
options.push({
|
options.push({
|
||||||
icon: renderIcon(item.is_disturb ? Remind : CloseRemind),
|
|
||||||
label: item.is_disturb ? '关闭免打扰' : '开启免打扰',
|
label: item.is_disturb ? '关闭免打扰' : '开启免打扰',
|
||||||
key: 'disturb'
|
key: 'disturb'
|
||||||
})
|
})
|
||||||
|
|
||||||
options.push({
|
options.push({
|
||||||
icon: renderIcon(Clear),
|
|
||||||
label: '移除会话',
|
label: '移除会话',
|
||||||
key: 'remove'
|
key: 'remove'
|
||||||
})
|
})
|
||||||
|
|
||||||
if (item.talk_type == 1) {
|
if (item.talk_type == 1) {
|
||||||
options.push({
|
options.push({
|
||||||
icon: renderIcon(Delete),
|
|
||||||
label: '删除好友',
|
label: '删除好友',
|
||||||
key: 'delete_contact'
|
key: 'delete_contact'
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
options.push({
|
options.push({
|
||||||
icon: renderIcon(Logout),
|
|
||||||
label: '退出群聊',
|
label: '退出群聊',
|
||||||
key: 'signout_group'
|
key: 'signout_group'
|
||||||
})
|
})
|
||||||
|
@ -79,6 +79,7 @@ export const useTalkRecord = (uid: number) => {
|
|||||||
loadConfig.status = 0
|
loadConfig.status = 0
|
||||||
|
|
||||||
let scrollHeight = 0
|
let scrollHeight = 0
|
||||||
|
console.log('加载数据列表load')
|
||||||
const el = document.getElementById('imChatPanel')
|
const el = document.getElementById('imChatPanel')
|
||||||
if (el) {
|
if (el) {
|
||||||
scrollHeight = el.scrollHeight
|
scrollHeight = el.scrollHeight
|
||||||
@ -88,7 +89,6 @@ export const useTalkRecord = (uid: number) => {
|
|||||||
if (code != 200) {
|
if (code != 200) {
|
||||||
return (loadConfig.status = 1)
|
return (loadConfig.status = 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 防止对话切换过快,数据渲染错误
|
// 防止对话切换过快,数据渲染错误
|
||||||
if (
|
if (
|
||||||
request.talk_type != loadConfig.talk_type ||
|
request.talk_type != loadConfig.talk_type ||
|
||||||
@ -118,9 +118,13 @@ export const useTalkRecord = (uid: number) => {
|
|||||||
el.scrollTop = el.scrollHeight
|
el.scrollTop = el.scrollHeight
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
console.log('el.scrollHeight',el.scrollHeight)
|
||||||
|
console.log('request.cursor == 0')
|
||||||
el.scrollTop = el.scrollHeight + 1000
|
el.scrollTop = el.scrollHeight + 1000
|
||||||
}, 50)
|
|
||||||
|
}, 500)
|
||||||
} else {
|
} else {
|
||||||
|
console.log('request.cursor !== 0')
|
||||||
el.scrollTop = el.scrollHeight - scrollHeight
|
el.scrollTop = el.scrollHeight - scrollHeight
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -131,7 +131,7 @@ const isActive = (menu) => {
|
|||||||
|
|
||||||
<footer class="menu-footer">
|
<footer class="menu-footer">
|
||||||
<div>
|
<div>
|
||||||
<a class="pointer" href="https://github.com/gzydong/LumenIM" target="_blank">
|
<a class="pointer" href="https://github.com/gzydong/IM" target="_blank">
|
||||||
<github-one theme="outline" size="22" :fill="color" :strokeWidth="2" />
|
<github-one theme="outline" size="22" :fill="color" :strokeWidth="2" />
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
@ -40,6 +40,11 @@ const routes = [
|
|||||||
path: '/:pathMatch(.*)*',
|
path: '/:pathMatch(.*)*',
|
||||||
name: '404 NotFound',
|
name: '404 NotFound',
|
||||||
component: () => import('@/views/other/not-found.vue')
|
component: () => import('@/views/other/not-found.vue')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/office',
|
||||||
|
name: 'office',
|
||||||
|
component: () => import('@/views/office/index.vue')
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -5,9 +5,8 @@ import {
|
|||||||
ServePublishMessage,
|
ServePublishMessage,
|
||||||
ServeCollectEmoticon
|
ServeCollectEmoticon
|
||||||
} from '@/api/chat'
|
} from '@/api/chat'
|
||||||
import { ServeGetGroupMembers } from '@/api/group'
|
import { ServeGetGroupMembers,ServeGroupDetail } from '@/api/group.js'
|
||||||
import { useEditorStore } from './editor'
|
import { useEditorStore } from './editor'
|
||||||
|
|
||||||
// 键盘消息事件定时器
|
// 键盘消息事件定时器
|
||||||
let keyboardTimeout = null
|
let keyboardTimeout = null
|
||||||
|
|
||||||
@ -46,7 +45,7 @@ export const useDialogueStore = defineStore('dialogue', {
|
|||||||
|
|
||||||
// 是否显示会话列表
|
// 是否显示会话列表
|
||||||
isShowSessionList: true,
|
isShowSessionList: true,
|
||||||
|
groupInfo: {} ,
|
||||||
// 群成员列表
|
// 群成员列表
|
||||||
members: [],
|
members: [],
|
||||||
|
|
||||||
@ -75,8 +74,6 @@ export const useDialogueStore = defineStore('dialogue', {
|
|||||||
|
|
||||||
// 更新对话信息
|
// 更新对话信息
|
||||||
setDialogue(data = {}) {
|
setDialogue(data = {}) {
|
||||||
|
|
||||||
console.log('data',data)
|
|
||||||
this.online = data.is_online == 1
|
this.online = data.is_online == 1
|
||||||
this.talk = {
|
this.talk = {
|
||||||
username: data.remark || data.name,
|
username: data.remark || data.name,
|
||||||
@ -94,6 +91,8 @@ export const useDialogueStore = defineStore('dialogue', {
|
|||||||
this.members = []
|
this.members = []
|
||||||
if (data.talk_type == 2) {
|
if (data.talk_type == 2) {
|
||||||
this.updateGroupMembers()
|
this.updateGroupMembers()
|
||||||
|
this.getGroupInfo()
|
||||||
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -126,7 +125,14 @@ export const useDialogueStore = defineStore('dialogue', {
|
|||||||
unshiftDialogueRecord(records) {
|
unshiftDialogueRecord(records) {
|
||||||
this.records.unshift(...records)
|
this.records.unshift(...records)
|
||||||
},
|
},
|
||||||
|
async getGroupInfo(){
|
||||||
|
const { code, data } = await ServeGroupDetail({
|
||||||
|
group_id: this.talk.receiver_id
|
||||||
|
})
|
||||||
|
if(code == 200){
|
||||||
|
this.groupInfo = data
|
||||||
|
}
|
||||||
|
},
|
||||||
// 推送对话记录
|
// 推送对话记录
|
||||||
addDialogueRecord(record) {
|
addDialogueRecord(record) {
|
||||||
// TOOD 需要通过 sequence 排序,保证消息一致性
|
// TOOD 需要通过 sequence 排序,保证消息一致性
|
||||||
|
@ -93,6 +93,7 @@ export const useTalkStore = defineStore('talk', {
|
|||||||
|
|
||||||
resp.then(({ code, data }) => {
|
resp.then(({ code, data }) => {
|
||||||
if (code == 200) {
|
if (code == 200) {
|
||||||
|
|
||||||
this.items = data.items.map((item: any) => {
|
this.items = data.items.map((item: any) => {
|
||||||
const value = formatTalkItem(item)
|
const value = formatTalkItem(item)
|
||||||
|
|
||||||
@ -104,7 +105,6 @@ export const useTalkStore = defineStore('talk', {
|
|||||||
if (value.is_robot == 1) {
|
if (value.is_robot == 1) {
|
||||||
value.is_online = 1
|
value.is_online = 1
|
||||||
}
|
}
|
||||||
|
|
||||||
return value
|
return value
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -130,3 +130,22 @@ export interface ITalkRecordExtraImage {
|
|||||||
width: number
|
width: number
|
||||||
height: number
|
height: number
|
||||||
}
|
}
|
||||||
|
export interface GroupInfo {
|
||||||
|
avatar: string;
|
||||||
|
created_at: string;
|
||||||
|
deptInfos: any[]; // 如果有具体结构可以进一步细化
|
||||||
|
group_id: number;
|
||||||
|
group_name: string;
|
||||||
|
group_num: number;
|
||||||
|
group_type: number;
|
||||||
|
is_disturb: number;
|
||||||
|
is_last_manager: boolean;
|
||||||
|
is_manager: boolean;
|
||||||
|
is_mute: number;
|
||||||
|
is_overt: number;
|
||||||
|
latest_notice_content: string;
|
||||||
|
latest_notice_title: string;
|
||||||
|
positionInfos: any[]; // 如果有具体结构可以进一步细化
|
||||||
|
profile: string;
|
||||||
|
visit_card: string;
|
||||||
|
};
|
@ -18,7 +18,7 @@ export function isLoggedIn() {
|
|||||||
*/
|
*/
|
||||||
export function getAccessToken() {
|
export function getAccessToken() {
|
||||||
// return storage.get(AccessToken) || ''
|
// return storage.get(AccessToken) || ''
|
||||||
return JSON.parse(localStorage.getItem('token'))||'46d71a72d8d845ad7ed23eba9bdde260e635407190c2ce1bf7fd22088e41682ea07773ec65cae8946d2003f264d55961f96e0fc5da10eb96d3a348c1664e9644ce2108c311309f398ae8ea1b8200bfd490e5cb6e8c52c9e5d493cbabb163368f8351420451a631dbfa749829ee4cda49b77b5ed2d3dced5d0f2b7dd9ee76ba5465c84a17c23af040cd92b6b2a4ea48befbb5c729dcdad0a9c9668befe84074cc24f78899c1d947f8e7f94c7eda5325b8ed698df729e76febb98549ef3482ae942fb4f4a1c92d21836fa784728f0c5483aab2760a991b6b36e6b10c84f840a6433a6ecc31dee36e8f1c6158818bc89d22b9b32c043123b3db4f35a7a79e1bbe97875bfa18428a4f5ed561887bfbfcab3bd61f2f9348af8bdb89da8c35a7a681fe828af1502b58ebc4ffb99f28fe91d5ba4b0245d1eb24a5ccda9be0cd9bef4d01'
|
return JSON.parse(localStorage.getItem('token'))||'79b5c732d96d2b27a48a99dfd4a5566c43aaa5796242e854ebe3ffc198d6876b9628e7b764d9af65ab5dbb2d517ced88170491b74b048c0ba827c0d3741462cb89dc59ed46653a449af837a8262941caaef1334d640773710f8cd96473bacfb190cba595a5d6a9c87d70f0999a3ebb41147213b31b4bdccffca66a56acf3baab5af0154f0dce360079f37709f78e13711036899344bddb0fb4cf0f2890287cb62c3fcbe33368caa5e213624577be8b8420ab75b1f50775ee16142a4321c5d56995f37354a66a969da98d95ba6e65d142ed097e04b411c1ebad2f62866d0ec7e1838420530a9941dbbcd00490199f8b89eb1ea28c6224649ca60080b7243593f7462085111e3bd3868564aa9a65a16e171ba833d4955a4555f3376cb64b66eb2304dafb03f182fe1719d09e84d345954edbf75b17358196e1378893c8c97b56a6'
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -3,14 +3,14 @@ import { isElectronMode } from '@/utils/common'
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div id="logo-name" v-if="!isElectronMode()">Lumen IM</div>
|
<div id="logo-name" v-if="!isElectronMode()"></div>
|
||||||
|
|
||||||
<section class="section">
|
<section class="section">
|
||||||
<router-view />
|
<router-view />
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<div class="copyright">
|
<div class="copyright">
|
||||||
<span>©2020 - 2023 Lumen IM 在线聊天</span>
|
<span>©2020 - 2023 在线聊天</span>
|
||||||
<span><a href="http://beian.miit.gov.cn" target="_blank">黔ICP备20006767号-2</a></span>
|
<span><a href="http://beian.miit.gov.cn" target="_blank">黔ICP备20006767号-2</a></span>
|
||||||
<span>Github源码</span>
|
<span>Github源码</span>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="amicable flex-center">
|
<div class="amicable flex-center">
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<img src="@/assets/image/welcome.svg" alt="" />
|
<img class="w-181px h-149px" src="@/assets/image/zu6146@2x.png" alt="" />
|
||||||
<p>LumenIM 欢迎您 (*^__^*)</p>
|
<p class="text-#999999 text-14px">开启你的聊天之旅</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -13,8 +13,8 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
-webkit-app-region: drag;
|
-webkit-app-region: drag;
|
||||||
.content {
|
.content {
|
||||||
width: 400px;
|
width: 181px;
|
||||||
height: 300px;
|
height: 149px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
color: #ccc;
|
color: #ccc;
|
||||||
margin-top: -10%;
|
margin-top: -10%;
|
||||||
|
@ -258,15 +258,22 @@ const state = reactive({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const items = computed((): ISession[] => {
|
const items = computed((): ISession[] => {
|
||||||
if (searchKeyword.value.length === 0) {
|
let filtered = talkStore.talkItems
|
||||||
return talkStore.talkItems
|
|
||||||
}
|
|
||||||
|
|
||||||
return talkStore.talkItems.filter((item: ISession) => {
|
if (searchKeyword.value.length > 0) {
|
||||||
|
filtered = filtered.filter((item: ISession) => {
|
||||||
let keyword = item.remark || item.name
|
let keyword = item.remark || item.name
|
||||||
|
|
||||||
return keyword.toLowerCase().indexOf(searchKeyword.value.toLowerCase()) != -1
|
return keyword.toLowerCase().indexOf(searchKeyword.value.toLowerCase()) != -1
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 置顶和非置顶分组
|
||||||
|
const topItems = filtered
|
||||||
|
.filter(item => item.is_top === 1)
|
||||||
|
.sort((a, b) => new Date(b.updated_at).getTime() - new Date(a.updated_at).getTime())
|
||||||
|
const normalItems = filtered.filter(item => item.is_top !== 1)
|
||||||
|
|
||||||
|
return [...topItems, ...normalItems]
|
||||||
})
|
})
|
||||||
watch(
|
watch(
|
||||||
() => talkStore,
|
() => talkStore,
|
||||||
|
@ -19,7 +19,7 @@ const labelColor=[
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="talk pointer" :class="{ actived: active }" @click="emit('tab-talk', data)">
|
<div :class="`talk pointer ${data.is_top === 1 ? 'bg-#F3F3F3' : ''} ${active ? 'actived' : ''}`" @click="emit('tab-talk', data)">
|
||||||
<div class="avatar-box relative">
|
<div class="avatar-box relative">
|
||||||
|
|
||||||
<avatarModule showGroupType :mode="data?.group_type === 0 ? 1 : 2"
|
<avatarModule showGroupType :mode="data?.group_type === 0 ? 1 : 2"
|
||||||
@ -57,11 +57,13 @@ const labelColor=[
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="tip">
|
<div class="tip">
|
||||||
<div v-if="data.is_disturb" class="disturb">
|
<div v-if="data.is_disturb" class="disturb flex justify-center items-center">
|
||||||
<!-- <n-icon :component="CloseRemind" /> -->
|
<!-- <n-icon :component="CloseRemind" /> -->
|
||||||
|
|
||||||
<span class="badge">
|
<span class="badge w-50px">
|
||||||
{{ data.unread_num > 99 ? '99+' : data.unread_num }}
|
<!-- {{ data.unread_num > 99 ? '99+' : data.unread_num }} -->
|
||||||
|
<img src="@/assets/image/xxxx@2x.png" class="w-11.1px h-13px mr-6px" alt="">
|
||||||
|
<span v-if="data.unread_num>0" class="w-10px h-10px bg-#D03050 rounded-50%"></span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -193,7 +195,6 @@ const labelColor=[
|
|||||||
display: flex;
|
display: flex;
|
||||||
padding-left: 5px;
|
padding-left: 5px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
.unread {
|
.unread {
|
||||||
color: #8f959e;
|
color: #8f959e;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
@ -216,7 +217,7 @@ const labelColor=[
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
--actived-bg: #ececec;
|
--actived-bg: #EEE9F8;
|
||||||
|
|
||||||
&:hover,
|
&:hover,
|
||||||
&.actived {
|
&.actived {
|
||||||
|
@ -39,20 +39,20 @@ const onMultiDelete = () => {
|
|||||||
dialogueStore.ApiDeleteRecord(msgIds)
|
dialogueStore.ApiDeleteRecord(msgIds)
|
||||||
}
|
}
|
||||||
|
|
||||||
const onContactModal = (data: { id: number; type: number }[]) => {
|
const onContactModal = (data: { receiver_id: number; talk_type: number }[]) => {
|
||||||
let msg_ids = dialogueStore.selectItems.map((item: any) => item.msg_id)
|
let msg_ids = dialogueStore.selectItems.map((item: any) => item.msg_id)
|
||||||
|
|
||||||
let user_ids: number[] = []
|
let user_ids: number[] = []
|
||||||
let group_ids: number[] = []
|
let group_ids: number[] = []
|
||||||
|
|
||||||
for (let o of data) {
|
for (let o of data) {
|
||||||
if (o.type == 1) {
|
if (o.talk_type == 1) {
|
||||||
user_ids.push(o.id)
|
user_ids.push(o.receiver_id)
|
||||||
} else {
|
} else {
|
||||||
group_ids.push(o.id)
|
group_ids.push(o.receiver_id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
console.log('user_ids',user_ids)
|
||||||
dialogueStore.ApiForwardRecord({
|
dialogueStore.ApiForwardRecord({
|
||||||
mode: forwardMode.value,
|
mode: forwardMode.value,
|
||||||
message_ids: msg_ids,
|
message_ids: msg_ids,
|
||||||
|
@ -227,6 +227,7 @@ const onClickNickname = (data: ITalkRecord) => {
|
|||||||
|
|
||||||
// 会话列表右键显示菜单
|
// 会话列表右键显示菜单
|
||||||
const onContextMenu = (e: any, item: ITalkRecord) => {
|
const onContextMenu = (e: any, item: ITalkRecord) => {
|
||||||
|
console.log('item',item)
|
||||||
if (!dialogueStore.isShowEditor || dialogueStore.isOpenMultiSelect) {
|
if (!dialogueStore.isShowEditor || dialogueStore.isOpenMultiSelect) {
|
||||||
return e.preventDefault()
|
return e.preventDefault()
|
||||||
}
|
}
|
||||||
@ -313,7 +314,7 @@ onMounted(() => {
|
|||||||
'multi-select-check': item.isCheck
|
'multi-select-check': item.isCheck
|
||||||
}">
|
}">
|
||||||
<!-- 多选按钮 -->
|
<!-- 多选按钮 -->
|
||||||
<aside v-if="dialogueStore.isOpenMultiSelect" class="checkbox-column">
|
<aside v-if="dialogueStore.isOpenMultiSelect" class="checkbox-column shrink-0">
|
||||||
<n-checkbox size="small" :checked="item.isCheck" @update:checked="item.isCheck = !item.isCheck" />
|
<n-checkbox size="small" :checked="item.isCheck" @update:checked="item.isCheck = !item.isCheck" />
|
||||||
</aside>
|
</aside>
|
||||||
<!-- 头像信息 -->
|
<!-- 头像信息 -->
|
||||||
|
@ -7,6 +7,7 @@ const dialogueStore = useDialogueStore()
|
|||||||
|
|
||||||
// 聊天版本滚动到底部
|
// 聊天版本滚动到底部
|
||||||
const onSkipBottom = () => {
|
const onSkipBottom = () => {
|
||||||
|
console.log('onSkipBottom')
|
||||||
let el = document.getElementById('imChatPanel')
|
let el = document.getElementById('imChatPanel')
|
||||||
if (el) {
|
if (el) {
|
||||||
el.scrollTo({
|
el.scrollTo({
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { reactive } from 'vue'
|
import { reactive } from 'vue'
|
||||||
|
import { useDialogueStore } from '@/store/modules/dialogue.js'
|
||||||
|
|
||||||
interface IDropdown {
|
interface IDropdown {
|
||||||
options: any[]
|
options: any[]
|
||||||
@ -19,7 +20,7 @@ const isRevoke = (uid: any, item: any): boolean => {
|
|||||||
|
|
||||||
return Math.floor(time / 1000 / 60) <= 2
|
return Math.floor(time / 1000 / 60) <= 2
|
||||||
}
|
}
|
||||||
|
const dialogueStore = useDialogueStore()
|
||||||
export function useMenu() {
|
export function useMenu() {
|
||||||
const dropdown: IDropdown = reactive({
|
const dropdown: IDropdown = reactive({
|
||||||
options: [],
|
options: [],
|
||||||
@ -47,20 +48,20 @@ export function useMenu() {
|
|||||||
|
|
||||||
dropdown.options.push({ label: '多选', key: 'multiSelect' })
|
dropdown.options.push({ label: '多选', key: 'multiSelect' })
|
||||||
dropdown.options.push({ label: '引用', key: 'quote' })
|
dropdown.options.push({ label: '引用', key: 'quote' })
|
||||||
if (isRevoke(uid, item)) {
|
if (isRevoke(uid, item)|| (dialogueStore.groupInfo as any).is_manager) {
|
||||||
dropdown.options.push({ label: `撤回`, key: 'revoke' })
|
dropdown.options.push({ label: `撤回`, key: 'revoke' })
|
||||||
}
|
}
|
||||||
dropdown.options.push({ label: '删除', key: 'delete' })
|
dropdown.options.push({ label: '删除', key: 'delete' })
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if ([3, 4, 5].includes(item.msg_type)) {
|
// if ([3, 4, 5].includes(item.msg_type)) {
|
||||||
dropdown.options.push({ label: '下载', key: 'download' })
|
// dropdown.options.push({ label: '下载', key: 'download' })
|
||||||
}
|
// }
|
||||||
|
|
||||||
if ([3].includes(item.msg_type)) {
|
// if ([3].includes(item.msg_type)) {
|
||||||
dropdown.options.push({ label: '收藏', key: 'collect' })
|
// dropdown.options.push({ label: '收藏', key: 'collect' })
|
||||||
}
|
// }
|
||||||
|
|
||||||
|
|
||||||
dropdown.x = e.clientX
|
dropdown.x = e.clientX
|
||||||
|
116
src/views/office/index.vue
Normal file
116
src/views/office/index.vue
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
<template>
|
||||||
|
<DocumentEditor
|
||||||
|
id="docEditor"
|
||||||
|
:documentServerUrl="documentServerUrl"
|
||||||
|
:config="config"
|
||||||
|
:events_onDocumentReady="onDocumentReady"
|
||||||
|
:onLoadComponentError="onLoadComponentError"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, onMounted } from 'vue'
|
||||||
|
import { useRoute } from 'vue-router'
|
||||||
|
import { DocumentEditor } from "@onlyoffice/document-editor-vue"
|
||||||
|
|
||||||
|
const documentServerUrl = 'https://onlyoffice.fontree.cn'
|
||||||
|
const route = useRoute()
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
// 动态插入 Content-Security-Policy meta 标签,只在本页面生效
|
||||||
|
if (!document.querySelector('meta[http-equiv="Content-Security-Policy"]')) {
|
||||||
|
const meta = document.createElement('meta')
|
||||||
|
meta.httpEquiv = 'Content-Security-Policy'
|
||||||
|
meta.content = 'upgrade-insecure-requests'
|
||||||
|
document.head.appendChild(meta)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 判断文件类型
|
||||||
|
function getDocumentTypes(url) {
|
||||||
|
const extension = url.split('.').pop().toLowerCase()
|
||||||
|
const types = {
|
||||||
|
'docx': { fileType: 'docx', documentType: 'word' },
|
||||||
|
'doc': { fileType: 'doc', documentType: 'word' },
|
||||||
|
'xlsx': { fileType: 'xlsx', documentType: 'cell' },
|
||||||
|
'xls': { fileType: 'xls', documentType: 'cell' },
|
||||||
|
'pptx': { fileType: 'pptx', documentType: 'slide' },
|
||||||
|
'ppt': { fileType: 'ppt', documentType: 'slide' },
|
||||||
|
'pdf': { fileType: 'pdf', documentType: 'word' }
|
||||||
|
}
|
||||||
|
return types[extension] || { fileType: 'docx', documentType: 'word' }
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = route.query.url
|
||||||
|
if (!url) {
|
||||||
|
alert('请提供文档 URL 参数')
|
||||||
|
}
|
||||||
|
const fileName = url ? url.split('/').pop() : ''
|
||||||
|
const { fileType, documentType } = getDocumentTypes(url || '')
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
document: {
|
||||||
|
fileType,
|
||||||
|
key: 'doc_' + Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15),
|
||||||
|
title: fileName,
|
||||||
|
url
|
||||||
|
},
|
||||||
|
documentType,
|
||||||
|
editorConfig: {
|
||||||
|
|
||||||
|
mode: 'view',
|
||||||
|
lang: 'zh-CN',
|
||||||
|
user: {
|
||||||
|
id: 'user_' + new Date().getTime(),
|
||||||
|
name: '访客用户'
|
||||||
|
},
|
||||||
|
customization: {
|
||||||
|
hideRightMenu: true, // 隐藏右侧菜单
|
||||||
|
about: false, // 不显示“关于”页面
|
||||||
|
help: false, // 不显示帮助菜单
|
||||||
|
chat: false,
|
||||||
|
commentAuthorOnly: false,
|
||||||
|
compactToolbar: true,
|
||||||
|
hideRightMenu: false,
|
||||||
|
compatibility: true,
|
||||||
|
showReviewChanges: false,
|
||||||
|
loaderLogo: '', // 设置为空字符串来隐藏加载 logo
|
||||||
|
logo: {
|
||||||
|
image: '', // 设置为空字符串
|
||||||
|
imageDark: '', // 设置为空字符串
|
||||||
|
url: '', // 设置为空字符串
|
||||||
|
visible: false // 设置为 false 来隐藏 logo
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onDocumentReady = () => {
|
||||||
|
console.log("文档加载完成")
|
||||||
|
}
|
||||||
|
|
||||||
|
const onLoadComponentError = (errorCode, errorDescription) => {
|
||||||
|
switch(errorCode) {
|
||||||
|
case -1: // 加载组件时发生未知错误
|
||||||
|
console.log(errorDescription)
|
||||||
|
break
|
||||||
|
case -2: // 从文档服务器加载 DocsAPI 时发生错误
|
||||||
|
console.log(errorDescription)
|
||||||
|
break
|
||||||
|
case -3: // DocsAPI 未定义
|
||||||
|
console.log(errorDescription)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
iframe[name="frameEditor"] {
|
||||||
|
width: 100% !important;
|
||||||
|
height: 100vh !important;
|
||||||
|
min-height: 100vh !important;
|
||||||
|
border: none !important;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -39,5 +39,5 @@
|
|||||||
"src/**/*.tsx",
|
"src/**/*.tsx",
|
||||||
"src/**/*.vue",
|
"src/**/*.vue",
|
||||||
"assets/**/*.jpg"
|
"assets/**/*.jpg"
|
||||||
],
|
, "src/store/modules/dialogue.js" ],
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user