400 lines
12 KiB
Vue
400 lines
12 KiB
Vue
<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>
|