Browse Source

集成云盘功能

lwx_v26
han2015 6 months ago
parent
commit
14b0e2affc
  1. 143
      src/api/doc/index.ts
  2. 54
      src/api/doc/type.ts
  3. 2
      src/utils/request.ts
  4. 403
      src/views/doc/manage.vue
  5. 161
      src/views/doc/share.vue
  6. 91
      src/views/doc/tools.ts

143
src/api/doc/index.ts

@ -0,0 +1,143 @@
/**
* @ 作者: han2015
* @ 时间: 2025-05-12 15:39:13
* @ 备注: 文档管理API
*/
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
import { matterPage,matterResutList,createDir,createShare,respCreateShare} from './type';
/**
*
*/
export function getShareList( uid:string,data?: matterPage): AxiosPromise<matterResutList> {
return request({
url: '/hxpan/api/share/page',
method: 'post',
headers: {
'User-Id':uid,
'Content-Type': 'application/x-www-form-urlencoded'
},
data: data
});
}
/**
* share browse
*/
export function getShareBrowse( uid:string,data?: matterPage): AxiosPromise<respCreateShare> {
return request({
url: '/hxpan/api/share/browse',
method: 'post',
headers: {
'User-Id':uid,
'Content-Type': 'application/x-www-form-urlencoded'
},
data: data
});
}
/**
* share delete
*/
export function postShareDelete( uid:string,data?: matterPage): AxiosPromise<respCreateShare> {
return request({
url: '/hxpan/api/share/delete',
method: 'post',
headers: {
'User-Id':uid,
'Content-Type': 'application/x-www-form-urlencoded'
},
data: data
});
}
/**
*
*/
export function getMatterList( uid:string,data?: matterPage): AxiosPromise<matterResutList> {
return request({
url: '/hxpan/api/matter/page',
method: 'post',
headers: {
'User-Id':uid,
'Content-Type': 'application/x-www-form-urlencoded'
},
data: data
});
}
/**
*
*/
export function postCreateDir(uid:string,data?: createDir){
return request({
url: '/hxpan/api/matter/create/directory',
method: 'post',
headers: {
'User-Id':uid,
'Content-Type': 'application/x-www-form-urlencoded'
},
data: data
});
}
/**
*
*/
export function postDelMatter(uid:string,data?: any){
return request({
url: '/hxpan/api/matter/delete',
method: 'post',
headers: {
'User-Id':uid,
'Content-Type': 'application/x-www-form-urlencoded'
},
data: data
});
}
/**
*
*/
export function postDelMatBatch(uid:string,data?: any){
return request({
url: '/hxpan/api/matter/delete/batch',
method: 'post',
headers: {
'User-Id':uid,
'Content-Type': 'application/x-www-form-urlencoded'
},
data: data
});
}
/**
*
*/
export function postCreateShare(uid:string,data?: createShare): AxiosPromise<respCreateShare>{
return request({
url: '/hxpan/api/share/create',
method: 'post',
headers: {
'User-Id':uid,
'Content-Type': 'application/x-www-form-urlencoded'
},
data: data
});
}
/**
*
*/
export function postMatterRename(uid:string,data?: {uuid:string;name:string}){
return request({
url: '/hxpan/api/matter/rename',
method: 'post',
headers: {
'User-Id':uid,
'Content-Type': 'application/x-www-form-urlencoded'
},
data: data
});
}

54
src/api/doc/type.ts

@ -0,0 +1,54 @@
/**
* @ 作者: han2015
* @ 时间: 2025-05-12 15:39:13
* @ 备注: 文档管理api
*/
/**
*
*/
export interface matterPage{
page?:number;
pageSize?:number;
puuid?:string;
deleted?:boolean;
orderDir?:string;
name?:string;
orderCreateTime?:string;
}
export interface matterInfo{
name?:string;
updateTime?:string;
uuid:string; //文件uuid
userUuid?:string; //owner uuid
puuid?:string; // parent dir uuid
path?:string;
size?:number;
dir?:boolean;
deleted?:boolean;
expireInfinity?:boolean;
expireTime?:string;
}
export type matterResutList = PageResult<matterInfo[]>
export interface createDir{
userUuid:string;
puuid:string;
name:string;
}
export interface createShare{
matterUuids:string;
expireInfinity:boolean;
expireTime:string;
}
export interface respCreateShare{
code?:string;
shareType?:string;
expireTime?:string;
name?:string;
uuid?:string;
}

