190 lines
5.5 KiB
Vue
190 lines
5.5 KiB
Vue
<template>
|
|
<div class="columns mx-0 px-0 py-2">
|
|
<!-- Tab Navigation -->
|
|
<div
|
|
:class="`column is-narrow p-0 pr-4 ${viewport < 2 ? 'px-0' : ''}`"
|
|
:style="`${viewport < 2 ? '' : 'border-right: 1px solid #B0B0B0;'}`"
|
|
>
|
|
<div
|
|
:class="['is-clickable p-3', i !== 0 && 'mt-2', getStyle(v)]"
|
|
style="width: 120px; border-radius: 4px;"
|
|
v-for="(v, i) in tabsArray"
|
|
:key="i"
|
|
@click="changeTab(v)"
|
|
>
|
|
{{ isVietnamese ? v.name : v.en }}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Tab Content -->
|
|
<div
|
|
:class="`column pl-4 ${viewport < 2 ? 'px-0' : 'pr-0 py-0'}`"
|
|
style="min-width: 0"
|
|
>
|
|
<div v-if="isLoading" class="has-text-centered">
|
|
<span class="icon is-large">
|
|
<SvgIcon v-bind="{ name: 'loading.svg', type: 'primary', size: 18 }" />
|
|
</span>
|
|
</div>
|
|
<component
|
|
:is="getComponent(activeComponent.component)"
|
|
v-else-if="activeComponent && activeComponent.component"
|
|
v-bind="componentProps"
|
|
@update="handleUpdate"
|
|
@close="emit('close')"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { ref, computed, defineAsyncComponent, watch } from "vue";
|
|
import { useStore } from "@/stores/index";
|
|
import { useNuxtApp } from "#app";
|
|
|
|
const props = defineProps({
|
|
tabs: [Array, Object],
|
|
row: Object,
|
|
pagename: String,
|
|
application: Object,
|
|
});
|
|
|
|
const emit = defineEmits(["modalevent", "close"]);
|
|
const store = useStore();
|
|
const nuxtApp = useNuxtApp();
|
|
const { $dialog, $calculate, $findapi, $getapi, $copy } = nuxtApp;
|
|
|
|
const tabsArray = computed(() => {
|
|
if (Array.isArray(props.tabs)) {
|
|
return props.tabs;
|
|
}
|
|
if (typeof props.tabs === 'object' && props.tabs !== null) {
|
|
return Object.keys(props.tabs)
|
|
.sort()
|
|
.map(key => props.tabs[key]);
|
|
}
|
|
return [];
|
|
});
|
|
|
|
const modules = import.meta.glob("@/components/**/*.vue");
|
|
function getComponent(path) {
|
|
if (!path || typeof path !== "string" || !path.includes("/")) return null;
|
|
const moduleKey = Object.keys(modules).find((key) => key.endsWith(`${path}.vue`));
|
|
if (moduleKey) return defineAsyncComponent(modules[moduleKey]);
|
|
console.warn(`Component not found: ${path}`);
|
|
return null;
|
|
}
|
|
|
|
const lang = computed(() => store.lang);
|
|
const isVietnamese = computed(() => lang.value === "vi");
|
|
const viewport = computed(() => store.viewport);
|
|
|
|
const record = ref(props.row || {});
|
|
const activeTab = ref(tabsArray.value.find(t => t.active === true || t.active === 'true')?.code || tabsArray.value[0]?.code || '');
|
|
const tabData = ref(null);
|
|
const isLoading = ref(false);
|
|
|
|
const activeComponent = computed(() => tabsArray.value.find(t => t.code === activeTab.value));
|
|
|
|
const componentProps = computed(() => {
|
|
const baseProps = {
|
|
row: record.value,
|
|
data: tabData.value,
|
|
pagename: props.pagename,
|
|
application: props.application,
|
|
};
|
|
|
|
const tabSpecificProps = activeComponent.value?.vbind || {};
|
|
const processedProps = {};
|
|
|
|
if (record.value) {
|
|
for (const key in tabSpecificProps) {
|
|
const value = tabSpecificProps[key];
|
|
if (typeof value === 'string' && value.startsWith('row.')) {
|
|
const recordKey = value.substring(4);
|
|
processedProps[key] = record.value[recordKey];
|
|
} else {
|
|
processedProps[key] = value;
|
|
}
|
|
}
|
|
}
|
|
|
|
return { ...baseProps, ...processedProps };
|
|
});
|
|
|
|
watch(activeTab, async (newTabCode) => {
|
|
const tabConfig = tabsArray.value.find(t => t.code === newTabCode);
|
|
if (tabConfig && tabConfig.api) {
|
|
await fetchTabData(tabConfig);
|
|
} else {
|
|
tabData.value = null;
|
|
}
|
|
}, { immediate: true });
|
|
|
|
async function fetchTabData(tabConfig) {
|
|
isLoading.value = true;
|
|
tabData.value = null;
|
|
try {
|
|
let conn = $findapi(tabConfig.api.name);
|
|
if (!conn) {
|
|
console.error(`API connection '${tabConfig.api.name}' not found.`);
|
|
return;
|
|
}
|
|
conn = $copy(conn);
|
|
|
|
if (tabConfig.api.params) {
|
|
let params = $copy(tabConfig.api.params);
|
|
for (const key in params.filter) {
|
|
const value = params.filter[key];
|
|
if (typeof value === 'string' && value.startsWith('record.')) {
|
|
const recordKey = value.substring(7);
|
|
params.filter[key] = record.value[recordKey];
|
|
}
|
|
}
|
|
conn.params = params;
|
|
}
|
|
|
|
const result = await $getapi([conn]);
|
|
const apiResult = result.find(r => r.name === tabConfig.api.name);
|
|
if (apiResult && apiResult.data) {
|
|
tabData.value = apiResult.data.rows;
|
|
}
|
|
} catch (error) {
|
|
console.error("Error fetching tab data:", error);
|
|
} finally {
|
|
isLoading.value = false;
|
|
}
|
|
}
|
|
|
|
function getStyle(tab) {
|
|
const canEnter = checkCondition(tab, false);
|
|
return tab.code === activeTab.value
|
|
? "has-background-primary has-text-white"
|
|
: `has-background-light ${canEnter ? "" : "has-text-grey-light"}`;
|
|
}
|
|
|
|
function checkCondition(tab, showAlert = true) {
|
|
if (!tab.condition) return true;
|
|
const context = { record: record.value };
|
|
const result = $calculate(context, [], tab.condition);
|
|
if (!result.success || !result.value) {
|
|
if (showAlert) {
|
|
$dialog("Vui lòng <b>lưu dữ liệu</b> hoặc hoàn tất các bước trước khi chuyển sang mục này.", "Thông báo");
|
|
}
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
function changeTab(tab) {
|
|
if (activeTab.value === tab.code) return;
|
|
if (!checkCondition(tab)) return;
|
|
activeTab.value = tab.code;
|
|
}
|
|
|
|
function handleUpdate(updatedRecord) {
|
|
record.value = { ...record.value, ...updatedRecord };
|
|
emit("modalevent", { name: "dataevent", data: record.value });
|
|
}
|
|
</script>
|