Browse Source

集成onlyoffice服务

pull/1/head
han2015 5 months ago
parent
commit
79e524044b
  1. 1
      .env.development
  2. 3
      .env.production
  3. 1
      package.json
  4. 13
      src/api/doc/type.ts
  5. 6
      src/router/index.ts
  6. 2
      src/views/doc/manage.vue
  7. 89
      src/views/doc/onlyoffice.vue
  8. 160
      src/views/doc/recentVisited.vue
  9. 90
      src/views/doc/share.vue
  10. 19
      src/views/doc/tools.ts

1
.env.development

@ -10,3 +10,4 @@ VITE_APP_BASE_URL = 'http://myvuetest.net'
VITE_APP_TOKEN_KEY = 'offlineAccessSystemAppToken'
VITE_APP_SJZT_URL = 'http://172.20.5.86/prod-api'
VITE_OFFICE_HOST='http://myvuetest.net/kkapi'
VITE_ONLYOFFICE_HOST = 'http://myvuetest.net/onlyoffice'

3
.env.production

@ -5,4 +5,5 @@ VITE_APP_PORT = 17777
VITE_APP_BASE_API = ''
VITE_APP_TOKEN_KEY = 'onlineAccessSystemAppToken'
VITE_APP_SJZT_URL = 'http://120.224.6.6:29911/prod-api'
VITE_OFFICE_HOST='https://gyhlw.hxgk.group/kkapi'
VITE_OFFICE_HOST='https://gyhlw.hxgk.group/kkapi'
VITE_ONLYOFFICE_HOST = 'https://gyhlw.hxgk.group/onlyoffice'

1
package.json