2
src/utils/request.ts

@ -12,7 +12,7 @@ const service = axios.create({
service.interceptors.request.use( service.interceptors.request.use(
(config: InternalAxiosRequestConfig) => { (config: InternalAxiosRequestConfig) => {
const userStore = useUserStoreHook(); const userStore = useUserStoreHook();
config.headers["Content-Type"] = "application/json;charset=utf-8"; //config.headers["Content-Type"] = "application/json;charset=utf-8";
if (userStore.tokenIng) { if (userStore.tokenIng) {
config.headers.Authorization = userStore.tokenIng; config.headers.Authorization = userStore.tokenIng;
} }

403
src/views/doc/manage.vue

@ -0,0 +1,403 @@
<!--
@ 作者: han2015
@ 时间: 2025-05-12 15:39:13
@ 备注: 文档管理组件
-->
<script lang="ts" setup>
import { getExpirTime, getFileIcon, readableSize} from "./tools"
import { useUserStore } from "@/store/modules/user";
import { getMatterList,postCreateDir,postDelMatter,postCreateShare,postMatterRename,postDelMatBatch} from "@/api/doc/index"
import { matterPage,matterInfo,respCreateShare } from "@/api/doc/type"
import { h } from 'vue'
import {
Delete,
Download,
Share,
Search,
Edit,
Promotion,
Folder,
} from '@element-plus/icons-vue'
import {ElSelect,ElOption, ElText,ElInput,TableInstance,ElMessage,UploadFile,UploadFiles,ElPagination} from "element-plus";
import { number } from "echarts";
//TODO: add file icons done!
//TODO: click on table-item 1preview on file .....................
// 2) go into dir-item done!
// 3search file done!
//TODO: rename matters done!
//TODO: get share list done
//TODO: visit share link done!
//TODO: select on table items for batch opts 1)delete done!
// 2)share done!
//TODO: test on Pagination done!
const userStore = useUserStore();
const uid="p0"+userStore.userInfoCont.userId;
const siteHost=import.meta.env.VITE_APP_BASE_URL;
const apiURL=import.meta.env.VITE_APP_BASE_API+"/hxpan/api"
const matterList = ref<matterInfo[]>([])
const searchname=ref("")
const currentUuid=ref("root") //
const newdir=ref("")
const currentHoverRow=ref("")
const breadcrumbList=ref<matterInfo[]>([{name:"根目录",uuid:"root", dir:true}]) //
const selectedValue = ref("sixhour")
const tabSelected=ref<matterInfo[]>([])
const multipleTableRef = ref<TableInstance>()
const paginInfo = ref({ page: 0, total: 0 })
const formHeaders=ref({
"user-id":uid,
})
const uploadFormData = computed(() => {
return {
userUuid: uid, // uuid
puuid: currentUuid.value, // uuidroot
}
});
const fileList=ref([])//upload files
//---------------------------
function onShareMatter(row?:matterInfo){
ElMessageBox({
title: '请选择分享有效时间',
message: () => h(ElSelect, {
defaultFirstOption:true,
modelValue: selectedValue.value,
'onUpdate:modelValue': (value) => {
selectedValue.value = value
},
valueKey: "value",
fallbackPlacements:['bottom-start'],
style: { width:'360px' }
},() => [
h(ElOption, { label: '六小时',key: 'sixhour', value: 'sixhour' }),
h(ElOption, { label: '一 天', key: 'oneday', value: 'oneday' }),
h(ElOption, { label: '三 天', key: 'threeday', value: 'threeday' }),
h(ElOption, { label: '一 周', key: 'oneweek', value: 'oneweek' }),
h(ElOption, { label: '一 月', key: 'onemonth', value: 'onemonth' }),
h(ElOption, { label: '三 月', key: 'threemonth', value: 'threemonth' }),
h(ElOption, { label: '永 久', key: 'permanent', value: 'permanent' }),
]),
showCancelButton: true
}).then(() => {
let param;
if (row){
param={matterUuids:row.uuid,expireInfinity:false,expireTime:""}
}else if (tabSelected.value.length>1){
param={matterUuids:tabSelected.value.map((item:matterInfo)=>item.uuid).join(","),expireInfinity:false,expireTime:""}
}
if(param){
if(selectedValue.value==='permanent'){
param.expireInfinity=true
}else{
param.expireTime=getExpirTime(selectedValue.value)
}
postCreateShare(uid,param).then((resp)=>{
showShareMessage(resp.data)
})
onLoadMatterList()
selectedValue.value="sixhour"
}
}).catch(() => {
selectedValue.value='sixhour'
return
})
}
function showShareMessage(row:respCreateShare){
let _shareURL=`${siteHost}/#/doc/share/?uuid=${row.uuid}&code=${row.code}`
ElMessageBox({
title: '分享详情',
customStyle: { '--el-messagebox-width':'800px',padding:'40px'},
message: () => h('div',{style:{display:'flex','flex-direction':'column','line-height':'34px', width:'600px'}},[
h(ElText,{style:{'align-self':'flex-start'}},()=>row.name),
h(ElText,{style:{'align-self':'flex-start'}},()=>"失效时间:"+row.expireTime),
h(ElText,{style:{'align-self':'flex-start'}},()=>"链接:"+_shareURL)
]),
confirmButtonText: '复制分享链接',
showCancelButton: true
}).then(()=>{
navigator.clipboard.writeText(_shareURL)
}).catch(() => {});
}
//----------------------------------------
//
function onDelMatter(row:matterInfo){
if (row.uuid){
ElMessageBox.confirm("确认删除此数据项?删除后不可恢复!", "警告", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
}).then(()=>{
postDelMatter(uid,{
"uuid":row.uuid
}).then(()=>onLoadMatterList())
})
}
}
function onDelMatBatch(){
ElMessageBox.confirm("确认删除选择的内容?", "警告", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
}).then(()=>{
postDelMatBatch(uid,{
"uuids":tabSelected.value.map((item:matterInfo)=>item.uuid).join(",")
}).then(()=>onLoadMatterList())
})
}
function onDownload(row:matterInfo){
ElMessageBox.confirm("确认下载此数据项?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
}).then(()=>{
if (row.uuid){
let _url= apiURL+`/alien/download/${row.uuid}/${row.name}`
window.open(_url)
}
})
}
function onMatterRename(row:matterInfo){
const newname=ref(row.name)
ElMessageBox({
title:"请输入新的文件名",
confirmButtonText: "确定",
cancelButtonText: "取消",
message: () => h(ElInput, {
style: { width:'360px' },
modelValue: newname.value,
'onUpdate:modelValue': (val) => {
newname.value = val
},
}),
}).then(() => {
postMatterRename(uid,{
uuid:row.uuid,
name:newname.value,
}).then(()=>onLoadMatterList())
})
}
//
function onLoadMatterList(name?:string){
let _page: matterPage = {
page: paginInfo.value.page,
pageSize: 50,
orderCreateTime: "DESC",
orderDir: "DESC"
};
//puuid
if(name){
_page.name=name
}else{
_page.puuid=currentUuid.value
_page.deleted=false
}
getMatterList(uid,_page).then((resp)=>{
//page+1 index1apiindex0
paginInfo.value={total:resp.data.totalPages,page:resp.data.page}
matterList.value=resp.data.data
})
}
//----------for dir-----------
function createDir(){
matterList.value?.unshift({
name:"",
userUuid:uid,
puuid:"",
uuid:"",
dir:true,
size:0,
deleted:false,
})
}
function onCreateDir(){
postCreateDir(uid,{
userUuid:uid,
puuid:currentUuid.value,
name:newdir.value,
}).then(()=> {
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)
currentUuid.value=breadcrumbList.value[breadcrumbList.value.length-1].uuid
onLoadMatterList()
}
}else{
//
currentUuid.value=row.uuid
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)
}
//http://172.20.2.87:6010/api/alien/preview/5a10aaf6-396e-4d9a-7e87-3c5c8029d4db/123.png?ir=fill_100_100
//
onMounted(() => {
onLoadMatterList()
});
const handleSelectionChange = (val:matterInfo[]) => {
tabSelected.value = val
}
</script>
<template>
<div class="app_container">
<el-row :gutter="24" style="margin: 12px 0px;">
<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"
:headers="formHeaders"
:data="uploadFormData"
:on-success="handleSingleUpload"
:on-error="handleSigLoadErr"
:show-file-list="false"
:action="apiURL+'/matter/upload'" :limit="1">
<span>上传</span>
</el-upload>
<!-- <el-button>上传文件夹</el-button> -->
<el-button @click="createDir">新建目录</el-button>
<span v-if="tabSelected.length>1" style="margin:12px">
<el-button @click="onShareMatter()">分享</el-button>
<el-button @click="onDelMatBatch">删除</el-button>
</span>
<el-button @click="onLoadMatterList()">刷新</el-button>
</el-col>
<el-col :span="8" class="search">
<el-input placeholder="搜索文件" v-model="searchname" @blur="searchname===''?onLoadMatterList():''"/>
<el-button :icon="Search" @click="onLoadMatterList(searchname)"></el-button>
</el-col>
</el-row>
<el-row :gutter="24" style="height: 84%;overflow-y: auto;">
<el-table
stripe
:data="matterList"
ref="multipleTableRef"
: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="650" 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="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="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>
</template>
<style lang="scss" scoped>
.app_container {
padding: 10px 30px 0px 30px;
height: calc(100% - 10px);
overflow: hidden;
overflow-y: auto;
width: 100%;
}
.search{
margin-right: 20px;
display:inherit;
}
.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>

161
src/views/doc/share.vue

@ -0,0 +1,161 @@
<!--
@ 作者: han2015
@ 时间: 2025-05-12 15:39:13
@ 备注: 文档管理组件
-->
<script lang="ts" setup>
import { useRoute } from 'vue-router'
import { useUserStore } from "@/store/modules/user";
import { getShareList,postDelMatter,getShareBrowse,postShareDelete} from "@/api/doc/index"
import { matterPage,matterInfo,respCreateShare } from "@/api/doc/type"
import { h } from 'vue'
import {
Delete,
Folder,
Share,
} from '@element-plus/icons-vue'
import {ElText } from "element-plus";
import { getExpirTime, getFileIcon,checkExpirTime } from "./tools"
const route = useRoute()
const userStore = useUserStore();
const uid="p0"+userStore.userInfoCont.userId;
const siteHost=import.meta.env.VITE_APP_BASE_URL
const apiURL=import.meta.env.VITE_APP_BASE_API+"/hxpan/api"
const matterList = ref<matterInfo[]>()
const browerMode=ref(false) //share 1)self-list(default) 2)brower
const currentHoverRow=ref("")
function showShareMessage(row:{uuid:string,code:string,name:string,expireTime:string}){
let _shareURL=`${siteHost}/#/doc/share?uuid=${row.uuid}&code=${row.code}`
ElMessageBox({
title: '分享详情',
customStyle: { '--el-messagebox-width':'800px',padding:'40px'},
message: () => h('div',{style:{display:'flex','flex-direction':'column','line-height':'34px', width:'600px'}},[
h(ElText,{style:{'align-self':'flex-start'}},()=>row.name),
h(ElText,{style:{'align-self':'flex-start'}},()=>"失效时间:"+row.expireTime),
h(ElText,{style:{'align-self':'flex-start'}},()=>"链接:"+_shareURL),
h(ElButton, {
type: 'primary',
style: { width: '20%' },
onClick: () => {
let _url=apiURL+`/share/zip?shareUuid=${row.uuid}&code=${row.code}&puuid=root&rootUuid=root`
window.open(_url)
}
},()=>'下载')
]),
confirmButtonText: '复制分享链接',
showCancelButton: true
}).then(()=>{
navigator.clipboard.writeText(_shareURL)
}).catch(() => {
if (browerMode.value){
location.href=`/#/doc/share`
}
});
}
//----------------------------------------
//
function onShareDelete(row:matterInfo){
if (row.uuid){
ElMessageBox.confirm("确认要取消此文件分享!", "警告", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
}).then(()=>{
postShareDelete(uid,{
"uuid":row.uuid
}).then(()=>onLoadShareList())
})
}
}
//
function onLoadShareList(){
let _page: matterPage = {
page: 0,
pageSize: 100,
orderCreateTime: "DESC",
orderDir: "DESC",
}
getShareList(uid,_page).then((resp)=>{
matterList.value=resp.data.data
})
}
function handleMouseEnter(row:any){
currentHoverRow.value=row.name
}
onMounted(() => {
const query = route.query
//
if (query.uuid && query.code){
browerMode.value=true
getShareBrowse("",{shareUuid:query.uuid,code:query.code,puuid:'root',rootUuid:'root'}).then((resp)=>{
showShareMessage(resp.data)
})
return
}
browerMode.value=false
onLoadShareList()
})
</script>
<template>
<div class="app_container">
<el-row :gutter="24" v-if="!browerMode">
<el-table
stripe
:data="matterList"
:header-cell-style="{ background: '#f5f8fd' }"
style="width: 100%"
row-key="value"
:row-style ="() => ({ height: '55px' })"
@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="26">
<component v-if="scope.row.dir" :is="Folder" />
<component v-else :is="getFileIcon(scope.row.name)" />
</el-icon>
<span style="margin-left: 10px">{{ checkExpirTime(scope.row)?scope.row.name+' (已过期)':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="Share" circle @click="showShareMessage(scope.row)"></el-button>
<el-button size="small" :icon="Delete" circle @click="onShareDelete(scope.row)"></el-button>
</div>
</template>
</el-table-column>
<el-table-column 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>
</div>
</template>
<style lang="scss" scoped>
.app_container {
padding: 10px 30px 0px 30px;
height: calc(100% - 10px);
overflow: hidden;
overflow-y: auto;
width: 100%;
}
</style>

91
src/views/doc/tools.ts

@ -0,0 +1,91 @@
import {
Document,
Picture,
VideoPlay,
Headset,
Files,
Tickets,
} from '@element-plus/icons-vue'
import { matterInfo } from "@/api/doc/type"
export function getExpirTime(val:string){
let now= Date.now() //haomiao
switch(val){
case "sixhour":
now+=6*3600*1000
break
case "oneday":
now+=24*3600*1000
break
case "threeday":
now+=3*24*3600*1000
break
case "oneweek":
now+=7*24*3600*1000
break
case "onemonth":
now+=30*24*3600*1000
break
case "threemonth":
now+=90*24*3600*1000
break
default:
now+=6*3600*1000
}
let nt=new Date(now)
return nt.toISOString().slice(0,10)+" "+nt.toLocaleTimeString()
}
export function checkExpirTime(val:matterInfo){
if (val.expireInfinity) return false
if (val.expireTime) {
let now = Date.now() //haomiao
let expireTime = new Date(val.expireTime).getTime()
if (expireTime > now) return false
}
return true
}
export const getFileIcon = (fileName:string) => {
const extension = fileName?.split('.').pop()?.toLowerCase();
switch(extension) {
case 'pdf':
case 'doc':
case 'docx':
case 'xls':
case 'xlsx':
return Document;
case 'jpg':
case 'jpeg':
case 'png':
case 'gif':
case 'svg':
return Picture;
case 'mp4':
case 'mov':
case 'avi':
return VideoPlay;
case 'mp3':
case 'wav':
return Headset;
case 'zip':
case 'rar':
case '7z':
return Files;
default:
return Tickets;
}
}
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"
//1024*1024*1024
if(val.size<1073741824) return (val.size/1048576).toFixed(1)+"Mb"
//1024*1024*1024*1024
if(val.size<1099511627776) return (val.size/1073741824).toFixed(1)+"Gb"
return "BIG"
}
Loading…
Cancel
Save