Browse Source

Merge branch 'yunpan'

qin_18
han2015 2 months ago
parent
commit
2f486033f4
  1. 5
      .env.development
  2. 4
      .env.production
  3. 13
      package-lock.json
  4. 1
      package.json
  5. 191
      src/api/doc/index.ts
  6. 223
      src/api/doc/space.ts
  7. 101
      src/api/doc/type.ts
  8. 152
      src/api/hr/user/share_ctrol.ts
  9. 2
      src/assets/icons/cell-icon.svg
  10. 24
      src/assets/icons/file-icon.svg
  11. 3
      src/assets/icons/folder-icon.svg
  12. 2
      src/assets/icons/img-icon.svg
  13. 19
      src/assets/icons/pdf-icon.svg
  14. 19
      src/assets/icons/slide-icon.svg
  15. 53
      src/assets/icons/word-icon.svg
  16. 37
      src/utils/pinia/stores/modules/orgMember.ts
  17. 22
      src/utils/router/index.ts
  18. 11
      src/views/common/bottom/index.vue
  19. 427
      src/views/doc/agent.vue
  20. 357
      src/views/doc/index.vue
  21. 776
      src/views/doc/manage.vue
  22. 93
      src/views/doc/onlyoffice.vue
  23. 13
      src/views/doc/popup.vue
  24. 35
      src/views/doc/preview.vue
  25. 222
      src/views/doc/sharePermission.vue
  26. 867
      src/views/doc/space.vue
  27. 388
      src/views/doc/spacePermission.vue
  28. 97
      src/views/doc/tools.ts

5
.env.development

@ -10,4 +10,7 @@ VITE_APP_BASE_URL = 'http://myvuetest.net'
VITE_APP_TOKEN_KEY = 'offlineAccessSystemAppToken'
VITE_APP_SJZT_URL = 'http://172.20.5.86/prod-api'
VITE_APP_SYSTEM_APP = 'stzl'
VITE_APP_AGAIN = 1
VITE_APP_AGAIN = 1
VITE_OFFICE_HOST='http://myvuetest.net/kkapi'
VITE_ONLYOFFICE_HOST = 'http://myvuetest.net/onlyoffice'
VITE_DEFAULT_AI_AGENT = '5bd9b0e9-d3f4-4089-670a-880009e925a8'

4
.env.production

@ -6,4 +6,6 @@ VITE_APP_BASE_API = ''
VITE_APP_TOKEN_KEY = 'onlineAccessSystemAppToken'
VITE_APP_SJZT_URL = 'http://120.224.6.6:29911/prod-api'
VITE_APP_SYSTEM_APP = 'stzl'
VITE_APP_AGAIN = 1
VITE_APP_AGAIN = 1
VITE_ONLYOFFICE_HOST = 'https://gyhlw.hxgk.group/onlyoffice'
VITE_DEFAULT_AI_AGENT = '23a3c2c5-2de1-40df-59fa-a9206b11861d'

13
package-lock.json

@ -10,6 +10,7 @@
"dependencies": {
"@element-plus/icons-vue": "^2.3.1",
"@nutui/nutui": "^4.0.0",
"@onlyoffice/document-editor-vue": "^1.6.1",
"@tinymce/tinymce-vue": "^6.1.0",
"@vueup/vue-quill": "^1.2.0",
"@wangeditor/editor": "^5.1.23",
@ -996,6 +997,18 @@
"resolved": "https://registry.npmmirror.com/@one-ini/wasm/-/wasm-0.1.1.tgz",
"integrity": "sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw=="
},
"node_modules/@onlyoffice/document-editor-vue": {
"version": "1.6.1",
"resolved": "https://registry.npmjs.org/@onlyoffice/document-editor-vue/-/document-editor-vue-1.6.1.tgz",
"integrity": "sha512-sdU7h684ESSdXvGNDcMf73UmToiZGMVO5QRIazTmGfm+bKOnT5ildomeagYFdnQaHQH0J28EJqc4jqXOcQbicA==",
"license": "Apache-2.0",
"dependencies": {
"lodash": "^4.17.21"
},
"peerDependencies": {
"vue": "^3.0.0"
}
},
"node_modules/@parcel/watcher": {
"version": "2.4.1",
"resolved": "https://registry.npmmirror.com/@parcel/watcher/-/watcher-2.4.1.tgz",

1
package.json

@ -43,6 +43,7 @@
"dependencies": {
"@element-plus/icons-vue": "^2.3.1",
"@nutui/nutui": "^4.0.0",
"@onlyoffice/document-editor-vue": "^1.6.1",
"@tinymce/tinymce-vue": "^6.1.0",
"@vueup/vue-quill": "^1.2.0",
"@wangeditor/editor": "^5.1.23",

191
src/api/doc/index.ts

@ -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
});
}

