herenshan112 2 months ago
parent
commit
5675d4d3bd
  1. 17
      src/api/doc/index.ts
  2. 4
      src/components/DesignForm/assembly/index.ts
  3. 82
      src/components/DesignForm/formControlPropertiNew.vue
  4. 3
      src/components/DesignForm/public/expand/org.vue
  5. 4
      src/components/DesignForm/public/expand/userDialog.vue
  6. 262
      src/views/doc/manage.vue
  7. 52
      src/views/doc/space.vue
  8. 80
      src/widget/lowcodetransfer/lowcodeTransfer.vue
  9. 896
      src/widget/lowcodetransfer/lowcodeTransfer1.vue

17
src/api/doc/index.ts

@ -99,6 +99,23 @@ export function getMatterList( uid:string,data?: matterPage): AxiosPromise<matte
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
});
}
/**
*
*/

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

@ -1043,7 +1043,9 @@ export default [
iconFont: 'fa-user-o',
control: {
// 组件所有属性
modelValue: ''
modelValue: '',
range:[],
queryBy:'org'
},
config: {}, // 其他配置信息
styles: {

82
src/components/DesignForm/formControlPropertiNew.vue

@ -15,6 +15,8 @@ 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,
@ -851,6 +853,14 @@ const attrList = computed(() => {
vIf: state.isSearch,
vShow: ["orgCentent"],
},
{
label: "数据范围",
value: config.expandUser,
path: "config.expand-user",
type: "expand-user",
vIf: state.isSearch,
vShow: ["expand-user"],
},
/* {
label: "添加时间水印",
value: config.lowcodeImage,
@ -2068,6 +2078,8 @@ watch(
.toString();
}else if(controlData.value.type === "orgCentent"){
haveOrgTreeInfo()
}else if(controlData.value.type === "expand-user"){
}
// start
// console.log(controlData.value.name)
@ -2386,6 +2398,10 @@ import AssociatedFormsTinyace from "@/widget/associatedforms/associatedFormsTiny
import AssociatedFormsTinyaceRange from "@/widget/associatedforms/associatedFormsTinyaceRange.vue";
import AssociatedFormsFillRole from "@/widget/associatedforms/associatedFormsFillRole.vue";
import AssociatedFormsChildFillRole from "@/widget/associatedforms/associatedFormsChildFillRole.vue";
import TransferSelectUserRange from "@/widget/lowcodetransfer/lowcodeTransfer1.vue";
const transferSelectUserRangeData = ref({"type":"lowcodeTransfer","unitName":"请选择","iconFont":"fa-arrows-h","control":{"modelValue":[],"fixedOptions":[{"id":"thefirstrootnode","label":"根节点1","unitName":"","disabled":false,"children":[]},{"id":"thesecondrootnode","label":"根节点2","unitName":"","disabled":false,"children":[]}]},"config":{"transferName":"请选择","transferDataSource":"数据源","apiUrl":"/javasys/lowCode/transfer/getOrgAndManTree","method":"post"},"name":"lowcodeTransfer1758525493710","item":{"label":"穿梭框"},"styles":{"divStyle":{"divbox":"all","transparency":100},"labelStyle":{"divbox":"all","transparency":100},"inputStyle":{"transparency":100}}})
const treeDefaultProps = {
//
@ -4044,6 +4060,31 @@ watch(
}
);
//
const userRangeDialogFlag = ref(false)
function handleUserRangeDialogFlag(){
userRangeDialogFlag.value = true
}
function checkedIdListChangedIndex(val:any){
//console.log(val)
controlData.value.control.range = val
}
let componentKey = 0
function reRenderComponent() {
componentKey++;
}
function handleExpandTabChange(){
controlData.value.control.range = []
}
/**
@ 作者: 秦东
@ 时间: 2024-12-23 10:39:48
@ -4463,6 +4504,9 @@ const aiAgentList = ref([
show-checkbox
multiple
style="width: 200px"
collapse-tags
collapse-tags-tooltip
:max-collapse-tags="2"
/>
</el-row>
@ -4476,6 +4520,13 @@ const aiAgentList = ref([
</el-row>
<el-row v-else-if="item.type === 'expand-user'">
{{controlData.control.range}}
<el-button @click="handleUserRangeDialogFlag">可选用户设置</el-button>
</el-row>
@ -5629,6 +5680,37 @@ const aiAgentList = ref([
</el-tab-pane>
</el-tabs>
</div>
<el-dialog
v-model="userRangeDialogFlag"
title="可选用户设置"
top="20px"
style="margin-top:70px;margin-left:270px;height:650px;width:750px"
>
<el-tabs v-model="controlData.control.queryBy" class="demo-tabs" @tab-change="handleExpandTabChange" >
<el-tab-pane label="根据组织筛选" name="org">
<TransferSelectUserRange v-if="controlData.control.queryBy=='org'" :key="componentKey" :data="transferSelectUserRangeData" :selected-value="controlData.control.range" @checked-id-list-changed="checkedIdListChangedIndex" @re-render-component="reRenderComponent" />
</el-tab-pane>
<el-tab-pane label="根据角色筛选" name="role">
<div style="width: 600px;">
根据角色筛选
</div>
</el-tab-pane>
</el-tabs>
<div style="position: absolute; top:587px;left:616px">
<el-button type="primary" @click="userRangeDialogFlag = false">
确定
</el-button>
</div>
</el-dialog>
<!-- 轮播图设置弹窗 20240122 start -->
<el-dialog

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

@ -322,6 +322,9 @@ const multiple = props.data.control.multiple=='1'
:show-checkbox="multiple"
:multiple="multiple"
:check-strictly="!multiple"
collapse-tags
collapse-tags-tooltip
:max-collapse-tags="4"
/>
<div></div>
</template>

4
src/components/DesignForm/public/expand/userDialog.vue

@ -196,7 +196,7 @@ const defaultProps = {
draggable
>
<el-row :gutter="20">
<el-col :span="6" style="padding: 0">
<el-col :span="5" style="padding: 0">
<div class="sidebar_tree">
<el-tree
ref="treeEl"
@ -209,7 +209,7 @@ const defaultProps = {
/>
</div>
</el-col>
<el-col :span="12">
<el-col :span="13">
<div class="search">
<el-input placeholder="请输入用户名" v-model="userName" clearable />
<el-button type="primary" @click="searchClick">查询</el-button>

262
src/views/doc/manage.vue

@ -7,7 +7,8 @@
import { getExpirTime, getFileIcon, readableSize,fileType} from "./tools"
import sharePermission from './sharePermission.vue';
import { useUserStore } from "@/store/modules/user";
import { getMatterList,postCreateDir,postDelMatter,postCreateShare,postMatterRename,postDelMatBatch,getMySpaces,doCreateSpace} from "@/api/doc/index"
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 "@/router";
@ -22,7 +23,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,doAccessManage} from "@/api/doc/space"
import {doDelSpace,doAccessManage,doCreateAiagent} from "@/api/doc/space"
import Space from "./space.vue";
//TODO: add file icons done!
@ -69,6 +70,7 @@ const spaceTreeRef = ref(); //space的树树组件的引用
let spaceNodeUid="" //currentNode
const modListOrGrild=ref(true)
const modRecycling=ref(false)
const Departs = computed(() => {
return `${'p0'+userStore.userInfoCont.userId},${userStore.userInfoCont.company},${userStore.userInfoCont.department},${userStore.userInfoCont.organization}`
@ -182,8 +184,12 @@ function showShareMessage(row:respCreateShare){
}
//----------------------------------------
function onDelNodeMatter(row:matterInfo){
onDelMatter(row,true)
}
//
function onDelMatter(row:matterInfo){
function onDelMatter(row:matterInfo,dir?:boolean){
if (row.uuid){
ElMessageBox.confirm(`确认删除( ${row.name}) ?删除后不可恢复!取消则放弃删除操作。`, "警告", {
confirmButtonText: "确定",
@ -193,11 +199,12 @@ function onDelMatter(row:matterInfo){
postDelMatter(uid,{
"uuid":row.uuid
}).then(()=>{
if (row.dir) {
currentNode.value.uuid = row.puuid ?? ""
currentNode.value.name = row.path ? row.path.replace(`/${row.name}`,'').match(/[^/]+$/g)?.pop() :"上级目录"
treeRef.value.remove(row.uuid)
if (row.dir || dir) {
const node = treeRef.value.getNode(row.uuid)
if (node) {
treeRef.value.remove(node)
}
currentNode.value.uuid = row.puuid ?? "root"
}
onLoadMatterList()
})
@ -282,6 +289,41 @@ function onSearchFile(name?:string){
})
}
//
function showRecycling(){
modRecycling.value=true
currentNode.value={} //
if(!PRIVATESPACE.value) { //
PRIVATESPACE.value=true
}
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 = {
@ -349,45 +391,54 @@ function onDBclickMatter(row:matterInfo){
//
function onNodeExpand(node: TreeNode, resolve: (data: matterTree[]) => void, reject: () => void) {
modRecycling.value=false
let cuuid ="root"
if ((node.data as matterTree).uuid) {
const cuuid = (node.data as matterTree).uuid
cuuid= (node.data as matterTree).uuid
currentNode.value = node.data
let _page: matterPage = {
page: 0,
pageSize: 50,
orderCreateTime: "DESC",
orderDir: "DESC",
puuid: cuuid,
deleted: false
};
getMatterList(uid, _page).then((resp) => {
paginInfo.value = { total: resp.data.totalPages, page: resp.data.page }
matterList.value = resp.data.data
// .filter((item)=>{
// return !item.dir
// })
let node_data = resp.data.data.filter((item) => {
return item.dir
}).map((val) => {
const copy = structuredClone(val)
copy.dir = !copy.dir
return copy
})
resolve(node_data)
}).catch(() => reject())
}
let _page: matterPage = {
page: 0,
pageSize: 50,
orderCreateTime: "DESC",
orderDir: "DESC",
puuid: cuuid,
deleted: false
};
getMatterList(uid, _page).then((resp) => {
paginInfo.value = { total: resp.data.totalPages, page: resp.data.page }
matterList.value = resp.data.data
// .filter((item)=>{
// return !item.dir
// })
let node_data = resp.data.data.filter((item) => {
return item.dir
}).map((val) => {
const copy = structuredClone(val)
copy.dir = !copy.dir
return copy
})
resolve(node_data)
}).catch(() => reject())
}
//
function onNodeClick(data:matterTree,node:TreeNode,self:any,env:any){
modRecycling.value=false
if(!PRIVATESPACE.value) {
PRIVATESPACE.value=true
}
if (currentNode.value.uuid === data.uuid) return;
const cuuid = data.uuid
currentNode.value = data
if (data){
if (currentNode.value.uuid === data.uuid) return;
//const cuuid = data.uuid
currentNode.value = data
}else{
currentNode.value={uuid:"root"}
}
onLoadMatterList()
return
let _page: matterPage = {
@ -528,7 +579,7 @@ async function handleFolderFile(option:File){
const _path = option.webkitRelativePath
const _dir=_path.replace(/\/[^/]+\w+$/,"") //,[^/]
const node = matterList.value.filter((item)=>{
return item.dir && item.path?.endsWith(_dir)
return item.dir && _dir.endsWith(item.name!)
})
let puuid=""
@ -536,10 +587,11 @@ async function handleFolderFile(option:File){
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 && item.path?.endsWith(_dir)
return item.dir && _dir.endsWith(item.name!)
})
if(newnodes.length>0){
puuid=newnodes[0].uuid
}else{
@ -567,6 +619,13 @@ 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 postCreateDir(uid,{
userUuid:uid,
puuid:uuid,
@ -580,7 +639,7 @@ async function doCreateMultyDir(path:string,uuid:string){
}
function handleMouseEnter(row:any){
currentHoverRow.value=row.name
currentHoverRow.value=row.uuid
}
//
function handleSingleUpload(response:any){
@ -626,6 +685,7 @@ function onNewSpace(){
function onSpaceNodeClick(data:matterTree,node:TreeNode,self:any,env:any){
if(PRIVATESPACE.value) { //
PRIVATESPACE.value=false
modRecycling.value=false
}
//
if(spaceNodeUid==data.uuid) return;
@ -719,15 +779,35 @@ function onSpacePManage(row:matterInfo){
})
}
//
function onAiAgent(row:matterInfo){
if(row.agent){
alert("当前目录已经是智能体目录")
return
}
ElMessageBox.confirm(`确认创建智能体( ${row.name}) ?`, "警告", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
}).then(()=>{
doCreateAiagent(uid,{
space:SpaceID.value.uuid,
matter:row.uuid
}).then(()=>{
router.replace({ query: { t: Date.now() } })
})
})
}
//------------------------------------------------------
//http://172.20.2.87:6010/api/alien/preview/5a10aaf6-396e-4d9a-7e87-3c5c8029d4db/123.png?ir=fill_100_100
//
onMounted(() => {
treeRef.value.append(
{name:'个人空间',uuid:'root',dir:false},
currentNode.value.uuid
)
// treeRef.value.append(
// {name:'',uuid:'root',dir:false},
// currentNode.value.uuid
// )
//
getMySpaces(uid,{roles:Departs.value}).then((resp)=>{
resp.data.forEach((item)=>{
@ -753,7 +833,10 @@ const handleSelectionChange = (val:matterInfo[]) => {
<template>
<div style="display:grid;grid-template-columns:1fr 4fr; width: 100%;height: 100%;">
<div class="menus_tree">
<el-tree
<div class="area_header">
<el-icon :size="20"><House /></el-icon><span class="area_name" > </span>
</div>
<el-tree
ref="treeRef"
style="max-width: 600px"
:data="treeData"
@ -768,11 +851,22 @@ const handleSelectionChange = (val:matterInfo[]) => {
<template #default="{ node, data }">
<div class="tree-item">
<span>{{ node.label }}</span>
<el-button size="small" :icon="Delete" circle @click="(e)=>{ e.stopPropagation(); onDelMatter(data)}"></el-button>
<el-dropdown>
<el-button size="small" :icon="Setting" circle ></el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item @click="(e)=>{ e.stopPropagation(); onDelNodeMatter(data)}">删除</el-dropdown-item>
<el-dropdown-item @click="(e)=>{ e.stopPropagation(); onMatterRename(data)}">重命名</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</template>
</el-tree>
<div style="display:flex;margin-top: 9px;background-color: white;"><span style="align-content: center;margin-left: 21px;color: #686854;">共享空间</span> <el-button style="margin: 10px 0px 10px auto;" :icon="Plus" @click="onNewSpace"> </el-button></div>
<div class="area_header">
<el-icon :size="20"><Collection /></el-icon> <span class="area_name" > </span>
<el-button size="small" style="margin: 0 5px 0 auto;" :icon="Plus" @click="onNewSpace"> </el-button>
</div>
<el-tree
ref="spaceTreeRef"
style="max-width: 600px"
@ -790,9 +884,11 @@ 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 v-if="!data.puuid" @click="(e)=>{ e.stopPropagation(); onAccessManage()}">成员管理</el-dropdown-item>
<el-dropdown-item @click="(e)=>{ e.stopPropagation(); onSpacePManage(data)}">权限管理</el-dropdown-item>
<el-dropdown-item v-if="data.puuid" @click="(e)=>{ e.stopPropagation(); onAiAgent(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-item @click="(e)=>{ e.stopPropagation(); spaceEleRef.onSpaceMatterRename(data)}">重命名</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
@ -800,10 +896,14 @@ const handleSelectionChange = (val:matterInfo[]) => {
</template>
</el-tree>
<div class="area_header" @click="showRecycling">
<el-icon :size="20"><Delete /></el-icon><span class="area_name" > </span>
</div>
</div>
<div v-if="PRIVATESPACE" class="app_container">
<el-row :gutter="24" style="margin: 12px 0px;">
<el-row v-if="modRecycling"><span style="margin: 12px 0px;font-weight: bold;font-size: 20px;"> 回收站 </span> </el-row>
<el-row v-else :gutter="24" style="margin: 12px 0px;">
<el-link v-if="currentNode.name!=='root'" @click="onNodeClick(treeData[0], null as unknown as TreeNode, null, null)">
<span style="font-weight: bold;margin-right: 5px;align-content: center;">根目录</span>/
</el-link>
@ -815,7 +915,7 @@ const handleSelectionChange = (val:matterInfo[]) => {
</el-col>
</el-row>
<el-row :gutter="24">
<el-row v-if="!modRecycling" :gutter="24">
<el-col :span="14">
<div class="el-button el-button--default" style="position: relative;">
<input type="file" style="position: absolute;opacity: 0;width: 50px;"
@ -854,7 +954,7 @@ const handleSelectionChange = (val:matterInfo[]) => {
:row-style ="() => ({ lineHeight: '36px' })"
@selection-change="handleSelectionChange"
@cell-mouse-enter="handleMouseEnter">
<el-table-column type="selection" width="50" />
<!-- <el-table-column type="selection" width="50" /> -->
<el-table-column width="450" 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" />
@ -869,14 +969,18 @@ const handleSelectionChange = (val:matterInfo[]) => {
</el-table-column>
<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> -->
<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="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 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="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>
</span>
</div>
</template>
</el-table-column>
@ -903,13 +1007,18 @@ const handleSelectionChange = (val:matterInfo[]) => {
<span style="margin: 5px 0;text-wrap-mode:nowrap;">{{ row.name }}</span>
</div>
</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 @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 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="onlyOfficeEdit(row)">编辑</li>
<li @click="onDelMatter(row)">删除</li>
<li @click="onMatterRename(row)">重命名</li>
</span>
</ul>
</div>
</div>
@ -947,6 +1056,19 @@ const handleSelectionChange = (val:matterInfo[]) => {
--el-tree-expand-icon-color:#4c4c4e;
}
}
.area_header{
display: flex;
margin-top: 9px;
padding: 12px 7px;
background-color: white;
align-items: center;
.area_name{
align-content: center;
margin-left: 8px;
color: #686854;
font-weight: bold;
}
}
.tree-item{
display: flex;
@ -975,7 +1097,7 @@ const handleSelectionChange = (val:matterInfo[]) => {
.search{
margin-left: auto;
margin-right: 20px;
margin-right: 26px;
display:inherit;
}
.shareDialog{

52
src/views/doc/space.vue

@ -9,7 +9,7 @@ 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,spaceMatterRename} from "@/api/doc/space"
doAiTraining ,spaceMatterRename} from "@/api/doc/space"
import { h } from 'vue'
import {
Delete,
@ -151,7 +151,6 @@ function onDelMatter(row:matterInfo){
"space":props.spaceid,
}).then(()=>{
currentNode.value.uuid = row.puuid ?? ""
currentNode.value.name = row.path ? row.path.replace(`/${row.name}`,'').match(/[^/]+$/g)?.pop() :"上级目录"
onLoadMatterList()
})
})
@ -275,7 +274,7 @@ function onCreateDir(){
}
//
function onMatterRename(row:matterInfo){
function onSpaceMatterRename(row:matterInfo){
const newname=ref(row.name)
ElMessageBox({
title:"请输入新的文件名",
@ -410,7 +409,7 @@ async function handleFolderFile(option:File){
const _path = option.webkitRelativePath
const _dir=_path.replace(/\/[^/]+\w+$/,"") //,[^/]
const node = matterList.value.filter((item)=>{
return item.dir && item.path?.endsWith(_dir)
return item.dir && _dir.endsWith(item.name!)
})
let puuid=""
@ -420,7 +419,7 @@ async function handleFolderFile(option:File){
matterList.value.push(...subs) //
const newnodes=matterList.value.filter((item)=>{
return item.dir && item.path?.endsWith(_dir)
return item.dir && _dir.endsWith(item.name!) //item.path?.endsWith(_dir)
})
if(newnodes.length>0){
puuid=newnodes[0].uuid
@ -451,6 +450,13 @@ 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(props.uid,{
puuid:uuid,
name:dirs[i],
@ -464,31 +470,7 @@ async function doCreateMultyDir(path:string,uuid:string){
}
//-------------------------AI section--------------
//
function onAiAgent(){
if(currentNode.value.uuid=="root"){
alert("根目录不支持创建智能体")
return
}
if(currentNode.value.agent){
alert("当前目录已经是智能体目录")
return
}
ElMessageBox.confirm(`确认创建智能体( ${currentNode.value.name}) ?`, "警告", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
}).then(()=>{
doCreateAiagent(props.uid,{
space:props.spaceid,
matter:currentNode.value.uuid
}).then(()=>{
router.replace({ query: { t: Date.now() } })
})
})
}
function handleAiUpload(info:matterInfo){
//
if (info.path?.startsWith(currentAgent.value.path)){
@ -587,7 +569,7 @@ const handleSelectionChange = (val:matterInfo[]) => {
tabSelected.value = val
}
defineExpose({handleDoubleClick,onDelMatter})
defineExpose({handleDoubleClick,onDelMatter,onSpaceMatterRename})
//
function isOwner(){
@ -632,9 +614,9 @@ function isOwner(){
</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-group v-if="isOwner()" class="control" style="margin: 0 10px;">
<el-button :icon="Plus" @click="onAiAgent">创建智能体</el-button>
</el-button-group>
</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>
@ -676,7 +658,7 @@ function isOwner(){
</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>
<el-button size="small" circle @click="onSpaceMatterRename(scope.row)"></el-button>
</span>
</div>
</template>
@ -711,7 +693,7 @@ function isOwner(){
<li v-if="row.permitVal! >= PERMITS.DOWNLOAD" @click="onDownload(row)">下载</li>
<span v-if="row.permitVal! >= PERMITS.EDIT" >
<li @click="onlyOfficeEdit(row)">编辑</li>
<li @click="onMatterRename(row)">重命名</li>
<li @click="onSpaceMatterRename(row)">重命名</li>
</span>
<span v-if="row.permitVal! >= PERMITS.MANAGER" >
<li @click="onDelMatter(row)">删除</li>
@ -744,7 +726,7 @@ function isOwner(){
}
.search{
margin-left: auto;
margin-right: 20px;
margin-right: -4px;
display:inherit;
}

80
src/widget/lowcodetransfer/lowcodeTransfer.vue

@ -196,7 +196,70 @@ const selectedValueCompu = computed({
const url = transferConfig.value.apiUrl;/* '/javasys/lowCode/transfer/getOrgAndManTree' */
let resData = ref([])
function endsWithGetOrgAndManTree(str) {
//
if (typeof str !== 'string') {
return false;
}
const suffix = 'getOrgAndManTree';
// false
if (str.length < suffix.length) {
return false;
}
//
return str.slice(-suffix.length) === suffix;
}
function processTreeData(node) {
if(!endsWithGetOrgAndManTree(url)){
return node
}
// id4
function hasLongIdNode(node) {
// id
if (node.id && node.id.length > 4) {
return true;
}
// childrennull
if (node.children && Array.isArray(node.children) && node.children.length > 0) {
for (const child of node.children) {
if (hasLongIdNode(child)) {
return true;
}
}
}
return false;
}
//
function processNode(node) {
// id
if (!hasLongIdNode(node)) {
return null;
}
//
const newNode = {...node};
//
if (newNode.children && Array.isArray(newNode.children) && newNode.children.length > 0) {
//
newNode.children = newNode.children
.map(child => processNode(child))
.filter(child => child !== null);
// childrennull
if (newNode.children.length === 0) {
newNode.children = null;
}
}
return newNode;
}
return processNode(node);
}
function getDetail() {
//console.log(11111)
@ -209,8 +272,15 @@ function getDetail() {
}
if (transferConfig.value.transferDataSource === "数据源") {
getDetail().then(({ data }) => {
const data1 = processTreeData(data)
//console.log(`穿`);
resData.value = data.children
resData.value = data1.children
// 2: node
userList.value = [{
id: '全选',
@ -408,7 +478,7 @@ const filterNode = (value, data, node) => {
}
// tag
const handleCloseTag = (tag) => {
checkedIdList.value = checkedIdList.value.filter(item => item !== tag.data.id);
if (!treeRef?.value) return
checkList.value = checkList.value.filter(item => {
treeRef.value.setChecked(tag, false)
@ -614,8 +684,10 @@ watch(transferConfig, (newValue, oldValue) => {
getDetail().then(({ data }) => {
const data1 = processTreeData(data)
//console.log(`穿`);
resData.value = data.children
resData.value = data1.children
// 2: node
userList.value = [{
id: '全选',

896
src/widget/lowcodetransfer/lowcodeTransfer1.vue

@ -0,0 +1,896 @@
<template>
<!-- {{ props.data?.control.fixedOptions }} --><!-- {{transferConfig}} --><!-- {{ resData }} --><!-- {{ count1 }} -->
<!-- <hr>
<hr>
<hr> -->
<div v-if="dataFinished" class="transfer"><!-- {{props.selectedValue}} -->
<div class="leftArea"><!-- {{ selectedValueCompu }}{{checkedIdList}} -->
<div class="transferName">{{ transferConfig.transferName }}</div>
<div style="padding-left: 15px; padding-right: 25px;margin-top: 10px;margin-bottom: 8px;">
<ElInput v-model="keyword" placeholder="搜索">
<template #prefix>
<ElIcon class="el-input__icon">
<Search />
</ElIcon>
</template>
</ElInput>
</div>
<div class="leftScroll">
<ElScrollbar height="100%">
<ElTree ref="treeRef" node-key="id" empty-text="暂无数据" :data="userList1" :props="treeProps"
show-checkbox highlight-current :default-expand-all="isExpandAll"
:filter-node-method="filterNode" @check="handleCheckList" @check-change="getCheckedList" />
</ElScrollbar>
</div>
</div>
<div class="rigthArea">
<div style="margin-bottom: 8px;">
<div class="transferName"><span>已选: {{ checkList.length }}</span>
<ElButton link style="float: right;margin-right: 5px;" @click="clearCheckList">清空</ElButton>
</div>
</div>
<ElScrollbar height="calc(100% - 50px)">
<ElTag v-for="user in checkList" :key="user.id"
style="display: block; font-size: 13px; letter-spacing: 1.5px;text-align: center;margin-top: 8px;height: 30px;width: 75%;margin-left:40px;padding-top: 6px;"
closable @close="handleCloseTag(user)">
{{
user.label
}}[{{
user.parent.data.label
}}]
</ElTag>
</ElScrollbar>
</div>
</div>
</template>
<script setup name="MultiTreeSelector">
import { ref, watch, onMounted } from 'vue'
import request from '@/utils/request';
import { forEach } from 'jszip';
const props = defineProps({
// eslint-disable-next-line vue/require-default-prop
data: {
type: Object,
default() {
return {};
},
},
selectedValue: {
type: Array,
default() {
return [];
},
},
})
const emits = defineEmits(['checkedIdListChanged', 'updateModel','reRenderComponent']);
/* const fixedOptions = ref([]) */
/* fixedOptions.value = props.data?.control.fixedOptions */
//const transferConfig = props.data?.config
const transferConfig = computed({
set(){
},
get(){
return props.data?.config
}
})
const treeProps = {
value: 'id',
label: 'label',
disabled: 'disabled',
children: 'children'
}
let dataFinished = ref(false)
const treeRef = ref()
const isExpandAll = ref(transferConfig.value.transferDataSource != "数据源") //
const keyword = ref('') //
const checkList = ref([]) // list
const userList = computed({
get(){
if (transferConfig.value.transferDataSource === "数据源") {
return [{
id: '全选',
label: '全选',
children: [...resData.value]
}]
}else{
return [{
id: '全选',
label: '全选',
children: props.data?.control.fixedOptions
}]
}
},
set(){
}
})
let userList1 = ref([]);
//
let checkedIdList = ref([]);
let count = 0
function waitAndReGet() {
/* console.log("waitAndReGet")
console.log(props.selectedValue)
console.log(props.selectedValue.value) */
setTimeout(() => {
let values = props.selectedValue.map(item => {
//
return item;
});
if (props.selectedValue && values) {
/* console.log("")
console.log(props.selectedValue)
console.log(values) */
//return props.selectedValue.value
checkedIdList.value = values
} else {
if (count < 3) {
count++
waitAndReGet()
}
/* console.log("waitAndReGet---else") */
}
}, 2000)
}
/*
*/
const selectedValueCompu = computed({
get() {
//console.log("get")
//nextTick(()=>{
let values = props.selectedValue.map(item => {
//
return item;
});
if (props.selectedValue && values) {
/* console.log(props.selectedValue.value)
console.log("computed---if") */
return values
} else {
waitAndReGet()
/* console.log("computed---else") */
return []
}
//})
},
set() { }
})
/* console.log(props.selectedValue) */
const url = transferConfig.value.apiUrl;/* '/javasys/lowCode/transfer/getOrgAndManTree' */
let resData = ref([])
function endsWithGetOrgAndManTree(str) {
//
if (typeof str !== 'string') {
return false;
}
const suffix = 'getOrgAndManTree';
// false
if (str.length < suffix.length) {
return false;
}
//
return str.slice(-suffix.length) === suffix;
}
function processTreeData(node) {
if(!endsWithGetOrgAndManTree(url)){
return node
}
// id4
function hasLongIdNode(node) {
// id
if (node.id && node.id.length > 4) {
return true;
}
// childrennull
if (node.children && Array.isArray(node.children) && node.children.length > 0) {
for (const child of node.children) {
if (hasLongIdNode(child)) {
return true;
}
}
}
return false;
}
//
function processNode(node) {
// id
if (!hasLongIdNode(node)) {
return null;
}
//
const newNode = {...node};
//
if (newNode.children && Array.isArray(newNode.children) && newNode.children.length > 0) {
//
newNode.children = newNode.children
.map(child => processNode(child))
.filter(child => child !== null);
// childrennull
if (newNode.children.length === 0) {
newNode.children = null;
}
}
return newNode;
}
return processNode(node);
}
function getDetail() {
//console.log(11111)
if (transferConfig.value.transferDataSource === "数据源") {
return request({
url: url,
method: transferConfig.value.method,
});
}
}
if (transferConfig.value.transferDataSource === "数据源") {
getDetail().then(({ data }) => {
checkedIdList.value = props.selectedValue
const data1 = processTreeData(data)
//console.log(`穿`);
resData.value = data1.children
// 2: node
userList.value = [{
id: '全选',
label: '全选',
children: [...resData.value]
}]
userList1.value = userList.value
if( treeRef.value){
treeRef.value.setCheckedKeys(checkedIdList.value, true)
}
setTimeout(() => {
checkedIdList.value.forEach(element => {
const _node = treeRef?.value?.getNode(element)
if (!_node) return
const _checkList = checkList.value
if (_node.checked) { //
if (_node.checked && _node.childNodes.length === 0) {
_checkList.push(_node)
}
// new Set()
checkList.value = Array.from(new Set(_checkList))
// ,,
if (_node.childNodes.length > 0) {
_node.childNodes.map(item => handleCheckList(item.data))
}
} else if (!_node.checked) { //
checkList.value = checkList.value.filter(item => item.checked)
}
});
}, 50);
});
}
const getCheckedList = () => {
checkedIdList.value = treeRef.value.getCheckedKeys(true)
}
let resData1 = ref([
{
id: '3',
label: '营销部',
children: [
{
id: '3-1',
label: '威震天'
},
{
id: '3-2',
label: '嫦娥'
}
]
},
{
id: '2',
label: '业务部',
children: [
{
id: '2-1-1',
label: '业务部子部1',
children: [
{
id: '2-1-0001',
label: '李白'
},
{
id: '2-1-0002',
label: '萧峰'
}
]
},
{
id: '2-2-1',
label: '业务部子部2',
children: [
{
id: '2-2-00006',
label: '武则天'
},
{
id: '2-2-00005',
label: '拿破仑'
}
]
}
]
},
{
id: '4',
label: '软件开发部',
children: [
{
id: '5-3',
label: '张辽'
},
{
id: '5-9',
label: '吕布'
},
{
id: '5-0',
label: '许褚'
},
]
}
])
//
const handleCheckList = (val) => {
//console.log(val)
const valId = val.id;
//console.log(valId)
const _node = treeRef?.value?.getNode(valId)
//console.log(_node)
if (!_node) return
const _checkList = checkList.value
//console.log(checkedIdList.value)
if (_node.checked||_node.childNodes.length > 0) {
if (_node.checked && _node.childNodes.length === 0) {
let count = 0
checkedIdList.value.forEach(element1 => {
if(element1==valId){
count++
}
});
if(count==1){
_checkList.push(_node)
}
}
//console.log(_checkList)
/* checkList.value.forEach(element => {
console.log(element.data.id)
}); */
//使 Map id
checkList.value = Array.from(new Map(
_checkList.map(item => [item.data.id, item])
).values());
// ,,
if (_node.childNodes.length > 0) {
_node.childNodes.map(item => handleCheckList(item.data))
}
} else if (!_node.checked) { //
checkList.value = checkList.value.filter(item => item.checked)
}
}
/*
* @node 当前节点
* @result 存结果的数组
* @key
* */
const getParentNode = (node, result, key) => {
let isPass = node?.data?.label?.indexOf(key) !== -1
isPass ? result.push(isPass) : ''
if (!isPass && node.level !== 1 && node.parent) {
return getParentNode(node.parent, result, key)
}
}
/*
* @value 过滤的关键字
* @data 被过滤的tree
* @node
* */
const filterNode = (value, data, node) => {
if (!value) {
data.disabled = false
return true
} else {
//,
// array.map(k => (!k.disabled) && (_childArr.push(k)))
(data.children) && (data.disabled = true)
}
let _arr = []
getParentNode(node, _arr, value)
let result = false
_arr.forEach(item => {
result = result || item
})
return result
}
// tag
const handleCloseTag = (tag) => {
checkedIdList.value = checkedIdList.value.filter(item => item !== tag.data.id);
if (!treeRef?.value) return
checkList.value = checkList.value.filter(item => {
treeRef.value.setChecked(tag, false)
if (!item?.data) return
return item.data.id !== tag.data.id
})
}
// CheckList
const clearCheckList = () => {
if (!treeRef?.value) return
treeRef.value.setCheckedKeys([])
while (checkList.value.length > 0) {
checkList.value.pop()
}
}
// /
const handleCheckAll = (array) => {
treeRef.value.setCheckedNodes(array)
array.map(item => {
handleCheckList(item)
})
}
// /
const expandAll = () => {
const nodesMap = treeRef.value.store.nodesMap
for (let key in nodesMap) {
nodesMap[key].expanded = isExpandAll.value
}
}
let watchCount = 0
// tree
watch(keyword, (newVal) => {
//console.log(fuzzyTreeSearch(userList.value,newVal))
userList1.value = fuzzyTreeSearch(userList.value,newVal)
//console.log(userList1.value)
//treeRef.value.data = fuzzyTreeSearch(treeRefValueData,newVal)
watchCount++
isShowMore()
})
function isShowMore() {
isExpandAll.value = true
let nodes = treeRef.value.store._getAllNodes();
nodes.forEach(item => {
item.expanded = true;
});
}
function fuzzyTreeSearch(treeData, keyword) {
if (!keyword?.trim()) return deepClone(treeData); //
const processNode = (node) => {
const newNode = deepClone(node); //
const isSelfMatch = newNode.label.toLowerCase().includes(keyword.toLowerCase());
//
let hasValidChild = false;
if (newNode.children?.length) {
newNode.children = newNode.children
.map(child => processNode(child))
.filter(Boolean); //
hasValidChild = newNode.children.length > 0;
}
//
if (isSelfMatch || hasValidChild) {
// 使
if (isSelfMatch && newNode.children?.length === 0) {
newNode.children = deepClone(node.children); //
}
return newNode;
}
return null;
};
return treeData.map(root => processNode(root)).filter(Boolean);
}
function deepClone(obj) {
if (obj === null || typeof obj !== 'object') return obj;
const clone = Array.isArray(obj) ? [] : {};
for (const key in obj) {
clone[key] = deepClone(obj[key]);
}
return clone;
}
//
if (transferConfig.value.transferDataSource === "固定选项") {
setTimeout(() => {
// 2: node
userList.value = [{
id: '全选',
label: '全选',
children: props.data?.control.fixedOptions
}]
userList1.value = userList.value
//console.log(treeRef)
/* console.log(checkedIdList.value) */
if( treeRef.value){
treeRef.value.setCheckedKeys(checkedIdList.value, true)
}
setTimeout(() => {
checkedIdList.value.forEach(element => {
const _node = treeRef?.value?.getNode(element)
if (!_node) return
const _checkList = checkList.value
if (_node.checked) { //
if (_node.checked && _node.childNodes.length === 0) {
_checkList.push(_node)
}
// new Set()
checkList.value = Array.from(new Set(_checkList))
// ,,
if (_node.childNodes.length > 0) {
_node.childNodes.map(item => handleCheckList(item.data))
}
} else if (!_node.checked) { //
checkList.value = checkList.value.filter(item => item.checked)
}
});
}, 50);
}, 50);
dataFinished.value = true
} else {
getDetail
dataFinished.value = true
}
/* setTimeout(()=>{
console.log("setTimeout")
if(props.data?.config.transferDataSource==="数据源"){
//emits('reRenderComponent');
count1.value++
}
},1500) */
function arrayEquals(arr1, arr2) {
//
if (!Array.isArray(arr1) || !Array.isArray(arr2)) {
return false;
}
//
if (arr1.length !== arr2.length) {
return false;
}
//
for (let i = 0; i < arr1.length; i++) {
const item1 = arr1[i];
const item2 = arr2[i];
//
if (Array.isArray(item1) && Array.isArray(item2)) {
if (!arrayEquals(item1, item2)) {
return false;
}
}
//
else if (typeof item1 === 'object' && item1 !== null &&
typeof item2 === 'object' && item2 !== null) {
//
const keys1 = Object.keys(item1);
const keys2 = Object.keys(item2);
if (keys1.length !== keys2.length) return false;
for (const key of keys1) {
if (!keys2.includes(key) || !arrayEquals([item1[key]], [item2[key]])) {
return false;
}
}
}
//
else {
if (item1 !== item2) {
return false;
}
}
}
return true;
}
// checkedIdList
watch(checkedIdList, (newValue, oldValue) => {
/* console.log(oldValue)
console.log('checkedIdList 发生了变化', newValue); */
if(!arrayEquals(newValue,oldValue)){
//console.log(1)
emits('checkedIdListChanged', newValue);
}else{
//console.log(2)
}
//emits('checkedIdListChanged', newValue);
}, { deep: true });
let count1 = ref(0);
/* onBeforeMount(() => {
console.log('onBeforeMount: 组件即将挂载');
});
onMounted(() => {
console.log('onMounted: 组件已挂载');
});
onBeforeUpdate(() => {
console.log('onBeforeUpdate: 数据即将更新到 DOM');
});
onUpdated(() => {
console.log('onUpdated: DOM 已更新');
if(props.data?.config.transferDataSource==="数据源"){
console.log("emits('reRenderComponent');")
emits('reRenderComponent');
}
});
onBeforeUnmount(() => {
console.log('onBeforeUnmount: 组件即将卸载,清除定时器');
});
onUnmounted(() => {
console.log('onUnmounted: 组件已卸载');
}); */
watch(transferConfig, (newValue, oldValue) => {
count1.value++
//emits('reRenderComponent', newValue);
try {
count1.value++
if (newValue.transferDataSource === "数据源") {
emits('reRenderComponent');
getDetail().then(({ data }) => {
alert(2)
const data1 = processTreeData(data)
//console.log(`穿`);
resData.value = data1.children
// 2: node
userList.value = [{
id: '全选',
label: '全选',
children: [...resData.value]
}]
userList1.value = userList.value
if( treeRef.value){
treeRef.value.setCheckedKeys(checkedIdList.value, true)
}
setTimeout(() => {
checkedIdList.value.forEach(element => {
const _node = treeRef?.value?.getNode(element)
if (!_node) return
const _checkList = checkList.value
if (_node.checked) { //
if (_node.checked && _node.childNodes.length === 0) {
_checkList.push(_node)
}
// new Set()
checkList.value = Array.from(new Set(_checkList))
// ,,
if (_node.childNodes.length > 0) {
_node.childNodes.map(item => handleCheckList(item.data))
}
} else if (!_node.checked) { //
checkList.value = checkList.value.filter(item => item.checked)
}
});
}, 50);
});
}
} catch (error) {
console.error('Error :', error);
//
//parsedData.value = null;
}
}, { deep: true });
watch(selectedValueCompu, (newValue, oldValue) => {
//console.log('selectedValueCompu ', newValue);
checkedIdList.value = selectedValueCompu.value
//
if (transferConfig.value.transferDataSource === "固定选项") {
setTimeout(() => {
// 2: node
userList.value = [{
id: '全选',
label: '全选',
children: props.data?.control.fixedOptions
}]
userList1.value = userList.value
//console.log(treeRef)
/* console.log(checkedIdList.value) */
if( treeRef.value){
treeRef.value.setCheckedKeys(checkedIdList.value, true)
}
setTimeout(() => {
checkedIdList.value.forEach(element => {
const _node = treeRef?.value?.getNode(element)
if (!_node) return
const _checkList = checkList.value
if (_node.checked) { //
if (_node.checked && _node.childNodes.length === 0) {
_checkList.push(_node)
}
// new Set()
checkList.value = Array.from(new Set(_checkList))
// ,,
if (_node.childNodes.length > 0) {
_node.childNodes.map(item => handleCheckList(item.data))
}
} else if (!_node.checked) { //
checkList.value = checkList.value.filter(item => item.checked)
}
});
}, 50);
}, 50);
dataFinished.value = true
} else {
getDetail
dataFinished.value = true
}
//emits('updateModel',newValue);
}, { deep: true });
</script>
<style scoped>
.transfer {
display: flex;
}
.transferName {
height: 30px;
padding-top: 8px;
background-color: #F5F7FA;
padding-left: 10px;
border-radius: 5px 5px 0 0;
border-bottom: 1px solid gainsboro;
}
.buttonArea {
display: flex;
flex-direction: row;
align-items: center;
}
.buttonArea2 {
height: 33px;
}
.leftScroll {
height: 367px;
}
.leftArea {
border: 1px solid gainsboro;
width: 300px;
border-radius: 5px;
height: 456px;
position: relative;
background-color: white;
}
.rigthArea {
border: 1px solid gainsboro;
width: 300px;
border-radius: 5px;
height: 456px;
background-color: white;
}
</style>
Loading…
Cancel
Save