907 lines
30 KiB
Vue
Executable File
907 lines
30 KiB
Vue
Executable File
<template>
|
|
<div>
|
|
<div class="mt-3 text__15 text__green" v-if="description">
|
|
<span>{{ description }} </span>
|
|
</div>
|
|
|
|
<!-- tab/grouped mode -->
|
|
<template :key="renderMain" v-if="isArrayItems()">
|
|
<!-- start:horizontal mode -->
|
|
<template v-if="displayMode == 'horizontal'">
|
|
<div class="horizontal">
|
|
<!-- Tabs -->
|
|
<ul class="flex bg-gray-100 rounded-t-md overflow-hidden h-12">
|
|
<li
|
|
v-for="(groupItem, index) in localFormElements"
|
|
:key="'groupItem' + index"
|
|
@click="setTab(index)"
|
|
class="cursor-pointer mt-2"
|
|
>
|
|
<a
|
|
class="px-4 py-2 block relative"
|
|
:class="
|
|
currentTab === index
|
|
? 'bg-white text-gray-900 rounded-t-md'
|
|
: 'text-gray-600 hover:bg-gray-200'
|
|
"
|
|
>
|
|
{{ groupItem.title }}
|
|
</a>
|
|
</li>
|
|
</ul>
|
|
|
|
<!-- Tab content -->
|
|
<div
|
|
class="tab-content bg-white p-4 border border-t-0 border-gray-300 rounded-b-md"
|
|
>
|
|
<div
|
|
v-for="(groupItem, index) in localFormElements"
|
|
:key="'tabcontent' + index"
|
|
>
|
|
<div v-show="currentTab === index" class="px-3">
|
|
<form>
|
|
<div class="grid grid-cols-12 gap-4">
|
|
<div
|
|
v-for="(formElement, index1) in getGroupListElements(
|
|
groupItem.items,
|
|
)"
|
|
:key="formElement.key + index1"
|
|
:class="giveColClass('', formElement)"
|
|
>
|
|
<component
|
|
:is="
|
|
componentMap[
|
|
returnComponentNameDefault(formElement, groupItem)
|
|
]
|
|
"
|
|
:ref="`${refKeyBase}_${formElement.key}`"
|
|
:key="'element' + formElement.key + '_' + index1"
|
|
:formElement="
|
|
addValueToFormElement(formElement, groupItem)
|
|
"
|
|
:otherData="otherData"
|
|
:isReadOnly="readOnly"
|
|
:multipleMode="multipleMode"
|
|
:acceptType="acceptType"
|
|
:class="giveColClass('', formElement)"
|
|
:inputClass="giveColClass('input', formElement)"
|
|
:labelClass="'col-12'"
|
|
class="inside-entity align-items-center"
|
|
:classComponentName="classComponentName"
|
|
@updateComponentOption="
|
|
(ev) => updateComponentOption(ev, formElement)
|
|
"
|
|
@link-route-clicked="linkRouteClicked"
|
|
@take-value="
|
|
(ev) => takeValueChanged(ev, formElement, groupItem)
|
|
"
|
|
@on-upload-file="onUploadFile"
|
|
@action-affected-item="
|
|
(ev) => actionAffectedItem(ev, formElement)
|
|
"
|
|
@keydown="
|
|
formBuilderAction('formBuilderKeydown', $event)
|
|
"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Submit Button -->
|
|
<div
|
|
v-if="isHorizontalButtonSubmit"
|
|
class="mt-4 flex justify-end"
|
|
>
|
|
<button
|
|
v-if="
|
|
isShowButtonAccordion(groupItem) &&
|
|
!isReadOnlyGroup(groupItem)
|
|
"
|
|
type="submit"
|
|
class="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700"
|
|
@click.prevent="saveGroupProperty(groupItem)"
|
|
>
|
|
{{ groupItem.submit_label ?? "ثبت" }}
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<!-- start:vertical mode -->
|
|
<div
|
|
class="flex flex-col md:flex-row"
|
|
v-else-if="displayMode == 'vertical'"
|
|
>
|
|
<!-- Sidebar -->
|
|
<div :class="listClass ?? 'md:w-1/4 w-1/3'">
|
|
<div
|
|
class="flex flex-col space-y-2"
|
|
role="tablist"
|
|
aria-orientation="vertical"
|
|
>
|
|
<button
|
|
v-for="(groupItem, index) in localFormElements"
|
|
:key="'groupItem' + index"
|
|
@click="setTab(index)"
|
|
class="px-4 py-2 text-left rounded-lg transition-colors duration-200"
|
|
:class="
|
|
currentTab == index
|
|
? 'bg-blue-500 text-white'
|
|
: 'bg-gray-100 text-gray-800 hover:bg-gray-200'
|
|
"
|
|
role="tab"
|
|
:aria-selected="currentTab == index"
|
|
>
|
|
{{ groupItem.title }}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Content -->
|
|
<div :class="mainClass ?? 'md:w-3/4 w-2/3'">
|
|
<div class="tab-content">
|
|
<div
|
|
class="tab-pane transition-all duration-300"
|
|
:class="currentTab == index ? 'block' : 'hidden'"
|
|
v-for="(groupItem, index) in localFormElements"
|
|
:key="'tab-pane' + index"
|
|
role="tabpanel"
|
|
>
|
|
<div class="ml-5 relative">
|
|
<div class="container-fluid">
|
|
<div class="grid grid-cols-12 gap-4">
|
|
<div
|
|
v-for="(formElement, index1) in getGroupListElements(
|
|
groupItem?.items,
|
|
)"
|
|
:key="index1"
|
|
:class="giveColClass('', formElement)"
|
|
>
|
|
<component
|
|
:is="
|
|
componentMap[
|
|
returnComponentNameDefault(formElement, groupItem)
|
|
]
|
|
"
|
|
:ref="`${refKeyBase}_${formElement.key}`"
|
|
:key="'element' + formElement.key + '_' + index1"
|
|
:formElement="
|
|
addValueToFormElement(formElement, groupItem)
|
|
"
|
|
:otherData="otherData"
|
|
:chartComponentName="formElement.chartComponentName"
|
|
:isReadOnly="readOnly"
|
|
:multipleMode="multipleMode"
|
|
:acceptType="acceptType"
|
|
:class="
|
|
giveColClass('', formElement) +
|
|
' inside-entity align-items-center'
|
|
"
|
|
:inputClass="giveColClass('input', formElement)"
|
|
:labelClass="'col-12'"
|
|
:classComponentName="classComponentName"
|
|
@treemap-node-click="
|
|
formBuilderAction('treemap-node-click', $event)
|
|
"
|
|
@updateComponentOption="
|
|
(ev) => updateComponentOption(ev, formElement)
|
|
"
|
|
@link-route-clicked="linkRouteClicked"
|
|
@take-value="
|
|
(ev) => takeValueChanged(ev, formElement, groupItem)
|
|
"
|
|
@on-upload-file="onUploadFile"
|
|
@action-affected-item="
|
|
(ev) => actionAffectedItem(ev, formElement)
|
|
"
|
|
@keydown="
|
|
formBuilderAction('formBuilderKeydown', $event)
|
|
"
|
|
:widthChart="'100%'"
|
|
:heightChart="'64dvh'"
|
|
:isOpenModal="false"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- accordion & accordion-show -->
|
|
<template
|
|
v-else-if="
|
|
displayMode == 'accordion' || displayMode == 'accordion-show'
|
|
"
|
|
>
|
|
<UAccordion
|
|
:items="accordionItems"
|
|
:type="displayMode === 'accordion-show' ? 'multiple' : 'single'"
|
|
:collapsible="displayMode !== 'accordion-show'"
|
|
:default-value="
|
|
displayMode === 'accordion-show'
|
|
? accordionItems.map((_, i) => String(i))
|
|
: []
|
|
"
|
|
:unmount-on-hide="false"
|
|
:ui="{
|
|
root: 'w-full space-y-2',
|
|
item: 'rounded-md border border-gray-200 dark:border-gray-700',
|
|
header: 'flex bg-gray-100 dark:bg-dark-primary-700 rounded-md',
|
|
trigger:
|
|
'group flex-1 items-center justify-between gap-2 py-3 px-4 font-medium text-start focus-visible:outline-primary cursor-pointer',
|
|
content:
|
|
'overflow-hidden data-[state=open]:animate-[accordion-down_200ms_ease-out] data-[state=closed]:animate-[accordion-up_200ms_ease-out]',
|
|
body: 'p-0',
|
|
label: 'text-base font-medium text-gray-900 dark:text-white',
|
|
trailingIcon:
|
|
'ms-auto size-5 text-gray-500 group-data-[state=open]:rotate-180 transition-transform',
|
|
}"
|
|
>
|
|
<template #content="{ item, index }">
|
|
<form @submit.prevent="saveGroupProperty(item.data)">
|
|
<div class="p-4">
|
|
<div v-if="item.data.is_array" class="mb-4">
|
|
<TableComponent
|
|
:key="`table-${index}`"
|
|
height="14em"
|
|
:tableColumns="item.data.table_columns"
|
|
:tagActions="item.data.tag_actions"
|
|
:items="localFormData[item.data.key]"
|
|
:itemKey="item.data.key"
|
|
:formElements="item.data.items"
|
|
:isReadOnly="isReadOnlyGroup(item.data)"
|
|
:showActions="!isReadOnlyGroup(item.data)"
|
|
@updateTableComponent="updateTableComponent"
|
|
@add-table-item="addTableItem(item.data)"
|
|
@edit-table-item="editTableItem($event, item.data)"
|
|
@action-table-item="actionTableItem($event, item.data)"
|
|
@delete-table-item="deleteTableItem($event, item.data)"
|
|
/>
|
|
</div>
|
|
|
|
<div v-else class="grid grid-cols-12 gap-4 mb-4">
|
|
<div
|
|
v-for="(formElement, idx) in getGroupListElements(
|
|
item.data.items,
|
|
)"
|
|
:key="`${formElement.key}-${idx}`"
|
|
:class="giveColClass('', formElement)"
|
|
>
|
|
<component
|
|
:is="
|
|
componentMap[
|
|
returnComponentNameDefault(formElement, item.data)
|
|
]
|
|
"
|
|
:ref="`${refKeyBase}_${formElement.key}`"
|
|
:formElement="
|
|
addValueToFormElement(formElement, item.data)
|
|
"
|
|
:otherData="otherData"
|
|
:isReadOnly="isReadOnlyGroup(item.data, formElement)"
|
|
:multipleMode="multipleMode"
|
|
:acceptType="acceptType"
|
|
:class="giveColClass('', formElement)"
|
|
:inputClass="giveColClass('input', formElement)"
|
|
classComponentName="inside-entity"
|
|
@updateComponentOption="
|
|
(ev) => updateComponentOption(ev, formElement)
|
|
"
|
|
@link-route-clicked="linkRouteClicked"
|
|
@take-value="
|
|
(ev) => takeValueChanged(ev, formElement, item.data)
|
|
"
|
|
@on-upload-file="onUploadFile"
|
|
@action-affected-item="
|
|
(ev) => actionAffectedItem(ev, formElement)
|
|
"
|
|
@openModalTags="openModalTags"
|
|
@keydown="formBuilderAction('formBuilderKeydown', $event)"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div
|
|
v-if="
|
|
isShowButtonAccordion(item.data) &&
|
|
!isReadOnlyGroup(item.data)
|
|
"
|
|
class="flex justify-end"
|
|
>
|
|
<UButton
|
|
type="submit"
|
|
variant="solid"
|
|
color="primary"
|
|
size="md"
|
|
class="px-4 cursor-pointer"
|
|
@click.prevent="saveGroupProperty(item.data)"
|
|
>
|
|
{{ item.data.submit_label || "ثبت" }}
|
|
</UButton>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</template>
|
|
</UAccordion>
|
|
</template>
|
|
|
|
<!-- default mode -->
|
|
<template v-else-if="displayMode == 'default'">
|
|
<div class="container-fluid">
|
|
<div class="row grid grid-cols-12 gap-4">
|
|
<div
|
|
v-for="(formElement, index) in getGroupListElements(
|
|
localFormElements,
|
|
)"
|
|
:key="formElement.key + index"
|
|
:class="giveColClass('', formElement)"
|
|
>
|
|
<component
|
|
:is="
|
|
componentMap[
|
|
returnComponentNameDefault(formElement, groupItem)
|
|
]
|
|
"
|
|
:ref="`${refKeyBase}_${formElement.key}`"
|
|
:key="index + formElement.key + render"
|
|
:formElement="
|
|
addValueToFormElement(formElement, localFormElements)
|
|
"
|
|
:otherData="otherData"
|
|
:isReadOnly="readOnly"
|
|
:multipleMode="multipleMode"
|
|
:acceptType="acceptType"
|
|
:class="giveColClass(formElement.type, formElement)"
|
|
:inputClass="giveColClass('input', formElement)"
|
|
:labelClass="giveColClass('label', formElement)"
|
|
class="inside-entity align-items-center"
|
|
:classComponentName="classComponentName"
|
|
@updateComponentOption="
|
|
(ev) => updateComponentOption(ev, formElement)
|
|
"
|
|
@on-action-handler="onActionHandler"
|
|
@link-route-clicked="linkRouteClicked"
|
|
@take-value="
|
|
(ev) => takeValueChanged(ev, formElement, localFormElements)
|
|
"
|
|
@on-upload-file="onUploadFile"
|
|
@action-affected-item="
|
|
(ev) => actionAffectedItem(ev, formElement)
|
|
"
|
|
@take-value-validate="
|
|
formBuilderAction('take-value-validate', $event)
|
|
"
|
|
@keydown="formBuilderAction('formBuilderKeydown', $event)"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</template>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import {
|
|
ref,
|
|
reactive,
|
|
computed,
|
|
watch,
|
|
onMounted,
|
|
onBeforeUnmount,
|
|
nextTick,
|
|
} from "vue";
|
|
import { cloneDeep } from "lodash";
|
|
|
|
// Async Components (same as before, but using defineAsyncComponent explicitly)
|
|
|
|
// --- جایگزینی بخش import و تعریف componentMap ---
|
|
import FormInputComponent from "@/components/lazy-load/form-builder/FormInputComponent.vue";
|
|
import FormTextareaComponent from "@/components/lazy-load/form-builder/FormTextareaComponent.vue";
|
|
import FormSelectComponent from "@/components/lazy-load/form-builder/FormSelectComponent.vue";
|
|
import FormDateComponent from "@/components/lazy-load/form-builder/FormDateComponent.vue";
|
|
import FormUploadFilesComponent from "@/components/lazy-load/form-builder/FormUploadFilesComponent.vue";
|
|
|
|
// Transform localFormElements into AccordionItem[]
|
|
const accordionItems = computed(() => {
|
|
return localFormElements.value.map((groupItem, index) => ({
|
|
label: groupItem.title,
|
|
// Optional: you can add `value` if needed for controlled mode
|
|
// value: String(index),
|
|
// Pass original data for slots
|
|
data: groupItem,
|
|
}));
|
|
});
|
|
// سایر کامپوننتها را هم به همین ترتیب اضافه کنید:
|
|
// import FormSelectComponent from '...';
|
|
// import FormLabelComponentReadOnly from '...';
|
|
|
|
const componentMap = {
|
|
FormInputComponent,
|
|
FormTextareaComponent,
|
|
FormSelectComponent,
|
|
FormDateComponent,
|
|
FormUploadFilesComponent,
|
|
// ... سایر کامپوننتهایی که استفاده میکنید
|
|
};
|
|
const props = defineProps({
|
|
refKeyBase: {
|
|
type: String,
|
|
default: "formbuilder",
|
|
},
|
|
displayMode: {
|
|
type: String,
|
|
default: "horizontal",
|
|
},
|
|
readOnly: {
|
|
type: Boolean,
|
|
default: false,
|
|
},
|
|
dataChart: {
|
|
type: Array,
|
|
default: () => [],
|
|
},
|
|
chartComponentName: {
|
|
type: String,
|
|
default: "",
|
|
},
|
|
formElements: {
|
|
type: Array,
|
|
default: () => [],
|
|
},
|
|
description: {
|
|
type: String,
|
|
default: "",
|
|
},
|
|
formData: {
|
|
type: Object,
|
|
default: () => ({}),
|
|
},
|
|
otherData: {
|
|
type: [Object, Array],
|
|
default: null,
|
|
},
|
|
previewMode: {
|
|
type: Boolean,
|
|
default: false,
|
|
},
|
|
classComponentName: {
|
|
type: String,
|
|
default: "",
|
|
},
|
|
multipleMode: {
|
|
type: Boolean,
|
|
default: true,
|
|
},
|
|
isHorizontalButtonSubmit: {
|
|
type: Boolean,
|
|
default: false,
|
|
},
|
|
acceptType: {
|
|
type: String,
|
|
default: "",
|
|
},
|
|
listClass: {
|
|
type: String,
|
|
default: "col-md-12",
|
|
},
|
|
mainClass: {
|
|
type: String,
|
|
default: "col-md-9",
|
|
},
|
|
});
|
|
|
|
// Emits (explicitly defined)
|
|
const emit = defineEmits(["form-builder-action"]);
|
|
function formBuilderAction(action, payload) {
|
|
emit("form-builder-action", {
|
|
action: action,
|
|
payload: payload,
|
|
});
|
|
}
|
|
// === Reactive State ===
|
|
const renderMain = ref(100);
|
|
const tableRender = ref(0);
|
|
const currentTab = ref(0);
|
|
const prevActiveTabIndex = ref(undefined);
|
|
const localFormElements = ref([]);
|
|
const localFormData = ref({});
|
|
const localFormDataChanged = ref({});
|
|
const activeRequest = ref([]);
|
|
|
|
let this_vm = getCurrentInstance();
|
|
|
|
// === Computed ===
|
|
const buttonText = computed(() => {
|
|
return localFormData.value?.id || localFormData.value?.guid
|
|
? "بروزرسانی"
|
|
: "افزودن";
|
|
});
|
|
|
|
// === Methods ===
|
|
const foundItemFormData = (key, index) => {
|
|
let item;
|
|
Object.keys(localFormData.value).forEach((nameItem) => {
|
|
if (nameItem === key) {
|
|
const arrayItemFound = localFormData.value[nameItem];
|
|
item = arrayItemFound?.[index];
|
|
}
|
|
});
|
|
return item;
|
|
};
|
|
|
|
const updateTableComponent = (event) => {
|
|
const { key, items } = event;
|
|
localFormData.value[key] = items;
|
|
localFormDataChanged.value[key] = items;
|
|
};
|
|
|
|
const isShowButtonAccordion = (item) => {
|
|
if (item.submit_label === "") return false;
|
|
return !props.readOnly;
|
|
};
|
|
|
|
const isReadOnlyGroup = (groupItem, item = undefined) => {
|
|
if (props.readOnly) return true;
|
|
let res = false;
|
|
if ("has_permission" in groupItem) res = !groupItem.has_permission;
|
|
if (item && "readOnly" in item && !res) res = item.readOnly;
|
|
return res;
|
|
};
|
|
|
|
const saveGroupProperty = (groupItem) => {
|
|
let changed_data = localFormDataChanged.value;
|
|
if (localFormDataChanged.value[groupItem.key]) {
|
|
changed_data = localFormDataChanged.value[groupItem.key];
|
|
}
|
|
let data = {};
|
|
let data_form = {};
|
|
|
|
if (!groupItem.type_data || groupItem.type_data === "normal") {
|
|
groupItem.items?.forEach((item) => {
|
|
if (changed_data[item.key]) data[item.key] = changed_data[item.key];
|
|
});
|
|
data_form = data;
|
|
} else {
|
|
data_form[groupItem.key] = changed_data;
|
|
}
|
|
formBuilderAction("saveFormGroup", data_form);
|
|
};
|
|
|
|
const onActionHandler = ({ action, key, value }) => {
|
|
const target = action.target;
|
|
updateValueFormData(target, value);
|
|
};
|
|
|
|
const isArrayItems = () => {
|
|
return Array.isArray(localFormElements.value);
|
|
};
|
|
|
|
const listenEventBus = ({ value, affectedTo }) => {
|
|
const refkey = `${props.refKeyBase}_${affectedTo.key}`;
|
|
const refs = this_vm.refs || {};
|
|
if (refs[refkey]) {
|
|
try {
|
|
const method =
|
|
affectedTo.action === "ufInitTextValue"
|
|
? (v) => refs[refkey][0]?.ufInitTextValue(v, true)
|
|
: (v) => refs[refkey][0]?.[affectedTo.action]?.(v);
|
|
method?.(value);
|
|
} catch (err) {
|
|
// silent
|
|
}
|
|
} else {
|
|
localFormData.value[affectedTo.key] = value;
|
|
localFormDataChanged.value[affectedTo.key] = value;
|
|
}
|
|
};
|
|
|
|
const setTab = (index) => {
|
|
if (prevActiveTabIndex.value !== undefined) {
|
|
localFormElements.value[prevActiveTabIndex.value].active = false;
|
|
}
|
|
currentTab.value = index;
|
|
prevActiveTabIndex.value = index;
|
|
|
|
formBuilderAction("setTab", index);
|
|
};
|
|
|
|
const getGroupListElements = (items) => {
|
|
if (!items) return [];
|
|
return items.filter((el) => !("ishide" in el) || el.ishide !== 1);
|
|
};
|
|
|
|
const findItemSchema = (key, items = undefined) => {
|
|
items = items || localFormElements.value;
|
|
for (let i = 0; i < items.length; i++) {
|
|
if (items[i]?.key === key) return items[i];
|
|
if (items[i].items) {
|
|
const found = findItemSchema(key, items[i].items);
|
|
if (found) return found;
|
|
}
|
|
}
|
|
return undefined;
|
|
};
|
|
|
|
const getSourceData = (itemData, key) => {
|
|
let elMom = itemData;
|
|
const elKeys = key.split("__");
|
|
for (let i = 0; i < elKeys.length - 1; i++) {
|
|
if (elMom?.[elKeys[i]]) {
|
|
elMom = elMom[elKeys[i]];
|
|
}
|
|
}
|
|
|
|
const finalKey = elKeys[elKeys.length - 1];
|
|
|
|
// console.log('getSourceData ', finalKey, elMom?.[finalKey], elMom);
|
|
return elMom?.[finalKey] ?? null;
|
|
};
|
|
|
|
const addValueToFormElement = (formElement, innerGroupItem) => {
|
|
const cloned = { ...formElement };
|
|
cloned["componentName"] = returnComponentNameDefault(cloned, innerGroupItem);
|
|
|
|
if (formElement.key) {
|
|
let formData = localFormData.value;
|
|
if (innerGroupItem && innerGroupItem?.type_data === "object") {
|
|
formData = localFormData.value[innerGroupItem.key] ?? {};
|
|
} else if (formElement.key == "file") {
|
|
formElement.key = "subtitle";
|
|
}
|
|
const value = getSourceData(formData, formElement.key);
|
|
cloned["value"] = value;
|
|
}
|
|
// console.log('addValueToFormElement ', formElement.key, cloned.value, cloned);
|
|
|
|
return cloned;
|
|
};
|
|
|
|
const returnComponentNameDefault = (item, groupItem) => {
|
|
const type = item.type;
|
|
// console.log("type ==> ", type);
|
|
const _readOnly = props.readOnly
|
|
? true
|
|
: groupItem
|
|
? isReadOnlyGroup(groupItem, item)
|
|
: false;
|
|
// console.log("_readOnly ==> ", _readOnly);
|
|
if (_readOnly) {
|
|
if (type === "textarea") return "FormTextareaComponent";
|
|
else if (type === "htmleditor") return "HtmlEditor";
|
|
else if (type === "tinyeditor") return "MyTinyMce";
|
|
else return "FormLabelComponentReadOnly";
|
|
}
|
|
|
|
switch (type) {
|
|
case "textarea":
|
|
return "FormTextareaComponent";
|
|
case "select":
|
|
return "FormSelectComponent";
|
|
case "string":
|
|
case "number":
|
|
case "float":
|
|
return "FormInputComponent";
|
|
case "label":
|
|
return "FormLabelComponentReadOnly";
|
|
case "tags":
|
|
return "tagsComponent";
|
|
case "selectTags":
|
|
return "SelectTagsComponent";
|
|
case "htmleditor":
|
|
return "HtmlEditor";
|
|
case "tinyeditor":
|
|
return "MyTinyMce";
|
|
case "date":
|
|
return "FormDateComponent";
|
|
case "range_date":
|
|
return "RangeDateComponent";
|
|
case "checkbox":
|
|
case "radio":
|
|
return "CheckboxComponent";
|
|
case "label_button":
|
|
return "LabelButtonComponent";
|
|
case "upload":
|
|
return "FormUploadFilesComponent";
|
|
case "multiFormSelectComponent":
|
|
return "MultiFormSelectComponent";
|
|
case "chart_content":
|
|
return "ChartContent";
|
|
default:
|
|
return "FormInputComponent";
|
|
}
|
|
};
|
|
|
|
const actionAffectedItem = ({ action, key, value }, schemaItem) => {
|
|
// console.log("actionAffectedItem ", action, key, value);
|
|
|
|
formBuilderAction("form-action-affected", { action, key, value, schemaItem });
|
|
};
|
|
|
|
const onUploadFile = (fileUploadData) => {
|
|
formBuilderAction("uploadFile", fileUploadData);
|
|
};
|
|
|
|
const takeValueObjectChanged = (value, formElement, innerGroupItem) => {
|
|
if (!innerGroupItem || innerGroupItem?.type_data !== "object") return false;
|
|
|
|
let root_object =
|
|
getSourceData(localFormData.value, innerGroupItem.key) ?? {};
|
|
root_object[formElement.key] = value;
|
|
localFormData.value[innerGroupItem.key] = root_object;
|
|
localFormDataChanged.value[innerGroupItem.key] = root_object;
|
|
return true;
|
|
};
|
|
|
|
const takeValueChanged = (value, schemaItem, innerGroupItem = null) => {
|
|
const key = schemaItem.key;
|
|
if (!takeValueObjectChanged(value, schemaItem, innerGroupItem)) {
|
|
if (typeof value === "string") value = value.trim() ?? null;
|
|
localFormData.value[key] = value;
|
|
localFormDataChanged.value[key] = value;
|
|
}
|
|
|
|
formBuilderAction("changeFormValues", { ...localFormDataChanged.value });
|
|
formBuilderAction("changeFormValueOne", { key, value });
|
|
};
|
|
|
|
const callItemMethod = (key, method, val) => {
|
|
const refkey = `${props.refKeyBase}_${key}`;
|
|
let refs = this_vm.refs || {};
|
|
if (refs[refkey]?.[0]?.[method]) {
|
|
refs[refkey][0][method](val);
|
|
}
|
|
};
|
|
|
|
const updateValueFormData = (key, value) => {
|
|
if (!value) return;
|
|
if (typeof value === "string") value = value.trim();
|
|
|
|
const refkey = `${props.refKeyBase}_${key}`;
|
|
let refs = this_vm.refs || {};
|
|
|
|
if (refs[refkey]?.initTextValue) {
|
|
refs[refkey].initTextValue(value);
|
|
}
|
|
|
|
localFormData.value[key] = value;
|
|
localFormDataChanged.value[key] = value;
|
|
formBuilderAction("changeFormValues", { ...localFormDataChanged.value });
|
|
formBuilderAction("changeFormValueOne", { key, value: value });
|
|
};
|
|
|
|
const giveColClass = (type, item) => {
|
|
// اگر کلاس دستی داده شده باشد استفاده میکنیم
|
|
// if (item.classes) {
|
|
// if (type === "") return item.classes;
|
|
// if (type === "label") return "col-auto";
|
|
// if (["input", "string", "textarea"].includes(type)) {
|
|
// return item.inputClass || item.classes;
|
|
// }
|
|
// }
|
|
|
|
// بررسی colSpan
|
|
const span = item.colSpan ?? 12; // پیشفرض کل عرض
|
|
const validSpan = Math.min(Math.max(span, 1), 12); // بین 1 تا 12
|
|
return `col-span-${validSpan}`;
|
|
};
|
|
|
|
const linkRouteClicked = (item) => {
|
|
if (!item.link_route) return;
|
|
const id_route = localFormData.value[item.link_route.id] ?? "";
|
|
const key = item.link_route.key;
|
|
const routeData = router.resolve({
|
|
name: item.link_route.name,
|
|
params: { id: id_route, key },
|
|
query: {},
|
|
});
|
|
window.open(routeData.href, "_blank");
|
|
};
|
|
|
|
// Table-specific handlers
|
|
const deleteTableItem = (event, schema) => {
|
|
const item = foundItemFormData(schema.key, event);
|
|
formBuilderAction("delete-table-item", {
|
|
schema,
|
|
item,
|
|
index: event,
|
|
});
|
|
};
|
|
|
|
const editTableItem = (event, schema) => {
|
|
const item = foundItemFormData(schema.key, event);
|
|
formBuilderAction("edit-table-item", {
|
|
schema,
|
|
item,
|
|
index: event,
|
|
});
|
|
};
|
|
|
|
const duplicateTableItem = (event, schema) => {
|
|
const item = foundItemFormData(schema.key, event);
|
|
formBuilderAction("duplicate-table-item", {
|
|
schema,
|
|
item,
|
|
index: event,
|
|
});
|
|
};
|
|
|
|
const actionTableItem = (event, schema) => {
|
|
emit(event.action, {
|
|
item: event.data.item,
|
|
index: event.data.index,
|
|
schema,
|
|
});
|
|
};
|
|
|
|
const addTableItem = (schema) => {
|
|
formBuilderAction("add-table-item", schema);
|
|
};
|
|
|
|
// Open modal for tags
|
|
const openModalTags = (item) => {
|
|
formBuilderAction("edit-property-tags", item);
|
|
};
|
|
|
|
// === Setup: Lifecycle & Watchers ===
|
|
import { getCurrentInstance } from "vue";
|
|
import { useNuxtApp } from "#app";
|
|
import { useRouter } from "vue-router";
|
|
|
|
const { $eventBus } = useNuxtApp();
|
|
const router = useRouter();
|
|
|
|
onMounted(() => {
|
|
localFormElements.value = props.formElements
|
|
? cloneDeep(props.formElements)
|
|
: [];
|
|
localFormData.value = props.formData ? cloneDeep(props.formData) : {};
|
|
renderMain.value++;
|
|
setTab(0);
|
|
|
|
// Event bus listener
|
|
if ($eventBus) {
|
|
$eventBus.on("catch-form-builder-event", listenEventBus);
|
|
}
|
|
});
|
|
|
|
onBeforeUnmount(() => {
|
|
if ($eventBus) {
|
|
$eventBus.off("catch-form-builder-event", listenEventBus);
|
|
}
|
|
});
|
|
|
|
// Watchers (deep + immediate)
|
|
watch(
|
|
() => props.formElements,
|
|
(newValue) => {
|
|
localFormElements.value = newValue ? cloneDeep(newValue) : [];
|
|
},
|
|
{ deep: true, immediate: true },
|
|
);
|
|
|
|
watch(
|
|
() => props.formData,
|
|
(newValue) => {
|
|
localFormData.value = newValue ? cloneDeep(newValue) : {};
|
|
},
|
|
{ deep: true, immediate: true },
|
|
);
|
|
|
|
// Expose methods if needed for parent refs (optional, if used externally)
|
|
defineExpose({
|
|
callItemMethod,
|
|
updateValueFormData,
|
|
setTab,
|
|
takeValueChanged,
|
|
});
|
|
</script>
|
|
|
|
<style lang="scss"></style>
|