487 lines
14 KiB
Vue
487 lines
14 KiB
Vue
<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> |