@ -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,357 @@ |
|||
<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,doAccessManage} from "@/api/doc/space" |
|||
import sharePermission from './sharePermission.vue'; |
|||
import spacePermission from './spacePermission.vue'; |
|||
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 dynamicVNode = ref<VNode | null>(null) //permission 组件的父组件 |
|||
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(row:matterInfo){ |
|||
dynamicVNode.value = h(sharePermission, { |
|||
uid: uid, |
|||
uuid: "", |
|||
spaceid:row.uuid, // |
|||
confirmFunc: (_list: string[],_infos:string[]) => { |
|||
// 组织权限数据 |
|||
//_len=_list.length |
|||
let permited = btoa(_list.join("|")) |
|||
doAccessManage(uid,{ |
|||
"space":row.uuid, |
|||
"roles":permited, |
|||
"owner":row.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:row.userUuid, |
|||
spaceid:row.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> |
|||
|
|||
<div v-if="dynamicVNode"> |
|||
<component :is="dynamicVNode" /> |
|||
</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="onAccessManage(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,222 @@ |
|||
<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': '749px'}" 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:'324px','overflow-y': 'auto'}" |
|||
:props="{label: 'name',children:'child',isLeaf:'dir'}" |
|||
:default-expanded-keys="[313]" |
|||
@node-click="onNodeClick" |
|||
/> |
|||
</div> |
|||
|
|||
</div> |
|||
<div class="tablelist"> |
|||
<el-table ref="tableMembersRef" |
|||
:data="members" |
|||
:row-key="row => row.keystr" |
|||
style="overflow-y: auto;height: 250px;" |
|||
@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> |
|||
<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: 100%; |
|||
padding: 10px; |
|||
border: 2px solid; |
|||
border-radius: 16px; |
|||
} |
|||
|
|||
</style> |
|||
|
|||
<style> |
|||
/* dialog的body内容样式设置*/ |
|||
.el-dialog{ |
|||
/* 让整个弹出窗口位置更高一些*/ |
|||
--el-dialog-margin-top:7vh; |
|||
} |
|||
|
|||
</style> |
|||
@ -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,388 @@ |
|||
<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="font-weight: bold;position: sticky;"> |
|||
<span style="width: 100px;text-align: center;">名称</span> |
|||
<div v-if="managerMode" class="buttons"> |
|||
<div class="box-title">管理员</div> |
|||
</div> |
|||
<div v-else style="margin: 0 0 0 auto;display: flex;"> |
|||
<el-tooltip placement="top" effect="dark" |
|||
content="禁止访问"> |
|||
<div class="box-title">不可见</div> |
|||
</el-tooltip> |
|||
<el-tooltip placement="top" effect="dark" |
|||
content="仅可预览,不可下载"> |
|||
<div class="box-title">仅预览</div> |
|||
</el-tooltip> |
|||
<el-tooltip placement="top" effect="dark" |
|||
content="可下载可见文件,不可上传文件"> |
|||
<div class="box-title">可下载</div> |
|||
</el-tooltip> |
|||
<el-tooltip placement="top" effect="dark" |
|||
content="可上传和下载可见文件"> |
|||
<div class="box-title">上传下载</div> |
|||
</el-tooltip> |
|||
<el-tooltip placement="top" effect="dark" |
|||
content="可上传、下载、编辑文档"> |
|||
<div class="box-title">编辑</div> |
|||
</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 style="width: 130px;overflow: hidden;">{{ 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{ |
|||
height: 565px; |
|||
overflow-y: scroll |
|||
} |
|||
.tips{ |
|||
display: flex; |
|||
align-items: center; |
|||
margin: 5px; |
|||
padding: 5px 32px; |
|||
background-color: #d7d1ca; |
|||
} |
|||
.box-title{ |
|||
width: 30px; |
|||
margin: 0 0 0 8px; |
|||
} |
|||
.tree-node{ |
|||
display: flex; |
|||
flex-direction: row; |
|||
width: 100%; |
|||
align-items: center; |
|||
.buttons{ |
|||
margin: 0 10px 0 auto; |
|||
span{ |
|||
width: 26px; |
|||
margin: 0 7px; |
|||
} |
|||
} |
|||
} |
|||
.el-dialog{ |
|||
--el-dialog-width:100%; |
|||
} |
|||
.el-checkbox{ |
|||
width: 10px; |
|||
} |
|||
|
|||
.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 "" |
|||
} |
|||