223
src/api/doc/space.ts

@ -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
});
}

101
src/api/doc/type.ts

@ -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'
}
});
}

152
src/api/hr/user/share_ctrol.ts

@ -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
});
}

2
src/assets/icons/cell-icon.svg

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg"><title>file_type_excel2</title><path d="M28.781,4.405H18.651V2.018L2,4.588V27.115l16.651,2.868V26.445H28.781A1.162,1.162,0,0,0,30,25.349V5.5A1.162,1.162,0,0,0,28.781,4.405Zm.16,21.126H18.617L18.6,23.642h2.487v-2.2H18.581l-.012-1.3h2.518v-2.2H18.55l-.012-1.3h2.549v-2.2H18.53v-1.3h2.557v-2.2H18.53v-1.3h2.557v-2.2H18.53v-2H28.941Z" style="fill:#20744a;fill-rule:evenodd"/><rect x="22.487" y="7.439" width="4.323" height="2.2" style="fill:#20744a"/><rect x="22.487" y="10.94" width="4.323" height="2.2" style="fill:#20744a"/><rect x="22.487" y="14.441" width="4.323" height="2.2" style="fill:#20744a"/><rect x="22.487" y="17.942" width="4.323" height="2.2" style="fill:#20744a"/><rect x="22.487" y="21.443" width="4.323" height="2.2" style="fill:#20744a"/><polygon points="6.347 10.673 8.493 10.55 9.842 14.259 11.436 10.397 13.582 10.274 10.976 15.54 13.582 20.819 11.313 20.666 9.781 16.642 8.248 20.513 6.163 20.329 8.585 15.666 6.347 10.673" style="fill:#ffffff;fill-rule:evenodd"/></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

24
src/assets/icons/file-icon.svg

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg version="1.0" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
width="800px" height="800px" viewBox="0 0 64 64" enable-background="new 0 0 64 64" xml:space="preserve">
<g>
<g>
<polygon fill="#F9EBB2" points="46,3.414 46,14 56.586,14 "/>
<path fill="#F9EBB2" d="M45,16c-0.553,0-1-0.447-1-1V2H8C6.896,2,6,2.896,6,4v56c0,1.104,0.896,2,2,2h48c1.104,0,2-0.896,2-2V16
H45z"/>
</g>
<path fill="#394240" d="M14,26c0,0.553,0.447,1,1,1h34c0.553,0,1-0.447,1-1s-0.447-1-1-1H15C14.447,25,14,25.447,14,26z"/>
<path fill="#394240" d="M49,37H15c-0.553,0-1,0.447-1,1s0.447,1,1,1h34c0.553,0,1-0.447,1-1S49.553,37,49,37z"/>
<path fill="#394240" d="M49,43H15c-0.553,0-1,0.447-1,1s0.447,1,1,1h34c0.553,0,1-0.447,1-1S49.553,43,49,43z"/>
<path fill="#394240" d="M49,49H15c-0.553,0-1,0.447-1,1s0.447,1,1,1h34c0.553,0,1-0.447,1-1S49.553,49,49,49z"/>
<path fill="#394240" d="M49,31H15c-0.553,0-1,0.447-1,1s0.447,1,1,1h34c0.553,0,1-0.447,1-1S49.553,31,49,31z"/>
<path fill="#394240" d="M15,20h16c0.553,0,1-0.447,1-1s-0.447-1-1-1H15c-0.553,0-1,0.447-1,1S14.447,20,15,20z"/>
<path fill="#394240" d="M59.706,14.292L45.708,0.294C45.527,0.112,45.277,0,45,0H8C5.789,0,4,1.789,4,4v56c0,2.211,1.789,4,4,4h48
c2.211,0,4-1.789,4-4V15C60,14.723,59.888,14.473,59.706,14.292z M46,3.414L56.586,14H46V3.414z M58,60c0,1.104-0.896,2-2,2H8
c-1.104,0-2-0.896-2-2V4c0-1.104,0.896-2,2-2h36v13c0,0.553,0.447,1,1,1h13V60z"/>
<polygon opacity="0.15" fill="#231F20" points="46,3.414 56.586,14 46,14 "/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

