This commit is contained in:
wanghu
2026-10-14 17:25:35 +08:00
parent d69ddf2cf1
commit c6b17e50c2
524 changed files with 31333 additions and 0 deletions
+487
View File
@@ -0,0 +1,487 @@
<template>
<div>
<ul class="opera-ul">
<Tooltip content="新增节点" placement="top" transfer>
<li @click="showAddNode" :class="{ disabled: !nodePermissions.canCreate }">
<SvgIcon name="tree-icon-1" />
</li>
</Tooltip>
<Tooltip content="编辑节点" placement="top" transfer>
<li @click="showEditNode" :class="{ disabled: !nodePermissions.canUpdate }">
<SvgIcon name="tree-icon-2" />
</li>
</Tooltip>
<Tooltip content="移动节点" placement="top" transfer>
<li @click="showMoveNode" :class="{ disabled: !nodePermissions.canMove }">
<SvgIcon name="tree-icon-3" />
</li>
</Tooltip>
<Tooltip content="删除节点" placement="top" transfer>
<li @click="showDeleteNode" :class="{ disabled: !nodePermissions.canDelete }">
<SvgIcon name="tree-icon-4" />
</li>
</Tooltip>
</ul>
<Select class="mt-8" v-model="curSubJectId" placeholder="请选择学科">
<Option v-for="item in subjectComp" :value="item.k">{{ item.v }}</Option>
</Select>
<MyTree v-if="!treeLoading" ref="refMyTree" :autoClick="false" :treeData="treeData" :setting="treeSetting"
@on-tree-click="treeLink" />
<div class="modal-wrap">
<Modal v-model="editNodeModal.modal" :title="editNodeModal.title" width="450px" :mask-closable="false">
<Form ref="refFormData" :label-width="100" :model="editNodeModal" class="myform mb-20 mr-20">
<Form-Item label="当前位置:">
{{ curNodePath.join(' > ') }}
</Form-Item>
<Form-Item label="节点名称:" prop="name"
:rules="{ required: true, message: '请输入节点名称', trigger: 'blur' }">
<Input ref="refNodeNameInput" maxlength="30" v-model="editNodeModal.name" />
</Form-Item>
</Form>
<template #footer>
<Button @click="editNodeModal.modal = false">取消</Button>
<Button type="primary" :loading="editNodeModal.loading" @click="saveEditNode">确认</Button>
</template>
</Modal>
<Modal id="modal-sortTreeNode-id" class-name="up-dowm-components" v-model="showMoveModalStatus" title="移动节点"
:closable="false" :mask-closable="false" width="600">
<div class="fix">
<Alert class="fl">拖拽到目标节点时,确保节点是展开状态,以保证节点数据一致</Alert>
<Button class="fr" @click="sendReloadTree()">刷新节点树</Button>
</div>
<MyTree class="tree-div" ref="refSortMyTree" :autoClick="false" :treeData="moveTreeData"
:setting="moveTreeSetting" @on-tree-drag="onTreeDragMove" />
<template #footer>
<Button @click="showMoveModalStatus = false" style="margin-left: 20px">关闭</Button>
</template>
</Modal>
</div>
</div>
</template>
<script lang="ts" setup>
import SvgIcon from "@/components/SvgIcon.vue";
import MyTree from '@/components/MyTree.vue'
import { useDefaultStore } from "@/store";
import { RootNode } from "@/types";
import { cloneDeep, isEmpty } from "lodash";
import useCurrentInstance from "@/utils/hook/useCurrentInstance";
import { get, last } from "lodash";
const props = defineProps<{
rootType: RootNode;
}>()
const emit = defineEmits<{
'on-change': [stage: number, subject: string, treeNode: any]
}>()
const curTreeNode = defineModel<any>('curTreeNode', { default: {} })
//学科学段
const curSubjectInfo = defineModel<any>('curSubjectInfo', {
default: {
stage: null,
subject: null
}
})
const store = useDefaultStore();
const { $utils } = useCurrentInstance();
const refMyTree = useTemplateRef('refMyTree');
const refFormData = useTemplateRef('refFormData')
const refNodeNameInput = useTemplateRef('refNodeNameInput')
const treeSetting = ref({
view: {
dblClickExpand: false,
showIcon: false,
},
edit: {
enable: true,
showRemoveBtn: false,
showRenameBtn: false,
drag: false,
},
check: {
enable: false,
},
callback: {},
})
const moveTreeSetting = ref({
view: { dblClickExpand: false, showIcon: true },
edit: {
enable: true,
showRemoveBtn: false,
showRenameBtn: false,
drag: {
isMove: true,
isCopy: false,
showArrow: true,
prev: true,
next: true,
inner: true,
autoExpandTrigger: true
}
},
check: {
enable: false
},
callback: {}
})
const showMoveModalStatus = ref(false)
const editNodeModal = reactive({
modal: false,
title: '',
loading: false,
name: null,
id: null,
})
const curSubJectId = ref<number | null>(null);
const subjectComp = computed(() => {
return store.getStageSubjects;
})
const paramsComp = computed(() => {
const find = subjectComp.value.find((item: any) => item.k == curSubJectId.value)
if (!find) return null;
else
return {
rootType: props.rootType,
stage: find.stage,
subject: find.code
}
})
const curNodePath = computed(() => {
if (!curTreeNode.value) return [];
let arr: any[] = curTreeNode.value.getPath().map((x: any) => x.name)
return arr;
})
const moveTreeData = computed(() => {
function formatTree(list: any[]): any[] {
return list
.map(node => {
const [, , canMove] = $utils.unpackPerms(node.permissions)
if (!canMove) return null
const copy = cloneDeep(node)
copy.drag = true
copy.children = copy.children?.length
? formatTree(copy.children)
: null
return copy
})
.filter(Boolean)
}
return formatTree(treeData.value)
})
const { loading: treeLoading, data: treeData, send: sendReloadTree, onSuccess: onNodeTreeSuccess } = useRequest(
() => Apis.NodeTree.getNodeTree({ params: paramsComp.value }),
{
initialData: [],
immediate: false,
})
watch(() => curSubJectId.value, async (val) => {
await nextTick();
curTreeNode.value = null;
curSubjectInfo.value.stage = paramsComp.value?.stage;
curSubjectInfo.value.subject = paramsComp.value?.subject;
if (!!val)
sessionStorage.setItem('bsch.learnmanage.cur_subject_id', val.toString())
sendReloadTree(-1);
})
onNodeTreeSuccess(() => reloadTree(-1))
onMounted(async () => {
await nextTick();
if (subjectComp.value.length > 0) {
let cacheStr = sessionStorage.getItem('bsch.learnmanage.cur_subject_id');
if (!!cacheStr) {
curSubJectId.value = Number(cacheStr);
}
else
curSubJectId.value = subjectComp.value[0].k;
}
})
async function reloadTree(id?: number) {
await nextTick();
console.log(id);
treeData.value.forEach((n: any) => (n.open = true));
if (!!curTreeNode.value && !!curTreeNode.value.id)
refMyTree.value?.reloadTree(curTreeNode.value.id)
else
refMyTree.value?.reloadTree(id)
}
function showAddNode() {
if (!nodePermissions.value.canCreate) return;
editNodeModal.title = '新增节点';
editNodeModal.modal = true;
editNodeModal.id = null;
editNodeModal.name = null;
nextTick().then(() => {
refNodeNameInput.value?.focus()
})
}
function showEditNode() {
if (!nodePermissions.value.canUpdate) return;
editNodeModal.title = '编辑节点';
editNodeModal.modal = true;
editNodeModal.id = curTreeNode.value.id;
editNodeModal.name = curTreeNode.value.name;
nextTick().then(() => {
refNodeNameInput.value.focus()
})
}
function showMoveNode() {
if (!nodePermissions.value.canMove) return;
showMoveModalStatus.value = true;
}
async function showDeleteNode() {
if (!nodePermissions.value.canDelete) return;
const temp = cloneDeep(curTreeNode.value);
const parentNode = temp.getParentNode();
Modal.confirm({
title: '删除知识树节点',
content: `确定删除名为【${curTreeNode.value.name}】的知识树节点记录?`,
onOk: async () => {
const id = curTreeNode.value.id;
await Apis.NodeTree.deleteNode({ params: { nodeId: id } })
Message.success('删除成功!');
curTreeNode.value = parentNode;
sendReloadTree()
}
});
}
async function saveEditNode() {
let isAdd = !!editNodeModal.id ? false : true;
const validate = await refFormData.value.validate();
if (!validate) {
Message.error('请填写红色星号内容!');
return;
}
const { id, name } = editNodeModal;
editNodeModal.loading = true;
if (isAdd) {
const parentId = get(curTreeNode.value, 'id', -1)
const brotherId = get(last(get(curTreeNode.value, 'children')), 'id', 0);
const params: any = {
rootType: props.rootType,
name, parentId, brotherId,
stage: paramsComp.value?.stage,
subject: paramsComp.value?.subject
};
await Apis.NodeTree.addNode({ params })
sendReloadTree()
}
else {
await Apis.NodeTree.updateNodeName({ params: { name, nodeId: id } })
sendReloadTree();
}
editNodeModal.loading = false;
editNodeModal.modal = false;
}
async function refreshMoveTree() {
// const id = selectedNode.value.id;
// await Apis.NodeTree.refreshSortTree({ params: { nodeId: id } })
// Message.success('刷新成功!');
// refTreeNode.value?.reloadTree()
}
//@ts-ignore
async function onTreeDragMove({ treeNodes, targetNode, moveType }) {
let mType = moveType == "inner" ? 3 : moveType == "prev" ? 1 : moveType == "next" ? 2 : 0;
let currentNode = treeNodes[0];
let nativeNode = currentNode;
const params = formatMoveTree(treeNodes[0], targetNode, mType);
await Apis.NodeTree.move({
params: params,
})
sendReloadTree();
}
function formatMoveTree(currentNode: any, targetNode: any, moveType: any) {
let nativeNode = currentNode;
let treeNode = targetNode;
let pathArr = treeNode.path.split(",");
let index2 = pathArr.findIndex((ele: any) => {
return ele == nativeNode.id;
});
if (index2 != -1) {
Message.warning("父节点无法移动到子节点下方");
return;
}
let newBrother = ""; //新兄长节点 【接口参数】
let newParent = ""; //新父节点 【接口参数】
console.log('treeNode', treeNode);
var parentNode = treeNode.getParentNode();
let chilList = !!parentNode ? parentNode.children : treeNode.children;
let index = chilList.findIndex((ele: any) => {
return ele.id == treeNode.id;
});
if (treeNode.id == nativeNode.id || (treeNode.id == nativeNode.parentId && moveType == 3)) {
//选中同一节点,无效移动
Message.warning("目标节点与选中节点冲突,请选择其他目标节点");
return;
} else if ((moveType == 1 || moveType == 2) && nativeNode.parentId == treeNode.parentId) {
if (moveType == 1) {
console.log('idnex:' + index);
index--;
//前面
newBrother = index == 0 ? 0 : chilList[index - 1].id;
} else {
//后面
newBrother = chilList[index].id;
}
newParent = "";
} else {
//跨父节点移动
if (moveType == 1) {
//选择的是前面
//前面
newBrother = index == 0 ? index : chilList[index - 1].id;
newParent = treeNode.parentId;
} else if (moveType == 2) {
//选择的是后面
//后面
newBrother = chilList[index].id;
newParent = treeNode.parentId;
} else {
//选择的是里面
//里面 选中的children里面的最后一个
newBrother = isEmpty(treeNode.children)
? 0
: treeNode.children[treeNode.children.length - 1].id;
newParent = treeNode.id;
}
}
return {
targetId: nativeNode.id,
newParent: newParent,
newBrother: newBrother,
}
}
async function treeLink(treeNode: any) {
const copyNode = cloneDeep(treeNode)
copyNode.children = [];
curTreeNode.value = copyNode;
// console.log('click', treeNode);
// console.log(copyNode.getPath().map((x: any) => x.name));
curSubjectInfo.value.stage = paramsComp.value?.stage;
curSubjectInfo.value.subject = paramsComp.value?.subject;
const { stage, subject } = curSubjectInfo.value;
emit('on-change', stage, subject, copyNode)
}
const nodePermissions = computed(() => {
if (!curTreeNode.value) return { canCreate: false, canUpdate: false, canMove: false, canDelete: false };
const [canCreate, canUpdate, canMove, canDelete] = $utils.unpackPerms(curTreeNode.value.permissions)
return {
canCreate,
canUpdate,
canMove,
canDelete,
}
})
async function onChangeSubject() { }
</script>
<style lang="scss">
.opera-ul {
display: flex;
justify-content: space-around;
align-items: center;
border-bottom: 1px solid #ebeef5;
padding-bottom: 10px;
li {
padding: 10px;
height: 38px;
border-radius: 50%;
cursor: pointer;
&.active,
&:hover {
background-color: #f5f5f5;
}
svg {
width: 18px;
height: 18px;
}
}
li.disabled {
cursor: not-allowed;
opacity: 0.4;
&:hover {
background-color: #fff;
}
}
}
#modal-sortTreeNode-id {
position: relative;
z-index: 1000;
height: 380px;
.tree-div {
z-index: 1001;
border: 1px solid #cacaca;
border-radius: 5px;
height: 300px;
}
.ztree {
margin-bottom: 20px;
}
}
</style>