8 changed files with 593 additions and 19 deletions
@ -0,0 +1,78 @@ |
|||
import request from '@/utils/request'; |
|||
import { AxiosPromise } from 'axios'; |
|||
import { matterPage,createDir,matterTreeList} from './type'; |
|||
|
|||
/** |
|||
* 获取空间目录文件 |
|||
*/ |
|||
export function getSpaceMatterList( uid:string,data?: matterPage): AxiosPromise<matterTreeList> { |
|||
return request({ |
|||
url: '/hxpan/api/space/page', |
|||
method: 'post', |
|||
headers: { |
|||
'Identifier':uid, |
|||
'Content-Type': 'application/x-www-form-urlencoded' |
|||
}, |
|||
data: data |
|||
}); |
|||
} |
|||
|
|||
/** |
|||
* 新建目录 |
|||
*/ |
|||
export function doCreateSpaceDir(uid:string,data?: createDir){ |
|||
return request({ |
|||
url: '/hxpan/api/space/directory', |
|||
method: 'post', |
|||
headers: { |
|||
'Identifier':uid, |
|||
'Content-Type': 'application/x-www-form-urlencoded' |
|||
}, |
|||
data: data |
|||
}); |
|||
} |
|||
|
|||
/** |
|||
* 空间权限控制管理 |
|||
*/ |
|||
export function doAccessManage(uid:string,data?: any){ |
|||
return request({ |
|||
url: '/hxpan/api/space/access', |
|||
method: 'post', |
|||
headers: { |
|||
'Identifier':uid, |
|||
'Content-Type': 'application/x-www-form-urlencoded' |
|||
}, |
|||
data: data |
|||
}); |
|||
} |
|||
|
|||
/** |
|||
* 删除空间 |
|||
*/ |
|||
export function doDelSpace(uid:string,data?: any){ |
|||
return request({ |
|||
url: '/hxpan/api/space/delete', |
|||
method: 'post', |
|||
headers: { |
|||
'Identifier':uid, |
|||
'Content-Type': 'application/x-www-form-urlencoded' |
|||
}, |
|||
data: data |
|||
}); |
|||
} |
|||
|
|||
/** |
|||
* 删除文件或目录 |
|||
*/ |
|||
export function doDelSpaceMatter(uid:string,data?: any){ |
|||
return request({ |
|||
url: '/hxpan/api/space/materdelete', |
|||
method: 'post', |
|||
headers: { |
|||
'Identifier':uid, |
|||
'Content-Type': 'application/x-www-form-urlencoded' |
|||
}, |
|||
data: data |
|||
}); |
|||
} |
|||
@ -0,0 +1,376 @@ |
|||
<!-- |
|||
@ 作者: han2015 |
|||
@ 时间: 2025-05-12 15:39:13 |
|||
@ 备注: 文档管理组件 |
|||
--> |
|||
<script lang="ts" setup> |
|||
import { getFileIcon, readableSize,fileType} from "./tools" |
|||
import sharePermission from './sharePermission.vue'; |
|||
import { matterPage,matterInfo} from "@/api/doc/type" |
|||
import { doAccessManage,getSpaceMatterList,doCreateSpaceDir,doDelSpaceMatter,doDelSpace} from "@/api/doc/space" |
|||
import { h, proxyRefs } from 'vue' |
|||
import { |
|||
Delete, |
|||
View, |
|||
Download, |
|||
Plus, |
|||
Edit, |
|||
Promotion, |
|||
Folder, |
|||
} from '@element-plus/icons-vue' |
|||
import {ElMessage,UploadFile,UploadFiles,ElPagination} from "element-plus"; |
|||
import preview from './preview.vue'; |
|||
import router from "@/router"; |
|||
|
|||
const matterList = ref<matterInfo[]>([]) |
|||
const newdir=ref("") //创建新目录时的目录名 |
|||
const currentHoverRow=ref("") //table 行的按钮控制 |
|||
const breadcrumbList=ref<matterInfo[]>([{name:"根目录",uuid:"root", dir:true}]) //面包屑导航 |
|||
|
|||
const selectedValue = ref("sixhour") //分享弹窗的时间变量 |
|||
|
|||
const tabSelected=ref<matterInfo[]>([]) //table组件多选数据维护 |
|||
const currentNode=ref<matterInfo>({}) //打开的路径层次 |
|||
|
|||
const dynamicVNode = ref<VNode | null>(null) //permission 组件的父组件 |
|||
const paginInfo = ref({ page: 0, total: 0 }) |
|||
|
|||
const props = withDefaults(defineProps<{ |
|||
uid:string, //当前用户的uuid,注意已经过base64编码 |
|||
spaceid:string, |
|||
spacename:string, |
|||
officeHost:string, |
|||
siteHost:string, |
|||
owner:string, |
|||
apiURL:string, |
|||
roles:string |
|||
}>(),{}) |
|||
|
|||
watch(props,()=>{ |
|||
onLoadMatterList() |
|||
}) |
|||
|
|||
const uploadFormData = computed(() => { |
|||
return { |
|||
space: props.spaceid, |
|||
puuid: currentNode.value.uuid, // 父目录的uuid,基目录为root |
|||
} |
|||
}); |
|||
const fileList=ref([])//upload files |
|||
|
|||
//--------------权限控制&添加空间成员------------- |
|||
function onAccessManage(){ |
|||
dynamicVNode.value = h(sharePermission, { |
|||
uid: props.uid, |
|||
uuid: "", |
|||
spaceid:props.spaceid, // |
|||
confirmFunc: (_list: string[],_infos:string[]) => { |
|||
// 组织权限数据 |
|||
//_len=_list.length |
|||
let permited = btoa(_list.join("|")) |
|||
doAccessManage(props.uid,{ |
|||
"space":props.spaceid, |
|||
"roles":permited, |
|||
"owner":props.owner, |
|||
"len":_list.length |
|||
}).then(()=>{ |
|||
|
|||
}) |
|||
}, |
|||
closeFunc: () => { |
|||
dynamicVNode.value=null |
|||
} |
|||
}) |
|||
} |
|||
|
|||
//删除空间 |
|||
function onDeleteSpace(){ |
|||
ElMessageBox.confirm(`确认删除空间 ( ${props.spacename}) ? 空间内所有文件将不可恢复!取消则放弃删除操作。`, "警告", { |
|||
confirmButtonText: "确定删除", |
|||
cancelButtonText: "取消", |
|||
type: "warning", |
|||
}).then(()=>{ |
|||
doDelSpace(props.uid,{ |
|||
"space":props.spaceid, |
|||
}).then(()=>{ |
|||
router.replace({ query: { t: Date.now() } }) |
|||
}) |
|||
}) |
|||
} |
|||
|
|||
//---------------------------------------- |
|||
//删除 |
|||
function onDelMatter(row:matterInfo){ |
|||
if (row.uuid){ |
|||
ElMessageBox.confirm(`确认删除( ${row.name}) ?删除后不可恢复!取消则放弃删除操作。`, "警告", { |
|||
confirmButtonText: "确定", |
|||
cancelButtonText: "取消", |
|||
type: "warning", |
|||
}).then(()=>{ |
|||
doDelSpaceMatter(props.uid,{ |
|||
"uuid":row.uuid, |
|||
"space":props.spaceid, |
|||
"roles":props.roles, |
|||
}).then(()=>{ |
|||
currentNode.value.uuid = row.puuid ?? "" |
|||
currentNode.value.name = row.path ? row.path.replace(`/${row.name}`,'').match(/[^/]+$/g)?.pop() :"上级目录" |
|||
onLoadMatterList() |
|||
}) |
|||
}) |
|||
} |
|||
} |
|||
|
|||
function onDownload(row:matterInfo){ |
|||
ElMessageBox.confirm("确认下载此数据项?", "提示", { |
|||
confirmButtonText: "确定", |
|||
cancelButtonText: "取消", |
|||
type: "warning", |
|||
}).then(()=>{ |
|||
if (row.uuid){ |
|||
let _url= props.apiURL+`/space/download/${row.uuid}/${row.name}?space=${props.spaceid}` |
|||
window.open(_url) |
|||
} |
|||
}) |
|||
} |
|||
|
|||
|
|||
//加载目录文件列表 |
|||
function onLoadMatterList(){ |
|||
let _page: matterPage = { |
|||
page: paginInfo.value.page, |
|||
pageSize: 50, |
|||
orderCreateTime: "DESC", |
|||
orderDir: "DESC", |
|||
puuid:currentNode.value.uuid, |
|||
deleted:false, |
|||
space:props.spaceid, |
|||
roles:props.roles, |
|||
}; |
|||
|
|||
getSpaceMatterList(props.uid,_page).then((resp)=>{ |
|||
//page+1 是由于分页的起始index是1,而后端api的分页index起始是0 |
|||
paginInfo.value={total:resp.data.totalPages, page:resp.data.page} |
|||
matterList.value=resp.data.data |
|||
}) |
|||
} |
|||
//----------for dir----------- |
|||
//该函数仅操作前端,为新文件夹命名 |
|||
function createDir(){ |
|||
matterList.value?.unshift({ |
|||
name:"", |
|||
userUuid:props.spaceid, |
|||
puuid:"", |
|||
uuid:"", |
|||
dir:true, |
|||
size:0, |
|||
deleted:false, |
|||
}) |
|||
} |
|||
//该函数为实际创建文件夹的函数 |
|||
function onCreateDir(){ |
|||
doCreateSpaceDir(props.uid,{ |
|||
puuid:currentNode.value.uuid, |
|||
name:newdir.value, |
|||
space:props.spaceid, |
|||
roles:props.roles, |
|||
}).then((resp)=> { |
|||
newdir.value="" |
|||
onLoadMatterList() |
|||
}) |
|||
.catch((e)=>{ |
|||
ElMessage.error(e.msg) |
|||
}) |
|||
} |
|||
//------------------------------------------ |
|||
|
|||
//打开一个目录 |
|||
function handleDoubleClick(row:matterInfo,ind?:number){ |
|||
if(row.dir){ |
|||
//table的双击事件也在此方法处理 |
|||
if(typeof(ind)==="number"){ |
|||
//返回某一级 |
|||
if(breadcrumbList.value.length>1){ |
|||
breadcrumbList.value=breadcrumbList.value.slice(0,ind+1) |
|||
currentNode.value=breadcrumbList.value[breadcrumbList.value.length-1] |
|||
onLoadMatterList() |
|||
} |
|||
}else{ |
|||
//进入下一级 |
|||
currentNode.value=row |
|||
breadcrumbList.value.push(row) |
|||
onLoadMatterList() |
|||
} |
|||
} |
|||
} |
|||
|
|||
function handleMouseEnter(row:any){ |
|||
currentHoverRow.value=row.name |
|||
} |
|||
//上传成功 |
|||
function handleSingleUpload(response:any){ |
|||
fileList.value=[] |
|||
onLoadMatterList() |
|||
} |
|||
interface uploadError{ |
|||
msg:string |
|||
} |
|||
//上传失败 |
|||
function handleSigLoadErr(error: Error, uploadFile: UploadFile, uploadFiles:UploadFiles){ |
|||
ElMessage.error(JSON.parse(error.message).msg) |
|||
} |
|||
//-------------------edit & preive file for space--------------------- |
|||
//文件预览 |
|||
function onPrivateView(row:matterInfo){ |
|||
const _type=fileType(row.name!) |
|||
if(_type!==""){ //office file |
|||
const info =btoa(encodeURIComponent(`${row.name}`)) //预览模式不需要绝对路径,只核对一下文件名即可 |
|||
const _url=`${props.siteHost}${props.apiURL}/space/download/${row.uuid}/${row.name}?space=${props.spaceid}` |
|||
//前半部分内容是为了校验信息,主要内容是fileurl |
|||
window.open(`/#/onlyoffice?name=${row.name}&dtype=${_type}&info=${info}&fileurl=`+encodeURIComponent(_url),"_blank") |
|||
}else{ |
|||
alert("暂不支持该类型预览") |
|||
} |
|||
} |
|||
|
|||
//onlyoffice在线编辑 |
|||
async function onlyOfficeEdit(row:matterInfo){ |
|||
const _type=fileType(row.name!) |
|||
if(_type===""){ |
|||
alert("暂不支持该类型编辑") |
|||
return |
|||
} |
|||
|
|||
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${row.path}`)) //编辑模式必须要全路径 |
|||
const _url=`${props.siteHost}${props.apiURL}/space/download/${row.uuid}/${row.name}?space=${props.spaceid}` |
|||
window.open(`/#/onlyoffice?name=${row.name}&dtype=${_type}&info=${info}&verify=${_verify}&fileurl=`+encodeURIComponent(_url),"_blank") |
|||
}) |
|||
} |
|||
|
|||
//------------------------------------------------------ |
|||
|
|||
//渲染完页面再执行 |
|||
onMounted(() => { |
|||
currentNode.value.uuid="root" |
|||
onLoadMatterList() |
|||
}); |
|||
|
|||
const handleSelectionChange = (val:matterInfo[]) => { |
|||
tabSelected.value = val |
|||
} |
|||
|
|||
//判断是不是空间的所有者 |
|||
function isOwner(){ |
|||
return props.uid===btoa(props.owner) |
|||
} |
|||
</script> |
|||
|
|||
<template> |
|||
<div> |
|||
<el-row :gutter="24" style="margin: 12px 0px;"> |
|||
<span class="el-breadcrumb" style="font-weight: bold;">[ {{ props.spacename }} ] : </span> |
|||
<el-breadcrumb separator="/"> |
|||
<el-breadcrumb-item v-for="(item,index) in breadcrumbList" |
|||
:key="index" @click="index===breadcrumbList.length-1?'': handleDoubleClick(item,index)"> |
|||
<span style="font-weight: bold;">{{ item.name }}</span> |
|||
</el-breadcrumb-item> |
|||
</el-breadcrumb> |
|||
</el-row> |
|||
|
|||
<el-row :gutter="24"> |
|||
<el-col :span="14"> |
|||
<el-upload class="el-button el-button--default" :file-list="fileList" |
|||
:data="uploadFormData" |
|||
:on-success="handleSingleUpload" |
|||
:on-error="handleSigLoadErr" |
|||
:show-file-list="false" |
|||
:action="apiURL+'/space/upload'" :limit="1"> |
|||
<span>上传文件</span> |
|||
</el-upload> |
|||
<el-button @click="createDir">新建目录</el-button> |
|||
<el-button type="danger" plain @click="onDelMatter({uuid:currentNode.uuid,name:currentNode.name,dir:true, |
|||
puuid:currentNode.puuid,path:currentNode.path})">删除目录</el-button> |
|||
</el-col> |
|||
<el-button-group class="control" v-if="isOwner()" style="margin-right: 10px;margin-left: auto;"> |
|||
<el-button :icon="Plus" @click="onAccessManage">成员</el-button> |
|||
<el-button :icon="Delete" @click="onDeleteSpace">删除</el-button> |
|||
</el-button-group> |
|||
</el-row> |
|||
|
|||
<el-row :gutter="24" style="height: 84%;overflow-y: auto;"> |
|||
<el-table |
|||
stripe |
|||
:data="matterList" |
|||
:header-cell-style="{ background: '#f5f8fd' }" |
|||
style="width: 100%" |
|||
row-key="uuid" |
|||
:row-style ="() => ({ lineHeight: '36px' })" |
|||
@selection-change="handleSelectionChange" |
|||
@cell-dblclick="handleDoubleClick" |
|||
@cell-mouse-enter="handleMouseEnter"> |
|||
<el-table-column type="selection" width="50" /> |
|||
<el-table-column width="450" property="name" label="文件名"> |
|||
<template #default="scope"> |
|||
<input type="text" autofocus placeholder="文件夹名" style="border:groove;height:30px;" v-model="newdir" @change="onCreateDir" v-if="scope.row.name===''" /> |
|||
<div v-if="scope.row.name" style="display: flex; align-items: center;"> |
|||
<el-icon :size="26"> |
|||
<component v-if="scope.row.dir" :is="Folder" /> |
|||
<component v-else :is="getFileIcon(scope.row.name)" /> |
|||
</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.name"> |
|||
<el-button size="small" :icon="Promotion" circle ></el-button> |
|||
<el-button size="small" :icon="Edit" circle @click="onlyOfficeEdit(scope.row)"></el-button> |
|||
<el-button size="small" :icon="View" circle @click="onPrivateView(scope.row)"></el-button> |
|||
<el-button size="small" :icon="Download" circle @click="onDownload(scope.row)"></el-button> |
|||
<el-button size="small" :icon="Delete" circle @click="onDelMatter(scope.row)"></el-button> |
|||
</div> |
|||
</template> |
|||
</el-table-column> |
|||
<el-table-column prop="size" width="100" :formatter="readableSize" label="大小" /> |
|||
|
|||
<el-table-column prop="updateTime" label="修改日期"> |
|||
<template #default="scope"> |
|||
<span v-if="scope.row.updateTime">{{ scope.row.updateTime.slice(0,16) }}</span> |
|||
</template> |
|||
</el-table-column> |
|||
</el-table> |
|||
</el-row> |
|||
|
|||
<el-row v-if="paginInfo.total>1" style="justify-content: center;"> |
|||
<el-pagination size="small" background layout="prev, pager, next" :current-page="paginInfo.page+1" @current-change="(value:number)=>{paginInfo.page=value-1;onLoadMatterList();}" :page-count="paginInfo.total" class="mt-4"/> |
|||
</el-row> |
|||
</div> |
|||
|
|||
<div v-if="dynamicVNode"> |
|||
<component :is="dynamicVNode" /> |
|||
</div> |
|||
</template> |
|||
|
|||
<style lang="scss" scoped> |
|||
|
|||
.shareDialog{ |
|||
--el-messagebox-width:'800px'; |
|||
padding:40px; |
|||
.el-text{ |
|||
align-self: flex-start; |
|||
} |
|||
} |
|||
|
|||
.dynamic-width-message-box-byme .el-message-box__message{ |
|||
width: 100%; |
|||
} |
|||
|
|||
</style> |
|||
Loading…
Reference in new issue