herenshan112 2 months ago
parent
commit
dcf414bf5a
  1. 16
      src/api/doc/space.ts
  2. 2
      src/components/DesignForm/assembly/index.ts
  3. 72
      src/components/DesignForm/formControlPropertiNew.vue
  4. 246
      src/components/DesignForm/public/expand/org.vue
  5. 20
      src/store/modules/orgMember.ts
  6. 126
      src/views/doc/agent.vue
  7. 58
      src/views/doc/manage.vue
  8. 28
      src/views/doc/onlyoffice.vue
  9. 4
      src/views/doc/recentVisited.vue
  10. 113
      src/views/doc/space.vue
  11. 107
      src/views/doc/spacePermission.vue
  12. 171
      src/widget/org/cont.vue

16
src/api/doc/space.ts

@ -74,6 +74,22 @@ export function doDelSpace(uid:string,data?: any){
});
}
/**
*
*/
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
});
}
/**
*
*/

2
src/components/DesignForm/assembly/index.ts

@ -1063,6 +1063,8 @@ export default [
icon: 'sliders',
iconFont: 'fa-sliders',
control: {
range:[],
multiple: '',
},
config: {},
styles: {

72
src/components/DesignForm/formControlPropertiNew.vue

@ -13,6 +13,8 @@ import validateInt from "./validateInt";
import { ValidateTextTypes } from "@/components/DesignForm/validateText";
import { ElMessage } from "element-plus";
import { formatNumber } from "@/api/DesignForm/utils";
import { getOrgTreeList } from "@/api/hr/org/index";
import { orgInfo } from "@/api/hr/org/type";
import {
PublicAtrr,
formStruct,
@ -833,6 +835,22 @@ const attrList = computed(() => {
vIf: state.isSearch,
vShow: ["lowcodeImage"],
},
{
label: "数据范围",
value: config.orgCentent,
path: "config.orgCentent",
type: "orgCentent_range",
vIf: state.isSearch,
vShow: ["orgCentent"],
},
{
label: "是否多选",
value: config.orgCentent,
path: "config.orgCentent",
type: "orgCentent_Multiple",
vIf: state.isSearch,
vShow: ["orgCentent"],
},
/* {
label: "添加时间水印",
value: config.lowcodeImage,
@ -2048,6 +2066,8 @@ watch(
controlData.value.control.fillRoles.child[0].id = uuidv4()
.replaceAll("-", "")
.toString();
}else if(controlData.value.type === "orgCentent"){
haveOrgTreeInfo()
}
// start
// console.log(controlData.value.name)
@ -2086,6 +2106,34 @@ watch(
// end
}
);
const orgTreeProps = {
children: "child",
label: "name",
}; //
const orgCententRange = ref<orgInfo[]>();
function haveOrgTreeInfo() {
//orgTreeLoading.value = true;
getOrgTreeList({ orgid: 309 })
.then(({ data }) => {
console.log("行政组织树对照值", data);
orgCententRange.value = data;
console.log(orgCententRange.value)
})
.finally(() => {
//orgTreeLoading.value = false;
});
}
//
function carouselImgUploadSuccess(
response: any,
@ -4404,6 +4452,30 @@ const aiAgentList = ref([
</el-row>
<el-row v-else-if="item.type === 'orgCentent_range'">
<!-- {{ controlData.control.range }} -->
<el-tree-select
v-model="controlData.control.range"
node-key="id"
:props="orgTreeProps"
:data="orgCententRange"
:render-after-expand="false"
show-checkbox
multiple
style="width: 200px"
/>
</el-row>
<el-row v-else-if="item.type === 'orgCentent_Multiple'">
<el-radio-group v-model="controlData.control.multiple">
<el-radio value="1"></el-radio>
<el-radio value=""></el-radio>
</el-radio-group>
</el-row>

246
src/components/DesignForm/public/expand/org.vue

@ -11,6 +11,7 @@ const props = withDefaults(
defineProps<{
modelValue?: string;
disabled?: boolean;
data?: Object;
}>(),
{}
);
@ -20,13 +21,33 @@ const emits = defineEmits<{
const value = computed({
get: () => {
if (props.modelValue != "" && props.modelValue != undefined) {
return props.modelValue * 1;
if(hasComma(props.modelValue)||multiple){
return commaStringToNumberArray(props.modelValue)
}else{
return props.modelValue * 1;
}
} else {
return props.modelValue;
}
},
set: (newVal: any) => {
emits("update:modelValue", newVal);
//console.log(newVal)
if(isArray(newVal)){
let str = arrayToCommaString(newVal)
//console.log(2)
//console.log(str)
emits("update:modelValue", str);
}else{
//console.log(1)
//console.log(newVal)
emits("update:modelValue", newVal);
}
// let newValJson:criteriaForPeopleList[] = JSON.parse(newVal)
// if(newValJson.length > 0){
// let userAry = new Array
@ -43,6 +64,62 @@ const value = computed({
// }
},
});
function hasComma(str: any) {
//
if (typeof str !== 'string') {
return false
}
// 使indexOf
// truefalse
return str.indexOf(',') !== -1;
}
function commaStringToNumberArray(str: string) {
//
if (typeof str !== 'string') {
throw new Error('输入必须是字符串');
}
//
if (str.trim() === '') {
return [];
}
//
return str.split(',').map(item => {
//
const trimmed = item.trim();
//
const number = Number(trimmed);
//
if (isNaN(number)) {
throw new Error(`无法将 "${trimmed}" 转换为有效数字`);
}
return number;
});
}
function isArray(variable: any) {
// 使
if (typeof Array.isArray === 'function') {
return Array.isArray(variable);
} else {
//
return Object.prototype.toString.call(variable) === '[object Array]';
}
}
function arrayToCommaString(numbers: any[]) {
//
if (!Array.isArray(numbers)) {
throw new Error('输入必须是一个数组');
}
// 使join
return numbers.join(',');
}
const orgTreeList = ref<orgInfo[]>();
const orgTreeLoading = ref(false); //
const orgTreeProps = {
@ -55,7 +132,27 @@ function haveOrgTreeInfo() {
getOrgTreeList({ orgid: 309 })
.then(({ data }) => {
console.log("行政组织树对照值", data);
orgTreeList.value = data;
//liwenxuan 250916 start
// IDIDididid
const targetIds = props.data.control.range
console.log(targetIds)
if(targetIds&&targetIds.length>0){
//
const filteredTree = filterOrganizationTree(data, targetIds);
orgTreeList.value = filteredTree
}else{
orgTreeList.value = data;
}
//liwenxuan 250916 end
})
.finally(() => {
orgTreeLoading.value = false;
@ -72,6 +169,145 @@ onMounted(() => {
haveOrgTreeInfo();
});
});
/**
* 过滤组织架构树仅保留顶层目标ID节点其所有祖先节点和子孙节点
* 顶层目标ID目标数组中无其他ID是其祖先的ID子节点ID会被自动忽略
* @param {Array} originalTree - 原始组织架构树形结构顶层为数组
* @param {Array<number>} targetIds - 目标节点ID数组可能包含子节点ID
* @returns {Array} 过滤后的组织架构树
*/
function filterOrganizationTree(originalTree: orgInfo[], targetIds: any) {
// 1. ID便/
const nodeMap = new Map();
buildNodeMap(originalTree, nodeMap);
// 2. IDIDIDID
const topTargetIds = filterChildTargetIds(targetIds, nodeMap);
if (topTargetIds.length === 0) {
console.warn("警告:所有目标ID无效或均为其他目标ID的子节点,返回空树");
return [];
}
// 3. IDID + ID + ID
const keepIds = collectKeepIds(topTargetIds, nodeMap);
// 4.
const filteredTree = filterTreeNodes(originalTree, keepIds);
return filteredTree;
}
/**
* 辅助函数1遍历树形结构构建节点ID节点映射表
* @param {Array} nodes - 当前层级的节点数组顶层/子节点数组
* @param {Map} nodeMap - 待构建的节点映射表
*/
function buildNodeMap(nodes: any[], nodeMap: Map<any, any>) {
if (!nodes || !Array.isArray(nodes)) return;
nodes.forEach(node => {
nodeMap.set(node.id, node);
// childnull
buildNodeMap(Array.isArray(node.child) ? node.child : [], nodeMap);
});
}
/**
* 辅助函数2预处理目标ID数组过滤掉是其他目标ID子节点的ID
* @param {Array<number>} targetIds - 原始目标ID数组
* @param {Map} nodeMap - 节点映射表
* @returns {Array<number>} 仅包含顶层目标ID的数组无父节点在目标数组内
*/
function filterChildTargetIds(targetIds: any[], nodeMap: Map<any, any>) {
if (!targetIds || !Array.isArray(targetIds)) return [];
// ID
const validTargetIds = targetIds.filter(id => nodeMap.has(id));
if (validTargetIds.length <= 1) return validTargetIds; // 1ID
// IDIDID
return validTargetIds.filter(currentId => {
let parentId = nodeMap.get(currentId).superior;
// ID
while (parentId !== undefined && nodeMap.has(parentId)) {
if (validTargetIds.includes(parentId)) {
console.log(`提示:ID ${currentId} 是目标ID ${parentId} 的子节点,已忽略`);
return false; // ID
}
parentId = nodeMap.get(parentId).superior; //
}
return true; // ID
});
}
/**
* 辅助函数3收集所有需保留的节点ID顶层目标ID + 所有祖先ID + 所有子孙ID
* @param {Array<number>} topTargetIds - 预处理后的顶层目标ID数组
* @param {Map} nodeMap - 节点映射表
* @returns {Set<number>} 需保留的节点ID集合去重
*/
function collectKeepIds(topTargetIds: any[], nodeMap: Map<any, any>) {
const keepIds = new Set();
topTargetIds.forEach((targetId: unknown) => {
const targetNode = nodeMap.get(targetId);
// 1.
keepIds.add(targetId);
// 2.
let parentId = targetNode.superior;
while (parentId !== undefined && nodeMap.has(parentId)) {
keepIds.add(parentId);
parentId = nodeMap.get(parentId).superior;
}
// 3. ...
collectDescendants(targetNode.child, nodeMap, keepIds);
});
return keepIds;
}
/**
* 辅助函数4递归收集某个节点的所有子孙节点ID
* @param {Array} childNodes - 当前节点的子节点数组
* @param {Map} nodeMap - 节点映射表
* @param {Set<number>} keepIds - 需保留的ID集合
*/
function collectDescendants(childNodes: any[], nodeMap: any, keepIds: Set<unknown>) {
if (!Array.isArray(childNodes)) return;
childNodes.forEach(childNode => {
keepIds.add(childNode.id);
collectDescendants(childNode.child, nodeMap, keepIds); //
});
}
/**
* 辅助函数5递归过滤树形节点仅保留在keepIds中的节点
* @param {Array} nodes - 当前层级的节点数组
* @param {Set<number>} keepIds - 需保留的节点ID集合
* @returns {Array} 过滤后的节点数组
*/
function filterTreeNodes(nodes: any[], keepIds: Set<unknown>) {
if (!Array.isArray(nodes) || !keepIds) return [];
return nodes.reduce((filtered, node) => {
if (keepIds.has(node.id)) {
//
const clonedNode = { ...node };
clonedNode.child = filterTreeNodes(Array.isArray(clonedNode.child) ? clonedNode.child : [], keepIds);
filtered.push(clonedNode);
}
return filtered;
}, []);
}
const multiple = props.data.control.multiple=='1'
</script>
<template>
<el-tree-select
@ -81,9 +317,11 @@ onMounted(() => {
node-key="id"
:props="orgTreeProps"
:data="orgTreeList"
check-strictly
:render-after-expand="false"
clearable
:show-checkbox="multiple"
:multiple="multiple"
:check-strictly="!multiple"
/>
<div></div>
</template>

20
src/store/modules/orgMember.ts

@ -1,33 +1,35 @@
import { defineStore } from 'pinia'
import { ref} from 'vue';
import request from "@/utils/request";
import { id } from 'element-plus/es/locale';
export const useOrgMemberStore = defineStore('orgMember', () => {
interface OrgMemberItem {
id: string;
label: string;
children?:OrgMemberItem[];
name: string;
child?:OrgMemberItem[];
}
const listMap = ref<Record<string, string>>({})
const dataTree = ref<OrgMemberItem>({ id: '', label: '', children: [] })
const dataTree = ref<OrgMemberItem>({ id: '', name: '', child: [] })
async function init() {
await request({
url: "/javasys/lowCode/transfer/getOrgAndManTree",
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=response.data
handleChildren(response.data.children)
dataTree.value={id:"313",name:"集团公司",child:response.data}
handleChildren(response.data)
});
}
function handleChildren(childs:any[]){
childs.forEach(item => {
listMap.value[item.id] = item.label;
if(item.children){
handleChildren(item.children)
listMap.value[item.id] = item.name;
if(item.child){
handleChildren(item.child)
}
});
}

126
src/views/doc/agent.vue

@ -26,7 +26,7 @@ const inputState=ref(true)
const conversations=ref<chatRecord[]>([])
const centHoverItem=ref("")
const interact_msg=ref<{ask:boolean,think:string,content:string}[]>([])
const interact_msg=ref<{ask:boolean,think:string,content:string,docinfo?:any[]}[]>([])
const props = withDefaults(defineProps<{
userid:string,
closefunc:()=>void,
@ -75,7 +75,7 @@ async function onSendTextToAI(){
}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`,{
@ -86,7 +86,7 @@ async function onSendTextToAI(){
user:atob(props.userid),//base64
},controller.value.signal
)
if (!res.ok) {
throw new Error(`HTTP ${res.status} ${res.statusText}`);
}
@ -94,11 +94,10 @@ async function onSendTextToAI(){
myquestion.value=""
const reader = res.body!.getReader();
const decoder = new TextDecoder('utf-8');
let chunk = ''; //
while (true) {
const {done, value} = await reader.read()
if (done) break;
//
chunk += decoder.decode(value, {stream: true});
@ -112,9 +111,13 @@ async function onSendTextToAI(){
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') {
@ -126,14 +129,13 @@ async function onSendTextToAI(){
const arr=respMsg.value.split("</think>")
if(arr.length===1){
interact_msg.value.push({ask:false,think:"",content:arr[0]})
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]})
interact_msg.value.push({ask:false,think:arr[0],content:arr[1],docinfo:docinfo})
}
respMsg.value=""
setAiChat({
"userid":atob(props.userid),
"uuid":conversation.value,
@ -180,6 +182,16 @@ function handleMouseLeave(){
}
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=""
@ -192,6 +204,13 @@ function resetContext(){
props.closefunc()
}
//ai
function formatRefContent(content:string){
let result=content.replaceAll(/"/g,'')
result=result.replaceAll(/Unnamed: /g,' ')
return result
}
//
onMounted(() => {
loadKnownLibList()
@ -212,7 +231,8 @@ onMounted(() => {
<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)">{{ item.brief }}
<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>
@ -224,6 +244,15 @@ onMounted(() => {
<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>
@ -306,14 +335,38 @@ onMounted(() => {
background-color: #dddddd;
padding: 10px 8px;
margin: 3px 14px 3px 0;
overflow: hidden;
text-overflow: ellipsis;
border-radius: 8px;
text-wrap-mode: nowrap;
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>
@ -322,4 +375,53 @@ onMounted(() => {
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>

58
src/views/doc/manage.vue

@ -22,7 +22,7 @@ import preview from './preview.vue';
import space from './space.vue';
import spacePermission from './spacePermission.vue';
import SvgIcon from "@/components/SvgIcon/index.vue";
import {doDelSpace} from "@/api/doc/space"
import {doDelSpace,doAccessManage} from "@/api/doc/space"
import Space from "./space.vue";
//TODO: add file icons done!
@ -254,10 +254,12 @@ function onMatterRename(row:matterInfo){
},
}),
}).then(() => {
postMatterRename(uid,{
uuid:row.uuid,
name:newname.value,
}).then(()=>onLoadMatterList())
if(newname.value&&newname.value!=""){
postMatterRename(uid,{
uuid:row.uuid,
name:newname.value,
}).then(()=>onLoadMatterList())
}
})
}
@ -658,6 +660,31 @@ function flushSpaceTree(uuid:string,data:matterTree[]){
spaceTreeRef.value.updateKeyChildren(uuid,data)
}
//
function onAccessManage(){
dynamicVNode.value = h(sharePermission, {
uid: uid,
uuid: "",
spaceid:SpaceID.value.uuid, //
confirmFunc: (_list: string[],_infos:string[]) => {
//
//_len=_list.length
let permited = btoa(_list.join("|"))
doAccessManage(uid,{
"space":SpaceID.value.uuid,
"roles":permited,
"owner":SpaceID.value.userUuid,
"len":_list.length
}).then(()=>{
})
},
closeFunc: () => {
dynamicVNode.value=null
}
})
}
//
function onDeleteSpace(row:matterInfo){
ElMessageBox.confirm(`确认删除空间 ( ${row.name}) ? 空间内所有文件将不可恢复!取消则放弃删除操作。`, "警告", {
@ -763,6 +790,7 @@ const handleSelectionChange = (val:matterInfo[]) => {
<el-button size="small" :icon="Setting" circle ></el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item @click="(e)=>{ e.stopPropagation(); onAccessManage(data)}">空间成员管理</el-dropdown-item>
<el-dropdown-item @click="(e)=>{ e.stopPropagation(); onDelSpaceMatter(data)}">删除</el-dropdown-item>
<el-dropdown-item @click="(e)=>{ e.stopPropagation(); onSpacePManage(data)}">权限管理</el-dropdown-item>
</el-dropdown-menu>
@ -815,7 +843,7 @@ const handleSelectionChange = (val:matterInfo[]) => {
</el-button-group>
</el-row>
<el-row :gutter="24" style="overflow-y: auto;">
<el-row :gutter="24" style="overflow-y: auto; height: 90%;">
<el-table v-if="modListOrGrild"
stripe
:data="matterList"
@ -839,7 +867,7 @@ const handleSelectionChange = (val:matterInfo[]) => {
</div>
</template>
</el-table-column>
<el-table-column width="250" align="center">
<el-table-column width="360" align="center">
<template #default="scope">
<div v-show="currentHoverRow === scope.row.name">
<!-- <el-button size="small" :icon="Promotion" circle ></el-button> -->
@ -848,6 +876,7 @@ const handleSelectionChange = (val:matterInfo[]) => {
<el-button size="small" :icon="Download" circle @click="onDownload(scope.row)"></el-button>
<el-button size="small" :icon="Edit" circle @click="onlyOfficeEdit(scope.row)"></el-button>
<el-button size="small" :icon="Delete" circle @click="onDelMatter(scope.row)"></el-button>
<el-button size="small" circle @click="onMatterRename(scope.row)"></el-button>
</div>
</template>
</el-table-column>
@ -876,10 +905,11 @@ const handleSelectionChange = (val:matterInfo[]) => {
</div>
<ul v-if="row.name!=''" class="grid-menus" v-show="currentHoverRow === row.name" @mouseleave="currentHoverRow=''">
<li v-if="getFileIcon(row.name)!='img'" @click="onPrivateView(row)">预览</li>
<li size="small" :icon="Share" @click="onShareMatter(row)">分享</li>
<li size="small" :icon="Download" @click="onDownload(row)">下载</li>
<li size="small" :icon="Edit" @click="onlyOfficeEdit(row)">编辑</li>
<li size="small" :icon="Delete" @click="onDelMatter(row)">删除</li>
<li @click="onShareMatter(row)">分享</li>
<li @click="onDownload(row)">下载</li>
<li @click="onlyOfficeEdit(row)">编辑</li>
<li @click="onDelMatter(row)">删除</li>
<li @click="onMatterRename(row)">重命名</li>
</ul>
</div>
</div>
@ -976,12 +1006,13 @@ const handleSelectionChange = (val:matterInfo[]) => {
.table-grid{
display: flex;
flex-wrap: wrap; /* 关键属性,允许子元素自动换行 */
align-content: flex-start;
.grid-item{
position: relative;
width: 134px;
width: 134px;
height: 145px;
margin: 5px;
.grid-box{
height: 150px;
display: flex;
flex-direction: column;
overflow: hidden;
@ -997,6 +1028,7 @@ const handleSelectionChange = (val:matterInfo[]) => {
line-height: 27px;
text-align: center;
color: #878989;
z-index: 90;
box-shadow:0px 0px 12px rgba(0,0,0,0.12);
li{
cursor: pointer;

28
src/views/doc/onlyoffice.vue

@ -53,22 +53,22 @@ onMounted(()=>{
const _url=decodeURIComponent(window.atob(query.fileurl))
const name=query.name?.toString()??""
const dtype=query.dtype?.toString()??"word"
//
if (_info.includes(name)){
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"
}
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"
}
}
})

4
src/views/doc/recentVisited.vue

@ -24,9 +24,9 @@ const props = withDefaults(defineProps<{
function onlyOfficeView(row:shareItem){
const _type=fileType(row.name!)
if(_type!==""){ //office file
const info =btoa(encodeURIComponent(`${row.userUuid}/root${row.matters[0].path}`))
const info =btoa(encodeURIComponent(`${row.name}`))
const _url=`${siteHost}${apiURL}/share/zip?shareUuid=${row.uuid}&code=${row.code}&uid=${props.uid}&dprt=${props.udprt}&puuid=root&rootUuid=root`
window.open(`/#/onlyoffice?name=${row.name}&dtype=${_type}&info=${info}&fileurl=`+encodeURIComponent(_url),"_blank")
window.open(`/#/onlyoffice?name=${row.name}&dtype=${_type}&info=${info}&fileurl=`+window.btoa(encodeURIComponent(_url)),"_blank")
}else{
//by kkFilePreview
let a = row.name ?? '';

113
src/views/doc/space.vue

@ -9,16 +9,16 @@ import sharePermission from './sharePermission.vue';
import spacePermission from './spacePermission.vue';
import { matterPage,matterInfo,matterTree,doFileUpload,matterPermit} from "@/api/doc/type"
import { doAccessManage,getSpaceMatterList,doCreateSpaceDir,doDelSpaceMatter,
doAiTraining ,doCreateAiagent} from "@/api/doc/space"
doAiTraining ,doCreateAiagent,spaceMatterRename} from "@/api/doc/space"
import { h } from 'vue'
import {
Delete,
View,
Download,
Plus,
Plus,Search,
Edit,
Setting,
Promotion,
Grid,List,
} from '@element-plus/icons-vue'
import {ElMessage,UploadFile,UploadFiles,ElPagination} from "element-plus";
import aiagent from './agent.vue';
@ -29,6 +29,7 @@ const orgMembers = useOrgMemberStore() //为了初始化
const defaultAiAgent=import.meta.env.VITE_DEFAULT_AI_AGENT
const matterList = ref<matterInfo[]>([])
const searchname=ref("") //
const newdirName=ref("") //
const currentHoverRow=ref("") //table
const breadcrumbList=ref<matterInfo[]>([{name:"根目录",uuid:"root", dir:true}]) //
@ -41,6 +42,7 @@ 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
@ -88,8 +90,18 @@ const uploadFormData = computed(() => {
}
});
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: props.uid,
uuid: "",
@ -159,10 +171,9 @@ function onDownload(row:matterInfo){
})
}
//
function onLoadMatterList(){
let _page: matterPage = {
function onLoadMatterList(name?:string){
let _page: matterPage= {
page: paginInfo.value.page,
pageSize: 50,
orderCreateTime: "DESC",
@ -172,6 +183,18 @@ function onLoadMatterList(){
space:props.spaceid,
};
if(name){
_page={
pageSize: 50,
orderCreateTime: "DESC",
orderDir: "DESC",
deleted:false,
name:name,
space:props.spaceid,
}
}
getSpaceMatterList(props.uid,_page).then((resp)=>{
//page+1 index1apiindex0
paginInfo.value={total:resp.data.totalPages, page:resp.data.page}
@ -251,6 +274,31 @@ function onCreateDir(){
})
}
//
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!=""){
spaceMatterRename(props.uid,{
space:props.spaceid,
uuid:row.uuid,
name:newname.value,
}).then(()=>onLoadMatterList())
}
})
}
//------------------------------------------
// @cell-dblclick="handleDoubleClick"
//
@ -445,7 +493,11 @@ function handleAiUpload(info:matterInfo){
//
if (info.path?.startsWith(currentAgent.value.path)){
doAiTraining(`/agents/${currentAgent.value.uuid}/updates`,{"matter":info.uuid}).then(resp=>{
console.log(resp)
ElMessage({
message: '已成功安排训练',
type: 'success',
plain: true,
})
})
}else{
alert("当前路径没有智能体")
@ -508,6 +560,13 @@ onMounted(() => {
//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
}
if (props.ismanager) {
CutLevelPermit.value=PERMITS.MANAGER
}else{
@ -537,7 +596,6 @@ function isOwner(){
</script>
<template>
<div>
<el-row :gutter="24" style="margin: 12px 0px;">
<span class="el-breadcrumb" style="font-weight: bold; align-content: center;">[ {{ props.spacename }} ] : </span>
<el-breadcrumb separator="/" style="align-content: center;">
@ -547,6 +605,11 @@ function isOwner(){
</el-breadcrumb-item>
</el-breadcrumb>
<span v-if="currentNode.uuid!='root'" style="font-weight: bold;margin:0 5px;align-content:center;">/ {{ currentNode.name }}</span>
<el-col :span="6" class="search">
<el-input placeholder="搜索文件" v-model="searchname" @blur="searchname===''?onLoadMatterList():''"/>
<el-button :icon="Search" @click="onLoadMatterList(searchname)"></el-button>
</el-col>
</el-row>
<el-row :gutter="24">
@ -568,15 +631,18 @@ function isOwner(){
<el-button @click="createDir">新建目录</el-button>
</el-col>
<el-button style="margin-left: auto;" @click="()=>currentAgent.model=true">AI助手</el-button>
<el-button-group v-if="isOwner()" class="control" style="margin: 0 10px;">
<el-button :icon="Plus" @click="onAccessManage">成员</el-button>
<el-button :icon="Plus" @click="onAiAgent">创建智能体</el-button>
<!-- <el-button :icon="Delete" @click="onDeleteSpace">删除</el-button> -->
</el-button-group>
<el-button-group style="margin-right:20px;">
<el-button :icon="List" @click="updateListOrGrid(true)"></el-button>
<el-button :icon="Grid" @click="updateListOrGrid(false)"></el-button>
</el-button-group>
</el-row>
<el-row :gutter="24" style="height: 84%;overflow-y: auto;">
<el-table v-if="listOrGrid"
<el-row :gutter="24" style="overflow-y: auto;height: 90%;">
<el-table v-if="modListOrGrild"
stripe
:data="matterList"
:header-cell-style="{ background: '#f5f8fd' }"
@ -603,12 +669,15 @@ function isOwner(){
<div v-show="currentHoverRow === scope.row.name" style="display:inline; margin-left:15px">
<el-button v-if="getFileIcon(scope.row.name)!='img'" size="small" :icon="View" circle @click="onPrivateView(scope.row)"></el-button>
<el-button v-if="scope.row.permitVal>=PERMITS.DOWNLOAD" size="small" :icon="Download" circle @click="onDownload(scope.row)"></el-button>
<el-button v-if="scope.row.permitVal>=PERMITS.EDIT" size="small" :icon="Edit" circle @click="onlyOfficeEdit(scope.row)"></el-button>
<span v-if="scope.row.permitVal>=PERMITS.MANAGER" class="manager_span" >
<el-button v-if="!scope.row.dir" size="small" circle @click="handleAiUpload(scope.row)">AI</el-button>
<el-button size="small" :icon="Delete" circle @click="onDelMatter(scope.row)"></el-button>
<el-button size="small" :icon="Setting" circle @click="onSpacePManage(scope.row)"></el-button>
</span>
<span v-if="scope.row.permitVal>=PERMITS.EDIT" class="manager_span">
<el-button size="small" :icon="Edit" circle @click="onlyOfficeEdit(scope.row)"></el-button>
<el-button size="small" circle @click="onMatterRename(scope.row)"></el-button>
</span>
</div>
</template>
</el-table-column>
@ -640,10 +709,13 @@ function isOwner(){
<ul v-if="row.name!=''" class="grid-menus" v-show="currentHoverRow === row.name" @mouseleave="currentHoverRow=''">
<li v-if="getFileIcon(row.name)!='img'" @click="onPrivateView(row)">预览</li>
<li v-if="row.permitVal! >= PERMITS.DOWNLOAD" @click="onDownload(row)">下载</li>
<li v-if="row.permitVal! >= PERMITS.EDIT" @click="onlyOfficeEdit(row)">编辑</li>
<span v-if="row.permitVal! >= PERMITS.EDIT" >
<li @click="onlyOfficeEdit(row)">编辑</li>
<li @click="onMatterRename(row)">重命名</li>
</span>
<span v-if="row.permitVal! >= PERMITS.MANAGER" >
<li @click="onDelMatter(row)">删除</li>
<li @click="onSpacePManage(row)">权限</li>
<li @click="onSpacePManage(row)">权限</li>
</span>
</ul>
</div>
@ -653,7 +725,6 @@ function isOwner(){
<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>
<aiagent :agent="currentAgent" :userid="uid" :uuid="currentAgent" :closefunc="()=>{currentAgent.model=false}"></aiagent>
@ -671,16 +742,22 @@ function isOwner(){
align-self: flex-start;
}
}
.search{
margin-left: auto;
margin-right: 20px;
display:inherit;
}
.table-grid{
display: flex;
flex-wrap: wrap; /* 关键属性,允许子元素自动换行 */
align-content: flex-start;
.grid-item{
position: relative;
width: 134px;
height: 135px;
margin: 5px;
.grid-box{
height: 150px;
display: flex;
flex-direction: column;
overflow: hidden;
@ -696,7 +773,7 @@ function isOwner(){
line-height: 27px;
text-align: center;
color: #878989;
z-index: 999;
z-index: 90;
box-shadow:0px 0px 12px rgba(0,0,0,0.12);
li{
cursor: pointer;

107
src/views/doc/spacePermission.vue

@ -8,6 +8,7 @@ import {
import { ElDialog, ElMessageBox,ElStep,TreeInstance} from 'element-plus';
import { useOrgMemberStore } from "@/store/modules/orgMember";
import { dataTool } from 'echarts';
const props = withDefaults(defineProps<{
uid:string, //uuid
uuid:string, //uuid
@ -21,16 +22,17 @@ 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
label:string
parentId?:string
name:string
superior?:string
indeterminate?:boolean,
radio?:number[];
children?: Tree[]
child?: Tree[]
ismanager?:boolean,
}
@ -48,7 +50,7 @@ async function onSavePermChange(){
managers.value.push("p0"+item.id)
}
if(item.children) collectManager(item)
if(item.child) collectManager(item)
})
await addSpaceManager(btoa(props.uid),{
@ -69,7 +71,7 @@ async function onSavePermChange(){
resultPermits[item.id]=item.radio[0]
}
if(item.children) collectNodePermits(item)
if(item.child) collectNodePermits(item)
})
spacePermit.value.data=JSON.stringify(resultPermits)
@ -85,18 +87,18 @@ async function onSavePermChange(){
}
function collectManager(node:Tree){
node.children?.forEach(ele => {
node.child?.forEach(ele => {
if(ele.ismanager){
managers.value.push("p0"+ele.id)
}
if(ele.children) collectManager(ele)
if(ele.child) collectManager(ele)
});
}
//
function collectNodePermits(node:Tree){
node.children?.forEach(ele => {
node.child?.forEach(ele => {
if(ele.radio&&ele.radio.length>0){
if(ele.indeterminate) {
ele.radio[0]+=10
@ -104,7 +106,7 @@ function collectNodePermits(node:Tree){
resultPermits[ele.id]=ele.radio[0]
}
if(ele.children){
if(ele.child){
collectNodePermits(ele)
}
});
@ -114,25 +116,25 @@ function collectNodePermits(node:Tree){
//
function onGroupValueChange(node:Tree, val:number[]){
if(node.indeterminate) node.indeterminate=false;
node.children?.forEach(ele => {
node.child?.forEach(ele => {
ele.radio=val
if(ele.children){
if(ele.child){
onGroupValueChange(ele,val)
}
});
if(node.parentId){
if(node.superior){
updateParentNode(node)
}
}
//
function updateParentNode(node:Tree){
if(node.parentId){
if(node.superior){
const pnode = treeRef.value?.getNode(node.parentId);
const pnode = treeRef.value?.getNode(node.superior);
if(pnode){
const tdata=pnode.data as Tree
if (tdata.children?.every(ele=>{
if (tdata.child?.every(ele=>{
if(ele.indeterminate) return !ele.indeterminate;//
if(ele.radio?.length==0) return ele.radio.length==tdata.radio?.length;
@ -165,10 +167,23 @@ function onShowManagers(){
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),
@ -183,21 +198,24 @@ function refreshSpaceData(){
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,label:orgMembers.listMap[mid],radio:[2]})
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=>{
@ -217,28 +235,28 @@ function refreshSpaceData(){
}
function addNode(key:string,node:Tree){
node.children?.forEach(ele => {
node.child?.forEach(ele => {
if(ele.id==key){
const eleClone=structuredClone(toRaw(ele)) //eleProxyclonetoRawobject
eleClone.radio=[2]
eleClone.parentId=ele.parentId
dataSource.value.push(eleClone) //{id:ele.id,label:ele.label,radio:2,stage:level,children:ele.children}
//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.children){
if(ele.child){
addNode(key,ele)
}
});
}
function checkNode(key:string,node:Tree):boolean{
for (const child of node.children||[]) {
if(child.id==key){
for (const ch of node.child||[]) {
if(ch.id==key){
return true
}
if(child.children){
if(ch.child){
//returnreturnfortruereturn
if(checkNode(key,child)) return true;
if(checkNode(key,ch)) return true;
}
}
@ -266,11 +284,26 @@ onMounted(()=>{
<span>管理员</span>
</div>
<div v-else class="buttons">
<span>禁止</span>
<span>仅预览</span>
<span>可下载</span>
<span>上传下载</span>
<span>编辑</span>
<el-tooltip placement="top" effect="dark"
content="禁止访问">
<span>不可见</span>
</el-tooltip>
<el-tooltip placement="top" effect="dark"
content="仅可预览,不可下载">
<span>仅预览</span>
</el-tooltip>
<el-tooltip placement="top" effect="dark"
content="可下载可见文件,不可上传文件">
<span>可下载</span>
</el-tooltip>
<el-tooltip placement="top" effect="dark"
content="可上传和下载可见文件">
<span>上传下载</span>
</el-tooltip>
<el-tooltip placement="top" effect="dark"
content="可上传、下载、编辑文档">
<span>编辑</span>
</el-tooltip>
</div>
</div>
<div class="tablelist">
@ -278,15 +311,17 @@ onMounted(()=>{
ref="treeRef"
:data="dataSource"
node-key="id"
default-expand-all
:expand-on-click-node="false"
accordion
:props="{label: 'name',children:'child'}"
:default-expanded-keys="firstLevelKeys"
:expand-on-click-node="false"
>
<template #default="{ node, data }">
<div class="tree-node">
<span>{{ node.label }}</span>
<span>{{ data.name }}</span>
<div v-if="managerMode" class="buttons">
<el-checkbox v-if="!data.children" v-model="data.ismanager" />
<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)">

171
src/widget/org/cont.vue

@ -5,6 +5,8 @@
-->
<script lang='ts' setup>
import { getgovcont } from '@/api/hr/org/index'
import { orgInfo } from "@/api/hr/org/type";
import { getOrgTreeList } from "@/api/hr/org/index";
const props = defineProps({
orgid:{
type: String,
@ -12,26 +14,177 @@ const props = defineProps({
}
})
const orgName = ref("")
/* const orgName = computed(()=>{
return str.value
}) */
//let str = ref("")
//
const pickOrgVal = (val:any) => {
if(val != "" && val != null){
getgovcont({id:val*1,idstr:val})
.then(({data}) =>{
// console.log("-3->",data,data.name)
orgName.value = data.name
})
.finally(()=>{
if(hasComma(val)){
const interval = setInterval(() => {
// reffalse
if (!orgTreeLoading.value) {
clearInterval(interval);
let str= ''
str = replaceOrgIdWithName(val,orgTreeList.value)
orgName.value = str
}
}, 200);
})
}else{
getgovcont({id:val*1,idstr:val})
.then(({data}) =>{
// console.log("-3->",data,data.name)
orgName.value = data.name
})
.finally(()=>{})
}
}
}
function removeLastChar(str: string | any[]) {
//
if (typeof str !== 'string') {
throw new Error('输入必须是字符串类型');
}
//
if (str.length === 0) {
return str; //
}
//
return str.slice(0, -1);
}
function hasComma(str: any) {
//
if (typeof str !== 'string') {
return false
}
// 使indexOf
// truefalse
return str.indexOf(',') !== -1;
}
function commaStringToNumberArray(str: string) {
//
if (typeof str !== 'string') {
throw new Error('输入必须是字符串');
}
//
if (str.trim() === '') {
return [];
}
//
return str.split(',').map(item => {
//
const trimmed = item.trim();
//
const number = Number(trimmed);
//
if (isNaN(number)) {
throw new Error(`无法将 "${trimmed}" 转换为有效数字`);
}
return number;
});
}
const orgTreeLoading = ref(true); //
onBeforeMount(() => {
pickOrgVal(props.orgid)
//pickOrgVal(props.orgid)
getOrgTreeList({ orgid: 309 })
.then(({ data }) => {
console.log("行政组织树对照值", data);
orgTreeList.value = data;
})
.finally(() => {
orgTreeLoading.value = false;
});
})
/**
* 2. 核心方法输入ID逗号字符串返回名称逗号字符串
* @param {string} idStr - 逗号分隔的ID字符串 "102,103"
* @returns {string} 逗号分隔的名称字符串 "企管部,IT"
*/
function replaceOrgIdWithName(idStr: string,orgTreeData: any) {
//
if (!idStr || typeof idStr !== "string") {
return "";
}
// 1ID
const idToNameMap = {};
// ID
function traverseNodes(nodes: any[]) {
// child: null
if (!nodes || !nodes.length) return;
nodes.forEach((node: { id: string | number; name: any; child: any; }) => {
// IDIDkey
idToNameMap[node.id] = node.name;
//
traverseNodes(node.child);
});
}
//
traverseNodes(orgTreeData);
// 2ID
const result = idStr
.split(",") // ID "102,103" ["102", "103"]
.map(idItem => {
const pureId = idItem.trim(); // "102, 103"
const idNum = Number(pureId); // ID
// IDID
return idToNameMap[idNum] || pureId;
})
.join(","); //
return result;
}
const orgTreeList = ref<orgInfo[]>();
//
const orgTreeProps = {
children: "child",
label: "name",
}; //
onMounted(() => {
pickOrgVal(props.orgid)
});
watch(() =>props.orgid,(val:string) => {
// console.log("-4->",val)

Loading…
Cancel
Save