新增OnlyOffice文档编辑器和Fluent图标库依赖,优化文件消息组件的下载功能,调整文本消息背景色,改进会话菜单逻辑,优化会话列表排序,修复部分样式问题。

This commit is contained in:
Phoenix 2025-05-21 19:57:07 +08:00
parent 91107e2f85
commit 0fe1119789
13 changed files with 152 additions and 91 deletions

View File

@ -18,6 +18,8 @@
"@highlightjs/vue-plugin": "^2.1.0",
"@iconify-json/ion": "^1.2.3",
"@kangc/v-md-editor": "^2.3.18",
"@onlyoffice/document-editor-vue": "^1.5.0",
"@vicons/fluent": "^0.13.0",
"@vicons/ionicons5": "^0.13.0",
"@vueup/vue-quill": "^1.2.0",
"@vueuse/core": "^10.7.0",

View File

@ -20,6 +20,12 @@ importers:
'@kangc/v-md-editor':
specifier: ^2.3.18
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':
specifier: ^0.13.0
version: 0.13.0
@ -594,6 +600,11 @@ packages:
resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
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':
resolution: {integrity: sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==}
engines: {node: '>= 10.0.0'}
@ -988,6 +999,9 @@ packages:
peerDependencies:
vue: ^3.0.0
'@vicons/fluent@0.13.0':
resolution: {integrity: sha512-bYGZsOE3qzvm3Cm43e7tybgGlr5ZUpYqtRZq0g0Tfupe8jIzLolpvQLNUt1zS8Mgt6goTbUk5YH7Fkv16jkykg==}
'@vicons/ionicons5@0.13.0':
resolution: {integrity: sha512-zvZKBPjEXKN7AXNo2Na2uy+nvuv6SP4KAMQxpKL2vfHMj0fSvuw7JZcOPCjQC3e7ayssKnaoFVAhbYcW6v41qQ==}
@ -4012,6 +4026,11 @@ snapshots:
'@nodelib/fs.scandir': 2.1.5
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':
optional: true
@ -4390,6 +4409,8 @@ snapshots:
dependencies:
vue: 3.5.13(typescript@5.2.2)
'@vicons/fluent@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))':

View File

