648 lines
18 KiB
Vue
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>
|