3
src/assets/icons/folder-icon.svg

@ -0,0 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 1024 1024" class="icon" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M525.1 270.1c16.5 0 28.1-5.4 25.7-12-2.4-6.6-7.9-16.1-12.2-21.2-4.3-5-31.6-9.1-48.1-9.1h-224c-16.5 0-34.8 1.9-40.8 4.3-5.9 2.4-15.3 19.2-17.4 25.9-2 6.7 9.8 12.1 26.3 12.1h290.5zM618 863.3l266.9-266.9v-78.2L539.8 863.3zM869.9 300.1H154.1c-8.3 0-15 6.7-15 15v533.2c0 8.3 6.7 15 15 15h343.3l387.5-387.5V315.1c0-8.2-6.7-15-15-15zM256.7 463.2c-28.7 0-51.9-23.2-51.9-51.9s23.2-51.9 51.9-51.9 51.9 23.2 51.9 51.9-23.3 51.9-51.9 51.9zM859.3 863.3l25.6-25.6v-78.2L781.1 863.3zM738.6 863.3L884.9 717v-78.2L660.5 863.3z" fill="#FFBC00" /><path d="M869.9 270.1H587L574.8 236c-7.7-21.4-31.5-38.2-54.2-38.2h-284c-23 0-46.4 17.3-53.1 39.3l-10.1 33.1h-19.2c-24.8 0-45 20.2-45 45v533.2c0 24.8 20.2 45 45 45h715.9c24.8 0 45-20.2 45-45V315.1c-0.2-24.8-20.3-45-45.2-45z m-633.3-42.3h284c10.2 0 22.5 8.7 26 18.3l8.6 24.1H204.7l7.4-24.3c2.9-9.5 14.6-18.1 24.5-18.1z m648.3 609.9l-25.7 25.7H781l103.9-103.9v78.2z m0-120.7L738.6 863.3h-78.2l224.5-224.5V717z m0-120.6L618 863.3h-78.2l345.1-345.1v78.2z m0-120.6L497.4 863.3H154.1c-8.3 0-15-6.7-15-15V315.1c0-8.3 6.7-15 15-15H870c8.3 0 15 6.7 15 15v160.7z" fill="#46287C" /><path d="M256.7 411.3m-51.9 0a51.9 51.9 0 1 0 103.8 0 51.9 51.9 0 1 0-103.8 0Z" fill="#FFFFFF" /></svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

2
src/assets/icons/img-icon.svg

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg"><title>file_type_image</title><path d="M30,5.851Q30,16,30,26.149H2Q2,16,2,5.851H30" style="fill:#2dcc9f"/><path d="M24.232,8.541a2.2,2.2,0,1,0,1.127.623,2.212,2.212,0,0,0-1.127-.623" style="fill:#fff"/><path d="M18.111,20.1q-2.724-3.788-5.45-7.575Q8.619,18.147,4.579,23.766q5.449,0,10.9,0,1.316-1.832,2.634-3.663" style="fill:#fff"/><path d="M22.057,16q-2.793,3.882-5.584,7.765,5.584,0,11.169,0Q24.851,19.882,22.057,16Z" style="fill:#fff"/></svg>

