Initial commit

This commit is contained in:
Viet An
2026-03-02 09:45:33 +07:00
commit d17a9e2588
415 changed files with 92113 additions and 0 deletions

View File

@@ -0,0 +1,189 @@
<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>