@ -206,7 +206,7 @@ textarea {
border-radius: 2px;
cursor: default;
user-select: none;
background-color: #dee0e3;
transform: scale(0.84);
transform-origin: left;
flex-shrink: 0;

BIN
src/assets/image/dofd.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 396 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 684 B

View File

@ -7,7 +7,8 @@ import excelText from '@/assets/image/excel-text.png'
import wordText from '@/assets/image/word-text.png'
import pdfText from '@/assets/image/pdf-text.png'
import fileText from '@/assets/image/file-text.png'
import { ArrowDownload16Filled } from '@vicons/fluent'
import { download } from '@/utils/functions.js'
//
const props = defineProps({
//
@ -92,6 +93,20 @@ const handleClick = () => {
'width=1200,height=900,left=200,top=200,toolbar=no,menubar=no,scrollbars=yes,resizable=yes,location=no,status=no'
);
}
//
const handleDownload = () => {
download(props.data.msg_id)
// const url = props.extra.path;
// if (!url) return;
// const a = document.createElement('a');
// a.href = url;
// a.download = url.split('/').pop() || 'download';
// document.body.appendChild(a);
// a.click();
// document.body.removeChild(a);
console.log('data',props.data)
}
</script>
<template>
@ -145,7 +160,14 @@ const handleClick = () => {
</div>
</div>
<!-- 文件大小信息 -->
<div class="file-size">{{ fileFormatSize(extra.size) }}</div>
<div class="flex justify-between items-center">
<div class="file-size">{{ fileFormatSize(extra.size) }}</div>
<div class="flex items-center">
<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>
</template>

View File

@ -43,7 +43,7 @@ textContent = textReplaceEmoji(textContent)
min-height: 30px;
padding: 3px;
color: var(--im-message-left-text-color);
background: var(--im-message-left-bg-color);
background: #F4F4FC;
border-radius: 0px 10px 10px 10px;
font-size: 14px;
&.right {

View File

@ -1,17 +1,5 @@
import { reactive, nextTick, computed, h, inject } from 'vue'
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 { useDialogueStore, useTalkStore } from '@/store'
import { ServeSecedeGroup } from '@/api/group'
@ -52,45 +40,45 @@ export function useSessionMenu() {
if (item.talk_type == 1) {
options.push({
icon: renderIcon(IdCard),
label: '好友信息',
key: 'info'
})
options.push({
icon: renderIcon(EditTwo),
label: '修改备注',
key: 'remark'
})
}
options.push({
icon: renderIcon(item.is_top ? ArrowDown : ArrowUp),
label: item.is_top ? '取消置顶' : '会话置顶',
key: 'top'
})
options.push({
icon: renderIcon(item.is_disturb ? Remind : CloseRemind),
label: item.is_disturb ? '关闭免打扰' : '开启免打扰',
key: 'disturb'
})
options.push({
icon: renderIcon(Clear),
label: '移除会话',
key: 'remove'
})
if (item.talk_type == 1) {
options.push({
icon: renderIcon(Delete),
label: '删除好友',
key: 'delete_contact'
})
} else {
options.push({
icon: renderIcon(Logout),
label: '退出群聊',
key: 'signout_group'
})

View File

@ -93,6 +93,7 @@ export const useTalkStore = defineStore('talk', {
resp.then(({ code, data }) => {
if (code == 200) {
this.items = data.items.map((item: any) => {
const value = formatTalkItem(item)
@ -104,7 +105,6 @@ export const useTalkStore = defineStore('talk', {
if (value.is_robot == 1) {
value.is_online = 1
}
return value
})

View File

@ -229,15 +229,22 @@ const state = reactive({
})
const items = computed((): ISession[] => {
if (searchKeyword.value.length === 0) {
return talkStore.talkItems
let filtered = talkStore.talkItems
if (searchKeyword.value.length > 0) {
filtered = filtered.filter((item: ISession) => {
let keyword = item.remark || item.name
return keyword.toLowerCase().indexOf(searchKeyword.value.toLowerCase()) != -1
})
}
return talkStore.talkItems.filter((item: ISession) => {
let keyword = item.remark || item.name
//
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 keyword.toLowerCase().indexOf(searchKeyword.value.toLowerCase()) != -1
})
return [...topItems, ...normalItems]
})
watch(
() => talkStore,

View File

@ -19,7 +19,7 @@ const labelColor=[
</script>
<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">
<avatarModule showGroupType :mode="data?.group_type === 0 ? 1 : 2"
@ -57,11 +57,13 @@ const labelColor=[
</div>
<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" /> -->
<span class="badge">
{{ data.unread_num > 99 ? '99+' : data.unread_num }}
<span class="badge w-50px">
<!-- {{ 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>
</div>
@ -193,7 +195,6 @@ const labelColor=[
display: flex;
padding-left: 5px;
align-items: center;
.unread {
color: #8f959e;
font-size: 12px;
@ -216,7 +217,7 @@ const labelColor=[
}
}
--actived-bg: #ececec;
--actived-bg: #EEE9F8;
&:hover,
&.actived {

View File

@ -313,7 +313,7 @@ onMounted(() => {
'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" />
</aside>
<!-- 头像信息 -->

View File

@ -1,23 +1,28 @@
<template>
<div id="office-div" ></div>
<DocumentEditor
id="docEditor"
:documentServerUrl="documentServerUrl"
:config="config"
:events_onDocumentReady="onDocumentReady"
:onLoadComponentError="onLoadComponentError"
/>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { DocumentEditor } from "@onlyoffice/document-editor-vue"
const placeholderRef = ref(null)
const documentServerUrl = 'https://onlyoffice.fontree.cn'
// OnlyOffice API
function loadScript(src) {
return new Promise((resolve, reject) => {
const script = document.createElement('script')
script.type = 'text/javascript'
script.src = src
script.onload = resolve
script.onerror = reject
document.head.appendChild(script)
})
}
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)
}
})
// URL
function getUrlParameter(name) {
@ -42,52 +47,67 @@ function getDocumentTypes(url) {
return types[extension] || { fileType: 'docx', documentType: 'word' }
}
onMounted(async () => {
// 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)
}
await loadScript('https://onlyoffice.fontree.cn/web-apps/apps/api/documents/api.js')
const url = getUrlParameter('url')
if (!url) {
alert('请提供文档 URL 参数')
return
}
const fileName = url.split('/').pop()
const { fileType, documentType } = getDocumentTypes(url)
// eslint-disable-next-line no-undef
new window.DocsAPI.DocEditor('office-div', {
document: {
fileType,
key: 'doc_' + Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15),
title: fileName,
url
const url = getUrlParameter('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: '访客用户'
},
documentType,
editorConfig: {
mode: 'view',
lang: 'zh-CN',
user: {
id: 'user_' + new Date().getTime(),
name: '访客用户'
},
customization: {
chat: false,
commentAuthorOnly: false,
compactToolbar: true,
hideRightMenu: false,
compatibility: true,
showReviewChanges: false
customization: {
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"] {
iframe[name="frameEditor"] {
width: 100% !important;
height: 100vh !important;
min-height: 100vh !important;