After

Width:  |  Height:  |  Size: 654 B

19
src/assets/icons/pdf-icon.svg

@ -0,0 +1,19 @@
<?xml version="1.0" ?>
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 400 400" xmlns="http://www.w3.org/2000/svg">
<defs>
<style>.cls-1{fill:#ff402f;}</style>
</defs>
<title/>
<g id="xxx-word">
<path class="cls-1" d="M325,105H250a5,5,0,0,1-5-5V25a5,5,0,0,1,10,0V95h70a5,5,0,0,1,0,10Z"/>
<path class="cls-1" d="M325,154.83a5,5,0,0,1-5-5V102.07L247.93,30H100A20,20,0,0,0,80,50v98.17a5,5,0,0,1-10,0V50a30,30,0,0,1,30-30H250a5,5,0,0,1,3.54,1.46l75,75A5,5,0,0,1,330,100v49.83A5,5,0,0,1,325,154.83Z"/>
<path class="cls-1" d="M300,380H100a30,30,0,0,1-30-30V275a5,5,0,0,1,10,0v75a20,20,0,0,0,20,20H300a20,20,0,0,0,20-20V275a5,5,0,0,1,10,0v75A30,30,0,0,1,300,380Z"/>
<path class="cls-1" d="M275,280H125a5,5,0,0,1,0-10H275a5,5,0,0,1,0,10Z"/>
<path class="cls-1" d="M200,330H125a5,5,0,0,1,0-10h75a5,5,0,0,1,0,10Z"/>
<path class="cls-1" d="M325,280H75a30,30,0,0,1-30-30V173.17a30,30,0,0,1,30-30h.2l250,1.66a30.09,30.09,0,0,1,29.81,30V250A30,30,0,0,1,325,280ZM75,153.17a20,20,0,0,0-20,20V250a20,20,0,0,0,20,20H325a20,20,0,0,0,20-20V174.83a20.06,20.06,0,0,0-19.88-20l-250-1.66Z"/>
<path class="cls-1" d="M145,236h-9.61V182.68h21.84q9.34,0,13.85,4.71a16.37,16.37,0,0,1-.37,22.95,17.49,17.49,0,0,1-12.38,4.53H145Zm0-29.37h11.37q4.45,0,6.8-2.19a7.58,7.58,0,0,0,2.34-5.82,8,8,0,0,0-2.17-5.62q-2.17-2.34-7.83-2.34H145Z"/>
<path class="cls-1" d="M183,236V182.68H202.7q10.9,0,17.5,7.71t6.6,19q0,11.33-6.8,18.95T200.55,236Zm9.88-7.85h8a14.36,14.36,0,0,0,10.94-4.84q4.49-4.84,4.49-14.41a21.91,21.91,0,0,0-3.93-13.22,12.22,12.22,0,0,0-10.37-5.41h-9.14Z"/>
<path class="cls-1" d="M245.59,236H235.7V182.68h33.71v8.24H245.59v14.57h18.75v8H245.59Z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

19
src/assets/icons/slide-icon.svg

@ -0,0 +1,19 @@
<?xml version="1.0" ?>
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 400 400" xmlns="http://www.w3.org/2000/svg">
<defs>
<style>.cls-1{fill:#fd7a00;}</style>
</defs>
<title/>
<g id="xxx-word">
<path class="cls-1" d="M325,105H250a5,5,0,0,1-5-5V25a5,5,0,1,1,10,0V95h70a5,5,0,0,1,0,10Z"/>
<path class="cls-1" d="M325,154.83a5,5,0,0,1-5-5V102.07L247.93,30H100A20,20,0,0,0,80,50v98.17a5,5,0,0,1-10,0V50a30,30,0,0,1,30-30H250a5,5,0,0,1,3.54,1.46l75,75A5,5,0,0,1,330,100v49.83A5,5,0,0,1,325,154.83Z"/>
<path class="cls-1" d="M300,380H100a30,30,0,0,1-30-30V275a5,5,0,0,1,10,0v75a20,20,0,0,0,20,20H300a20,20,0,0,0,20-20V275a5,5,0,0,1,10,0v75A30,30,0,0,1,300,380Z"/>
<path class="cls-1" d="M275,280H125a5,5,0,1,1,0-10H275a5,5,0,0,1,0,10Z"/>
<path class="cls-1" d="M200,330H125a5,5,0,1,1,0-10h75a5,5,0,0,1,0,10Z"/>
<path class="cls-1" d="M325,280H75a30,30,0,0,1-30-30V173.17a30,30,0,0,1,30-30h.2l250,1.66a30.09,30.09,0,0,1,29.81,30V250A30,30,0,0,1,325,280ZM75,153.17a20,20,0,0,0-20,20V250a20,20,0,0,0,20,20H325a20,20,0,0,0,20-20V174.83a20.06,20.06,0,0,0-19.88-20l-250-1.66Z"/>
<path class="cls-1" d="M157.07,236h-9.61V182.68H169.3q9.34,0,13.85,4.71a16.37,16.37,0,0,1-.37,22.95,17.49,17.49,0,0,1-12.38,4.53H157.07Zm0-29.37h11.37q4.45,0,6.8-2.19a7.58,7.58,0,0,0,2.34-5.82,8,8,0,0,0-2.17-5.62q-2.17-2.34-7.83-2.34H157.07Z"/>
<path class="cls-1" d="M203.95,249.32h-9.06V196.55h8.52v6.88q4.1-7.69,12.07-7.7a12.1,12.1,0,0,1,10.47,5.7q3.91,5.7,3.91,14.57a27.84,27.84,0,0,1-3.73,14.61A12.15,12.15,0,0,1,215,236.82q-7.62,0-11.05-6.33Zm0-29.49A10.23,10.23,0,0,0,206.5,227a7.55,7.55,0,0,0,5.68,2.83,6.57,6.57,0,0,0,6.23-3.73,20.16,20.16,0,0,0,2-9.47,20.91,20.91,0,0,0-2-9.79,6.37,6.37,0,0,0-5.94-3.77,7.5,7.5,0,0,0-6,3.09,12.51,12.51,0,0,0-2.48,8.09Z"/>
<path class="cls-1" d="M256.25,229v7a31.56,31.56,0,0,1-6.17.86,12.57,12.57,0,0,1-6.17-1.43,8.72,8.72,0,0,1-3.77-3.91q-1.19-2.48-1.19-7.64V203.46H234v-6.91h5.43l.82-10.27,7.3-.66v10.94h7.93v6.91h-7.93v19.26q0,3.71,1.35,5.06t5.1,1.35Q255.08,229.13,256.25,229Z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

53
src/assets/icons/word-icon.svg

@ -0,0 +1,53 @@
<?xml version="1.0" ?>
<!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'>
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 512 512" version="1.1" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="file__x2C__doc__x2C__word__x2C__document__x2C_">
<g id="Layer_116">
<g>
<g>
<polygon points="107.071,25.467 349.079,25.467 420.601,97.709 420.601,447.742 107.071,447.742 " style="fill-rule:evenodd;clip-rule:evenodd;fill:#FFFFFF;"/>
<path d="M420.601,454.602H107.071c-3.79,0-6.859-3.068-6.859-6.859V25.467c0-3.79,3.069-6.86,6.859-6.86 h242.008c1.831,0,3.583,0.729,4.877,2.033l71.522,72.242c1.271,1.285,1.986,3.02,1.986,4.827v350.033 C427.465,451.533,424.391,454.602,420.601,454.602z M113.932,440.883h299.813V100.531L346.22,32.327H113.932V440.883z" style="fill:#4C8CF9;"/>
</g>
<g>
<rect height="105.6" style="fill-rule:evenodd;clip-rule:evenodd;fill:#4C8CF9;" width="313.532" x="129.98" y="177.615"/>
</g>
<g>
<path d="M213.358,258.326h-11.056v-9.818h11.056V258.326z M224.679,258.326v-55.961h19.047 c6.793,0,12.387,2.157,16.782,6.594c4.394,4.304,6.523,9.953,6.523,16.817v9.278c0,6.864-2.13,12.513-6.523,16.821 c-4.396,4.305-9.989,6.451-16.782,6.451H224.679z M235.868,211.111v38.608h7.326c3.994,0,7.057-1.345,9.322-4.038 c2.264-2.689,3.464-6.318,3.464-10.627v-9.416c0-4.304-1.2-7.8-3.464-10.489c-2.265-2.689-5.328-4.038-9.322-4.038H235.868z M320.441,235.458c0,6.864-2.262,12.513-6.524,16.95c-4.263,4.579-9.856,6.727-16.785,6.727c-6.791,0-12.385-2.148-16.647-6.727 c-4.258-4.437-6.392-10.085-6.392-16.95v-10.086c0-6.864,2.134-12.508,6.392-17.087c4.263-4.438,9.856-6.727,16.647-6.727 c6.796,0,12.522,2.29,16.785,6.727c4.263,4.579,6.524,10.223,6.524,17.087V235.458z M309.255,225.234 c0-4.437-1.064-7.938-3.193-10.76c-2.134-2.822-5.194-4.171-8.93-4.171c-3.859,0-6.791,1.349-8.787,4.171 c-2.134,2.822-3.198,6.323-3.198,10.76v10.224c0,4.441,1.064,8.076,3.198,10.902c2.134,2.689,5.062,4.167,8.787,4.167 c3.863,0,6.796-1.478,8.93-4.167c2.129-2.827,3.193-6.461,3.193-10.902V225.234z M370.925,239.229l0.129,0.266 c0,6.057-1.73,10.765-5.461,14.395c-3.863,3.5-9.058,5.245-15.711,5.245c-6.663,0-11.99-2.148-16.253-6.452 c-4.13-4.309-6.259-9.957-6.259-16.684v-11.169c0-6.727,2.129-12.371,6.126-16.679c4.13-4.304,9.324-6.594,15.849-6.594 c6.925,0,12.256,1.753,16.12,5.387c3.858,3.496,5.727,8.337,5.589,14.527v0.266h-10.921c0-3.762-0.799-6.589-2.528-8.475 c-1.868-2.015-4.529-2.96-8.26-2.96c-3.326,0-5.992,1.349-7.86,4.038c-1.996,2.689-2.927,6.186-2.927,10.489v11.031 c0,4.304,0.931,7.805,3.064,10.627c1.996,2.693,4.791,4.038,8.26,4.038c3.46,0,6.121-0.941,7.723-2.827 c1.729-1.881,2.528-4.708,2.528-8.471H370.925z" style="fill:#FEFEFE;"/>
</g>
<g>
<path d="M263.498,146.357c-14.012,0-26.189-5.08-32.584-13.591c-2.275-3.028-1.666-7.328,1.365-9.604 c3.028-2.285,7.328-1.666,9.606,1.363c3.76,5.006,12.038,8.113,21.612,8.113c0.004,0,0.009,0,0.018,0 c9.691-0.005,18.639-3.263,22.269-8.108c2.276-3.033,6.575-3.644,9.604-1.372c3.032,2.271,3.647,6.571,1.376,9.604 c-6.286,8.379-19.02,13.591-33.239,13.596C263.516,146.357,263.507,146.357,263.498,146.357z" style="fill:#4C8CF9;"/>
</g>
<g>
<path d="M143.968,233.173c-0.025,0-0.051,0-0.078,0c-41.685-0.455-66.41-11.325-73.49-32.308 c-9.211-27.307,17.035-62.661,32.457-74.669c2.992-2.322,7.303-1.785,9.627,1.198c2.329,2.991,1.792,7.3-1.198,9.631 c-13.25,10.315-34.504,39.829-27.885,59.454c6.245,18.51,36.424,22.709,60.64,22.975c3.788,0.041,6.826,3.147,6.784,6.933 C150.784,230.149,147.722,233.173,143.968,233.173z" style="fill:#4C8CF9;"/>
</g>
<g>
<polygon points="349.079,97.709 420.601,97.709 349.079,25.467 " style="fill-rule:evenodd;clip-rule:evenodd;fill:#D4E4FF;"/>
<path d="M420.601,104.569h-71.521c-3.79,0-6.86-3.07-6.86-6.86V25.467c0-2.781,1.68-5.286,4.249-6.346 c2.579-1.056,5.534-0.455,7.488,1.519l71.522,72.242c1.95,1.969,2.519,4.914,1.454,7.47 C425.868,102.908,423.372,104.569,420.601,104.569z M355.938,90.85h48.217l-48.217-48.703V90.85z" style="fill:#4C8CF9;"/>
</g>
<g>
<path d="M239.339,493.393c-0.376,0-0.757-0.027-1.143-0.092l-22.509-3.766 c-3.306-0.557-5.727-3.414-5.727-6.77v-35.023c0-3.789,3.069-6.859,6.859-6.859c3.791,0,6.86,3.07,6.86,6.859v29.215 l16.782,2.809c3.738,0.629,6.259,4.162,5.633,7.9C245.536,491.021,242.629,493.393,239.339,493.393z" style="fill:#4C8CF9;"/>
</g>
<g>
<path d="M325.25,493.393c-0.376,0-0.762-0.027-1.146-0.092l-22.507-3.766 c-3.309-0.557-5.727-3.414-5.727-6.77v-35.023c0-3.789,3.069-6.859,6.859-6.859s6.86,3.07,6.86,6.859v29.215l16.78,2.809 c3.739,0.629,6.259,4.162,5.635,7.9C331.445,491.021,328.536,493.393,325.25,493.393z" style="fill:#4C8CF9;"/>
</g>
<g>
<path d="M290.873,65.149c6.259-1.744,13.188,0.945,16.519,6.865 c4.13,7.13,1.73,16.143-5.332,20.18c-7.057,4.167-15.978,1.744-19.979-5.382c-3.193-5.52-2.396-12.375,1.469-16.95 c1.331,1.748,3.726,2.285,5.727,1.207C291.276,69.862,292.075,67.168,290.873,65.149L290.873,65.149z" style="fill-rule:evenodd;clip-rule:evenodd;fill:#4C8CF9;"/>
</g>
<g>
<path d="M228.808,65.149c6.259-1.744,13.185,0.945,16.65,6.865 c3.994,7.13,1.597,16.143-5.463,20.18c-6.924,4.167-15.982,1.744-19.979-5.382c-3.196-5.52-2.397-12.375,1.466-16.95 c1.331,1.748,3.861,2.285,5.727,1.207C229.34,69.862,230.006,67.168,228.808,65.149L228.808,65.149z" style="fill-rule:evenodd;clip-rule:evenodd;fill:#4C8CF9;"/>
</g>
<g>
<path d="M373.72,326.398H164.61c-3.791,0-6.86-3.07-6.86-6.859c0-3.791,3.069-6.861,6.86-6.861h209.11 c3.79,0,6.859,3.07,6.859,6.861C380.579,323.328,377.51,326.398,373.72,326.398z" style="fill:#4C8CF9;"/>
</g>
<g>
<path d="M373.72,361.914H164.61c-3.791,0-6.86-3.07-6.86-6.859c0-3.791,3.069-6.861,6.86-6.861h209.11 c3.79,0,6.859,3.07,6.859,6.861C380.579,358.844,377.51,361.914,373.72,361.914z" style="fill:#4C8CF9;"/>
</g>
<g>
<path d="M300.463,397.563H164.61c-3.791,0-6.86-3.07-6.86-6.859c0-3.791,3.069-6.861,6.86-6.861h135.853 c3.79,0,6.864,3.07,6.864,6.861C307.327,394.492,304.253,397.563,300.463,397.563z" style="fill:#4C8CF9;"/>
</g>
</g>
</g>
</g>
<g id="Layer_1"/>
</svg>

After

Width:  |  Height:  |  Size: 6.2 KiB

37
src/utils/pinia/stores/modules/orgMember.ts

@ -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 }
})

