research_ui/components/research/components/ResearchTreeList.vue
2025-02-04 16:10:58 +03:30

648 lines
18 KiB
Vue

<template>
<div class="tree-list-container text-end">
<div class="d-flex align-items-center py-1 my-2 border-bottom">
<label for="">پوشه جدید :</label>
<button
class="btn"
@click="openModal('FormAddToTree', ' فرم ایجاد خصوصیت ')"
>
<svg class="icon icon-Component-212--1">
<use xlink:href="#icon-Component-212--1"></use>
</svg>
</button>
</div>
<div class="firefox-scrollbar v-jstree">
<v-jstree
allow-transition
:data="data"
:draggable="true"
ref="tree"
klass="tree-rtl"
size="large"
multiple
tree-rtl
textFieldName="text"
valueFieldName="item"
@item-click="itemclick"
@item-toggle="itemtoggle"
@item-drag-start="onItemDragStart"
@item-drag-end="onItemDragEnd"
@item-drop-before="onItemDropBefore"
@item-drop="onItemDrop"
>
<slot name="node">
<div
class="row tree-item mx-0"
style="width: 25em"
:class="{
'active-item': activeItem && activeItem.id === node.model.id,
}"
@click="setActiveItem(node.model, node.vm)"
>
<div class="col-8">
<div v-if="node.model.isEdit" class="d-flex align-items-center">
<input
type="text"
class="form-control"
v-model="node.model.text"
/>
<button @click.prevent="node.model.isEdit = false" class="btn">
Save
</button>
</div>
<div
class="text-truncate"
v-tooltip="node.model.text"
style="width: 100px"
v-else
>
{{ node.model.text }}
</div>
</div>
<div class="col-4 button-group" v-if="node.model.id !== 0">
<button
@click="removeNode(node.vm)"
class="btn p-0"
style="font-size: 0.6rem"
>
<svg class="icon icon-Component-295--1">
<use xlink:href="#icon-Component-295--1"></use>
</svg>
</button>
<button
@click.prevent="editNode(node.vm)"
class="btn p-0"
style="font-size: 0.6rem"
>
<svg class="icon icon-Component-242--1">
<use xlink:href="#icon-Component-242--1"></use>
</svg>
</button>
<button
@click.prevent="addChildNode(node.model, node.vm)"
class="btn p-0"
style="font-size: 0.6rem"
>
<svg class="icon icon-Component-133--1">
<use xlink:href="#icon-Component-133--1"></use>
</svg>
</button>
</div>
</div>
</slot>
</v-jstree>
</div>
<base-modal
v-if="uploadForFirstTime"
@canel="closeModal()"
:showHeaderCloseButton="true"
:modalTitle="modalTitle"
class="borhan-modal"
modalSize="modal-lg"
height="auto"
maxHeight="20em"
overflow="hidden"
width="30em"
:showSaveButton="true"
:hasFooter="false"
>
<component
:is="slotComponentName"
:uploadForFirstTime="uploadForFirstTime"
:editingItem="editingItem"
@close="closeModal()"
@addNew="addNewItem($event)"
@edit="edit($event)"
@addNewChildren="addNewChildren($event)"
></component>
</base-modal>
</div>
</template>
<script>
// import VJstree from "vue-jstree";
import researchApi from "~/apis/researchApi";
import { mapActions, mapState } from "pinia";
import { useResearchStore } from "~/stores/researchStore";
/**
* @vue-event {Number} increment - Emit counter's value after increment
* @vue-event {Number} decrement - Emit counter's value after decrement
*/
export default {
beforeMount() {
this.httpService = new HttpService(import.meta.env.VITE_BASE_URL);
},
props: {
treeItems: {
default() {
return [];
},
},
newProjects: {
default() {
return [];
},
},
},
data() {
return {
isEdit: false,
// searchText: "",
uploadForFirstTime: false,
slotComponentName: null,
modalTitle: null,
data: [
{
text: "همه",
name: "Root",
id: "0",
pid: "Root",
opened: false,
selected: false,
disabled: true,
loading: false,
children: [],
label: {
backgroundColor: "#ee6666",
},
},
],
httpService: undefined,
editingItem: "",
editingNode: "",
activeItem: null,
colors: [
"#5470C6",
"#d44646",
"#008b8b",
"#3BA272",
"#FC8452",
"#9A60B4",
"#EA7CCC",
"#F59F00",
"#ddc4b0",
"#73C0DE",
"#b333ad",
"#e86c6b",
"#39cfed",
],
currentIndex: 0,
};
},
watch: {
treeItems(newValue) {
this.data[0].children = [];
if (Array.isArray(newValue)) {
newValue.forEach((element, index) => {
const color = this.getNextColor();
var node = {
item: element,
text: element.title,
name: element.title,
id: element.id,
pid: 0,
opened: false,
selected: false,
disabled: false,
loading: false,
children: [],
label: {
backgroundColor: color,
},
itemStyle: {
color: color,
},
};
// if (element.children >= 1) {
// node.children = [];
// }
this.data[0].children.push(node);
this.dataForTreeMapSetter(this.data);
});
}
},
// data(newValue){
// console.log("🚀 ~ data ~ newValue:", newValue)
// },
},
computed: {
...mapState(useResearchStore, ["researchTermsGetter"]),
},
methods: {
...mapActions(useResearchStore, ["dataForTreeMapSetter"]),
getNextColor() {
// Get the color at the current index
const color = this.colors[this.currentIndex];
// Update the index to the next color
this.currentIndex = (this.currentIndex + 1) % this.colors.length;
return color;
},
// #region function Default for tree
/**
* داده‌های لیست درخت را از API دریافت می‌کند.
* @param {number} [parent=0] - شناسه والد برای دریافت فرزندان.
* @param {boolean} [ischildren=false] - پرچمی برای نشان دادن دریافت گره‌های فرزند.
* @param {Object} [parentItem={}] - شیء آیتم والد.
* @returns {Promise} - پاسخ API که شامل داده‌های درخت است.
*/
getListTree(parent = 0, ischildren = false, parentItem = {}) {
const payload = {
projectid: this.newProjects?.id,
parent: parent,
sortby: "id",
offset: 0,
limit: 100,
listtype: 0,
};
let url = researchApi.subject.list;
return this.httpService.formDataRequest(url, payload).then((res) => {
return res.data;
});
},
/**
* مدال افزودن گره فرزند را باز می‌کند.
* @param {Object} parentNode - مدل گره والد.
* @param {Object} node - مدل نمای گره.
*/
addChildNode(parentNode, node) {
this.editingItem = node.model;
this.editingNode = node;
this.openModal("FormAddChildrenToTree", " فرم ایجاد فرزند ");
},
/**
* یک گره را از درخت پس از تأیید حذف می‌کند.
* @param {Object} vm - مدل نمای گره برای حذف.
*/
removeNode(vm) {
mySwalConfirm({
title: "هشدار!!!",
html: `از حذف <b>${vm.model.text}</b> اطمینان دارید؟ `,
icon: "warning",
}).then((result) => {
if (result.isConfirmed) {
this.editingItem = vm.model;
this.editingNode = vm;
this.getRemov(this.editingItem);
if (this.editingItem.id !== undefined) {
var index = this.editingNode.parentItem.indexOf(this.editingItem);
this.editingNode.parentItem.splice(index, 1);
}
}
});
},
/**
* درخواست حذف یک آیتم از API را ارسال می‌کند.
* @param {Object} item - آیتمی که باید حذف شود.
*/
getRemov(item) {
const payload = {
subjectid: item.id,
projectid: this.newProjects?.id,
listid: item.id,
};
let url = researchApi.subject.delete;
this.httpService.postRequest(url, payload).then((res) => {
mySwalToast({
title: "موفق",
html: "با موفقیت حذف شد",
icon: "success",
});
});
},
/**
* مدال ویرایش یک گره را باز می‌کند.
* @param {Object} node - مدل نمای گره برای ویرایش.
*/
editNode(node) {
this.editingItem = node.model;
this.editingNode = node;
this.openModal("FormEditToTree", " فرم ویرایش خصوصیت ");
// this.editingNode = node;
},
/**
* رویداد کلیک روی آیتم را مدیریت می‌کند و فرزندان را اگر قبلاً بارگیری نشده باشند بارگیری می‌کند.
* @param {Object} nodeItem - آیتم گره کلیک شده.
*/
itemclick(nodeItem) {
if (nodeItem.model.children.length >= 1) return;
let parent = nodeItem.model;
try {
this.getListTree(parent.id, true, parent).then((list) => {
list.forEach((element, index) => {
var node = {
item: element,
text: element.title,
name: element.title,
id: element.id,
pid: parent.id,
opened: false,
selected: false,
disabled: false,
loading: false,
lineStyle: parent.lineStyle,
label: parent.label,
children: [],
};
const nodeExists = parent.children.some(
(child) => child.id === node.id
);
if (!nodeExists) {
parent.children.push(node);
}
});
});
} catch (error) {
console.error("Error fetching children nodes:", error);
}
},
/**
* رویداد تغییر وضعیت آیتم را مدیریت می‌کند.
* @param {Object} data - داده‌های آیتم تغییر وضعیت داده شده.
*/
itemtoggle(data) {
},
/**
* آیتم فعال را تنظیم می‌کند و رویداد 'on-click' را ارسال می‌کند.
* @param {Object} item - آیتمی که باید به عنوان فعال تنظیم شود.
*/
setActiveItem(item) {
this.activeItem = item;
this.$emit("on-click", item);
},
// #endregion
// #region function for modals
/**
* مدال را می‌بندد و پرچم uploadForFirstTime را تنظیم مجدد می‌کند.
*/
closeModal() {
$("#base-modal").modal("hide");
setTimeout(() => {
this.uploadForFirstTime = false;
}, 500);
},
/**
* یک مدال با مؤلفه و عنوان مشخص باز می‌کند.
* @param {string} componentName - نام مؤلفه‌ای که باید در مدال بارگیری شود.
* @param {string} title - عنوان مدال.
*/
openModal(componentName, title) {
this.uploadForFirstTime = true;
this.slotComponentName = componentName;
this.modalTitle = title;
setTimeout(() => {
$("#base-modal").modal({ backdrop: "static", keyboard: false }, "show");
}, 500);
},
/**
* یک آیتم جدید به درخت اضافه می‌کند.
* @param {Object} item - آیتمی که باید اضافه شود.
*/
addNewItem(item) {
// console.log("🚀 ~ addNewItem ~ item:", item);
const payload = {
id: undefined,
listtype: 0,
parent: 0,
projectid: this.newProjects?.id,
title: item,
};
let url = researchApi.subject.add;
this.httpService.postRequest(url, payload).then((res) => {
mySwalToast({
title: "موفق",
html: res.data.message,
icon: "success",
});
res.data.forEach((element, index) => {
var node = {
item: element,
text: element.title,
name: element.title,
id: element.id,
pid: 0,
opened: false,
selected: false,
disabled: false,
loading: false,
children: [],
};
this.data[0].children.push(node);
this.dataForTreeMapSetter(this.data);
});
});
},
/**
* یک آیتم موجود در درخت را ویرایش می‌کند.
* @param {string} item - عنوان جدید برای آیتم.
*/
edit(item) {
this.editingItem.text = item;
const payload = {
subjectid: this.editingItem.id,
projectid: this.newProjects?.id,
title: this.editingItem.text,
};
let url = researchApi.subject.edit;
this.httpService.formDataRequest(url, payload).then((res) => {
mySwalToast({
title: "موفق",
html: "تغییر نام با موفقیت انجام شد ",
icon: "success",
});
});
},
/**
* یک آیتم فرزند جدید به درخت اضافه می‌کند.
* @param {Object} item - آیتم فرزند که باید اضافه شود.
*/
addNewChildren(item) {
const payload = {
projectid: this.newProjects?.id,
parent: this.editingItem.id,
listid: this.editingItem.id,
listtype: 0,
title: item,
};
let url = researchApi.subject.add;
this.httpService.postRequest(url, payload).then((res) => {
mySwalToast({
title: "موفق",
html: " با موفقیت انجام شد ",
icon: "success",
});
res.data.forEach((element, index) => {
var node = {
item: element,
text: element.title,
name: element.title,
id: element.id,
pid: 0,
opened: false,
selected: false,
disabled: false,
loading: false,
children: [],
};
this.editingItem.children.push(node);
});
});
},
// #endregion
// #region function draggable for tree
/**
* قبل از انداختن آیتم، رویداد را مدیریت می‌کند.
* @param {Object} node - گره‌ای که آیتم به آن انداخته می‌شود.
* @param {Object} item - آیتمی که درگ شده است.
* @param {Object} draggedItem - آیتمی که در حال درگ شدن است.
* @param {Object} e - رویداد.
*/
onItemDropBefore(node, item, draggedItem, e) {
this.itemclick(node);
},
/**
* رویداد انداختن آیتم را مدیریت می‌کند.
* @param {Object} node - گره‌ای که آیتم به آن انداخته شده است.
* @param {Object} item - آیتمی که درگ شده است.
* @param {Object} draggedItem - آیتمی که در حال درگ شدن است.
* @param {Object} e - رویداد.
*/
onItemDrop(node, item, draggedItem, e) {
const cloneId = draggedItem.id;
// const fromId = draggedItem.id;
const toId = item?.id;
this.moveFromFolderToFolder(cloneId, toId);
},
/**
* آیتم را از یک فولدر به فولدر دیگر منتقل می‌کند.
* @param {number} cloneId - شناسه آیتمی که باید منتقل شود.
* @param {number} toId - شناسه فولدر مقصد.
*/
moveFromFolderToFolder(cloneId, toId) {
const formData = {
id: cloneId,
newparent: toId ?? 0,
projectid: this.researchTermsGetter?.id,
};
this.moveItem(formData);
},
/**
* آیتم را جابجا می‌کند.
* @param {Object} formData - داده‌های فرم برای جابجایی آیتم.
*/
moveItem(formData) {
let url = listUrl() + researchApi.subject.move;
this.httpService
.formDataRequest(url, formData)
.then((res) => {
// console.log("🚀 ~ .then ~ res:", res);
mySwalToast({
title: "تبریک",
html: res.message,
icon: "success",
});
})
.catch((err) => {
mySwalToast({
title: "خطا!!!",
html: err?.message,
icon: "error",
});
});
},
onItemDragStart() {},
onItemDragEnd() {},
// #endregion
moveToParent(node) {
let cloneId = node.model.id;
this.moveFromFolderToRoot(cloneId);
},
moveUp() {},
moveDown() {},
},
};
</script>
<style lang="scss">
.item-content {
display: flex;
justify-content: space-between;
width: 100%;
}
.button-group {
display: none;
align-items: center;
}
.tree-item:hover .button-group {
display: flex;
}
.tree-open {
.tree-children {
.tree-anchor {
.tree-item {
width: 150px;
}
}
}
}
</style>
<style lang="scss" scoped>
.tree-list-container {
width: 100%;
// height: calc(100vh - 10em);
// overflow-y: auto;
.tree-item {
width: 220px;
position: relative;
&:hover {
color: var(--primary-color); /* رنگ پس‌زمینه هاور سفارشی */
}
.icon {
&:hover {
cursor: pointer;
color: var(--primary-color);
}
}
}
.active-item {
background-color: var(--hover-color) !important;
}
.tree {
text-align: right;
height: calc(100vh - 10em);
overflow-y: auto;
scrollbar-width: thin;
scrollbar-color: #ccc #eee;
}
.v-jstree {
}
}
</style>