@ -0,0 +1,191 @@ |
|||||
|
/** |
||||
|
* @ 作者: han2015 |
||||
|
* @ 时间: 2025-05-12 15:39:13 |
||||
|
* @ 备注: 文档管理API |
||||
|
*/ |
||||
|
|
||||
|
import request from '@/utils/axios/index'; |
||||
|
import { AxiosPromise } from 'axios'; |
||||
|
import { matterPage,matterResutList,createDir,createShare,respCreateShare,matterTreeList} from './type'; |
||||
|
|
||||
|
|
||||
|
/** |
||||
|
* 获取分享列表 |
||||
|
*/ |
||||
|
export function getShareList( uid:string,data?: matterPage): AxiosPromise<matterResutList> { |
||||
|
return request({ |
||||
|
url: '/hxpan/api/share/page', |
||||
|
method: 'post', |
||||
|
headers: { |
||||
|
'Identifier':uid, |
||||
|
'Content-Type': 'application/x-www-form-urlencoded' |
||||
|
}, |
||||
|
data: data |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 创建空间 |
||||
|
*/ |
||||
|
export function doCreateSpace( uid:string,_name:string) { |
||||
|
return request({ |
||||
|
url: '/hxpan/api/space/create', |
||||
|
method: 'post', |
||||
|
headers: { |
||||
|
'Identifier':uid, |
||||
|
'Content-Type': 'application/x-www-form-urlencoded' |
||||
|
}, |
||||
|
data: {name:_name} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 获取我的空间列表 |
||||
|
*/ |
||||
|
export function getMySpaces(uid:string,data?: any){ |
||||
|
return request({ |
||||
|
url: '/hxpan/api/space/list', |
||||
|
method: 'post', |
||||
|
headers: { |
||||
|
'Identifier':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: { |
||||
|
'Identifier':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: { |
||||
|
'Identifier':uid, |
||||
|
'Content-Type': 'application/x-www-form-urlencoded' |
||||
|
}, |
||||
|
data: data |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 获取目录文件 |
||||
|
*/ |
||||
|
//export function getMatterList( uid:string,data?: matterPage): AxiosPromise<matterResutList> {
|
||||
|
export function getMatterList( uid:string,data?: matterPage): AxiosPromise<matterTreeList> { |
||||
|
return request({ |
||||
|
url: '/hxpan/api/matter/page', |
||||
|
method: 'post', |
||||
|
headers: { |
||||
|
'Identifier':uid, |
||||
|
'Content-Type': 'application/x-www-form-urlencoded' |
||||
|
}, |
||||
|
data: data |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 获取回收站文件 |
||||
|
*/ |
||||
|
//export function getMatterList( uid:string,data?: matterPage): AxiosPromise<matterResutList> {
|
||||
|
export function getRecyclingList( uid:string,data?: matterPage): AxiosPromise<matterTreeList> { |
||||
|
return request({ |
||||
|
url: '/hxpan/api/matter/recycling', |
||||
|
method: 'post', |
||||
|
headers: { |
||||
|
'Identifier':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: { |
||||
|
'Identifier':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: { |
||||
|
'Identifier':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: { |
||||
|
'Identifier':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: { |
||||
|
'Identifier':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: { |
||||
|
'Identifier':uid, |
||||
|
'Content-Type': 'application/x-www-form-urlencoded' |
||||
|
}, |
||||
|
data: data |
||||
|
}); |
||||
|
} |
||||
@ -0,0 +1,223 @@ |
|||||
|
import request from '@/utils/axios/index'; |
||||
|
import { AxiosPromise } from 'axios'; |
||||
|
import { matterPage,createDir,matterTreeList,matterInfo} 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 doCreateAiagent(uid:string,data?: {space:string,matter:string}){ |
||||
|
return request({ |
||||
|
url: '/hxpan/api/space/aiagent', |
||||
|
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 spaceMatterRename(uid:string,data?: any){ |
||||
|
return request({ |
||||
|
url: '/hxpan/api/space/rename', |
||||
|
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 |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
//-------------------AI backend APIs---------------
|
||||
|
/** |
||||
|
* 文档训练 |
||||
|
*/ |
||||
|
export function doAiTraining(_url:string,data?: any){ |
||||
|
return request({ |
||||
|
url: '/aibot'+_url, |
||||
|
method: 'post', |
||||
|
headers: { |
||||
|
'Content-Type': 'application/x-www-form-urlencoded' |
||||
|
}, |
||||
|
data: data |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
export interface aiChatData{ |
||||
|
inputs:object; |
||||
|
query?:string; |
||||
|
response_mode:string; |
||||
|
conversation_id?:string; |
||||
|
user:string, |
||||
|
file?:[] |
||||
|
} |
||||
|
|
||||
|
|
||||
|
/** |
||||
|
* 问答api |
||||
|
*/ |
||||
|
export function doAiChat(_url:string,data: aiChatData,sig?:AbortSignal){ |
||||
|
return fetch( |
||||
|
_url,{ |
||||
|
method: 'post', |
||||
|
headers: { |
||||
|
'Content-Type': 'application/json' |
||||
|
}, |
||||
|
signal:sig, |
||||
|
body: JSON.stringify(data) |
||||
|
} |
||||
|
) |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 工作流 |
||||
|
*/ |
||||
|
export function doAiWorkflow(_url:string,data: aiChatData){ |
||||
|
return fetch( |
||||
|
_url,{ |
||||
|
method: 'post', |
||||
|
headers: { |
||||
|
'Content-Type': 'application/json' |
||||
|
}, |
||||
|
body: JSON.stringify(data) |
||||
|
} |
||||
|
) |
||||
|
} |
||||
|
/** |
||||
|
* 通过userid获取记录列表 |
||||
|
* @requires userid |
||||
|
*/ |
||||
|
export function getAiChatList(data:any){ |
||||
|
return request({ |
||||
|
url: '/aibot/chat/list', |
||||
|
method: 'post', |
||||
|
data: data |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 获取单个会话 |
||||
|
* @requires userid |
||||
|
* @requires uuid |
||||
|
*/ |
||||
|
export function getAiChat(data: any){ |
||||
|
return request({ |
||||
|
url: '/aibot/chat', |
||||
|
method: 'post', |
||||
|
data: data |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 删除单个会话 |
||||
|
* @requires userid |
||||
|
* @requires uuid |
||||
|
*/ |
||||
|
export function delAiChat(data: any){ |
||||
|
return request({ |
||||
|
url: '/aibot/chat/del', |
||||
|
method: 'post', |
||||
|
data: data |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 更新会话 |
||||
|
* @requires userid |
||||
|
* @requires uuid |
||||
|
*/ |
||||
|
export function setAiChat(data: any){ |
||||
|
return request({ |
||||
|
url: '/aibot/chat/update', |
||||
|
method: 'post', |
||||
|
data: data |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 获取智能体 |
||||
|
*/ |
||||
|
export function getAiagentList(data?: any){ |
||||
|
return request({ |
||||
|
url: '/aibot/agent/list', |
||||
|
method: 'post', |
||||
|
data: data |
||||
|
}); |
||||
|
} |
||||
@ -0,0 +1,101 @@ |
|||||
|
/** |
||||
|
* @ 作者: han2015 |
||||
|
* @ 时间: 2025-05-12 15:39:13 |
||||
|
* @ 备注: 文档管理api 结构定义 |
||||
|
*/ |
||||
|
import request from '@/utils/axios/index'; |
||||
|
import { AxiosPromise } from 'axios'; |
||||
|
/** |
||||
|
* 拉去首页文件列表 |
||||
|
*/ |
||||
|
export interface matterPage{ |
||||
|
page?:number; |
||||
|
pageSize?:number; |
||||
|
puuid?:string; |
||||
|
deleted?:boolean; |
||||
|
orderDir?:string; |
||||
|
name?:string; |
||||
|
space?: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; |
||||
|
agent?:boolean; |
||||
|
dir?:boolean; |
||||
|
deleted?:boolean; |
||||
|
expireInfinity?:boolean; |
||||
|
expireTime?:string; |
||||
|
permitVal?:number; //该值是当前用户permits的解析结果
|
||||
|
permits?:matterPermit; |
||||
|
} |
||||
|
export interface matterPermit{ |
||||
|
id:number; |
||||
|
MatterUuid:string; |
||||
|
data: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 { |
||||
|
manager?:boolean; |
||||
|
children:matterTree[] |
||||
|
} |
||||
|
|
||||
|
export type matterTreeList = PageResult<matterTree[]> |
||||
|
|
||||
|
export type matterResutList = PageResult<matterInfo[]> |
||||
|
|
||||
|
export interface createDir{ |
||||
|
userUuid?:string; |
||||
|
puuid:string; |
||||
|
name:string; |
||||
|
space?:string; |
||||
|
} |
||||
|
|
||||
|
export interface createShare{ |
||||
|
matterUuids:string; |
||||
|
expireInfinity:boolean; |
||||
|
expireTime:string; |
||||
|
permitList:string; |
||||
|
permitInfos:string; |
||||
|
len:number; |
||||
|
} |
||||
|
|
||||
|
export interface respCreateShare{ |
||||
|
code?:string; |
||||
|
shareType?:string; |
||||
|
expireTime?:string; |
||||
|
name?:string; |
||||
|
uuid?:string; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 文件上传 |
||||
|
*/ |
||||
|
export function doFileUpload(params:FormData,_url:string): AxiosPromise<matterInfo> { |
||||
|
return request({ |
||||
|
url: _url, |
||||
|
method: 'post', |
||||
|
data: params, |
||||
|
headers: { |
||||
|
'Content-Type': 'multipart/form-data' |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
@ -0,0 +1,152 @@ |
|||||
|
import request from '@/utils/axios/index'; |
||||
|
import { AxiosPromise } from 'axios'; |
||||
|
|
||||
|
export interface shareOrgInfo{ |
||||
|
id:number |
||||
|
name:string |
||||
|
superior:number |
||||
|
level:number |
||||
|
status:boolean |
||||
|
child :shareOrgInfo[] |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 搜索条件 |
||||
|
*/ |
||||
|
export interface parsArchList extends PageQuery{ |
||||
|
page:number, |
||||
|
pagesize:number |
||||
|
adminorg?:number; //行政组织
|
||||
|
keywords?:string; |
||||
|
} |
||||
|
|
||||
|
export interface memberInfo{ |
||||
|
id:number, |
||||
|
name:string, |
||||
|
icon:string, |
||||
|
company:number, |
||||
|
maindeparment:number, |
||||
|
sunmaindeparment:number, |
||||
|
adminorg:number, |
||||
|
state:number, |
||||
|
keystr:string, |
||||
|
positionname :string, |
||||
|
maindeparmentname:string |
||||
|
} |
||||
|
|
||||
|
export type shareArchivesList = PageResult<memberInfo[]> |
||||
|
|
||||
|
/** |
||||
|
* 获取行政组织树 |
||||
|
*/ |
||||
|
export function getOrgTreeList(data: {orgid?:number}): AxiosPromise<shareOrgInfo[]>{ |
||||
|
return request({ |
||||
|
url: '/hrapi/org/govnewthreeing', |
||||
|
method: 'post', |
||||
|
data: data |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 获取人员档案 |
||||
|
*/ |
||||
|
export function getArchivesListPage(data?: parsArchList): AxiosPromise<shareArchivesList> { |
||||
|
return request({ |
||||
|
url: '/hrapi/staff/archiveslistcont', |
||||
|
method: 'post', |
||||
|
data: data |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 获取文档成员权限列表 |
||||
|
*/ |
||||
|
export function getPermitedList(uid:string,data:{uuid:string}): AxiosPromise<{permited:string[],infos:string[]}> { |
||||
|
return request({ |
||||
|
url: '/hxpan/api/share/permits', |
||||
|
method: 'post', |
||||
|
headers: { |
||||
|
'Identifier':uid, |
||||
|
'Content-Type': 'application/x-www-form-urlencoded' |
||||
|
}, |
||||
|
data: data |
||||
|
}); |
||||
|
} |
||||
|
/** |
||||
|
* 获取空间成员列表 |
||||
|
*/ |
||||
|
export function getSpaceMemberList(uid:string,data:{space?:string,matter:string}): AxiosPromise<{ |
||||
|
members:string[], |
||||
|
dprts:string[], |
||||
|
managers:string, |
||||
|
permits:Object, |
||||
|
}> { |
||||
|
return request({ |
||||
|
url: '/hxpan/api/space/members', |
||||
|
method: 'post', |
||||
|
headers: { |
||||
|
'Identifier':uid, |
||||
|
'Content-Type': 'application/x-www-form-urlencoded' |
||||
|
}, |
||||
|
data: data |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 添加空间管理员 |
||||
|
*/ |
||||
|
export function addSpaceManager(uid:string,data:{space:string,mangers:string}): AxiosPromise<{members:string[],dprts:string[]}> { |
||||
|
return request({ |
||||
|
url: '/hxpan/api/space/addmanagers', |
||||
|
method: 'post', |
||||
|
headers: { |
||||
|
'Identifier':uid, |
||||
|
'Content-Type': 'application/x-www-form-urlencoded' |
||||
|
}, |
||||
|
data: data |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 更新matter权限 |
||||
|
*/ |
||||
|
export function updateSpaceMetterPermit(uid:string,data:{space:string,matter:string,permits:Object}): AxiosPromise<{members:string[],dprts:string[]}> { |
||||
|
return request({ |
||||
|
url: '/hxpan/api/space/permit/update', |
||||
|
method: 'post', |
||||
|
headers: { |
||||
|
'Identifier':uid, |
||||
|
}, |
||||
|
data: data |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 重置matter特定权限 |
||||
|
*/ |
||||
|
export function resetSpaceMatterPermit(uid:string,data:{space:string,matter:string}): AxiosPromise<{members:string[],dprts:string[]}> { |
||||
|
return request({ |
||||
|
url: '/hxpan/api/space/permit/reset', |
||||
|
method: 'post', |
||||
|
headers: { |
||||
|
'Identifier':uid, |
||||
|
'Content-Type': 'application/x-www-form-urlencoded' |
||||
|
}, |
||||
|
data: data |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 保存文档成员权限修改 |
||||
|
*/ |
||||
|
export function postPermitedList(uid:string,data?:{permitList:string,permitInfos:string, update:string,uid:string,uuid:string,len:number}){ |
||||
|
return request({ |
||||
|
url: '/hxpan/api/share/permits', |
||||
|
method: 'post', |
||||
|
headers: { |
||||
|
'Identifier':uid, |
||||
|
'Content-Type': 'application/x-www-form-urlencoded' |
||||
|
}, |
||||
|
data: data |
||||
|
}); |
||||
|
} |
||||
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 654 B |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 6.2 KiB |
@ -0,0 +1,37 @@ |
|||||
|
import { defineStore } from 'pinia' |
||||
|
import { ref} from 'vue'; |
||||
|
import request from "@/utils/axios/index"; |
||||
|
|
||||
|
export const useOrgMemberStore = defineStore('orgMember', () => { |
||||
|
interface OrgMemberItem { |
||||
|
id: string; |
||||
|
name: string; |
||||
|
child?:OrgMemberItem[]; |
||||
|
} |
||||
|
|
||||
|
const listMap = ref<Record<string, string>>({}) |
||||
|
const dataTree = ref<OrgMemberItem>({ id: '', name: '', child: [] }) |
||||
|
|
||||
|
async function init() { |
||||
|
await request({ |
||||
|
url: "/systemapi/app/get_org_everyone_people",//"172.20.2.87:39168",
|
||||
|
method: "post", |
||||
|
data:{id:"313",all: 1} |
||||
|
}).then((response) => { |
||||
|
// assuming response.data is an array of OrgMemberItem
|
||||
|
dataTree.value={id:"313",name:"集团公司",child:response.data} |
||||
|
handleChildren(response.data) |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
function handleChildren(childs:any[]){ |
||||
|
childs.forEach(item => { |
||||
|
listMap.value[item.id] = item.name; |
||||
|
if(item.child){ |
||||
|
handleChildren(item.child) |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
init() |
||||
|
return { listMap,dataTree } |
||||
|
}) |
||||
@ -0,0 +1,427 @@ |
|||||
|
<!-- |
||||
|
@ 作者: han2015 |
||||
|
@ 时间: 2025-05-12 15:39:13 |
||||
|
@ 备注: aibot组件 |
||||
|
--> |
||||
|
<script lang="ts" setup> |
||||
|
import { |
||||
|
Promotion, |
||||
|
Remove |
||||
|
} from '@element-plus/icons-vue' |
||||
|
import { doAiTraining,doAiChat,aiChatData,getAiChatList,getAiChat,setAiChat,delAiChat} from "@/api/doc/space" |
||||
|
import {ElText,ElInput, ButtonInstance} from "element-plus"; |
||||
|
import { VueMarkdown } from '@crazydos/vue-markdown' |
||||
|
import rehypeRaw from 'rehype-raw' |
||||
|
import remarkGfm from 'remark-gfm' |
||||
|
|
||||
|
//选中的咨询模式 |
||||
|
const checkedModel = ref([]) |
||||
|
//支持的模式 |
||||
|
const aimodels = [{name:'联网检索',key:"onlineSearch"}, {name:'公司知识库',key:"useDataset"}] |
||||
|
const baseURL=import.meta.env.VITE_APP_BASE_API |
||||
|
const conversation=ref("") //当前会话的uuid |
||||
|
const myquestion=ref('') |
||||
|
const controller = ref<AbortController | null>(null) |
||||
|
const inputState=ref(true) |
||||
|
const conversations=ref<chatRecord[]>([]) |
||||
|
const centHoverItem=ref("") |
||||
|
|
||||
|
const interact_msg=ref<{ask:boolean,think:string,content:string,docinfo?:any[]}[]>([]) |
||||
|
const props = withDefaults(defineProps<{ |
||||
|
userid:string, |
||||
|
closefunc:()=>void, |
||||
|
agent:{model:boolean,name:string,uuid:string} |
||||
|
}>(),{}) |
||||
|
|
||||
|
const respMsg=ref("") |
||||
|
|
||||
|
//消息体 |
||||
|
interface message{ |
||||
|
ask:boolean, |
||||
|
think:string, |
||||
|
content:string |
||||
|
} |
||||
|
//会话记录 |
||||
|
interface chatRecord{ |
||||
|
uuid:string, |
||||
|
agentuuid:string, |
||||
|
brief:string, |
||||
|
messages:message[] |
||||
|
} |
||||
|
|
||||
|
/* 中断函数,供按钮调用 */ |
||||
|
function abortFetch() { |
||||
|
if (controller.value) { |
||||
|
controller.value.abort() |
||||
|
controller.value = null |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
async function onSendTextToAI(){ |
||||
|
if(myquestion.value==="")return; |
||||
|
inputState.value=false |
||||
|
|
||||
|
const params={ |
||||
|
"onlineSearch":"否", |
||||
|
"useDataset":"否" |
||||
|
} |
||||
|
for (let item of checkedModel.value){ |
||||
|
if(item==="onlineSearch") params.onlineSearch="是" |
||||
|
if(item==="useDataset") params.useDataset="是" |
||||
|
} |
||||
|
if (conversation.value==""){ |
||||
|
//开启新对话 |
||||
|
interact_msg.value=[{ask:true,think:"", content:myquestion.value}] |
||||
|
}else{ |
||||
|
interact_msg.value.push({ask:true,think:"", content:myquestion.value}) |
||||
|
} |
||||
|
let docinfo:any=[] |
||||
|
controller.value = new AbortController(); |
||||
|
try{ |
||||
|
const res= await doAiChat(`${baseURL}/aibot/agents/${props.agent.uuid}/chat`,{ |
||||
|
inputs: params, |
||||
|
query:myquestion.value, |
||||
|
response_mode:"streaming", |
||||
|
conversation_id:conversation.value, |
||||
|
user:atob(props.userid),//这里已经base64解析了 |
||||
|
},controller.value.signal |
||||
|
) |
||||
|
|
||||
|
if (!res.ok) { |
||||
|
throw new Error(`HTTP ${res.status} ${res.statusText}`); |
||||
|
} |
||||
|
|
||||
|
myquestion.value="" |
||||
|
const reader = res.body!.getReader(); |
||||
|
const decoder = new TextDecoder('utf-8'); |
||||
|
|
||||
|
let chunk = ''; // 行缓存 |
||||
|
while (true) { |
||||
|
const {done, value} = await reader.read() |
||||
|
|
||||
|
// 服务器可能一次返回多行,需要手动按行拆分 |
||||
|
chunk += decoder.decode(value, {stream: true}); |
||||
|
const lines = chunk.split(/\n/); |
||||
|
chunk = lines.pop() || '' |
||||
|
for (const line of lines) { |
||||
|
if (!line.trim()) continue; // 跳过空行 |
||||
|
if (line.startsWith('data: ')) { |
||||
|
const data = line.slice(6); |
||||
|
const json = JSON.parse(data); |
||||
|
if(json.event==="message"){ |
||||
|
conversation.value=json.conversation_id |
||||
|
respMsg.value+=json.answer |
||||
|
}else if(json.event==="message_end"){ |
||||
|
docinfo=json.metadata.retriever_resources |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (done) break;//能否放到结尾??? |
||||
|
} |
||||
|
}catch (e: any) { |
||||
|
if (e.name === 'AbortError') { |
||||
|
console.log('用户手动中断') |
||||
|
} |
||||
|
}finally{ |
||||
|
inputState.value=true |
||||
|
} |
||||
|
|
||||
|
const arr=respMsg.value.split("</think>") |
||||
|
if(arr.length===1){ |
||||
|
interact_msg.value.push({ask:false,think:"",content:arr[0],docinfo:docinfo}) |
||||
|
}else{ |
||||
|
//思考模式 |
||||
|
interact_msg.value.push({ask:false,think:arr[0],content:arr[1],docinfo:docinfo}) |
||||
|
} |
||||
|
|
||||
|
respMsg.value="" |
||||
|
setAiChat({ |
||||
|
"userid":atob(props.userid), |
||||
|
"uuid":conversation.value, |
||||
|
"brief":interact_msg.value[0].content, |
||||
|
"content":JSON.stringify(interact_msg.value) |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
//加载交流记录列表 |
||||
|
function loadKnownLibList(){ |
||||
|
//userid 需要 base64解析 |
||||
|
getAiChatList({"userid":atob(props.userid)}).then(resp=>{ |
||||
|
conversations.value=resp.data |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
//查看具体的历史记录 |
||||
|
function showChat(uuid:string){ |
||||
|
getAiChat({ |
||||
|
"userid":atob(props.userid), |
||||
|
"uuid":uuid |
||||
|
}).then(resp=>{ |
||||
|
interact_msg.value = JSON.parse(resp.data.content) |
||||
|
conversation.value=resp.data.uuid |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
//删除具体的历史记录 |
||||
|
function onDelChat(uuid:string){ |
||||
|
delAiChat({ |
||||
|
"userid":atob(props.userid), |
||||
|
"uuid":uuid |
||||
|
}).then(resp=>{ |
||||
|
loadKnownLibList() |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
function handleMouseEnter(row:any){ |
||||
|
if(centHoverItem.value==row.uuid) return; |
||||
|
centHoverItem.value=row.uuid |
||||
|
} |
||||
|
function handleMouseLeave(){ |
||||
|
centHoverItem.value="" |
||||
|
} |
||||
|
|
||||
|
function newContext(){ |
||||
|
const c =conversations.value.find(c=>c.uuid==conversation.value) |
||||
|
if(!c){ |
||||
|
conversations.value.push({ |
||||
|
"agentuuid":props.agent.uuid, |
||||
|
"uuid":conversation.value, |
||||
|
"brief":interact_msg.value[0].content, |
||||
|
"messages":interact_msg.value |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
inputState.value=true |
||||
|
interact_msg.value=[] |
||||
|
conversation.value="" |
||||
|
loadKnownLibList() //刷新对话列表 |
||||
|
} |
||||
|
|
||||
|
function resetContext(){ |
||||
|
interact_msg.value=[] |
||||
|
conversation.value="" |
||||
|
props.closefunc() |
||||
|
} |
||||
|
|
||||
|
//处理ai返回的引用信息 |
||||
|
function formatRefContent(content:string){ |
||||
|
let result=content.replaceAll(/"/g,'') |
||||
|
result=result.replaceAll(/Unnamed: /g,' ') |
||||
|
return result |
||||
|
} |
||||
|
|
||||
|
//渲染完页面再执行 |
||||
|
onMounted(() => { |
||||
|
loadKnownLibList() |
||||
|
}); |
||||
|
</script> |
||||
|
|
||||
|
<template> |
||||
|
<el-drawer |
||||
|
:model-value="agent.model" |
||||
|
:title="agent.name+' : 知识库'" |
||||
|
direction="rtl" |
||||
|
size="80%" |
||||
|
@close="resetContext" |
||||
|
:style="{padding:'17px',backgroundColor:'#f3f3f3'}"> |
||||
|
<div style="display:grid;grid-template-columns:1fr 4fr; width: 100%;height: 100%;"> |
||||
|
<div style="overflow-y: auto;"> |
||||
|
<ul> |
||||
|
<li class="action_menu" @click="newContext"> |
||||
|
【新建会话】 |
||||
|
</li> |
||||
|
<li class="list_item" v-for="item in conversations" @mouseover="handleMouseEnter(item)" @mouseleave="handleMouseLeave()" @click="showChat(item.uuid)"> |
||||
|
<span>{{ item.brief }}</span> |
||||
|
<el-button v-show="centHoverItem == item.uuid" icon="Delete" size="small" circle @click="(e)=>{e.stopPropagation();onDelChat(item.uuid)}"></el-button> |
||||
|
</li> |
||||
|
</ul> |
||||
|
</div> |
||||
|
<div style="position: relative;background: white;overflow-y: auto;"> |
||||
|
<div class="reply_area" > |
||||
|
<template v-for="msg of interact_msg"> |
||||
|
<el-text v-if="msg.ask" class="t_ask" >{{ msg.content }}</el-text> |
||||
|
<div v-else class="t_resp"> |
||||
|
<el-text style="white-space: pre-line" v-html="msg.think"></el-text> |
||||
|
<VueMarkdown :markdown="msg.content" :rehype-plugins="[rehypeRaw]" :remark-plugins="[remarkGfm]" ></VueMarkdown> |
||||
|
<div v-if="msg.docinfo?.length>0" class="doc_ref"> |
||||
|
引用<hr> |
||||
|
<el-tooltip v-for="doc in msg.docinfo" placement="top" effect="dark"> |
||||
|
<template #content> |
||||
|
<div v-html="formatRefContent(doc.content)" /> |
||||
|
</template> |
||||
|
<span>{{doc.document_name}}</span> |
||||
|
</el-tooltip> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
</template> |
||||
|
<VueMarkdown :markdown="respMsg" :rehype-plugins="[rehypeRaw]" class="t_resp"></VueMarkdown> |
||||
|
</div> |
||||
|
<div class="question_com" :class="{newquestion:conversation!='' || !inputState}"> |
||||
|
<h1 v-show="conversation =='' && inputState" style="font-size: 56px;margin: 10px;">恒信高科AI平台</h1> |
||||
|
<el-checkbox-group v-model="checkedModel" style="display:flex; margin-bottom: 10px;"> |
||||
|
<el-checkbox-button v-for="mod in aimodels" :value="mod.key"> |
||||
|
{{ mod.name }} |
||||
|
</el-checkbox-button> |
||||
|
</el-checkbox-group> |
||||
|
|
||||
|
<el-input placeholder="问灵犀..." v-model="myquestion" input-style="border-radius: 20px;" |
||||
|
resize="none" :autosize="{minRows: 4}" type="textarea" /> |
||||
|
<el-button :style="{display :inputState ? '':'none'}" type="primary" :icon="Promotion" circle @click="onSendTextToAI"/> |
||||
|
<el-button :style="{display :inputState ? 'none':''}" type="primary" :icon="Remove" circle @click="abortFetch"/> |
||||
|
<span>内容由 AI 生成,请仔细甄别</span> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
</el-drawer> |
||||
|
</template> |
||||
|
|
||||
|
<style lang="scss" scoped> |
||||
|
.app_container { |
||||
|
padding: 10px 30px 0px 30px; |
||||
|
height: 100%; |
||||
|
width: 100%; |
||||
|
} |
||||
|
.newquestion{ |
||||
|
bottom: 20px; |
||||
|
} |
||||
|
.question_com{ |
||||
|
position: fixed; |
||||
|
width: 52%; |
||||
|
margin: 0 50px; |
||||
|
text-align: center; |
||||
|
display: block; |
||||
|
button{ |
||||
|
position: absolute; |
||||
|
bottom: 25px; |
||||
|
right: 5px; |
||||
|
} |
||||
|
} |
||||
|
.reply_area{ |
||||
|
display: flex; |
||||
|
min-height: 20%; |
||||
|
flex-direction: column; |
||||
|
margin: 15px 15px 110px 0px; |
||||
|
} |
||||
|
.t_ask{ |
||||
|
align-self: end; |
||||
|
line-height: 34px; |
||||
|
background-color: rgb(188 211 241); |
||||
|
padding: 0 30px; |
||||
|
border-radius:10px; |
||||
|
margin: 15px; |
||||
|
} |
||||
|
.t_resp{ |
||||
|
align-self: start; |
||||
|
line-height: 30px; |
||||
|
margin: 20px 33px; |
||||
|
font-size: 16px; |
||||
|
color: black; |
||||
|
} |
||||
|
.dynamic-width-message-box-byme .el-message-box__message{ |
||||
|
width: 100%; |
||||
|
} |
||||
|
.action_menu{ |
||||
|
background-color: white; |
||||
|
padding: 10px 8px; |
||||
|
margin: 3px 14px 18px 0; |
||||
|
border-radius: 8px; |
||||
|
border: 1px solid #c9c6c6; |
||||
|
} |
||||
|
.list_item{ |
||||
|
display: flex; |
||||
|
background-color: #dddddd; |
||||
|
padding: 10px 8px; |
||||
|
margin: 3px 14px 3px 0; |
||||
|
border-radius: 8px; |
||||
|
span{ |
||||
|
width: 90%; |
||||
|
overflow: hidden; |
||||
|
text-overflow: ellipsis; |
||||
|
text-wrap-mode: nowrap; |
||||
|
} |
||||
|
button{ |
||||
|
margin-left: auto; |
||||
|
} |
||||
|
} |
||||
|
.doc_ref{ |
||||
|
margin: 16px; |
||||
|
display: flex; |
||||
|
flex-wrap: wrap; |
||||
|
hr{ |
||||
|
width: 90%; |
||||
|
margin: inherit; |
||||
|
border: none; |
||||
|
border-top: .5px solid rgb(26 25 25 / 23%); |
||||
|
} |
||||
|
span{ |
||||
|
width: 300px; |
||||
|
overflow: hidden; |
||||
|
text-overflow: ellipsis; |
||||
|
text-wrap-mode: nowrap; |
||||
|
margin: 2px 10px; |
||||
|
padding: 0 10px; |
||||
|
background-color: #d7d5d5; |
||||
|
border-radius: 6px; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
</style> |
||||
|
<style> |
||||
|
think { |
||||
|
color: #939393; |
||||
|
margin-bottom: 8px; |
||||
|
display: block; |
||||
|
} |
||||
|
|
||||
|
.t_resp{ |
||||
|
h2,h3,h4,h5{ |
||||
|
margin: 12px 0; |
||||
|
} |
||||
|
p{ |
||||
|
margin-left: 16px; |
||||
|
} |
||||
|
p+ul{ |
||||
|
margin-left: 56px; |
||||
|
} |
||||
|
li p{ |
||||
|
margin-left: 0; |
||||
|
} |
||||
|
ol{ |
||||
|
margin-left: 14px; |
||||
|
ul{ |
||||
|
margin-left: 30px; |
||||
|
li{ |
||||
|
list-style: disc; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
ol>li{ |
||||
|
list-style-type: decimal; |
||||
|
} |
||||
|
ul{ |
||||
|
margin-left: 30px; |
||||
|
list-style: disc; |
||||
|
ul{ |
||||
|
margin-left: 30px; |
||||
|
} |
||||
|
} |
||||
|
table{ |
||||
|
border: 0px solid; |
||||
|
border-spacing: 1px; |
||||
|
border-collapse: collapse; |
||||
|
th{ |
||||
|
background-color: #e5e5e5; |
||||
|
border: 1px solid; |
||||
|
min-width: 180px; |
||||
|
} |
||||
|
td{ |
||||
|
border: 1px solid; |
||||
|
min-width: 180px; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,350 @@ |
|||||
|
<script lang="ts" setup> |
||||
|
|
||||
|
import { userStror } from "@/utils/pinia/stores/modules/userOrders"; |
||||
|
import router from "@/utils/router"; |
||||
|
import BottomPage from '@/views/common/bottom/index.vue' |
||||
|
import { matterInfo} from "@/api/doc/type" |
||||
|
import { getMySpaces,doCreateSpace} from "@/api/doc/index" |
||||
|
import SvgIcon from "@/components/svgIcon/index.vue"; |
||||
|
import { doDelSpace,spaceMatterRename} from "@/api/doc/space" |
||||
|
import { h } from 'vue' |
||||
|
import { |
||||
|
Delete, |
||||
|
Promotion, |
||||
|
Edit, |
||||
|
Setting, |
||||
|
} from '@element-plus/icons-vue' |
||||
|
|
||||
|
const userStore = userStror(); |
||||
|
const uid=btoa("p0"+userStore.userInfoCont.userId); |
||||
|
const rawUid="p0"+userStore.userInfoCont.userId |
||||
|
const spaceTreeData=ref<matterInfo[]>([])//{name:'个人空间',uuid:'root',children:[]} |
||||
|
const showPopup=ref(false) |
||||
|
const currentHoverRow=ref<matterInfo>({}) //table 行的按钮控制 |
||||
|
const Departs = computed(() => { |
||||
|
return `${'p0'+userStore.userInfoCont.userId},${userStore.userInfoCont.company},${userStore.userInfoCont.department},${userStore.userInfoCont.organization}` |
||||
|
}) |
||||
|
|
||||
|
const CutLevelPermit=ref(0) |
||||
|
|
||||
|
function onSelectSpace(data:matterInfo,recycling?:boolean){ |
||||
|
if(data){ |
||||
|
router.push({ |
||||
|
name: "spaces", |
||||
|
params:{fa:7, spaceid:data.uuid}, |
||||
|
state:{ |
||||
|
space: toRaw(data), //这里一定要用toRaw |
||||
|
} |
||||
|
}); |
||||
|
}else{ |
||||
|
if(recycling) { |
||||
|
router.push({ path: "/mysapce",query:{fa:7,recycling:recycling}}); |
||||
|
return |
||||
|
} |
||||
|
router.push({ path: "/mysapce",query:{fa:7}}); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
//-------------------space feature--------------------- |
||||
|
function onNewSpace(){ |
||||
|
const newname=ref("") |
||||
|
ElMessageBox({ |
||||
|
title:"请输入空间名称", |
||||
|
customStyle: { bottom:'200px'}, |
||||
|
confirmButtonText: "确定", |
||||
|
cancelButtonText: "取消", |
||||
|
message: () => h(ElInput, { |
||||
|
style: { width:'360px' }, |
||||
|
modelValue: newname.value, |
||||
|
'onUpdate:modelValue': (val) => { |
||||
|
newname.value = val |
||||
|
}, |
||||
|
}), |
||||
|
}).then(() => { |
||||
|
if(newname.value!==""){ |
||||
|
doCreateSpace(uid,newname.value).then((resp)=>{ |
||||
|
//spaceTreeRef.value.append({name:resp.data.name,uuid:resp.data.uuid,dir:false,userUuid:resp.data.userUuid}) |
||||
|
router.replace({ query: { t: Date.now() } }) //直接刷新 |
||||
|
}) |
||||
|
} |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
function onSpaceMatterRename(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(() => { |
||||
|
if(newname.value&&newname.value!=""){ |
||||
|
spaceMatterRename(uid,{ |
||||
|
space:row.uuid, |
||||
|
uuid:row.uuid, |
||||
|
name:newname.value, |
||||
|
}).then(()=>{ |
||||
|
router.replace({ query: { t: Date.now() } }) //直接刷新 |
||||
|
}) |
||||
|
} |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
// //空间成员管理 |
||||
|
// function onAccessManage(){ |
||||
|
// dynamicVNode.value = h(sharePermission, { |
||||
|
// uid: uid, |
||||
|
// uuid: "", |
||||
|
// spaceid:SpaceID.value.uuid, // |
||||
|
// confirmFunc: (_list: string[],_infos:string[]) => { |
||||
|
// // 组织权限数据 |
||||
|
// //_len=_list.length |
||||
|
// let permited = btoa(_list.join("|")) |
||||
|
// doAccessManage(uid,{ |
||||
|
// "space":SpaceID.value.uuid, |
||||
|
// "roles":permited, |
||||
|
// "owner":SpaceID.value.userUuid, |
||||
|
// "len":_list.length |
||||
|
// }).then(()=>{ |
||||
|
|
||||
|
// }) |
||||
|
// }, |
||||
|
// closeFunc: () => { |
||||
|
// dynamicVNode.value=null |
||||
|
// } |
||||
|
// }) |
||||
|
// } |
||||
|
|
||||
|
//删除空间 |
||||
|
function onDeleteSpace(row:matterInfo){ |
||||
|
ElMessageBox.confirm(`确认删除空间 ( ${row.name}) ? 空间内所有文件将不可恢复!取消则放弃删除操作。`, "警告", { |
||||
|
confirmButtonText: "确定删除", |
||||
|
cancelButtonText: "取消", |
||||
|
type: "warning", |
||||
|
}).then(()=>{ |
||||
|
doDelSpace(uid,{ |
||||
|
"space":row.uuid, |
||||
|
}).then(()=>{ |
||||
|
router.replace({ query: { t: Date.now() } }) |
||||
|
}) |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
//空间权限控制管理 |
||||
|
function onSpacePManage(row:matterInfo){ |
||||
|
// dynamicVNode.value=h(spacePermission,{ |
||||
|
// uid:rawUid, //当前用户的uuid |
||||
|
// uuid:row.uuid, //文档的uuid |
||||
|
// suid:SpaceID.value.userUuid, |
||||
|
// spaceid:SpaceID.value.uuid, //空间uuid |
||||
|
// closeFunc:()=>dynamicVNode.value=null |
||||
|
// }) |
||||
|
} |
||||
|
//------------------------------------------------------ |
||||
|
|
||||
|
function onSpaceConfig(row:matterInfo){ |
||||
|
showPopup.value=true; |
||||
|
currentHoverRow.value=row |
||||
|
if (row.manager) { |
||||
|
CutLevelPermit.value=9 |
||||
|
}else{ |
||||
|
let _pert: Record<string, number> |
||||
|
_pert = JSON.parse(row.permits!.data) |
||||
|
let val=_pert[rawUid.replace("p0","")] |
||||
|
if(val){ |
||||
|
CutLevelPermit.value = val |
||||
|
}else{ |
||||
|
CutLevelPermit.value = 0//没有权限!! |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
onMounted(()=>{ |
||||
|
if(userStore.userInfoCont == ""){ |
||||
|
userStore.getInfo().then(()=>{ |
||||
|
getMySpaces(uid,{roles:Departs.value}).then((resp)=>{ |
||||
|
resp.data.forEach((item)=>{ |
||||
|
let ismanager=false |
||||
|
if(item.userUuid==rawUid || item.managers.includes(rawUid)) ismanager=true; |
||||
|
spaceTreeData.value.push({name:item.name,uuid:item.uuid,dir:false,userUuid:item.userUuid,manager:ismanager,permits:item.permits}) |
||||
|
}) |
||||
|
}) |
||||
|
}) |
||||
|
}else{ |
||||
|
getMySpaces(uid,{roles:Departs.value}).then((resp)=>{ |
||||
|
resp.data.forEach((item)=>{ |
||||
|
let ismanager=false |
||||
|
if(item.userUuid==rawUid || item.managers.includes(rawUid)) ismanager=true; |
||||
|
spaceTreeData.value.push({name:item.name,uuid:item.uuid,dir:false,userUuid:item.userUuid,manager:ismanager,permits:item.permits}) |
||||
|
}) |
||||
|
}) |
||||
|
} |
||||
|
}) |
||||
|
|
||||
|
</script> |
||||
|
|
||||
|
<template> |
||||
|
<div class="app_container"> |
||||
|
<div> |
||||
|
我的 |
||||
|
<ul> |
||||
|
<li @click="onSelectSpace(null)"> |
||||
|
<div style="display: flex;align-items: center;"><svg-icon icon-class="folder-icon" :size="30" /> |
||||
|
<span>我的空间</span> |
||||
|
</div> |
||||
|
</li> |
||||
|
<li @click="onSelectSpace(null,true)"> |
||||
|
<div style="display: flex;align-items: center;"><svg-icon icon-class="folder-icon" :size="30" /> <span>回收站</span> |
||||
|
</div> |
||||
|
</li> |
||||
|
</ul> |
||||
|
</div> |
||||
|
<div style="margin: 20px 0;"> |
||||
|
共享 |
||||
|
<ul> |
||||
|
<li v-for="value in spaceTreeData" @click="onSelectSpace(value)"> |
||||
|
<div style="display: flex;align-items: center;"> |
||||
|
<svg-icon icon-class="folder-icon" :size="30" /> |
||||
|
<span>{{ value.name }}</span> |
||||
|
<el-button type="text" icon="MoreFilled" size="small" |
||||
|
@click="(e)=>{e.stopPropagation(); onSpaceConfig(value);}"></el-button> |
||||
|
</div> |
||||
|
<!-- <hr> --> |
||||
|
</li> |
||||
|
</ul> |
||||
|
</div> |
||||
|
<div style="margin: 20px 0; display: flex;align-items:center;" @click="onNewSpace"><Plus style="width: 18px; height: 18px;"></Plus> 创建共享空间</div> |
||||
|
</div> |
||||
|
<div v-if="showPopup" class="mask" @click="showPopup = false"></div> |
||||
|
<!-- 主体 --> |
||||
|
<Transition name="popuper"> |
||||
|
<div v-if="showPopup" class="bs-wrapper"> |
||||
|
<div class="popupTitle"> |
||||
|
<svg-icon icon-class="folder-icon" size="30px"/> |
||||
|
{{ currentHoverRow.name }} |
||||
|
<el-button type="text" @click="showPopup=false">关闭</el-button> |
||||
|
</div> |
||||
|
<hr> |
||||
|
<div class="blocker-list" v-if="CutLevelPermit>=5"> |
||||
|
<span class="blocker" @click="onSpaceMatterRename(currentHoverRow)"> |
||||
|
<Edit style="width: 24px; height: 24px;"></Edit>成员管理</span> |
||||
|
<span class="blocker" @click="onSpacePManage(currentHoverRow)"> |
||||
|
<Setting style="width: 24px; height: 24px;"></Setting>权限设置</span> |
||||
|
<span class="blocker" @click="onDeleteSpace(currentHoverRow)"> |
||||
|
<Delete style="width: 24px; height: 24px;"></Delete>删除</span> |
||||
|
<span class="blocker" @click="onSpaceMatterRename(currentHoverRow)"> |
||||
|
<Promotion style="width: 24px; height: 24px;"></Promotion>重命名</span> |
||||
|
</div> |
||||
|
</div> |
||||
|
</Transition> |
||||
|
<BottomPage /> |
||||
|
</template> |
||||
|
|
||||
|
<style lang="scss" scoped> |
||||
|
.app_container { |
||||
|
padding: 40px 29px;; |
||||
|
height: calc(100vh - 65px); |
||||
|
overflow: hidden; |
||||
|
position: relative; |
||||
|
} |
||||
|
hr{ |
||||
|
margin: 8px 5px 8px auto; |
||||
|
border: none; |
||||
|
width: 88%; |
||||
|
align-self: center; |
||||
|
background: #63616145; |
||||
|
} |
||||
|
ul >li{ |
||||
|
padding: 10px 0px 10px 10px; |
||||
|
background: white; |
||||
|
border-radius: 10px; |
||||
|
margin: 4px; |
||||
|
span{ |
||||
|
margin-left: 10px; |
||||
|
} |
||||
|
button{ |
||||
|
margin-left: auto; |
||||
|
margin-right: 0px; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
//---------------animation |
||||
|
/* 遮罩: popup 的遮罩 */ |
||||
|
.mask{ |
||||
|
position: fixed; |
||||
|
inset: 0; |
||||
|
background:rgba(0,0,0,.4); |
||||
|
z-index:999; |
||||
|
} |
||||
|
|
||||
|
.bs-wrapper{ |
||||
|
position: fixed; |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
left:0; |
||||
|
right:0; |
||||
|
bottom:0; |
||||
|
height:36vh; /* 半屏停住 */ |
||||
|
background:#f1f1f1; |
||||
|
border-radius:16px 16px 0 0; |
||||
|
z-index:1000; |
||||
|
overflow-y:auto; |
||||
|
padding: 8px 16px; |
||||
|
hr{ |
||||
|
margin: 8px 0; |
||||
|
border: none; |
||||
|
width: 88%; |
||||
|
align-self: center; |
||||
|
background: #63616145; |
||||
|
} |
||||
|
} |
||||
|
.popupTitle{ |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
button{ |
||||
|
margin-left: auto; |
||||
|
margin-right: 5px; |
||||
|
} |
||||
|
} |
||||
|
.blocker-list{ |
||||
|
display: grid; |
||||
|
grid-template-columns: 1fr 1fr 1fr 1fr; |
||||
|
font-size: small; |
||||
|
.blocker{ |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
align-items: center; |
||||
|
padding-top: 15px; |
||||
|
background-color: white; |
||||
|
border-radius: 5px; |
||||
|
margin: 6px; |
||||
|
width: 70px; |
||||
|
height: 70px; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.popuper-enter-from{ |
||||
|
transform:translateY(100%); |
||||
|
opacity:0; |
||||
|
} |
||||
|
/* 进入过程 */ |
||||
|
.popuper-enter-active{ |
||||
|
transition:all .3s ease-out; |
||||
|
} |
||||
|
/* 离开后:回到下方 */ |
||||
|
.popuper-leave-to{ |
||||
|
transform:translateY(50px); |
||||
|
opacity:0; |
||||
|
} |
||||
|
.popuper-leave-active{ |
||||
|
transition:all .3s ease-in; |
||||
|
} |
||||
|
//--------------------------- |
||||
|
|
||||
|
</style> |
||||
@ -0,0 +1,776 @@ |
|||||
|
<!-- |
||||
|
@ 作者: han2015 |
||||
|
@ 时间: 2025-05-12 15:39:13 |
||||
|
@ 备注: 文档管理组件 |
||||
|
--> |
||||
|
<script lang="ts" setup> |
||||
|
import { getExpirTime, getFileIcon, readableSize,fileType} from "./tools" |
||||
|
import sharePermission from './sharePermission.vue'; |
||||
|
import { useRoute } from 'vue-router' |
||||
|
import { userStror } from "@/utils/pinia/stores/modules/userOrders"; |
||||
|
import BottomPage from '@/views/common/bottom/index.vue' |
||||
|
import { getMatterList,postCreateDir,postDelMatter,postCreateShare,postMatterRename, |
||||
|
postDelMatBatch,getMySpaces,doCreateSpace,getRecyclingList} from "@/api/doc/index" |
||||
|
import { matterPage,matterInfo,respCreateShare,matterTree, doFileUpload,matterPermit} from "@/api/doc/type" |
||||
|
import { h } from 'vue' |
||||
|
import router from "@/utils/router"; |
||||
|
import { |
||||
|
Delete,View,Download,Share,Search,Edit, |
||||
|
Avatar,Grid,List, |
||||
|
ArrowLeft, |
||||
|
} from '@element-plus/icons-vue' |
||||
|
import {ElSelect,ElOption, ElText,ElInput,TableInstance,ElMessage,UploadFile, |
||||
|
UploadFiles,ElPagination,ElTree,TreeNode,ElDropdown,ElDropdownItem} from "element-plus"; |
||||
|
import preview from './preview.vue'; |
||||
|
import space from './space.vue'; |
||||
|
import spacePermission from './spacePermission.vue'; |
||||
|
import SvgIcon from '@/components/svgIcon/index.vue' |
||||
|
|
||||
|
|
||||
|
const userStore = userStror(); |
||||
|
const route = useRoute() |
||||
|
const uid=btoa("p0"+userStore.userInfoCont.userId); |
||||
|
const rawUid="p0"+userStore.userInfoCont.userId |
||||
|
const siteHost=document.location.origin; |
||||
|
const apiURL=import.meta.env.VITE_APP_BASE_API+"/hxpan/api" |
||||
|
const officeHost=import.meta.env.VITE_OFFICE_HOST |
||||
|
const showPopup=ref(false) |
||||
|
const currentHoverRow=ref<matterInfo>({}) //table 行的按钮控制 |
||||
|
const modListOrGrild=ref(true) //列表显示还是栅格显示 |
||||
|
|
||||
|
const matterList = ref<matterInfo[]>([]) |
||||
|
const searchname=ref("") |
||||
|
const newdirName=ref("") //创建新目录时的目录名 |
||||
|
const selectedValue = ref("sixhour") //分享弹窗的世间变量 |
||||
|
const tabSelected=ref<matterInfo[]>([]) //table组件多选数据维护 |
||||
|
//to support tree mode refactor |
||||
|
const breadcrumbList=ref<matterInfo[]>([{name:"根目录",uuid:"root", dir:true}]) //面包屑导航 |
||||
|
const currentNode=ref<matterTree>({}) //打开的路径层次 |
||||
|
|
||||
|
const dynamicVNode = ref<VNode | null>(null) //permission 组件的父组件 |
||||
|
|
||||
|
const multipleTableRef = ref<TableInstance>() |
||||
|
const paginInfo = ref({ page: 0, total: 0 }) |
||||
|
|
||||
|
const PRIVATESPACE = ref(true) //是空间状态的控制 2种:私有云盘和共享空间 |
||||
|
|
||||
|
const modRecycling=ref(false) |
||||
|
const uploadFormData = computed(() => { |
||||
|
return { |
||||
|
userUuid: uid, // 用户的uuid |
||||
|
puuid: currentNode.value.uuid, // 父目录的uuid,基目录为root |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
|
||||
|
//--------------单文件分享------------- |
||||
|
function onShareMatter(row?:matterInfo){ |
||||
|
const showSharePermission=ref(false) |
||||
|
let permited ="" |
||||
|
let infos="" |
||||
|
let _len=0 |
||||
|
ElMessageBox({ |
||||
|
title: row?.name+' 请选择分享有效时间', |
||||
|
message: () => h('div',{style:{ width:'660px'}},[ |
||||
|
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' }), |
||||
|
]), |
||||
|
h(ElButton, { |
||||
|
style: "width:30px;margin:0 10px;", |
||||
|
icon: Avatar, |
||||
|
onClick: () => { |
||||
|
showSharePermission.value = true |
||||
|
} |
||||
|
}), |
||||
|
h(sharePermission, { |
||||
|
uid: uid, |
||||
|
uuid: row?.uuid ?? "", |
||||
|
modelValue: showSharePermission.value, |
||||
|
confirmFunc: (_list: string[],_infos:string[]) => { |
||||
|
// 组织权限数据 |
||||
|
_len=_list.length |
||||
|
permited = btoa(_list.join("|")) |
||||
|
infos=_infos.join("|"), |
||||
|
showSharePermission.value = false |
||||
|
}, |
||||
|
closeFunc: () => { |
||||
|
showSharePermission.value = false |
||||
|
} |
||||
|
}) |
||||
|
] |
||||
|
), |
||||
|
showCancelButton: true |
||||
|
}).then(() => { |
||||
|
let param; |
||||
|
if (row){ |
||||
|
param={matterUuids:row.uuid,expireInfinity:false,expireTime:"", |
||||
|
permitList:permited,len:_len,permitInfos:infos} |
||||
|
}else if (tabSelected.value.length>1){ |
||||
|
param={matterUuids:tabSelected.value.map((item:matterInfo)=>item.uuid).join(","),expireInfinity:false,expireTime:"", |
||||
|
permitList:permited,len:_len,permitInfos:infos} |
||||
|
} |
||||
|
|
||||
|
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 onDelNodeMatter(row:matterInfo){ |
||||
|
onDelMatter(row,true) |
||||
|
} |
||||
|
//删除 |
||||
|
function onDelMatter(row:matterInfo,dir?:boolean){ |
||||
|
if (row.uuid){ |
||||
|
ElMessageBox.confirm(`确认删除( ${row.name}) ?删除后不可恢复!取消则放弃删除操作。`, "警告", { |
||||
|
confirmButtonText: "确定", |
||||
|
cancelButtonText: "取消", |
||||
|
type: "warning", |
||||
|
}).then(()=>{ |
||||
|
postDelMatter(uid,{ |
||||
|
"uuid":row.uuid |
||||
|
}).then(()=>{ |
||||
|
if (row.dir || dir) { |
||||
|
const node = treeRef.value.getNode(row.uuid) |
||||
|
if (node) { |
||||
|
treeRef.value.remove(node) |
||||
|
} |
||||
|
currentNode.value.uuid = row.puuid ?? "root" |
||||
|
} |
||||
|
onLoadMatterList() |
||||
|
}) |
||||
|
}) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function onDelMatBatch(){ |
||||
|
ElMessageBox.confirm("确认删除选择的内容?", "警告", { |
||||
|
confirmButtonText: "确定", |
||||
|
cancelButtonText: "取消", |
||||
|
type: "warning", |
||||
|
}).then(()=>{ |
||||
|
postDelMatBatch(uid,{ |
||||
|
"uuids":tabSelected.value.map((item:matterInfo)=>item.uuid).join(",") |
||||
|
}).then(()=>{ |
||||
|
router.replace({ query: { t: Date.now() } }) |
||||
|
}) |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
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 getImagePreivewURL(_uuid:string,_name:string){ |
||||
|
return `${apiURL}/alien/preview/${_uuid}/${_name}?ir=fill_100_100` |
||||
|
} |
||||
|
|
||||
|
function getImageDownloadURL(_uuid:string,_name:string){ |
||||
|
return `${apiURL}/alien/preview/${_uuid}/${_name}` |
||||
|
} |
||||
|
|
||||
|
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(() => { |
||||
|
if(newname.value&&newname.value!=""){ |
||||
|
postMatterRename(uid,{ |
||||
|
uuid:row.uuid, |
||||
|
name:newname.value, |
||||
|
}).then(()=>onLoadMatterList()) |
||||
|
} |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
//加载文件列表 |
||||
|
function onSearchFile(name?:string){ |
||||
|
let _page: matterPage = { |
||||
|
page: 0, |
||||
|
pageSize: 50, |
||||
|
orderCreateTime: "DESC", |
||||
|
orderDir: "DESC", |
||||
|
name:name |
||||
|
}; |
||||
|
|
||||
|
getMatterList(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.filter((item)=>{ |
||||
|
return !item.dir |
||||
|
}) |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
//获取回收站文件 |
||||
|
function showRecycling(){ |
||||
|
currentNode.value={} //清空当前节点 |
||||
|
|
||||
|
getRecyclingList(uid,{}).then((resp)=>{ |
||||
|
paginInfo.value={total:1,page:0} |
||||
|
matterList.value=resp.data |
||||
|
}) |
||||
|
} |
||||
|
//恢复删除文件 |
||||
|
function restoreMatter(row:matterInfo){ |
||||
|
if (row.uuid){ |
||||
|
ElMessageBox.confirm(`确认要恢复( ${row.name}) ?`, "警告", { |
||||
|
confirmButtonText: "确定", |
||||
|
cancelButtonText: "取消", |
||||
|
type: "warning", |
||||
|
}).then(()=>{ |
||||
|
postDelMatter(uid,{ |
||||
|
"uuid":row.uuid, |
||||
|
"restore":"true", |
||||
|
}).then(()=>{ |
||||
|
ElMessage({ |
||||
|
message: '已成功恢复文件,请在个人空间查看。', |
||||
|
type: 'success', |
||||
|
plain: true, |
||||
|
}) |
||||
|
}) |
||||
|
}) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
//加载目录文件列表 |
||||
|
function onLoadMatterList(){ |
||||
|
let _page: matterPage = { |
||||
|
page: paginInfo.value.page, |
||||
|
pageSize: 50, |
||||
|
orderCreateTime: "DESC", |
||||
|
orderDir: "DESC", |
||||
|
puuid:currentNode.value.uuid, |
||||
|
deleted:false |
||||
|
}; |
||||
|
|
||||
|
getMatterList(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 |
||||
|
//2025-08-07: 保持space一样,文件夹一并显示 |
||||
|
// .filter((item)=>{ |
||||
|
// return !item.dir |
||||
|
// }) |
||||
|
}) |
||||
|
} |
||||
|
//----------for dir----------- |
||||
|
function createDir(){ |
||||
|
if(matterList.value){ |
||||
|
if (matterList.value[0].name=="") return; |
||||
|
} |
||||
|
matterList.value?.unshift({ |
||||
|
name:"", |
||||
|
userUuid:uid, |
||||
|
puuid:"", |
||||
|
uuid:"", |
||||
|
dir:true, |
||||
|
size:0, |
||||
|
deleted:false, |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
function onCreateDir(){ |
||||
|
postCreateDir(uid,{ |
||||
|
userUuid:uid, |
||||
|
puuid:currentNode.value.uuid, |
||||
|
name:newdirName.value, |
||||
|
}).then((resp)=> { |
||||
|
newdirName.value="" |
||||
|
matterList.value?.shift() //删除头部的空位 |
||||
|
matterList.value?.push(resp.data) //把新建的添加到最后 |
||||
|
}) |
||||
|
.catch((e)=>{ |
||||
|
ElMessage.error(e.msg) |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
function onDBclickMatter(row:matterInfo,ind?:number){ |
||||
|
if (getFileIcon(row.name)=="img"||row.deleted) return; |
||||
|
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() |
||||
|
} |
||||
|
}else{ |
||||
|
onPrivateView(row) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
//文件预览 |
||||
|
function onPrivateView(row:matterInfo){ |
||||
|
if(row.dir){ |
||||
|
alert("目录不支持预览") |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
let _token=document.cookie.match(/hxpan=([\w-]*)/) |
||||
|
if (_token&&_token.length>1){ |
||||
|
_token=_token[1] |
||||
|
} |
||||
|
|
||||
|
const _type=fileType(row.name!) |
||||
|
if(_type!==""){ //office file |
||||
|
const info =btoa(encodeURIComponent(`${row.name}`)) //预览模式不需要绝对路径,只核对一下文件名即可 |
||||
|
const _url=`${siteHost}${apiURL}/alien/download/${row.uuid}/${row.name}?access_token=${_token}` |
||||
|
//前半部分内容是为了校验信息,主要内容是fileurl |
||||
|
//window.open(`/#/onlyoffice?name=${row.name}&dtype=${_type}&info=${info}&fileurl=`+window.btoa(encodeURIComponent(_url)),"_blank") |
||||
|
router.push({ path: "/onlyoffice",query:{fa:7,name:row.name,dtype:_type,info:info,fileurl: window.btoa(encodeURIComponent(_url)) }}); |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
const a = `${row.uuid}${row.name}` |
||||
|
const _url=`${siteHost}${apiURL}/alien/download/${row.uuid}/${row.name}?access_token=${_token}&fullfilename=${a}` |
||||
|
|
||||
|
dynamicVNode.value=h(preview,{ |
||||
|
url:`${officeHost}/kkpreview/onlinePreview?url=`+window.btoa(unescape(encodeURIComponent(_url))), |
||||
|
closeFunc:()=>dynamicVNode.value=null |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
|
||||
|
//--------------UPGRADE: multy file upload section---------------- |
||||
|
|
||||
|
//自定义上传,目的是支持多文件上传, 使用html原生组件,是为了解决并发问题 |
||||
|
async function onCustomUpload(e:Event){ |
||||
|
const files = e.target!.files??[] |
||||
|
for(let ff of files){ |
||||
|
await handleSingleFile(ff) |
||||
|
} |
||||
|
onLoadMatterList() //刷新 |
||||
|
} |
||||
|
|
||||
|
async function handleSingleFile(ff:File){ |
||||
|
const fields=new FormData() |
||||
|
fields.append("userUuid",uploadFormData.value.userUuid) |
||||
|
fields.append("puuid",uploadFormData.value.puuid) |
||||
|
fields.append("file",ff) |
||||
|
|
||||
|
const res = await doFileUpload(fields,'/hxpan/api/matter/upload') |
||||
|
if(res.code!=200){ |
||||
|
console.log(ff.name+"上传失败! ") |
||||
|
alert(ff.name+"上传失败! ") |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
//----------------------------------------------------------- |
||||
|
|
||||
|
function updateListOrGrid(val:boolean){ |
||||
|
modListOrGrild.value=val |
||||
|
if(val){ |
||||
|
localStorage.setItem("listOrGrid","true") |
||||
|
}else{ |
||||
|
localStorage.setItem("listOrGrid","false") |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
//http://172.20.2.87:6010/api/alien/preview/5a10aaf6-396e-4d9a-7e87-3c5c8029d4db/123.png?ir=fill_100_100 |
||||
|
//渲染完页面再执行 |
||||
|
onMounted(() => { |
||||
|
if (route.query.recycling){ |
||||
|
modRecycling.value=true |
||||
|
showRecycling() |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
let val =localStorage.getItem("listOrGrid") |
||||
|
if(val&&val=="false"){ |
||||
|
modListOrGrild.value=false |
||||
|
}else{ |
||||
|
modListOrGrild.value=true |
||||
|
} |
||||
|
|
||||
|
currentNode.value={name:'个人空间',uuid:'root',dir:false} |
||||
|
onLoadMatterList() |
||||
|
}); |
||||
|
|
||||
|
|
||||
|
</script> |
||||
|
|
||||
|
<template> |
||||
|
<div v-if="PRIVATESPACE" class="app_container"> |
||||
|
<el-row v-if="modRecycling"> |
||||
|
<el-col class="search" style="padding:0px;width: 80%;"> |
||||
|
<el-button type="text" :icon="ArrowLeft" @click="router.back()"> </el-button> |
||||
|
<span style="margin: 0px 10px;font-weight: bold;font-size: 20px;"> 回收站 </span> |
||||
|
</el-col> |
||||
|
</el-row> |
||||
|
|
||||
|
<el-row v-else :gutter="24" style="margin: 12px 0px;"> |
||||
|
<el-col class="search" style="padding:0px;width: 80%;"> |
||||
|
<el-button type="text" :icon="ArrowLeft" @click="router.back()"> </el-button> |
||||
|
<el-input placeholder="搜索文件" v-model="searchname" @blur="searchname===''?onLoadMatterList():''"/> |
||||
|
<el-button :icon="Search" @click="onSearchFile(searchname)"></el-button> |
||||
|
</el-col> |
||||
|
<div class="nav-header"> |
||||
|
<el-breadcrumb separator=">" style="align-content: center;"> |
||||
|
<el-breadcrumb-item v-for="(item,index) in breadcrumbList" |
||||
|
:key="index" @click="onDBclickMatter(item,index)"> |
||||
|
<span style="font-weight: bold;">{{ item.name }}</span> |
||||
|
</el-breadcrumb-item> |
||||
|
</el-breadcrumb> |
||||
|
|
||||
|
<span v-if="modListOrGrild" @click="updateListOrGrid(false)"><Grid class="plus-icon"></Grid></span> |
||||
|
<span v-else @click="updateListOrGrid(true)"><List class="plus-icon"></List></span> |
||||
|
</div> |
||||
|
</el-row> |
||||
|
|
||||
|
<el-row v-if="!modRecycling" :gutter="24"> |
||||
|
<el-col> |
||||
|
<div class="el-button el-button--default" style="position: relative;"> |
||||
|
<el-icon><Upload /></el-icon> |
||||
|
<input type="file" style="position: absolute;opacity: 0;width: 50px;" |
||||
|
@change="onCustomUpload" multiple /> |
||||
|
上传 |
||||
|
</div> |
||||
|
|
||||
|
<el-button icon="plus" @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-col> |
||||
|
</el-row> |
||||
|
|
||||
|
<el-row :gutter="24" > |
||||
|
<el-table v-if="modListOrGrild" |
||||
|
stripe |
||||
|
:data="matterList" |
||||
|
ref="multipleTableRef" |
||||
|
:header-cell-style="{ background: '#f5f8fd' }" |
||||
|
row-key="uuid" |
||||
|
:row-style ="() => ({ lineHeight: '36px'})"> |
||||
|
<el-table-column property="name"> |
||||
|
<template #default="scope"> |
||||
|
<input v-if="scope.row.name===''" v-model="newdirName" type="link" autofocus |
||||
|
placeholder="文件夹名" style="border:groove;height:30px;" @change="onCreateDir" /> |
||||
|
<div v-else style="display: flex; align-items: center;" > |
||||
|
<svg-icon v-if="scope.row.dir" icon-class="folder-icon" size="30px"/> |
||||
|
<el-image v-else-if="getFileIcon(scope.row.name)==='img'" style="width: 30px;" |
||||
|
:preview-src-list="[getImageDownloadURL(scope.row.uuid,scope.row.name)]" |
||||
|
:src="getImagePreivewURL(scope.row.uuid,scope.row.name)" /> |
||||
|
<svg-icon v-else :icon-class="getFileIcon(scope.row.name)+'-icon'" size="30px" /> |
||||
|
|
||||
|
<span style="margin-left: 10px" @click="onDBclickMatter(scope.row)" >{{ scope.row.name }} |
||||
|
<br> <span>{{readableSize(scope.row)}}</span> <span v-if="scope.row.updateTime">/ {{ scope.row.updateTime.slice(0,16) }}</span> |
||||
|
</span> |
||||
|
|
||||
|
<el-button v-if="scope.row.deleted" class="setBtn" type="text" @click="restoreMatter(scope.row)">恢复</el-button> |
||||
|
<el-button v-else class="setBtn" type="text" icon="MoreFilled" size="small" |
||||
|
@click="(e)=>{e.stopPropagation(); showPopup=true; currentHoverRow=scope.row;}"></el-button> |
||||
|
</div> |
||||
|
</template> |
||||
|
</el-table-column> |
||||
|
</el-table> |
||||
|
|
||||
|
<div class="table-grid" v-else> |
||||
|
<div class="grid-item" v-for="row in matterList"> |
||||
|
<div class="grid"> |
||||
|
<div v-if="row.name===''"> |
||||
|
<svg-icon icon-class="folder-icon" size="80px"/> |
||||
|
<input v-model="newdirName" type="text" autofocus placeholder="文件夹名" style="border:groove;height:30px;" @change="onCreateDir" /> |
||||
|
</div> |
||||
|
|
||||
|
<div v-else class="grid-box" @click="onDBclickMatter(row)"> |
||||
|
<el-button class="setBtn" type="text" icon="MoreFilled" size="small" |
||||
|
@click="(e)=>{e.stopPropagation(); showPopup=true; currentHoverRow=row;}"></el-button> |
||||
|
|
||||
|
<svg-icon v-if="row.dir" icon-class="folder-icon" size="50px"/> |
||||
|
<el-image v-else-if="getFileIcon(row.name)==='img'" style="width: 50px;" :preview-src-list="[getImageDownloadURL(row.uuid,row.name)]" :src="getImagePreivewURL(row.uuid,row.name)" /> |
||||
|
<svg-icon v-else :icon-class="getFileIcon(row.name)+'-icon'" size="50px"/> |
||||
|
<span style="margin: 5px 0;text-wrap-mode:nowrap;font-size: 13px;">{{ row.name }}</span> |
||||
|
</div> |
||||
|
</div> |
||||
|
<ul v-if="row.name!=''" class="grid-menus" v-show="currentHoverRow === row.uuid" @mouseleave="currentHoverRow=''"> |
||||
|
<span v-if="row.deleted"> |
||||
|
<li @click="restoreMatter(row)">恢复</li> |
||||
|
</span> |
||||
|
<span v-else> |
||||
|
<li v-if="getFileIcon(row.name)!='img'" @click="onPrivateView(row)">预览</li> |
||||
|
<li @click="onShareMatter(row)">分享</li> |
||||
|
<li @click="onDownload(row)">下载</li> |
||||
|
<li @click="onDelMatter(row)">删除</li> |
||||
|
<li @click="onMatterRename(row)">重命名</li> |
||||
|
</span> |
||||
|
</ul> |
||||
|
</div> |
||||
|
</div> |
||||
|
</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="showPopup" class="mask" @click="showPopup = false"></div> |
||||
|
<!-- 主体 --> |
||||
|
<Transition name="popuper"> |
||||
|
<div v-if="showPopup" class="bs-wrapper"> |
||||
|
<div class="popupTitle"> |
||||
|
<svg-icon v-if="currentHoverRow.dir" icon-class="folder-icon" size="30px"/> |
||||
|
<svg-icon v-else :icon-class="getFileIcon(currentHoverRow.name)+'-icon'" size="30px"/> |
||||
|
{{ currentHoverRow.name }} | {{readableSize(currentHoverRow)}} |
||||
|
<el-button type="text" @click="showPopup=false">关闭</el-button> |
||||
|
</div> |
||||
|
<hr> |
||||
|
<div class="blocker-list"> |
||||
|
<span class="blocker" @click="onPrivateView(currentHoverRow)"> |
||||
|
<View class="plus-icon"></View>预览</span> |
||||
|
<span class="blocker" @click="onShareMatter(currentHoverRow)"> |
||||
|
<View class="plus-icon"></View>分享</span> |
||||
|
<span class="blocker" @click="onDownload(currentHoverRow)"> |
||||
|
<Download class="plus-icon"></Download>下载</span> |
||||
|
<span class="blocker" @click="onDelMatter(currentHoverRow)"> |
||||
|
<Delete class="plus-icon"></Delete>删除</span> |
||||
|
<span class="blocker" @click="onMatterRename(currentHoverRow)"> |
||||
|
<Edit class="plus-icon"></Edit>重命名</span> |
||||
|
</div> |
||||
|
</div> |
||||
|
</Transition> |
||||
|
|
||||
|
<div v-if="dynamicVNode"> |
||||
|
<component :is="dynamicVNode" /> |
||||
|
</div> |
||||
|
<BottomPage /> |
||||
|
</template> |
||||
|
|
||||
|
<style lang="scss" scoped> |
||||
|
.app_container { |
||||
|
padding: 10px 15px 0px 15px; |
||||
|
height: calc(100vh - 65px); |
||||
|
overflow: hidden; |
||||
|
position: relative; |
||||
|
} |
||||
|
.nav-header{ |
||||
|
margin-top: 21px; |
||||
|
width: 100%; |
||||
|
display: flex; |
||||
|
span{ |
||||
|
margin-left: auto; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.search{ |
||||
|
margin-left: auto; |
||||
|
display:inherit; |
||||
|
.el-input{ |
||||
|
margin-left: 15px; |
||||
|
} |
||||
|
} |
||||
|
.shareDialog{ |
||||
|
--el-messagebox-width:'800px'; |
||||
|
padding:40px; |
||||
|
.el-text{ |
||||
|
align-self: flex-start; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.spaceitem{ |
||||
|
height: 50px; |
||||
|
align-content: center; |
||||
|
padding: 14px; |
||||
|
color: #606266; |
||||
|
font-weight:bold; |
||||
|
} |
||||
|
|
||||
|
.dynamic-width-message-box-byme .el-message-box__message{ |
||||
|
width: 100%; |
||||
|
} |
||||
|
|
||||
|
.el-overlay-message-box{ |
||||
|
top: 200x; |
||||
|
bottom: auto; |
||||
|
} |
||||
|
|
||||
|
.setBtn{ |
||||
|
margin-right: 10px; |
||||
|
margin-left: auto; |
||||
|
} |
||||
|
|
||||
|
//---------------animation |
||||
|
/* 遮罩: popup 的遮罩 */ |
||||
|
.mask{ |
||||
|
position: fixed; |
||||
|
inset: 0; |
||||
|
background:rgba(0,0,0,.4); |
||||
|
z-index:999; |
||||
|
} |
||||
|
|
||||
|
.bs-wrapper{ |
||||
|
position: fixed; |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
left:0; |
||||
|
right:0; |
||||
|
bottom:0; |
||||
|
height:36vh; /* 半屏停住 */ |
||||
|
background:#f1f1f1; |
||||
|
border-radius:16px 16px 0 0; |
||||
|
z-index:1000; |
||||
|
overflow-y:auto; |
||||
|
padding: 8px 16px; |
||||
|
hr{ |
||||
|
margin: 8px 0; |
||||
|
border: none; |
||||
|
width: 88%; |
||||
|
align-self: center; |
||||
|
background: #63616145; |
||||
|
} |
||||
|
} |
||||
|
.popupTitle{ |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
button{ |
||||
|
margin-left: auto; |
||||
|
margin-right: 5px; |
||||
|
} |
||||
|
} |
||||
|
.blocker-list{ |
||||
|
display: grid; |
||||
|
grid-template-columns: 1fr 1fr 1fr 1fr; |
||||
|
font-size: small; |
||||
|
.blocker{ |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
align-items: center; |
||||
|
padding-top: 15px; |
||||
|
background-color: white; |
||||
|
border-radius: 5px; |
||||
|
margin: 6px; |
||||
|
width: 70px; |
||||
|
height: 70px; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.popuper-enter-from{ |
||||
|
transform:translateY(100%); |
||||
|
opacity:0; |
||||
|
} |
||||
|
/* 进入过程 */ |
||||
|
.popuper-enter-active{ |
||||
|
transition:all .3s ease-out; |
||||
|
} |
||||
|
/* 离开后:回到下方 */ |
||||
|
.popuper-leave-to{ |
||||
|
transform:translateY(50px); |
||||
|
opacity:0; |
||||
|
} |
||||
|
.popuper-leave-active{ |
||||
|
transition:all .3s ease-in; |
||||
|
} |
||||
|
//--------------------------- |
||||
|
|
||||
|
.plus-icon{ |
||||
|
width: 20px; |
||||
|
height: 20px; |
||||
|
} |
||||
|
|
||||
|
.table-grid{ |
||||
|
display: grid; |
||||
|
grid-template-columns: 1fr 1fr 1fr; |
||||
|
align-content: flex-start; |
||||
|
.grid-item{ |
||||
|
position: relative; |
||||
|
width: 112px; |
||||
|
height: 112px; |
||||
|
margin: 5px; |
||||
|
background: white; |
||||
|
border-radius: 10px; |
||||
|
.setBtn{ |
||||
|
position: absolute; |
||||
|
top: 0px; |
||||
|
right: -10px; |
||||
|
} |
||||
|
.grid-box{ |
||||
|
padding-top: 18px; |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
overflow: hidden; |
||||
|
align-items: center; |
||||
|
text-align:center; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
</style> |
||||
|
<style> |
||||
|
:root{ |
||||
|
--el-index-normal:auto; |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,93 @@ |
|||||
|
<script lang="ts" setup> |
||||
|
import { useRoute } from 'vue-router' |
||||
|
import { DocumentEditor } from "@onlyoffice/document-editor-vue"; |
||||
|
import { userStror } from "@/utils/pinia/stores/modules/userOrders"; |
||||
|
import router from "@/utils/router"; |
||||
|
|
||||
|
const route = useRoute() |
||||
|
const userStore = userStror(); |
||||
|
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 config=ref({ |
||||
|
document: { |
||||
|
title: "Example Document Title.docx", |
||||
|
url:"", //云盘服务文件地址 |
||||
|
key:"" |
||||
|
}, |
||||
|
|
||||
|
documentType: "word", |
||||
|
editorConfig: { |
||||
|
"lang": "zh-CN",//"en-US", |
||||
|
mode: "view",//设置编辑模式,view, edit,review |
||||
|
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(window.atob(query.fileurl)) |
||||
|
const name=query.name?.toString()??"" |
||||
|
const dtype=query.dtype?.toString()??"word" |
||||
|
|
||||
|
//验证一下文件名是否合规 |
||||
|
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+(new Date().getTime().toString()) //合作编辑必须要有一个unique key |
||||
|
config.value.documentType=dtype |
||||
|
config.value.editorConfig.callbackUrl+=`?info=${_info}` |
||||
|
//设置编辑模式 |
||||
|
if(!query.verify) return; |
||||
|
const _verify = atob(query.verify) |
||||
|
//1:必须是验证通过,以true结尾 2:校验uuid是否匹配 |
||||
|
if(_verify.endsWith("true") && _key.includes(_verify.replace("true",""))){ |
||||
|
config.value.editorConfig.mode="edit" |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
}) |
||||
|
|
||||
|
</script> |
||||
|
|
||||
|
<template> |
||||
|
<el-button type="text" style="position: absolute;" @click="router.back()">返回</el-button> |
||||
|
<DocumentEditor |
||||
|
id="docEditor" |
||||
|
style="height: inherit;" |
||||
|
:documentServerUrl="onlyOfficeHost" |
||||
|
:config="config" |
||||
|
:onLoadComponentError="onLoadComponentError" |
||||
|
/> |
||||
|
</template> |
||||
|
<style> |
||||
|
body{ |
||||
|
height: 100vh; |
||||
|
} |
||||
|
#app{ |
||||
|
height: 100%; |
||||
|
} |
||||
|
|
||||
|
</style> |
||||
@ -0,0 +1,13 @@ |
|||||
|
<template #default="scope"> |
||||
|
<div v-show="currentHoverRow === scope.row.uuid"> |
||||
|
<span v-if="scope.row.deleted"> |
||||
|
<el-button type="text" @click="restoreMatter(scope.row)">恢复</el-button> |
||||
|
</span> |
||||
|
<span v-else> |
||||
|
<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="Delete" circle @click="onDelMatter(scope.row)"></el-button> |
||||
|
</span> |
||||
|
</div> |
||||
|
</template> |
||||
@ -0,0 +1,35 @@ |
|||||
|
<script lang="ts" setup> |
||||
|
|
||||
|
import { ElDialog } from 'element-plus'; |
||||
|
|
||||
|
const props = withDefaults(defineProps<{ |
||||
|
url:string, |
||||
|
closeFunc:()=>void, //父级只需销毁组件 |
||||
|
}>(),{}) |
||||
|
|
||||
|
onMounted(()=>{}) |
||||
|
|
||||
|
</script> |
||||
|
|
||||
|
<template> |
||||
|
<el-dialog :model-value="true" :style="{height: '90%',width:'90%'}" v-on:close="closeFunc()"> |
||||
|
<template #default> |
||||
|
<iframe id="preivew" width="100%" height="100%" :src="props.url"></iframe> |
||||
|
</template> |
||||
|
|
||||
|
</el-dialog> |
||||
|
</template> |
||||
|
<style> |
||||
|
/* dialog的body内容样式设置*/ |
||||
|
.el-dialog__body{ |
||||
|
height: 96%; |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
align-items: center; |
||||
|
} |
||||
|
.el-dialog{ |
||||
|
/* 让整个弹出窗口位置更高一些*/ |
||||
|
--el-dialog-margin-top:7vh; |
||||
|
} |
||||
|
|
||||
|
</style> |
||||
@ -0,0 +1,203 @@ |
|||||
|
<script lang="ts" setup> |
||||
|
import { |
||||
|
getOrgTreeList, |
||||
|
getArchivesListPage, |
||||
|
shareOrgInfo, |
||||
|
memberInfo, |
||||
|
getPermitedList, |
||||
|
getSpaceMemberList, |
||||
|
postPermitedList} from '@/api/hr/user/share_ctrol' |
||||
|
|
||||
|
import { ElDialog, ElMessageBox,TableInstance, TreeInstance } from 'element-plus'; |
||||
|
|
||||
|
const props = withDefaults(defineProps<{ |
||||
|
uid:string, //当前用户的uuid |
||||
|
uuid:string, //文档的uuid |
||||
|
spaceid?:string, //支持空间feature |
||||
|
confirmFunc?:(data:string[],infos:string[])=>void, //父级组件完全接管提交流程,组件不在做相关处理 |
||||
|
closeFunc:(refresh?:boolean)=>void, //父级只需销毁组件 |
||||
|
}>(),{}) |
||||
|
|
||||
|
const treeRef=ref<TreeInstance>() //tree组件的引用,为了实现初始选中状态 |
||||
|
const treeData =ref<shareOrgInfo[]>([]) // 组织结构树的数据源 |
||||
|
const members=ref<memberInfo[]>([]) //tablelist's data |
||||
|
const permited=new Set<string>() //文档成员的id列表,用集合结构为了快速实现增删修改 |
||||
|
const treeSelected=new Array<number>() //所有选中的tree节点 |
||||
|
const permitedInfos=new Set<string>() //文档成员的姓名职位信息集合,为了实现与成员列表的同步修改 |
||||
|
const tableMembersRef=ref<TableInstance>() //table组件的引用,为了实现初始选中状态 |
||||
|
const memberName=ref("")//通过姓名查找成员 |
||||
|
|
||||
|
function onNodeClick(data:shareOrgInfo){ |
||||
|
members.value=[]//这里在切换时应该清理现有数据,容易造成tableRef选中事件失效 |
||||
|
getArchivesListPage({adminorg:data.id, page:1,pagesize:13}).then(resp=>{ |
||||
|
if(resp.data.count >100){ |
||||
|
ElMessageBox.alert("人数过多,不再显示具体人员,请分配部门可见权限") |
||||
|
return |
||||
|
} |
||||
|
members.value=resp.data.list |
||||
|
|
||||
|
//设置默认选中的人员 |
||||
|
setTimeout(() => { |
||||
|
members.value.forEach(row => { |
||||
|
if (permited.has('p0'+row.keystr)) { |
||||
|
// 确保 row-key 正确并且数据一致,手动选中行, 还要设置:reserve-selection="true"!!! |
||||
|
tableMembersRef.value.toggleRowSelection(row,true) |
||||
|
} |
||||
|
}) |
||||
|
}, 500) |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
function onSavePermChange(){ |
||||
|
let _dprt= treeRef.value?.getCheckedKeys() //这里不能只获取叶子节点,因为有些员工的所属部门并不是叶子节点 |
||||
|
let _dprtNodes= treeRef.value?.getCheckedNodes(false,true) |
||||
|
let _list = permited.keys().toArray().filter(val=>val!=='') //成员的key |
||||
|
let _infos = permitedInfos.keys().toArray().filter(val=>(val!==''&&!val.includes(":"))) //成员的姓名和部门信息 |
||||
|
|
||||
|
//把部门权限加入list列表 |
||||
|
//dprt:d101d102d303.... |
||||
|
_list.push("dprt:d"+_dprt?.join("d")) |
||||
|
//把部门名称加入infos列表 |
||||
|
_dprtNodes?.forEach((item=>_infos.push(item.level+":"+item.name))) |
||||
|
|
||||
|
//由父级组件自主处理,并自动清理本弹窗组件 |
||||
|
if(props.confirmFunc&&_list){ |
||||
|
props.confirmFunc(_list,_infos) |
||||
|
props.closeFunc() //关闭销毁 |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
//btoa 是浏览器默认base64码工具 |
||||
|
postPermitedList(props.uid,{ |
||||
|
permitList: btoa(_list.join("|")), |
||||
|
permitInfos:_infos.join("|"), |
||||
|
update: "true", |
||||
|
uid: props.uid, |
||||
|
uuid: props.uuid, |
||||
|
len: _list.length |
||||
|
}).then(resp=>{ |
||||
|
props.closeFunc(true) |
||||
|
}).catch(()=>{ |
||||
|
ElMessageBox.alert("处理失败") |
||||
|
return |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
function onManualSelect(select:[],row:memberInfo){ |
||||
|
if(permited.has('p0'+row.keystr)){ //取消 |
||||
|
permited.delete('p0'+row.keystr) |
||||
|
permitedInfos.delete(`${row.name}-${row.maindeparmentname}-${row.positionname}`) |
||||
|
}else{ |
||||
|
permited.add('p0'+row.keystr) //添加 |
||||
|
permitedInfos.add(`${row.name}-${row.maindeparmentname}-${row.positionname}`) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function onSelectionAll(news:memberInfo[]){ |
||||
|
if(news.length>0){ |
||||
|
members.value.forEach((item)=>{ |
||||
|
permited.add('p0'+item.keystr) |
||||
|
permitedInfos.add(`${item.name}-${item.maindeparmentname}-${item.positionname}`) |
||||
|
}) |
||||
|
}else{ |
||||
|
members.value.forEach((item)=>{ |
||||
|
permited.delete('p0'+item.keystr) |
||||
|
permitedInfos.delete(`${item.name}-${item.maindeparmentname}-${item.positionname}`) |
||||
|
}) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function onFindMemberByName(name:string){ |
||||
|
getArchivesListPage({keywords:name, page:1,pagesize:20}).then(resp=>{ |
||||
|
members.value=resp.data.list |
||||
|
memberName.value="" |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
onMounted(()=>{ |
||||
|
//在批量分享文件的时候,是没有uuid的,可以避免服务器的错误提示:uuid should not null |
||||
|
if(props.uuid!==""){ |
||||
|
getPermitedList(props.uid,{uuid:props.uuid}).then(resp=>{ |
||||
|
resp.data?.permited?.forEach(item=>{ |
||||
|
if (item.startsWith("dprt")){ |
||||
|
let arr=item.replace("dprt:d","").split("d") |
||||
|
arr.forEach(val=>treeSelected.push(parseInt(val))) |
||||
|
return |
||||
|
} |
||||
|
permited.add(item) //userUuids constitue the permited list |
||||
|
}); |
||||
|
|
||||
|
resp.data?.infos?.forEach(item=>{ |
||||
|
permitedInfos.add(item) |
||||
|
}) |
||||
|
}) |
||||
|
}else if(props.spaceid!==""){ |
||||
|
//空间支持 |
||||
|
getSpaceMemberList(props.uid,{space:props.spaceid}).then(resp=>{ |
||||
|
resp.data?.members?.forEach(item=>{ |
||||
|
permited.add(item) //userUuids constitue the permited list |
||||
|
}); |
||||
|
|
||||
|
resp.data?.dprts?.forEach(val=>{ |
||||
|
if(val.match("[a-z]")) return; //去除可能不合规的内容 |
||||
|
treeSelected.push(parseInt(val)) |
||||
|
}) |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
getOrgTreeList({}).then(resp=>{ |
||||
|
treeData.value=resp.data |
||||
|
}) |
||||
|
}) |
||||
|
|
||||
|
</script> |
||||
|
|
||||
|
<template> |
||||
|
<el-dialog :model-value="true" :style="{'max-height': '650px'}" v-on:close="closeFunc()"> |
||||
|
<template #header> |
||||
|
<span>成员管理</span> |
||||
|
</template> |
||||
|
<div style="display: grid;width: 100%;grid-template-columns:1fr 1fr;"> |
||||
|
<div class="menus_tree"> |
||||
|
<el-input |
||||
|
v-model="memberName" |
||||
|
class="w-60 mb-2" |
||||
|
placeholder="查找成员" |
||||
|
@change="onFindMemberByName(memberName)" |
||||
|
/> |
||||
|
<el-tree |
||||
|
ref="treeRef" |
||||
|
:data="treeData" |
||||
|
node-key="id" |
||||
|
show-checkbox |
||||
|
:default-checked-keys="treeSelected" |
||||
|
:check-on-click-leaf="false" |
||||
|
:style="{maxHeight:'500px','overflow-y': 'auto'}" |
||||
|
:props="{label: 'name',children:'child',isLeaf:'dir'}" |
||||
|
:default-expanded-keys="[313]" |
||||
|
@node-click="onNodeClick" |
||||
|
/> |
||||
|
</div> |
||||
|
<div class="tablelist"> |
||||
|
<el-table ref="tableMembersRef" |
||||
|
:data="members" |
||||
|
:row-key="row => row.keystr" |
||||
|
style="overflow-y: auto;height: 500px;" |
||||
|
@select="onManualSelect" |
||||
|
@select-all="onSelectionAll" |
||||
|
> |
||||
|
<el-table-column type="selection" :reserve-selection="true" width="30" /> |
||||
|
<el-table-column property="name" label="成员"></el-table-column> |
||||
|
<el-table-column property="positionname" label="职位"></el-table-column> |
||||
|
</el-table> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<template #footer> |
||||
|
<div class="dialog-footer"> |
||||
|
<el-button @click="closeFunc()">取消</el-button> |
||||
|
<el-button type="primary" @click="onSavePermChange">保存</el-button> |
||||
|
</div> |
||||
|
</template> |
||||
|
</el-dialog> |
||||
|
</template> |
||||
@ -0,0 +1,867 @@ |
|||||
|
<!-- |
||||
|
@ 作者: han2015 |
||||
|
@ 时间: 2025-05-12 15:39:13 |
||||
|
@ 备注: 共享空间组件 |
||||
|
--> |
||||
|
<script lang="ts" setup> |
||||
|
import { getFileIcon, readableSize,fileType} from "./tools" |
||||
|
import sharePermission from './sharePermission.vue'; |
||||
|
import spacePermission from './spacePermission.vue'; |
||||
|
import { userStror } from "@/utils/pinia/stores/modules/userOrders"; |
||||
|
import { matterPage,matterInfo,doFileUpload} from "@/api/doc/type" |
||||
|
import { doAccessManage,getSpaceMatterList,doCreateSpaceDir,doDelSpaceMatter, |
||||
|
doAiTraining ,spaceMatterRename,doCreateAiagent} from "@/api/doc/space" |
||||
|
import { h } from 'vue' |
||||
|
import { |
||||
|
Delete, |
||||
|
View, |
||||
|
Download, |
||||
|
Promotion, |
||||
|
Search, |
||||
|
Edit, |
||||
|
Setting, |
||||
|
Grid,List, |
||||
|
ArrowLeft, |
||||
|
} from '@element-plus/icons-vue' |
||||
|
import {ElMessage,UploadFile,UploadFiles,ElPagination} from "element-plus"; |
||||
|
import aiagent from './agent.vue'; |
||||
|
import router from "@/utils/router"; |
||||
|
import SvgIcon from "@/components/svgIcon/index.vue"; |
||||
|
import BottomPage from '@/views/common/bottom/index.vue' |
||||
|
|
||||
|
|
||||
|
const userStore = userStror(); |
||||
|
const uid=btoa("p0"+userStore.userInfoCont.userId); |
||||
|
const rawUid="p0"+userStore.userInfoCont.userId |
||||
|
const siteHost=document.location.origin; |
||||
|
const apiURL=import.meta.env.VITE_APP_BASE_API+"/hxpan/api" |
||||
|
const officeHost=import.meta.env.VITE_OFFICE_HOST |
||||
|
const ismanager=ref(false); |
||||
|
const showPopup=ref(false) |
||||
|
const spaceName=ref("") |
||||
|
const owner=ref("")//空间创建者 |
||||
|
|
||||
|
const defaultAiAgent=import.meta.env.VITE_DEFAULT_AI_AGENT |
||||
|
const matterList = ref<matterInfo[]>([]) |
||||
|
const searchname=ref("") //按文件名查找文件 |
||||
|
const newdirName=ref("") //创建新目录时的目录名 |
||||
|
const currentHoverRow=ref<matterInfo>({}) //table 行的按钮控制 |
||||
|
const breadcrumbList=ref<matterInfo[]>([{name:"根目录",uuid:"root", dir:true}]) //面包屑导航 |
||||
|
const currentNode=ref<matterInfo>({}) //打开的路径层次 |
||||
|
|
||||
|
const dynamicVNode = ref<VNode | null>(null) //permission 组件的父组件 |
||||
|
const paginInfo = ref({ page: 0, total: 0 }) |
||||
|
|
||||
|
const CutLevelPermit=ref(0) //当前层级的权限值 |
||||
|
const modListOrGrild=ref(true) //列表显示还是栅格显示 |
||||
|
enum PERMITS { |
||||
|
FORBID, //0 |
||||
|
VIEW, //1 |
||||
|
DOWNLOAD, //2 |
||||
|
UPLOAD, //3 |
||||
|
UPANDDOWNLOAD, //4 |
||||
|
EDIT, //5 |
||||
|
MANAGER, //6 |
||||
|
} |
||||
|
|
||||
|
const props = withDefaults(defineProps<{ |
||||
|
spaceid:string, |
||||
|
}>(),{}) |
||||
|
|
||||
|
//-----------AI--------------------- |
||||
|
const currentAgent=ref<{model:boolean,name:string,uuid:string,path:string}>({}) |
||||
|
|
||||
|
//--------------------------------- |
||||
|
|
||||
|
//属性变更,特别是为了处理空间切换的状态更新 |
||||
|
// watch(spaceid,()=>{ |
||||
|
// currentNode.value.uuid="root" |
||||
|
// currentNode.value.name="根目录" |
||||
|
// currentAgent.value.path="root" |
||||
|
// onLoadMatterList() |
||||
|
// }) |
||||
|
|
||||
|
const uploadFormData = computed(() => { |
||||
|
return { |
||||
|
space: props.spaceid, |
||||
|
puuid: currentNode.value.uuid, // 父目录的uuid,基目录为root |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
function updateListOrGrid(val:boolean){ |
||||
|
modListOrGrild.value=val |
||||
|
if(val){ |
||||
|
localStorage.setItem("listOrGrid","true") |
||||
|
}else{ |
||||
|
localStorage.setItem("listOrGrid","false") |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
//--------------权限控制&添加空间成员------------- |
||||
|
function onAccessManage(){ |
||||
|
//迁移到了manage文件中,暂时不需要了 |
||||
|
dynamicVNode.value = h(sharePermission, { |
||||
|
uid: uid, |
||||
|
uuid: "", |
||||
|
spaceid:props.spaceid, // |
||||
|
confirmFunc: (_list: string[],_infos:string[]) => { |
||||
|
// 组织权限数据 |
||||
|
//_len=_list.length |
||||
|
let permited = btoa(_list.join("|")) |
||||
|
doAccessManage(uid,{ |
||||
|
"space":props.spaceid, |
||||
|
"roles":permited, |
||||
|
"owner":owner.value, |
||||
|
"len":_list.length |
||||
|
}).then(()=>{ |
||||
|
|
||||
|
}) |
||||
|
}, |
||||
|
closeFunc: () => { |
||||
|
dynamicVNode.value=null |
||||
|
} |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
//文档权限控制管理 |
||||
|
function onSpacePManage(row:matterInfo){ |
||||
|
dynamicVNode.value=h(spacePermission,{ |
||||
|
uid:rawUid, //当前用户的uuid |
||||
|
uuid:row.uuid, //文档的uuid |
||||
|
suid:owner.value, |
||||
|
spaceid:props.spaceid, //空间uuid |
||||
|
closeFunc:()=>dynamicVNode.value=null |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
|
||||
|
//---------------------------------------- |
||||
|
//删除 |
||||
|
function onDelMatter(row:matterInfo){ |
||||
|
if (row.uuid){ |
||||
|
ElMessageBox.confirm(`确认删除( ${row.name}) ?删除后不可恢复!取消则放弃删除操作。`, "警告", { |
||||
|
confirmButtonText: "确定", |
||||
|
cancelButtonText: "取消", |
||||
|
type: "warning", |
||||
|
}).then(()=>{ |
||||
|
doDelSpaceMatter(uid,{ |
||||
|
"uuid":row.uuid, |
||||
|
"space":props.spaceid, |
||||
|
}).then(()=>{ |
||||
|
currentNode.value.uuid = row.puuid ?? "" |
||||
|
onLoadMatterList() |
||||
|
}) |
||||
|
}) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function onDownload(row:matterInfo){ |
||||
|
ElMessageBox.confirm("确认下载此数据项?", "提示", { |
||||
|
confirmButtonText: "确定", |
||||
|
cancelButtonText: "取消", |
||||
|
type: "warning", |
||||
|
}).then(()=>{ |
||||
|
if (row.uuid){ |
||||
|
let _url= apiURL+`/space/download/${row.uuid}/${row.name}?space=${props.spaceid}` |
||||
|
window.open(_url) |
||||
|
} |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
//加载目录文件列表 |
||||
|
function onLoadMatterList(name?:string){ |
||||
|
let _page: matterPage= { |
||||
|
page: paginInfo.value.page, |
||||
|
pageSize: 50, |
||||
|
orderCreateTime: "DESC", |
||||
|
orderDir: "DESC", |
||||
|
puuid:currentNode.value.uuid, |
||||
|
deleted:false, |
||||
|
space:props.spaceid, |
||||
|
}; |
||||
|
|
||||
|
if(name){ |
||||
|
_page={ |
||||
|
pageSize: 50, |
||||
|
orderCreateTime: "DESC", |
||||
|
orderDir: "DESC", |
||||
|
deleted:false, |
||||
|
name:name, |
||||
|
space:props.spaceid, |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
|
||||
|
getSpaceMatterList(uid,_page).then((resp)=>{ |
||||
|
//page+1 是由于分页的起始index是1,而后端api的分页index起始是0 |
||||
|
paginInfo.value={total:resp.data.totalPages, page:resp.data.page} |
||||
|
if (ismanager.value) { |
||||
|
resp.data.data.forEach(item => { |
||||
|
item.permitVal=PERMITS.MANAGER |
||||
|
}) |
||||
|
matterList.value=resp.data.data |
||||
|
}else{ |
||||
|
matterList.value=resp.data.data.filter(item=>{ //具体的权限验证和过滤 |
||||
|
|
||||
|
if(item.permits.ID==0){ |
||||
|
item.permitVal=CutLevelPermit.value |
||||
|
}else{ |
||||
|
//如果该文档有设定权限,解析该文档的具体权限 |
||||
|
let _pert: Record<string, number> |
||||
|
_pert = JSON.parse(item.permits!.data) |
||||
|
let val=_pert[rawUid.replace("p0","")] |
||||
|
if(val){ |
||||
|
item.permitVal = val |
||||
|
}else{ |
||||
|
item.permitVal = PERMITS.FORBID //没有权限!! |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if(item.permitVal>0){ |
||||
|
return true |
||||
|
} |
||||
|
|
||||
|
return false |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
|
||||
|
//展开的时候暂时不做目录更新,看以后的使用情况 |
||||
|
let node_data = matterList.value.filter(item=> { |
||||
|
return item.dir |
||||
|
}).map(val => { |
||||
|
const copy = structuredClone(toRaw(val)) |
||||
|
copy.dir = !copy.dir |
||||
|
return copy |
||||
|
}) |
||||
|
}) |
||||
|
} |
||||
|
//----------for dir----------- |
||||
|
//该函数仅操作前端,为新文件夹命名 |
||||
|
function createDir(){ |
||||
|
matterList.value?.unshift({ |
||||
|
name:"", |
||||
|
userUuid:props.spaceid, |
||||
|
puuid:"", |
||||
|
uuid:"", |
||||
|
dir:true, |
||||
|
size:0, |
||||
|
deleted:false, |
||||
|
}) |
||||
|
} |
||||
|
//该函数为实际创建文件夹的函数 |
||||
|
function onCreateDir(){ |
||||
|
doCreateSpaceDir(uid,{ |
||||
|
puuid:currentNode.value.uuid, |
||||
|
name:newdirName.value, |
||||
|
space:props.spaceid, |
||||
|
}).then((resp)=> { |
||||
|
newdirName.value="" |
||||
|
onLoadMatterList() |
||||
|
}) |
||||
|
.catch((e)=>{ |
||||
|
ElMessage.error(e.msg) |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
//文件重命名 |
||||
|
function onSpaceMatterRename(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(() => { |
||||
|
if(newname.value&&newname.value!=""){ |
||||
|
spaceMatterRename(uid,{ |
||||
|
space:props.spaceid, |
||||
|
uuid:row.uuid, |
||||
|
name:newname.value, |
||||
|
}).then(()=>onLoadMatterList()) |
||||
|
} |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
//------------------------------------------ |
||||
|
//打开一个目录 |
||||
|
function handleDoubleClick(row:matterInfo,ind?:number){ |
||||
|
if (getFileIcon(row.name)=="img"||row.deleted) return; |
||||
|
if(row.dir){ |
||||
|
//设置当前文件夹的权限等级 |
||||
|
if(row.permitVal){ |
||||
|
CutLevelPermit.value=row.permitVal |
||||
|
}else{ |
||||
|
if (ismanager.value) { |
||||
|
CutLevelPermit.value=9 |
||||
|
}else{ |
||||
|
let _pert: Record<string, number> |
||||
|
_pert = JSON.parse(row.permits!.data) |
||||
|
let val=_pert[rawUid.replace("p0","")] |
||||
|
if(val){ |
||||
|
CutLevelPermit.value = val |
||||
|
}else{ |
||||
|
CutLevelPermit.value = PERMITS.FORBID //没有权限!! |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if(row.agent){ |
||||
|
currentAgent.value={name:row.name,model:false,uuid:row.uuid,path:row.path} |
||||
|
} |
||||
|
|
||||
|
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() |
||||
|
} |
||||
|
}else{ |
||||
|
onPrivateView(row) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function handleMouseEnter(row:any){ |
||||
|
currentHoverRow.value=row.name |
||||
|
} |
||||
|
//上传成功 |
||||
|
function handleSingleUpload(response:any){ |
||||
|
handleAiUpload(response.data) |
||||
|
onLoadMatterList() |
||||
|
} |
||||
|
interface uploadError{ |
||||
|
msg:string |
||||
|
} |
||||
|
//上传失败 |
||||
|
function handleSigLoadErr(error: Error, uploadFile: UploadFile, uploadFiles:UploadFiles){ |
||||
|
ElMessage.error(JSON.parse(error.message).msg) |
||||
|
} |
||||
|
|
||||
|
//自定义上传,目的是支持多文件上传, 使用原生组件,是为了解决并发问题 |
||||
|
async function onCustomUpload(e:Event){ |
||||
|
const files = e.target!.files??[] |
||||
|
for(let ff of files){ |
||||
|
await handleSingleFile(ff) |
||||
|
} |
||||
|
onLoadMatterList() //刷新 |
||||
|
} |
||||
|
|
||||
|
async function handleSingleFile(ff:File){ |
||||
|
const fields=new FormData() |
||||
|
fields.append("space",uploadFormData.value.space) |
||||
|
fields.append("puuid",uploadFormData.value.puuid) |
||||
|
fields.append("file",ff) |
||||
|
|
||||
|
const res = await doFileUpload(fields,'/hxpan/api/space/upload') |
||||
|
if(res.code!=200){ |
||||
|
console.log(ff.name+"上传失败! ") |
||||
|
alert(ff.name+"上传失败! ") |
||||
|
} |
||||
|
|
||||
|
//上传后继续AI服务训练 |
||||
|
handleAiUpload(res.data) |
||||
|
} |
||||
|
|
||||
|
//文件夹上传,原理:自定义的input组件,一次拿到所有需要上传的文件列表,然后依次上传 |
||||
|
//能减少并发问题 |
||||
|
async function uploadFolder(e:Event){ |
||||
|
const files = e.target!.files??[] |
||||
|
for(let f of files){ |
||||
|
await handleFolderFile(f) |
||||
|
} |
||||
|
onLoadMatterList() //刷新 |
||||
|
} |
||||
|
|
||||
|
async function handleFolderFile(option:File){ |
||||
|
//根据路径,来判断需不需要重建目录 |
||||
|
const _path = option.webkitRelativePath |
||||
|
const _dir=_path.replace(/\/[^/]+\w+$/,"") //只保留文件夹目录,[^/]就是用来限制,只能是最后一个目录 |
||||
|
const node = matterList.value.filter((item)=>{ |
||||
|
return item.dir && _dir.endsWith(item.name!) |
||||
|
}) |
||||
|
|
||||
|
let puuid="" |
||||
|
//说明是新的目录,需要新建;如果存在,直接上传文件即可 |
||||
|
if(node.length==0){ |
||||
|
const subs= await doCreateMultyDir(_dir,currentNode.value.uuid) |
||||
|
matterList.value.push(...subs) //这里如果子文件夹多的时候,可能会造成第一级路径多次创建,只是造成资源浪费,问题不大 |
||||
|
|
||||
|
const newnodes=matterList.value.filter((item)=>{ |
||||
|
return item.dir && _dir.endsWith(item.name!) //item.path?.endsWith(_dir) |
||||
|
}) |
||||
|
if(newnodes.length>0){ |
||||
|
puuid=newnodes[0].uuid |
||||
|
}else{ |
||||
|
console.log(_path+"上传失败! ") |
||||
|
alert(_path +" 上传失败! ") |
||||
|
return |
||||
|
} |
||||
|
}else{ |
||||
|
puuid=node[0].uuid |
||||
|
} |
||||
|
|
||||
|
const fields = new FormData() |
||||
|
fields.append('file', option) |
||||
|
fields.append("space",uploadFormData.value.space) |
||||
|
fields.append("puuid",puuid) |
||||
|
const res = await doFileUpload(fields,'/hxpan/api/space/upload') |
||||
|
if(res.code!=200){ |
||||
|
console.log(_path+"上传失败! ") |
||||
|
alert(_path +" 上传失败! ") |
||||
|
} |
||||
|
//上传后继续AI服务训练,是不是需要上传,在handleAiUpload里面有检测 |
||||
|
handleAiUpload(res.data) |
||||
|
} |
||||
|
|
||||
|
//创建多级目录,并返回matterinfos数组 |
||||
|
async function doCreateMultyDir(path:string,uuid:string){ |
||||
|
const list=[]; |
||||
|
const dirs=path.split("/") |
||||
|
for(let i=0;i<dirs.length;i++){ |
||||
|
const node = matterList.value.filter((item)=>{ |
||||
|
return item.dir && dirs[i].endsWith(item.name!) |
||||
|
}) |
||||
|
if(node.length>0) { |
||||
|
uuid = node[0].uuid |
||||
|
continue; |
||||
|
} |
||||
|
await doCreateSpaceDir(uid,{ |
||||
|
puuid:uuid, |
||||
|
name:dirs[i], |
||||
|
space:props.spaceid, |
||||
|
}).then((resp)=> { |
||||
|
uuid=resp.data.uuid //第一级别的uuid, 是第二级的puuid |
||||
|
list.push(resp.data) |
||||
|
}) |
||||
|
} |
||||
|
return list |
||||
|
} |
||||
|
|
||||
|
//-------------------------AI section-------------- |
||||
|
|
||||
|
function handleAiUpload(info:matterInfo){ |
||||
|
//只有当前路径是智能体,上传文件才会进行训练 |
||||
|
if (info.path?.startsWith(currentAgent.value.path)){ |
||||
|
doAiTraining(`/agents/${currentAgent.value.uuid}/updates`,{"matter":info.uuid}).then(resp=>{ |
||||
|
ElMessage({ |
||||
|
message: '已成功安排训练', |
||||
|
type: 'success', |
||||
|
plain: true, |
||||
|
}) |
||||
|
}) |
||||
|
}else{ |
||||
|
alert("当前路径没有智能体") |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
//处理空间智能体创建 |
||||
|
function onAiAgent(row:matterInfo){ |
||||
|
if(row.agent){ |
||||
|
alert("当前目录已经是智能体目录") |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
ElMessageBox.confirm(`确认创建智能体( ${row.name}) ?`, "警告", { |
||||
|
confirmButtonText: "确定", |
||||
|
cancelButtonText: "取消", |
||||
|
type: "warning", |
||||
|
}).then(()=>{ |
||||
|
doCreateAiagent(uid,{ |
||||
|
space:props.spaceid, |
||||
|
matter:row.uuid |
||||
|
}).then(()=>{ |
||||
|
router.replace({ query: { t: Date.now() } }) |
||||
|
}) |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
//------------------------------------------------- |
||||
|
|
||||
|
//-------------------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=`${siteHost}${apiURL}/space/download/${row.uuid}/${row.name}?space=${props.spaceid}` |
||||
|
//前半部分内容是为了校验信息,主要内容是fileurl |
||||
|
//router.push(`/onlyoffice?name=${row.name}&dtype=${_type}&info=${info}&fileurl=`+window.btoa(encodeURIComponent(_url)) |
||||
|
router.push({ path: "/onlyoffice",query:{fa:7,name:row.name,dtype:_type,info:info,fileurl: window.btoa(encodeURIComponent(_url)) }}); |
||||
|
}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=`${siteHost}${apiURL}/space/download/${row.uuid}/${row.name}?space=${props.spaceid}` |
||||
|
window.open(`/#/onlyoffice?name=${row.name}&dtype=${_type}&info=${info}&verify=${_verify}&fileurl=`+window.btoa(encodeURIComponent(_url)),"_blank") |
||||
|
}).catch(()=>{ |
||||
|
console.log("close") |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
function getSpaceImageViewURL(_uuid:string,_name:string){ |
||||
|
return `${apiURL}/space/download/${_uuid}/${_name}?space=${props.spaceid}&ir=fill_100_100&` |
||||
|
} |
||||
|
|
||||
|
function getSpaceImageDURL(_uuid:string,_name:string){ |
||||
|
return `${apiURL}/space/download/${_uuid}/${_name}?space=${props.spaceid}` |
||||
|
} |
||||
|
|
||||
|
//------------------------------------------------------ |
||||
|
|
||||
|
//渲染完页面再执行 |
||||
|
onMounted(() => { |
||||
|
|
||||
|
const c_space= history.state.space |
||||
|
currentNode.value.uuid="root" |
||||
|
//设置默认的AI智能体 |
||||
|
currentAgent.value={name:"通用AI",model:false,uuid:defaultAiAgent,path:"root"} |
||||
|
|
||||
|
let val =localStorage.getItem("listOrGrid") |
||||
|
if(val&&val=="false"){ |
||||
|
modListOrGrild.value=false |
||||
|
}else{ |
||||
|
modListOrGrild.value=true |
||||
|
} |
||||
|
|
||||
|
spaceName.value=c_space.name |
||||
|
owner.value=c_space.userUuid |
||||
|
|
||||
|
if (c_space.manager) { |
||||
|
ismanager.value=true |
||||
|
CutLevelPermit.value=PERMITS.MANAGER |
||||
|
}else{ |
||||
|
let _pert: Record<string, number> |
||||
|
_pert = JSON.parse(c_space.permits.data) |
||||
|
let val=_pert[rawUid.replace("p0","")] |
||||
|
if(val){ |
||||
|
CutLevelPermit.value = val |
||||
|
}else{ |
||||
|
CutLevelPermit.value = PERMITS.FORBID //没有权限!! |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
onLoadMatterList() |
||||
|
}); |
||||
|
|
||||
|
|
||||
|
//判断是不是空间的所有者 |
||||
|
// function isOwner(){ |
||||
|
// return uid==props.owner |
||||
|
// } |
||||
|
</script> |
||||
|
|
||||
|
<template> |
||||
|
<div class="app_container"> |
||||
|
<el-row :gutter="24" style="margin: 12px 0px;"> |
||||
|
<el-col class="search" style="padding:0px;width: 80%;"> |
||||
|
<el-button type="text" :icon="ArrowLeft" @click="router.back()"> </el-button> |
||||
|
<el-input placeholder="搜索文件" v-model="searchname" @blur="searchname===''?onLoadMatterList():''"/> |
||||
|
<el-button :icon="Search" @click="onLoadMatterList(searchname)"></el-button> |
||||
|
</el-col> |
||||
|
|
||||
|
<div class="nav-header"> |
||||
|
<el-breadcrumb separator=">" style="align-content: center;"> |
||||
|
<el-breadcrumb-item><span style="font-weight: bold; align-content: center;">[ {{ spaceName }} ]</span></el-breadcrumb-item> |
||||
|
<el-breadcrumb-item v-for="(item,index) in breadcrumbList" |
||||
|
:key="index" @click="handleDoubleClick(item,index)"> |
||||
|
<span style="font-weight: bold;cursor: pointer;">{{ item.name }}</span> |
||||
|
</el-breadcrumb-item> |
||||
|
</el-breadcrumb> |
||||
|
|
||||
|
<span v-if="modListOrGrild" @click="updateListOrGrid(false)"><Grid class="plus-icon"></Grid></span> |
||||
|
<span v-else @click="updateListOrGrid(true)"><List class="plus-icon"></List></span> |
||||
|
</div> |
||||
|
</el-row> |
||||
|
|
||||
|
<el-row :gutter="24"> |
||||
|
<el-col :span="14" v-if="CutLevelPermit>=PERMITS.UPLOAD"> |
||||
|
<div class="el-button el-button--default" style="position: relative;"> |
||||
|
<el-icon><Upload /></el-icon> |
||||
|
<input type="file" style="position: absolute;opacity: 0;width: 50px;" |
||||
|
@change="onCustomUpload" multiple /> |
||||
|
上传 |
||||
|
</div> |
||||
|
|
||||
|
<el-button icon="plus" @click="createDir">新建</el-button> |
||||
|
</el-col> |
||||
|
<el-button style="margin-left: auto;" @click="()=>currentAgent.model=true">AI助手</el-button> |
||||
|
|
||||
|
|
||||
|
</el-row> |
||||
|
|
||||
|
<el-row :gutter="24" style="overflow-y: auto;height: 80%;"> |
||||
|
<el-table v-if="modListOrGrild" |
||||
|
stripe |
||||
|
:data="matterList" |
||||
|
:header-cell-style="{ background: '#f5f8fd' }" |
||||
|
style="width: 100%" |
||||
|
row-key="uuid" |
||||
|
:row-style ="() => ({ lineHeight: '36px' })"> |
||||
|
<el-table-column property="name" label="文件名"> |
||||
|
<template #default="scope"> |
||||
|
<input v-if="scope.row.name===''" v-model="newdirName" type="text" autofocus placeholder="文件夹名" style="border:groove;height:30px;" @change="onCreateDir" /> |
||||
|
<div v-else style="display: flex; align-items: center;" @click="handleDoubleClick(scope.row)" > |
||||
|
<svg-icon v-if="scope.row.dir" icon-class="folder-icon" :size="30"/> |
||||
|
<el-image v-else-if="getFileIcon(scope.row.name)==='img'" style="width: 30px;" :preview-src-list="[getSpaceImageDURL(scope.row.uuid,scope.row.name)]" :src="getSpaceImageViewURL(scope.row.uuid,scope.row.name)" /> |
||||
|
<svg-icon v-else :icon-class="getFileIcon(scope.row.name)+'-icon'" :size="30" /> |
||||
|
|
||||
|
<span style="margin-left: 10px">{{ scope.row.name }} |
||||
|
<el-tag v-if="scope.row.agent" effect="dark" size="small" type="success" round >智能体</el-tag> |
||||
|
<br> <span>{{readableSize(scope.row)}}</span> <span v-if="scope.row.updateTime">/ {{ scope.row.updateTime.slice(0,16) }}</span> |
||||
|
</span> |
||||
|
<el-button style="margin-right: 10px;margin-left: auto;" type="text" icon="MoreFilled" size="small" |
||||
|
@click="(e)=>{e.stopPropagation(); showPopup=true; currentHoverRow=scope.row;}"></el-button> |
||||
|
</div> |
||||
|
|
||||
|
</template> |
||||
|
</el-table-column> |
||||
|
|
||||
|
</el-table> |
||||
|
|
||||
|
<div class="table-grid" v-else> |
||||
|
<div class="grid-item" v-for="row in matterList"> |
||||
|
<div class="grid"> |
||||
|
<div v-if="row.name===''"> |
||||
|
<svg-icon icon-class="folder-icon" size="80px"/> |
||||
|
<input v-model="newdirName" type="text" autofocus placeholder="文件夹名" style="border:groove;height:30px;" @change="onCreateDir" /> |
||||
|
</div> |
||||
|
|
||||
|
<div v-else class="grid-box" @click="handleDoubleClick(row)" > |
||||
|
<el-button class="setBtn" type="text" icon="MoreFilled" size="small" |
||||
|
@click="(e)=>{e.stopPropagation(); showPopup=true; currentHoverRow=row;}"></el-button> |
||||
|
|
||||
|
<svg-icon v-if="row.dir" icon-class="folder-icon" size="50px"/> |
||||
|
<el-image v-else-if="getFileIcon(row.name)==='img'" style="width: 50px;" |
||||
|
:preview-src-list="[getSpaceImageDURL(row.uuid,row.name)]" |
||||
|
:src="getSpaceImageViewURL(row.uuid,row.name)" /> |
||||
|
<svg-icon v-else :icon-class="getFileIcon(row.name)+'-icon'" size="50px"/> |
||||
|
<span style="margin: 5px 0;text-wrap-mode:nowrap;font-size: 13px;">{{ row.name }}</span> |
||||
|
<el-tag v-if="row.agent" effect="dark" size="small" type="success" round >智能体</el-tag> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</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> |
||||
|
|
||||
|
<!-- <aiagent :agent="currentAgent" :userid="uid" :uuid="currentAgent" :closefunc="()=>{currentAgent.model=false}"></aiagent> --> |
||||
|
</div> |
||||
|
|
||||
|
<div v-if="showPopup" class="mask" @click="showPopup = false"></div> |
||||
|
<!-- 主体 --> |
||||
|
<Transition name="popuper"> |
||||
|
<div v-if="showPopup" class="bs-wrapper"> |
||||
|
<div class="popupTitle"> |
||||
|
<svg-icon v-if="currentHoverRow.dir" icon-class="folder-icon" size="30px"/> |
||||
|
<svg-icon v-else :icon-class="getFileIcon(currentHoverRow.name)+'-icon'" size="30px"/> |
||||
|
{{ currentHoverRow.name }} | {{readableSize(currentHoverRow)}} |
||||
|
<el-button type="text" @click="showPopup=false">关闭</el-button> |
||||
|
</div> |
||||
|
<hr> |
||||
|
<div class="blocker-list"> |
||||
|
<span class="blocker" v-if="getFileIcon(currentHoverRow.name)!='img'" @click="onPrivateView(currentHoverRow)"> |
||||
|
<View class="plus-icon"></View>预览</span> |
||||
|
<span class="blocker" v-if="currentHoverRow.permitVal>=PERMITS.DOWNLOAD" @click="onDownload(currentHoverRow)"> |
||||
|
<Download class="plus-icon"></Download>下载</span> |
||||
|
<span class="blocker" v-if="currentHoverRow.permitVal>=PERMITS.MANAGER &&!currentHoverRow.dir" @click="handleAiUpload(currentHoverRow)"> |
||||
|
<Promotion class="plus-icon"></Promotion>AI训练</span> |
||||
|
<span class="blocker" v-if="currentHoverRow.permitVal>=PERMITS.MANAGER" @click="onDelMatter(currentHoverRow)"> |
||||
|
<Delete class="plus-icon"></Delete>删除</span> |
||||
|
<span class="blocker" v-if="currentHoverRow.permitVal>=PERMITS.EDIT" @click="onSpaceMatterRename(currentHoverRow)"> |
||||
|
<Edit class="plus-icon"></Edit>重命名</span> |
||||
|
<span class="blocker" v-if="currentHoverRow.permitVal>=PERMITS.MANAGER" @click="onSpacePManage(currentHoverRow)"> |
||||
|
<Setting class="plus-icon"></Setting>权限设置</span> |
||||
|
</div> |
||||
|
</div> |
||||
|
</Transition> |
||||
|
<div v-if="dynamicVNode"> |
||||
|
<component :is="dynamicVNode" /> |
||||
|
</div> |
||||
|
|
||||
|
<BottomPage /> |
||||
|
|
||||
|
</template> |
||||
|
|
||||
|
<style lang="scss" scoped> |
||||
|
.app_container { |
||||
|
padding: 10px 15px 0px 15px; |
||||
|
height: calc(100vh - 65px); |
||||
|
overflow: hidden; |
||||
|
position: relative; |
||||
|
} |
||||
|
.nav-header{ |
||||
|
margin-top: 21px; |
||||
|
width: 100%; |
||||
|
display: flex; |
||||
|
span{ |
||||
|
margin-left: auto; |
||||
|
} |
||||
|
} |
||||
|
.shareDialog{ |
||||
|
--el-messagebox-width:'800px'; |
||||
|
padding:40px; |
||||
|
.el-text{ |
||||
|
align-self: flex-start; |
||||
|
} |
||||
|
} |
||||
|
.search{ |
||||
|
margin-left: auto; |
||||
|
display:inherit; |
||||
|
.el-input{ |
||||
|
margin-left: 15px; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
//---------------animation |
||||
|
/* 遮罩: popup 的遮罩 */ |
||||
|
.mask{ |
||||
|
position: fixed; |
||||
|
inset: 0; |
||||
|
background:rgba(0,0,0,.4); |
||||
|
z-index:999; |
||||
|
} |
||||
|
|
||||
|
.bs-wrapper{ |
||||
|
position: fixed; |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
left:0; |
||||
|
right:0; |
||||
|
bottom:0; |
||||
|
height:36vh; /* 半屏停住 */ |
||||
|
background:#f1f1f1; |
||||
|
border-radius:16px 16px 0 0; |
||||
|
z-index:1000; |
||||
|
overflow-y:auto; |
||||
|
padding: 8px 16px; |
||||
|
hr{ |
||||
|
margin: 8px 0; |
||||
|
border: none; |
||||
|
width: 88%; |
||||
|
align-self: center; |
||||
|
background: #63616145; |
||||
|
} |
||||
|
} |
||||
|
.popupTitle{ |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
button{ |
||||
|
margin-left: auto; |
||||
|
margin-right: 5px; |
||||
|
} |
||||
|
} |
||||
|
.blocker-list{ |
||||
|
display: grid; |
||||
|
grid-template-columns: 1fr 1fr 1fr 1fr; |
||||
|
font-size: small; |
||||
|
.blocker{ |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
align-items: center; |
||||
|
padding-top: 15px; |
||||
|
background-color: white; |
||||
|
border-radius: 5px; |
||||
|
margin: 6px; |
||||
|
width: 70px; |
||||
|
height: 70px; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.popuper-enter-from{ |
||||
|
transform:translateY(100%); |
||||
|
opacity:0; |
||||
|
} |
||||
|
/* 进入过程 */ |
||||
|
.popuper-enter-active{ |
||||
|
transition:all .3s ease-out; |
||||
|
} |
||||
|
/* 离开后:回到下方 */ |
||||
|
.popuper-leave-to{ |
||||
|
transform:translateY(50px); |
||||
|
opacity:0; |
||||
|
} |
||||
|
.popuper-leave-active{ |
||||
|
transition:all .3s ease-in; |
||||
|
} |
||||
|
//--------------------------- |
||||
|
|
||||
|
.plus-icon{ |
||||
|
width: 20px; |
||||
|
height: 20px; |
||||
|
} |
||||
|
|
||||
|
.table-grid{ |
||||
|
display: grid; |
||||
|
grid-template-columns: 1fr 1fr 1fr; |
||||
|
align-content: flex-start; |
||||
|
.grid-item{ |
||||
|
position: relative; |
||||
|
width: 112px; |
||||
|
height: 112px; |
||||
|
margin: 5px; |
||||
|
background: white; |
||||
|
border-radius: 10px; |
||||
|
.setBtn{ |
||||
|
position: absolute; |
||||
|
top: 0px; |
||||
|
right: -5px; |
||||
|
} |
||||
|
.grid-box{ |
||||
|
padding-top: 16px; |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
overflow: hidden; |
||||
|
align-items: center; |
||||
|
text-align:center; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.manager_span{ |
||||
|
margin-left: 10px; |
||||
|
} |
||||
|
|
||||
|
.dynamic-width-message-box-byme .el-message-box__message{ |
||||
|
width: 100%; |
||||
|
} |
||||
|
|
||||
|
</style> |
||||
|
|
||||
|
<style lang="scss"> |
||||
|
.el-table--default .el-table__cell{ |
||||
|
padding: 10px 0; |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,382 @@ |
|||||
|
<script lang="ts" setup> |
||||
|
import { |
||||
|
resetSpaceMatterPermit, |
||||
|
getSpaceMemberList, |
||||
|
addSpaceManager, |
||||
|
updateSpaceMetterPermit, |
||||
|
} from '@/api/hr/user/share_ctrol' |
||||
|
|
||||
|
import { ElDialog, ElMessageBox,ElStep,TreeInstance} from 'element-plus'; |
||||
|
import { useOrgMemberStore } from "@/utils/pinia/stores/modules/orgMember"; |
||||
|
const props = withDefaults(defineProps<{ |
||||
|
uid:string, //当前用户的uuid |
||||
|
uuid:string, //文档的uuid |
||||
|
suid:string, //空间的创建者 |
||||
|
spaceid:string, //空间uuid |
||||
|
closeFunc:(refresh?:boolean)=>void, //父级只需销毁组件 |
||||
|
}>(),{}) |
||||
|
|
||||
|
const orgMembers = useOrgMemberStore() |
||||
|
const spacePermit=ref<{id:number,data:string,matterUid:string}>({}) //权限 |
||||
|
const treeRef=ref<TreeInstance>() //tree组件的引用,为了实现初始选中状态 |
||||
|
const managerMode=ref(false) |
||||
|
const managers=ref<string[]>([]) |
||||
|
const firstLevelKeys = ref<string[]>([]) //默认展开的部门 |
||||
|
|
||||
|
let resultPermits: Record<string, number> = {}; |
||||
|
|
||||
|
interface Tree { |
||||
|
id: string |
||||
|
name:string |
||||
|
superior?:string |
||||
|
indeterminate?:boolean, |
||||
|
radio?:number[]; |
||||
|
child?: Tree[] |
||||
|
ismanager?:boolean, |
||||
|
} |
||||
|
|
||||
|
const dataSource = ref<Tree[]>([]) |
||||
|
|
||||
|
//权限组件保存 |
||||
|
async function onSavePermChange(){ |
||||
|
resultPermits={} |
||||
|
|
||||
|
// 管理员设置 |
||||
|
if (managerMode.value){ |
||||
|
managers.value=[] //先清空 |
||||
|
dataSource.value.forEach(item=>{ |
||||
|
if(item.ismanager){ |
||||
|
managers.value.push("p0"+item.id) |
||||
|
} |
||||
|
|
||||
|
if(item.child) collectManager(item) |
||||
|
}) |
||||
|
|
||||
|
await addSpaceManager(btoa(props.uid),{ |
||||
|
space:props.spaceid, |
||||
|
mangers:managers.value.join("|") |
||||
|
}).then(resp=>{ |
||||
|
console.log(resp) |
||||
|
}) |
||||
|
props.closeFunc() |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
dataSource.value.forEach(item=>{ |
||||
|
if(item.radio&&item.radio.length>0){ |
||||
|
if(item.indeterminate) { |
||||
|
item.radio[0]+=10 |
||||
|
} |
||||
|
resultPermits[item.id]=item.radio[0] |
||||
|
} |
||||
|
|
||||
|
if(item.child) collectNodePermits(item) |
||||
|
}) |
||||
|
|
||||
|
spacePermit.value.data=JSON.stringify(resultPermits) |
||||
|
|
||||
|
updateSpaceMetterPermit(btoa(props.uid),{ |
||||
|
space:props.spaceid, |
||||
|
matter:props.uuid, |
||||
|
permits:spacePermit.value |
||||
|
}).then(resp=>{ |
||||
|
console.log(resp) |
||||
|
}) |
||||
|
props.closeFunc() |
||||
|
} |
||||
|
|
||||
|
function collectManager(node:Tree){ |
||||
|
node.child?.forEach(ele => { |
||||
|
if(ele.ismanager){ |
||||
|
managers.value.push("p0"+ele.id) |
||||
|
} |
||||
|
|
||||
|
if(ele.child) collectManager(ele) |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
//去遍历查找特殊设置的节点,并保存 |
||||
|
function collectNodePermits(node:Tree){ |
||||
|
node.child?.forEach(ele => { |
||||
|
if(ele.radio&&ele.radio.length>0){ |
||||
|
if(ele.indeterminate) { |
||||
|
ele.radio[0]+=10 |
||||
|
} |
||||
|
resultPermits[ele.id]=ele.radio[0] |
||||
|
} |
||||
|
|
||||
|
if(ele.child){ |
||||
|
collectNodePermits(ele) |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
//递归修改子级元素 |
||||
|
function onGroupValueChange(node:Tree, val:number[]){ |
||||
|
if(node.indeterminate) node.indeterminate=false; |
||||
|
node.child?.forEach(ele => { |
||||
|
ele.radio=val |
||||
|
if(ele.child){ |
||||
|
onGroupValueChange(ele,val) |
||||
|
} |
||||
|
}); |
||||
|
if(node.superior){ |
||||
|
updateParentNode(node) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
//递归更新父级 |
||||
|
function updateParentNode(node:Tree){ |
||||
|
if(node.superior){ |
||||
|
|
||||
|
const pnode = treeRef.value?.getNode(node.superior); |
||||
|
if(pnode){ |
||||
|
const tdata=pnode.data as Tree |
||||
|
if (tdata.child?.every(ele=>{ |
||||
|
if(ele.indeterminate) return !ele.indeterminate;//注意这里有个取反计算 |
||||
|
|
||||
|
if(ele.radio?.length==0) return ele.radio.length==tdata.radio?.length; |
||||
|
return ele.radio?.length==tdata.radio?.length && ele.radio[0]==tdata.radio[0] |
||||
|
})) { |
||||
|
tdata.indeterminate=false |
||||
|
}else{ |
||||
|
tdata.indeterminate=true |
||||
|
} |
||||
|
|
||||
|
updateParentNode(tdata) |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
} |
||||
|
|
||||
|
|
||||
|
//重置当前文档权限 |
||||
|
function delMatterPermit(matter_uuid:string){ |
||||
|
resetSpaceMatterPermit(btoa(props.uid),{ |
||||
|
space:props.spaceid, |
||||
|
matter:matter_uuid, |
||||
|
}).then(()=>{ |
||||
|
refreshSpaceData() |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
function onShowManagers(){ |
||||
|
managers.value.forEach(item=>{ |
||||
|
const node = treeRef.value?.getNode(item.replace("p0","")); |
||||
|
if(node && node.data){ |
||||
|
node.data.ismanager=true |
||||
|
setParentIndeterminate(node.data as Tree) |
||||
|
} |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
function setParentIndeterminate(node:Tree){ |
||||
|
if(node.superior){ |
||||
|
const pnode = treeRef.value?.getNode(node.superior); |
||||
|
if(pnode){ |
||||
|
const tdata=pnode.data as Tree |
||||
|
tdata.indeterminate=true |
||||
|
|
||||
|
setParentIndeterminate(tdata) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function refreshSpaceData(){ |
||||
|
getSpaceMemberList( |
||||
|
btoa(props.uid), |
||||
|
{space:props.spaceid,matter:props.uuid} |
||||
|
).then(resp=>{ |
||||
|
resp.data?.dprts?.forEach(item=>{ |
||||
|
for(let data of dataSource.value){ |
||||
|
if (data.id==item) return; |
||||
|
if (checkNode(item,data)) return; |
||||
|
} |
||||
|
|
||||
|
addNode(item,orgMembers.dataTree) |
||||
|
}); |
||||
|
|
||||
|
if(dataSource.value.length>0){//只添加第一个最顶级部门 |
||||
|
firstLevelKeys.value.push(dataSource.value[0].id) |
||||
|
} |
||||
|
|
||||
|
resp.data?.members?.forEach(item=>{ |
||||
|
const mid=item.replace("p0","") |
||||
|
for(let data of dataSource.value){ |
||||
|
if(checkNode(mid,data)) return; |
||||
|
} |
||||
|
|
||||
|
dataSource.value.push({id:mid,name:orgMembers.listMap[mid],radio:[]}) |
||||
|
}); |
||||
|
|
||||
|
managers.value=resp.data?.managers.split("|") |
||||
|
spacePermit.value.id=resp.data?.permits.ID |
||||
|
spacePermit.value.matterUid=resp.data?.permits.MatterUuid |
||||
|
spacePermit.value.data=resp.data.permits.data!="" ? resp.data.permits.data : "{}" |
||||
|
|
||||
|
nextTick(() => { |
||||
|
const permitJson = JSON.parse(spacePermit.value.data) //object |
||||
|
Object.keys(permitJson).forEach(key=>{ |
||||
|
const node = treeRef.value?.getNode(key); |
||||
|
if(node && node.data){ |
||||
|
if(permitJson[key]>10){ |
||||
|
node.data.radio = [permitJson[key]%10] |
||||
|
node.data.indeterminate=true |
||||
|
}else{ |
||||
|
node.data.radio = [permitJson[key]] |
||||
|
} |
||||
|
} |
||||
|
}) |
||||
|
}); |
||||
|
|
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
function addNode(key:string,node:Tree){ |
||||
|
node.child?.forEach(ele => { |
||||
|
if(ele.id==key){ |
||||
|
const eleClone=structuredClone(toRaw(ele)) //这里ele是Proxy,无法clone,所以要toRaw拿到原始object |
||||
|
//eleClone.radio=[2] |
||||
|
eleClone.superior=ele.superior |
||||
|
dataSource.value.push(eleClone) //{id:ele.id,name:ele.name,radio:2,stage:level,children:ele.children} |
||||
|
return |
||||
|
} |
||||
|
if(ele.child){ |
||||
|
addNode(key,ele) |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
function checkNode(key:string,node:Tree):boolean{ |
||||
|
for (const ch of node.child||[]) { |
||||
|
if(ch.id==key){ |
||||
|
return true |
||||
|
} |
||||
|
if(ch.child){ |
||||
|
//这里不能直接return,因为return后,就中断了for循环,但是true的情况下可以直接return |
||||
|
if(checkNode(key,ch)) return true; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
onMounted(()=>{ |
||||
|
refreshSpaceData() |
||||
|
}) |
||||
|
|
||||
|
</script> |
||||
|
|
||||
|
<template> |
||||
|
<el-dialog :model-value="true" :style="{'max-height': '750px'}" v-on:close="closeFunc()"> |
||||
|
<template #header> |
||||
|
<el-button link @click="managerMode=false;">文档权限管理</el-button> |
||||
|
<el-button v-if="spacePermit.matterUid==spaceid&&uid==suid" link @click="managerMode=true; onShowManagers()">管理员管理</el-button> |
||||
|
</template> |
||||
|
|
||||
|
<div v-if="uuid==spacePermit.matterUid&&uuid!=spaceid" class="tips">当前文档存在定制权限,与空间权限不一致。<el-button size="small" @click="delMatterPermit(uuid)">恢复</el-button></div> |
||||
|
<div class="tree-node" style="width:93%;font-weight: bold;position: sticky;"> |
||||
|
<span style="width: 200px;text-align: center;">名称</span> |
||||
|
<div v-if="managerMode" class="buttons"> |
||||
|
<span>管理员</span> |
||||
|
</div> |
||||
|
<div v-else class="buttons"> |
||||
|
<el-tooltip placement="top" effect="dark" |
||||
|
content="禁止访问"> |
||||
|
<span>不可见</span> |
||||
|
</el-tooltip> |
||||
|
<el-tooltip placement="top" effect="dark" |
||||
|
content="仅可预览,不可下载"> |
||||
|
<span>仅预览</span> |
||||
|
</el-tooltip> |
||||
|
<el-tooltip placement="top" effect="dark" |
||||
|
content="可下载可见文件,不可上传文件"> |
||||
|
<span>可下载</span> |
||||
|
</el-tooltip> |
||||
|
<el-tooltip placement="top" effect="dark" |
||||
|
content="可上传和下载可见文件"> |
||||
|
<span>上传下载</span> |
||||
|
</el-tooltip> |
||||
|
<el-tooltip placement="top" effect="dark" |
||||
|
content="可上传、下载、编辑文档"> |
||||
|
<span>编辑</span> |
||||
|
</el-tooltip> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="tablelist"> |
||||
|
<el-tree |
||||
|
ref="treeRef" |
||||
|
:data="dataSource" |
||||
|
node-key="id" |
||||
|
accordion |
||||
|
:props="{label: 'name',children:'child'}" |
||||
|
:default-expanded-keys="firstLevelKeys" |
||||
|
:expand-on-click-node="false" |
||||
|
> |
||||
|
<template #default="{ node, data }"> |
||||
|
<div class="tree-node"> |
||||
|
<span>{{ data.name }}</span> |
||||
|
|
||||
|
<div v-if="managerMode" class="buttons"> |
||||
|
<el-checkbox v-model="data.ismanager" :indeterminate="data.indeterminate" /> |
||||
|
</div> |
||||
|
<div v-else class="buttons"> |
||||
|
<el-checkbox-group :min="0" :max="1" v-model="data.radio" @change="(val)=>onGroupValueChange(data,val)"> |
||||
|
<el-checkbox key="forbid" :indeterminate="data.indeterminate" :value="0" /> |
||||
|
<el-checkbox key="view" :indeterminate="data.indeterminate" :value="1" /> |
||||
|
<el-checkbox key="download" :indeterminate="data.indeterminate" :value="2" /> |
||||
|
<el-checkbox key="updown" :indeterminate="data.indeterminate" :value="3" /> |
||||
|
<el-checkbox key="edit" :indeterminate="data.indeterminate" :value="4" /> |
||||
|
</el-checkbox-group> |
||||
|
</div> |
||||
|
</div> |
||||
|
</template> |
||||
|
</el-tree> |
||||
|
</div> |
||||
|
|
||||
|
<template #footer> |
||||
|
<div class="dialog-footer"> |
||||
|
<el-button @click="closeFunc()">取消</el-button> |
||||
|
<el-button type="primary" @click="onSavePermChange">保存</el-button> |
||||
|
</div> |
||||
|
</template> |
||||
|
</el-dialog> |
||||
|
</template> |
||||
|
|
||||
|
<style lang="scss" scoped> |
||||
|
.tablelist{ |
||||
|
width: 94%; |
||||
|
height: 565px; |
||||
|
overflow-y: scroll |
||||
|
} |
||||
|
.tips{ |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
margin: 5px; |
||||
|
padding: 5px 32px; |
||||
|
background-color: #d7d1ca; |
||||
|
} |
||||
|
.tree-node{ |
||||
|
display: flex; |
||||
|
flex-direction: row; |
||||
|
width: 100%; |
||||
|
align-items: center; |
||||
|
.buttons{ |
||||
|
margin: 0 50px 0 auto; |
||||
|
span{ |
||||
|
width: 26px; |
||||
|
margin: 0 7px; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
.el-checkbox{ |
||||
|
width: 26px; |
||||
|
} |
||||
|
|
||||
|
.el-tree{ |
||||
|
/* 让整个弹出窗口位置更高一些*/ |
||||
|
--el-tree-node-content-height:33px; |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,97 @@ |
|||||
|
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()??''; |
||||
|
if (".doc, .docx, .html, .md, .txt, .wps, .xml".includes(extension)){ |
||||
|
return "word" |
||||
|
} else if(".csv, .xls, .xlsb, .xlsm, .xlsx".includes(extension)){ |
||||
|
return "cell" |
||||
|
}else if(".jpg, .jpeg, .png, .gif".includes(extension)){ |
||||
|
return "img" |
||||
|
}else if (extension==="pdf"){ |
||||
|
return "pdf" |
||||
|
}else if(".ppt, .pptm, .pptx".includes(extension)){ |
||||
|
return "slide" |
||||
|
}else if(".mp4, .mov, avi".includes(extension)){ |
||||
|
return "video" |
||||
|
}else if(".mp3, .wav".includes(extension)){ |
||||
|
return "audio" |
||||
|
} |
||||
|
|
||||
|
return "file" |
||||
|
} |
||||
|
|
||||
|
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" |
||||
|
} |
||||
|
|
||||
|
|
||||
|
export function fileType(name:string){ |
||||
|
const suffix=name.match(/(\.[a-zA-Z]+$)/) |
||||
|
if(suffix==null){ return ""} |
||||
|
|
||||
|
if (".doc, .docx, .html, .md, .txt, .wps, .xml".includes(suffix[0])){ |
||||
|
return "word" |
||||
|
} else if(".csv, .xls, .xlsb, .xlsm, .xlsx".includes(suffix[0])){ |
||||
|
return "cell" |
||||
|
}else if(".ppt, .pptm, .pptx".includes(suffix[0])){ |
||||
|
return "slide" |
||||
|
}else if (suffix[0]===".pdf"){ |
||||
|
return "pdf" |
||||
|
} |
||||
|
return "" |
||||
|
} |
||||