22
src/utils/router/index.ts

@ -75,6 +75,28 @@ export const staticRouting : RouteRecordRaw[] = [
path: '/work_wechat',
component: () => import('@/views/lookCodePage/index.vue'),
meta: { hidden: true },
},
{
path: '/doc',
component: () => import('@/views/doc/index.vue'),
meta: { hidden: true },
},
{
path: '/mysapce',
component: () => import('@/views/doc/manage.vue'),
meta: { hidden: true },
},
{
path: '/onlyoffice',
component: () => import('@/views/doc/onlyoffice.vue'),
meta: { hidden: true },
},
{
path: '/spaces/:spaceid',
name: 'spaces',
props: route => ({ spaceid: route.params.spaceid}),
component: () => import('@/views/doc/space.vue'),
meta: { hidden: true },
}
]

11
src/views/common/bottom/index.vue

@ -29,6 +29,9 @@ const openPage = (val:number) => {
case 5:
router.push({ path: "/user",query:{fa:5}});
break;
case 6:
router.push({ path: "/doc",query:{fa:6}});
break;
default:
router.push({ path: "/"});
}
@ -48,10 +51,10 @@ const openPage = (val:number) => {
<div class="footSvg"><SvgIcon icon-class="all" :size="20" /></div>
<div>应用</div>
</div>
<div @click="openPage(4)" :class="footerActive==4?'footerBox active':'footerBox'">
<div class="footSvg"><SvgIcon icon-class="publish" :size="20" /></div>
<div>动态</div>
</div>
<div @click="openPage(6)" :class="footerActive==6?'footerBox active':'footerBox'">
<div class="footSvg"><SvgIcon icon-class="folder" :size="20" /></div>
<div>云盘</div>
</div>
<div @click="openPage(5)" :class="footerActive==5?'footerBox active':'footerBox'">
<div class="footSvg"><SvgIcon icon-class="user" :size="20" /></div>
<div>我的</div>

427
src/views/doc/agent.vue

@ -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>

357
src/views/doc/index.vue

@ -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>

776
src/views/doc/manage.vue

@ -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, // uuidroot
}
});
//---------------------------
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 index1apiindex0
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 index1apiindex0
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>

