Files
2025_Vuejs_Mpi_Panel/src/components/global/DataTable.vue

400 lines
12 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="list-wrapper-container">
<div class="list-wrapper-scrollbar-top" ref="scrollbarTopRef">
<div class="scrollbar-content"></div>
</div>
<div class="list-wrapper" ref="listWrapperRef" @scroll="onScroll">
<table class="table-border table-colored table-list">
<thead>
<tr>
<th class="table-head-row-number" v-if="rowNumber">
<div class="table-head-content">Sıra No</div>
</th>
<template v-for="(headCell, h) in tableHeader">
<th :style="[headCell.style || '']">
<div
:class="[
'table-head-content',
headCell.sort !== undefined && headCell.sort ? 'clickable' : ''
]"
@click="SortColumn(headCell)">
<span>{{ headCell.title }}</span>
<template
v-if="
headCell.sort !== undefined && headCell.sort && tableData.length > 1
">
<i
class="ico-c ico-sort"
v-if="
localSort?.sortOrder === '' ||
headCell.name !== localSort?.sortColumn
">
<svg>
<use href="@/assets/images/icons.svg#arrowsortable" />
</svg>
</i>
<i
:class="[
'ico-c',
'ico-sort',
localSort?.sortOrder === 'asc' ? 'order-asc' : ''
]"
v-if="
headCell.name === localSort?.sortColumn &&
localSort?.sortOrder !== ''
">
<svg>
<use href="@/assets/images/icons.svg#arrowline" />
</svg>
</i>
</template>
</div>
</th>
</template>
<th
:style="[rowActionStyle || '']"
v-if="rowActions !== undefined && rowActions.length > 0">
<div class="table-head-content">İşlemler</div>
</th>
</tr>
</thead>
<tbody>
<tr v-if="tableData.length === 0 && !isPreview">
<td :colspan="ColSpan">Veri bulunamadı</td>
</tr>
<tr
v-else
v-for="(dataRow, i) in tableData"
@click="LocalRowAction(dataRow)"
:class="[rowAction !== undefined && rowAction !== '' ? 'clickable' : '']">
<td v-if="rowNumber">
{{
showPagination && localPagination.pageNumber !== undefined
? (Number(localPagination.pageNumber) - 1) *
Number(localPagination.pageSize) +
i +
1
: i + 1
}}
</td>
<slot :name="'dataRow' + i" :rowData="dataRow" :rowIndex="i">
<template v-for="(dataCell, j) in tableHeader">
<td v-if="dataCell.computeHtml === undefined">
<slot
:name="'dataCell' + j"
:cellData="CellData(dataRow, dataCell.name)"
:cellIndex="j">
<div class="table-inner-content">
{{
dataCell.compute !== undefined
? dataCell.compute(dataRow)
: dataRow[dataCell.name]
}}
</div>
</slot>
</td>
<td v-else v-html="dataCell.computeHtml(dataRow)"></td>
</template>
</slot>
<td
v-if="rowActions !== undefined && rowActions.length > 0"
:class="[actionFixed ? 'action-fixed' : '']">
<template v-for="(action, ai) in rowActions">
<button
:class="[
'button-c button-icon button-export',
action.class !== undefined ? 'back-grad back-grad-' + action.class : ''
]"
@click="LocalRowDataAction($event, action.action, dataRow, i)">
<i class="ico-c" v-if="action.icon !== undefined">
<svg>
<use :href="icourl + '#' + action.icon"></use>
</svg>
</i>
<span class="panel-date">{{ action.text }}</span>
</button>
</template>
</td>
</tr>
<template
v-if="
localTotalValues !== undefined &&
Object.keys(localTotalValues).length > 0 &&
tableData.length > 0
">
<tr>
<td :colspan="totalValuesSpanLength" class="table-cell-align-right">
<strong>TOPLAM:</strong>
</td>
<template v-for="(val, i) in tableHeader">
<td v-if="localTotalValues[val.name] !== undefined">
{{
val.price
? globalStore.toTrLocale(localTotalValues[val.name])
: localTotalValues[val.name]
}}
</td>
</template>
</tr>
</template>
</tbody>
</table>
</div>
</div>
<data-table-pagination
v-if="pagination !== undefined && showPagination && !isPreview"
v-model:pagination="localPagination"
:isUseRoute="isUseRoute" />
</template>
<script setup lang="ts">
import { ref, reactive, computed, watch, onMounted, onUnmounted, nextTick } from 'vue'
import type { Ref } from 'vue'
import { useGlobalStore } from '@/stores/globalStore'
import icourl from '@/assets/images/icons.svg'
const globalStore = useGlobalStore()
import { useRouter, useRoute } from 'vue-router'
const router = useRouter()
const route = useRoute()
interface ITableHead {
[key: string]: any
title: string
name: string
compute?: Function
sort?: boolean
computeHtml?: Function
}
interface IPagination {
[key: string]: any
pageNumber?: number
pageSize: number
totalRecords: number
}
interface ISort {
[key: string]: any
sortColumn?: string
sortOrder?: string
}
export interface Props {
tableHeader: ITableHead[]
tableData: Record<string, any>[]
rowAction?: Function | string
pagination?: IPagination
sortData?: ISort
rowNumber?: boolean
totalValues?: Record<string, any>
isPreview?: boolean
rowActions?: Record<string, any>[]
actionFixed?: boolean
rowActionStyle?: string
isUseRoute?: boolean
}
const props = withDefaults(defineProps<Props>(), {
tableData: () => [],
rowNumber: false,
isPreview: false,
actionFixed: false,
isUseRoute: false
})
const emit = defineEmits(['update:sortData', 'update:pagination'])
const localSort: Ref<ISort> = ref(props.sortData as ISort)
const localPagination: Ref<IPagination> = ref(props.pagination as IPagination)
const localTotalValues = reactive<Record<string, any>>(
props.totalValues as Record<string, any>
)
const totalValuesSpanLength = ref<number>(0)
if (localTotalValues !== undefined) {
let totalValuesLength = Object.keys(localTotalValues).length
let headerLength: number =
props.rowNumber !== undefined
? props.tableHeader.length + 1
: props.tableHeader.length
totalValuesSpanLength.value = headerLength - totalValuesLength
}
const showPagination = computed<boolean>(() => {
if (props.pagination !== undefined) {
if (Math.ceil(localPagination.value.totalRecords / globalStore.perPage) > 1)
return true
else return false
} else {
return false
}
})
const ColSpan = computed<number>(() => {
var span = props.tableHeader.length
if (props.rowNumber) span++
if (props.rowActions) span++
return span
})
const LocalRowAction = (d: Record<string, any>) => {
if (props.rowAction !== undefined) {
;(props.rowAction! as Function)(d)
}
}
const LocalRowDataAction = (
e: Event,
action: Function | undefined,
dataRow: Record<string, any>,
i: number | string
) => {
if (action !== undefined) {
e.stopImmediatePropagation()
action(dataRow, i)
}
}
const SortColumn = (d: Record<string, any>) => {
if (d.sort && props.tableData.length > 1) {
let order = ''
if (localSort.value.sortColumn === d.name) {
if (localSort.value.sortOrder === '') order = 'desc'
if (localSort.value.sortOrder === 'desc') order = 'asc'
if (localSort.value.sortOrder === 'asc') {
order = ''
delete localSort.value.sortColumn
}
localSort.value.sortOrder = order
} else {
localSort.value.sortColumn = d.name
localSort.value.sortOrder = 'desc'
}
if (props.isUseRoute) {
router.push({
query: {
...route.query,
sortColumn: localSort.value.sortColumn,
sortOrder: localSort.value.sortOrder
}
})
}
emit('update:sortData', localSort.value)
}
}
const CellData = (d: Record<string, any>, key: string): any => {
if (d[key] === null) return d
else return d[key]
}
watch(
() => localPagination,
() => {
emit('update:pagination', localPagination.value)
},
{ deep: true }
)
watch(
() => props.tableData,
() => {
// Tablo verisi değiştiğinde scrollbar'ı güncelle
setTimeout(() => {
syncScrollbars()
}, 100)
},
{ deep: true }
)
const listWrapperRef = ref<HTMLElement | null>(null)
const scrollbarTopRef = ref<HTMLElement | null>(null)
const onScroll = (e: Event) => {
const target = e.target as HTMLElement
if (scrollbarTopRef.value) {
scrollbarTopRef.value.scrollLeft = target.scrollLeft
}
}
const syncScrollbars = () => {
if (listWrapperRef.value && scrollbarTopRef.value) {
const table = listWrapperRef.value.querySelector('table')
if (table) {
const tableWidth = table.scrollWidth
const wrapperWidth = listWrapperRef.value.clientWidth
if (tableWidth > wrapperWidth) {
scrollbarTopRef.value.style.display = 'block'
scrollbarTopRef.value.scrollLeft = listWrapperRef.value.scrollLeft
// Scrollbar wrapper'ın içeriğinin genişliğini tablo genişliğine eşitle
const scrollbarContent = scrollbarTopRef.value.querySelector('.scrollbar-content') as HTMLElement | null
if (scrollbarContent) {
scrollbarContent.style.width = tableWidth + 'px'
scrollbarContent.style.height = '1px'
}
} else {
scrollbarTopRef.value.style.display = 'none'
}
}
}
}
const onTopScroll = (e: Event) => {
const target = e.target as HTMLElement
if (listWrapperRef.value) {
listWrapperRef.value.scrollLeft = target.scrollLeft
}
}
onMounted(async () => {
await nextTick()
if (scrollbarTopRef.value) {
scrollbarTopRef.value.addEventListener('scroll', onTopScroll)
}
// İlk scrollbar senkronizasyonu
setTimeout(() => {
syncScrollbars()
}, 100)
const resizeObserver = new ResizeObserver(() => {
syncScrollbars()
})
if (listWrapperRef.value) {
resizeObserver.observe(listWrapperRef.value)
}
const tableObserver = new MutationObserver(() => {
syncScrollbars()
})
if (listWrapperRef.value) {
const table = listWrapperRef.value.querySelector('table')
if (table) {
tableObserver.observe(table, { childList: true, subtree: true, attributes: true })
}
}
})
onUnmounted(() => {
if (scrollbarTopRef.value) {
scrollbarTopRef.value.removeEventListener('scroll', onTopScroll)
}
})
</script>
<style scoped>
.action-fixed {
position: absolute;
z-index: 9;
top: 0;
right: 0;
}
.table-inner-content {
max-height: 400px;
overflow: hidden;
text-overflow: ellipsis;
-webkit-line-clamp: 20;
line-clamp: 20;
-webkit-box-orient: vertical;
display: -webkit-box;
}
</style>