@ -42,6 +42,7 @@
"dependencies": {
"@dnd-kit/core": "^6.1.0",
"@element-plus/icons-vue": "^2.3.1",
"@onlyoffice/document-editor-vue": "^1.5.0",
"@tinymce/tinymce-vue": "^5.1.1",
"@vitejs/plugin-vue": "^4.2.3",
"@vueup/vue-quill": "^1.2.0",

13
src/api/doc/type.ts

@ -31,6 +31,19 @@ export interface matterInfo{
expireTime?:string;
}
export interface shareItem{
name?:string;
updateTime?:string;
uuid:string; //文件uuid
code:string;
userUuid?:string; //owner uuid
puuid?:string; // parent dir uuid
path?:string;
expireInfinity?:boolean;
expireTime?:string;
permitList?:string;
}
export interface matterTree extends matterInfo {
children:matterTree[]
}

6
src/router/index.ts

@ -33,6 +33,12 @@ export const constantRoutes: RouteRecordRaw[] = [
meta: { hidden: true },
},
{
path: "/onlyoffice",
component: () => import("@/views/doc/onlyoffice.vue"),
meta: { hidden: true },
},
{
path: "/",
component: Layout,

2
src/views/doc/manage.vue

@ -503,7 +503,7 @@ const handleSelectionChange = (val:matterInfo[]) => {
<el-button size="small" :icon="View" circle @click="onPrivateView(scope.row)"></el-button>
<el-button size="small" :icon="Share" circle @click="onShareMatter(scope.row)"></el-button>
<el-button size="small" :icon="Download" circle @click="onDownload(scope.row)"></el-button>
<el-button size="small" :icon="Edit" circle @click="onMatterRename(scope.row)"></el-button>
<!-- <el-button size="small" :icon="Edit" circle @click="onMatterRename(scope.row)"></el-button> -->
<el-button size="small" :icon="Delete" circle @click="onDelMatter(scope.row)"></el-button>
</div>
</template>

89
src/views/doc/onlyoffice.vue

@ -0,0 +1,89 @@
<script lang="ts" setup>
import { useRoute } from 'vue-router'
import { DocumentEditor } from "@onlyoffice/document-editor-vue";
import { useUserStore } from "@/store/modules/user";
const route = useRoute()
const userStore = useUserStore();
const siteHost=document.location.origin;
const apiURL=import.meta.env.VITE_APP_BASE_API+"/hxpan/api"
const onlyOfficeHost= import.meta.env.VITE_ONLYOFFICE_HOST
const props = withDefaults(defineProps<{
fileurl:string
}>(),{})
const config=ref({
document: {
title: "Example Document Title.docx",
url:"", //
key:""
},
documentType: "word",
editorConfig: {
"lang": "zh-CN",//"en-US",
mode: "view",//view, editreview
callbackUrl: `${siteHost}${apiURL}/matter/save`, //
user: {
id: userStore.userInfoCont.number,
name: userStore.userInfoCont.nickname,
},
}
})
function onLoadComponentError (errorCode:number, errorDescription:string) {
switch(errorCode) {
case -1: // Unknown error loading component
alert(errorDescription);
break;
case -2: // Error load DocsAPI from http://documentserver/
alert(errorDescription);
break;
case -3: // DocsAPI is not defined
alert(errorDescription);
break;
}
}
onMounted(()=>{
const query = route.query
if (query.fileurl){
const info=query.info?.toString()??"error"
const _info=decodeURIComponent(atob(info))
const _url=decodeURIComponent(query.fileurl)
const name=query.name?.toString()??""
const dtype=query.dtype?.toString()??"word"
//
if (_info.includes(name)){
config.value.document.url=_url
config.value.document.title=name
const _key=_url.match(/(\w+-\w+-\w+)/)![0]
if(_key) config.value.document.key=_key //unique key
config.value.documentType=dtype
config.value.editorConfig.callbackUrl+=`?info=${_info}`
//
if(!query.verify) return;
const _verify = atob(query.verify)
//1true 2uuid
if(_verify.endsWith("true") && _key.includes(_verify.replace("true",""))){
config.value.editorConfig.mode="edit"
}
}
}
})
</script>
<template>
<DocumentEditor
id="docEditor"
style="height: inherit;"
:documentServerUrl="onlyOfficeHost"
:config="config"
:onLoadComponentError="onLoadComponentError"
/>
</template>
<style>
</style>

160
src/views/doc/recentVisited.vue

@ -0,0 +1,160 @@
<script lang="ts" setup>
import {
Document,
Delete,
Edit,
View,
} from '@element-plus/icons-vue'
import { shareItem} from "@/api/doc/type"
import {fileType } from "./tools"
const apiURL=import.meta.env.VITE_APP_BASE_API+"/hxpan/api"
const siteHost=document.location.origin;
const officeHost=import.meta.env.VITE_OFFICE_HOST
const currentHoverRow=ref("") //table
const visitList=ref<shareItem[]>([])
let _db:Object; //indexedDB
const props = withDefaults(defineProps<{
uid:string,
udprt:string,
}>(),{})
//线
function onlyOfficeView(row:shareItem){
const _type=fileType(row.name!)
if(_type!==""){ //office file
const info =btoa(encodeURIComponent(`${row.userUuid}/root${row.matters[0].path}`))
const _url=`${siteHost}${apiURL}/share/zip?shareUuid=${row.uuid}&code=${row.code}&uid=${props.uid}&dprt=${props.udprt}&puuid=root&rootUuid=root`
window.open(`/#/onlyoffice?name=${row.name}&dtype=${_type}&info=${info}&fileurl=`+encodeURIComponent(_url),"_blank")
}else{
//by kkFilePreview
let a = row.name ?? '';
if(a.endsWith('...')){
a=`${row.uuid}-${row.code}.zip`
}
//
a=a.match("(\.[a-zA-Z]+)$")
if (a && a.length>0) {
a=`${row.uuid}-${row.code}${a[0]}`
}else{
a=`${row.uuid}${row.name}`
}
let _url=`${siteHost}${apiURL}/share/zip?shareUuid=${row.uuid}&code=${row.code}&uid=${props.uid}&dprt=${props.udprt}&puuid=root&rootUuid=root&fullfilename=${a}`
window.open(`${officeHost}/kkpreview/onlinePreview?url=`+window.btoa(encodeURIComponent(_url)),"_blank")
}
}
//线
function onlyOfficeEdit(row:shareItem){
const _type=fileType(row.name!)
if(_type===""){
alert("暂不支持该类型编辑")
return
}
const pstate=(row.permitList?.includes(props.uid)|| row.userUuid===atob(props.uid))
if (!pstate){
alert("你没有权限编辑")
return
}
ElMessageBox.confirm("线上资源有限,确定继续线上编辑吗", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
}).then(()=>{
//office file
//base64 encode for MASK
const _verify = btoa(row.uuid.match(/(\w+-\w+)/)[0]+`${pstate}`) //
const info =btoa(encodeURIComponent(`${row.userUuid}/root${row.matters[0].path}`))
const _url=`${siteHost}${apiURL}/share/zip?shareUuid=${row.uuid}&code=${row.code}&uid=${props.uid}&dprt=${props.udprt}&puuid=root&rootUuid=root`
window.open(`/#/onlyoffice?name=${row.name}&dtype=${_type}&info=${info}&verify=${_verify}&fileurl=`+encodeURIComponent(_url),"_blank")
})
}
//indexedDB
function onClearHistory(row:shareItem){
_db.transaction('vlist','readwrite').objectStore("vlist").delete(row.uuid);
visitList.value=visitList.value.filter((item)=>{
return item.uuid!==row.uuid
})
}
function handleMouseEnter(row:any){
currentHoverRow.value=row.uuid
}
onMounted(()=>{
const request = indexedDB.open('visitList')
request.onupgradeneeded = (event) => {
//
_db = event.target.result;
if (!_db.objectStoreNames.contains('vlist')) {
//
const store = _db.createObjectStore('vlist', {
keyPath: 'uuid', //
});
}
};
request.onsuccess = (e) => {
if (!_db) _db= e.target.result;
const store = _db.transaction('vlist', 'readonly').objectStore("vlist");
store.openCursor().onsuccess = (event) => {
const cursor = event.target.result;
if (cursor) {
visitList.value.push(cursor.value);
cursor.continue();
}
};
};
})
onUnmounted(()=>{
if (_db) _db.close()
})
</script>
<template>
<el-row :gutter="24">
<el-table
stripe
:data="visitList"
:header-cell-style="{ background: '#f5f8fd' }"
style="width: 100%"
row-key="uuid"
:row-style ="() => ({ height: '30px' })"
@cell-mouse-enter="handleMouseEnter">
<el-table-column style="width: 70%;" property="name" label="最近访问">
<template #default="scope">
<div style="display: flex; align-items: center">
<el-icon :size="20"><component :is="Document" /></el-icon>
<span style="margin-left: 10px">{{scope.row.name }}</span>
</div>
</template>
</el-table-column>
<el-table-column width="250" align="center">
<template #default="scope">
<div v-show="currentHoverRow === scope.row.uuid">
<el-button size="small" :icon="View" circle @click="onlyOfficeView(scope.row)"></el-button>
<el-button size="small" :icon="Edit" circle @click="onlyOfficeEdit(scope.row)"></el-button>
<el-button size="small" :icon="Delete" circle @click="onClearHistory(scope.row)"></el-button>
</div>
</template>
</el-table-column>
<el-table-column width="100" prop="expireTime" label="失效日期">
<template #default="scope">
<span v-if="scope.row.expireTime">{{ scope.row.expireInfinity? '永久有效': scope.row.expireTime.slice(0,16) }}</span>
</template>
</el-table-column>
</el-table>
</el-row>
</template>
<style>
</style>

90
src/views/doc/share.vue

@ -14,14 +14,16 @@ import { h } from 'vue'
import {
Delete,
Folder,
Edit,
Share,
Avatar,
View,
} from '@element-plus/icons-vue'
import {ElText } from "element-plus";
import {getFileIcon,checkExpirTime } from "./tools"
import {ElText,ElButton } from "element-plus";
import {getFileIcon,checkExpirTime,fileType } from "./tools"
import sharePermission from './sharePermission.vue';
import preview from './preview.vue';
import recentVisited from './recentVisited.vue';
const route = useRoute()
const userStore = useUserStore();
@ -40,7 +42,6 @@ import type { VNode } from 'vue'
const dynamicVNode = ref<VNode | null>(null) //permission
function showShareMessage(row:{uuid:string,code:string,name:string,expireTime:string}){
let _shareURL=`${siteHost}/#/doc/share?uuid=${row.uuid}&code=${row.code}`
ElMessageBox({
@ -71,6 +72,7 @@ function showShareMessage(row:{uuid:string,code:string,name:string,expireTime:st
confirmButtonText: '复制分享链接',
showCancelButton: true
}).then(()=>{
if(!navigator.clipboard) alert("clipboard 不可用")
navigator.clipboard.writeText(_shareURL)
}).catch(() => {
if (browerMode.value){
@ -113,23 +115,59 @@ function onShareMember(row:matterInfo){
//
function onShareView(row:matterInfo){
let a = row.name ?? '';
if(a.endsWith('...')){
a=`${row.uuid}-${row.code}.zip`
}
//
a=a.match("(\.[a-zA-Z]+)$")
if (a && a.length>0) {
a=`${row.uuid}-${row.code}${a[0]}`
const _type=fileType(row.name!)
if(_type!==""){ //office file
const info =btoa(encodeURIComponent(`${row.name}`)) //
const _url=`${siteHost}${apiURL}/share/zip?shareUuid=${row.uuid}&code=${row.code}&uid=${uid}&dprt=${udprt}&puuid=root&rootUuid=root`
window.open(`/#/onlyoffice?name=${row.name}&dtype=${_type}&info=${info}&fileurl=`+encodeURIComponent(_url),"_blank")
}else{
a=`${row.uuid}${row.name}`
//by kkFilePreview
let a = row.name ?? '';
if(a.endsWith('...')){
a=`${row.uuid}-${row.code}.zip`
}
//
a=a.match("(\.[a-zA-Z]+)$")
if (a && a.length>0) {
a=`${row.uuid}-${row.code}${a[0]}`
}else{
a=`${row.uuid}${row.name}`
}
let _url=`${siteHost}${apiURL}/share/zip?shareUuid=${row.uuid}&code=${row.code}&uid=${uid}&dprt=${udprt}&puuid=root&rootUuid=root&fullfilename=${a}`
//window.open(`${officeHost}/kkpreview/onlinePreview?url=`+window.btoa(encodeURIComponent(_url)),"_blank")
dynamicVNode.value=h(preview,{
url:`${officeHost}/kkpreview/onlinePreview?url=`+window.btoa(encodeURIComponent(_url)),
closeFunc:()=>dynamicVNode.value=null
})
}
}
let _url=`${siteHost}${apiURL}/share/zip?shareUuid=${row.uuid}&code=${row.code}&uid=${uid}&dprt=${udprt}&puuid=root&rootUuid=root&fullfilename=${a}`
dynamicVNode.value=h(preview,{
url:`${officeHost}/kkpreview/onlinePreview?url=`+window.btoa(unescape(encodeURIComponent(_url))),
closeFunc:()=>dynamicVNode.value=null
//onlyoffice线
async function onlyOfficeEdit(row:matterInfo){
const _type=fileType(row.name!)
if(_type===""){
alert("暂不支持该类型编辑")
return
}
let filepath=""
await getShareBrowse("",{shareUuid:row.uuid,code:row.code,puuid:'root',rootUuid:'root',dprt:udprt}).then((resp)=>{
console.log(resp.data,"<<<<<<<<<<<")
filepath= resp.data.matters[0].path
})
ElMessageBox.confirm("线上资源有限,确定继续线上编辑吗", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
}).then(()=>{
//office file
//base64 encode for MASK
const _verify = btoa(row.uuid.match(/(\w+-\w+)/)![0]+"true") //
const info =btoa(encodeURIComponent(`${row.userUuid}/root${filepath}`)) //
const _url=`${siteHost}${apiURL}/share/zip?shareUuid=${row.uuid}&code=${row.code}&uid=${uid}&dprt=${udprt}&puuid=root&rootUuid=root`
window.open(`/#/onlyoffice?name=${row.name}&dtype=${_type}&info=${info}&verify=${_verify}&fileurl=`+encodeURIComponent(_url),"_blank")
})
}
@ -164,9 +202,17 @@ onMounted(() => {
browerMode.value=true
getShareBrowse("",{shareUuid:query.uuid,code:query.code,puuid:'root',rootUuid:'root',dprt:udprt}).then((resp)=>{
showShareMessage(resp.data)
return
const request = indexedDB.open('visitList')
request.onsuccess = (e) => {
const db = e.target.result;
const store = db.transaction('vlist','readwrite').objectStore("vlist");
store.put(resp.data)
db.close()
}
})
return
}
browerMode.value=false
onLoadShareList()
})
@ -175,6 +221,7 @@ onMounted(() => {
<template>
<div class="app_container">
<el-row :gutter="24" v-if="!browerMode">
<recentVisited :uid="uid" :udprt="udprt" style="margin-left:0;width: 100%;margin-bottom: 10px;"></recentVisited>
<el-table
stripe
:data="matterList"
@ -183,7 +230,7 @@ onMounted(() => {
row-key="uuid"
:row-style ="() => ({ height: '55px' })"
@cell-mouse-enter="handleMouseEnter">
<el-table-column style="width: 70%;" property="name" label="文件名">
<el-table-column style="width: 80%;" property="name" label="我的文件分享">
<template #default="scope">
<div style="display: flex; align-items: center">
<el-icon :size="26">
@ -197,6 +244,7 @@ onMounted(() => {
<el-table-column width="250" align="center">
<template #default="scope">
<div v-show="currentHoverRow === scope.row.uuid">
<el-button size="small" :icon="Edit" circle @click="onlyOfficeEdit(scope.row)"></el-button>
<el-button size="small" :icon="Avatar" circle @click="()=>{drawerModel=true; permitListRef=scope.row.permitInfos}"></el-button>
<el-button size="small" :icon="View" circle @click="onShareView(scope.row)"></el-button>
<el-button size="small" :icon="Share" circle @click="showShareMessage(scope.row)"></el-button>
@ -205,7 +253,7 @@ onMounted(() => {
</template>
</el-table-column>
<el-table-column prop="expireTime" label="失效日期">
<el-table-column width="100" prop="expireTime" label="失效日期">
<template #default="scope">
<span v-if="scope.row.expireTime">{{ scope.row.expireInfinity? '永久有效': scope.row.expireTime.slice(0,16) }}</span>
</template>
@ -234,7 +282,7 @@ onMounted(() => {
</ul>
<span v-else-if="permitListRef==''"> 所有人员都可访问</span>
</el-drawer>
<div v-if="dynamicVNode">
<div v-if="dynamicVNode" style="height: inherit;">
<component :is="dynamicVNode" />
</div>
</template>

19
src/views/doc/tools.ts

@ -78,7 +78,7 @@ export const getFileIcon = (fileName:string) => {
}
}
export function readableSize(val:matterInfo){
export function readableSize(val:matterInfo){
if(val.size<1024) return "1 Kb"
//1024*1024
if(val.size<1048576) return (val.size/1024).toFixed(1)+"Kb"
@ -88,4 +88,19 @@ export const getFileIcon = (fileName:string) => {
if(val.size<1099511627776) return (val.size/1073741824).toFixed(1)+"Gb"
return "BIG"
}
}
export function fileType(name:string){
const suffix=name.match(/(\.[a-zA-Z]+$)/)[0]
if (".doc, .docx, .html, .md, .txt, .wps, .xml".includes(suffix)){
return "word"
} else if(".csv, .xls, .xlsb, .xlsm, .xlsx".includes(suffix)){
return "cell"
}else if(".ppt, .pptm, .pptx".includes(suffix)){
return "slide "
}else if (suffix===".pdf"){
return "pdf"
}
return ""
}
Loading…
Cancel
Save