93
src/views/doc/onlyoffice.vue

@ -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, editreview
callbackUrl: `${siteHost}${apiURL}/matter/save`, //
user: {
id: userStore.userInfoCont.number,
name: userStore.userInfoCont.nickname,
},
}
})
function onLoadComponentError (errorCode:number, errorDescription:string) {
switch(errorCode) {
case -1: // Unknown error loading component
alert(errorDescription);
break;
case -2: // Error load DocsAPI from http://documentserver/
alert(errorDescription);
break;
case -3: // DocsAPI is not defined
alert(errorDescription);
break;
}
}
onMounted(()=>{
const query = route.query
if (query.fileurl){
const info=query.info?.toString()??"error"
const _info=decodeURIComponent(atob(info))
const _url=decodeURIComponent(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)
//1true 2uuid
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>

13
src/views/doc/popup.vue

@ -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>

35
src/views/doc/preview.vue

@ -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>

222
src/views/doc/sharePermission.vue

@ -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(()=>{
//uuiduuid 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>

867
src/views/doc/space.vue

@ -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, // uuidroot
}
});
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 index1apiindex0
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 +" 上传失败! ")
}
//AIhandleAiUpload
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>

388
src/views/doc/spacePermission.vue

@ -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)) //eleProxyclonetoRawobject
//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){
//returnreturnfortruereturn
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>

97
src/views/doc/tools.ts

@ -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 ""
}
Loading…
Cancel
Save