Files
web/app/components/datatable/ScrollBox.vue
2026-05-19 10:00:15 +07:00

193 lines
4.7 KiB
Vue

<template>
<div
:style="{
maxHeight,
overflowY: 'auto',
}"
>
<div
v-for="(v, i) in rows"
:key="i"
class=""
>
<button
class="button is-white rounded-none fs-14 font-normal w-full is-justify-content-space-between"
type="button"
@click="doClick(v, i)"
>
<span>{{ $stripHtml(v[name] || v.fullname || v.code || "n/a", 75) }}</span>
<span
v-if="checked[i] && notick !== true"
class="icon right-3"
>
<Icon
name="material-symbols:check-rounded"
:size="17"
class="has-text-primary"
/>
</span>
</button>
<p
class="control py-0"
v-if="show"
>
<span
class="icon-text has-text-grey mr-2 fs-13"
v-if="show.author"
>
<SvgIcon v-bind="{ name: 'user.svg', type: 'gray', size: 15 }"></SvgIcon>
<span>{{ v[show.author] }}</span>
</span>
<span
class="icon-text has-text-grey mr-2 fs-13"
v-if="show.view"
>
<SvgIcon v-bind="{ name: 'view.svg', type: 'gray', size: 15 }"></SvgIcon>
<span>{{ v[show.view] }}</span>
</span>
<span
class="fs-13 has-text-grey"
v-if="show.time"
>{{ $dayjs(v["create_time"]).fromNow(true) }}</span
>
<span class="tooltip">
<a
class="icon ml-1"
v-if="show.link"
@click="doClick(v, i, 'newtab')"
>
<SvgIcon v-bind="{ name: 'opennew.svg', type: 'gray', size: 15 }"></SvgIcon>
</a>
<span class="tooltiptext">Mở trong tab mớ</span>
</span>
<span
class="tooltip"
v-if="show.rename"
>
<a
class="icon ml-1"
@click="$emit('rename', v, i)"
>
<SvgIcon v-bind="{ name: 'pen1.svg', type: 'gray', size: 15 }"></SvgIcon>
</a>
<span class="tooltiptext">Đổi tên</span>
</span>
<span
class="tooltip"
v-if="show.rename"
>
<a
class="icon has-text-danger ml-1"
@click="$emit('remove', v, i)"
>
<SvgIcon v-bind="{ name: 'bin1.svg', type: 'gray', size: 15 }"></SvgIcon>
</a>
<span class="tooltiptext">Xóa</span>
</span>
</p>
</div>
</div>
</template>
<script setup>
const props = defineProps({
data: Array,
name: String,
maxHeight: String,
perpage: Number,
sort: String,
selects: String,
keyval: String,
show: Object,
notick: Boolean,
});
const emit = defineEmits(["selected"]);
const { $copy, $multiSort, $remove } = useNuxtApp();
const currentPage = ref(1);
const total = ref(props.data.length);
const rows = ref(props.data.slice(0, props.perpage));
const selected = ref([]);
const checked = ref({});
const array = ref([]);
getdata();
watch(
() => props.data,
() => getdata(),
{ deep: true },
);
watch(
() => props.selects,
(newVal) => {
console.log("props.selects changed", newVal);
getSelect();
},
{ deep: true },
);
function getdata() {
currentPage.value = 1;
array.value = $copy(props.data);
if (props.sort) {
const f = {};
if (props.show?.time) {
f.create_time = "desc";
}
if (props.sort.startsWith("-")) {
f[props.sort.slice(1)] = "desc";
} else {
f[props.sort] = "asc";
}
$multiSort(array.value, f);
}
rows.value = array.value.slice(0, props.perpage);
getSelect();
}
function getSelect() {
if (!props.selects) return;
selected.value = [];
checked.value = {};
props.selects.map((v) => {
const idx = rows.value.findIndex((x) => x[props.keyval ? props.keyval : props.name] === v);
if (idx >= 0) {
selected.value.push(rows.value[idx]);
checked.value[idx] = true;
}
});
}
function doClick(v, i, type) {
checked.value = { [i]: true };
const idx = selected.value.findIndex((x) => x.id === v.id);
idx >= 0 ? $remove(selected.value) : selected.value.push(v);
emit("selected", v, type);
}
function handleScroll(e) {
const bottom = e.target.scrollHeight - e.target.scrollTop - 5 < e.target.clientHeight;
if (bottom) {
if (total.value ? total.value > rows.value.length : true) {
currentPage.value += 1;
let arr = array.value.filter(
(_, index) => index >= (currentPage.value - 1) * props.perpage && index < currentPage.value * props.perpage,
);
rows.value = rows.value.concat(arr);
}
}
}
</script>
<style scoped>
.button.is-ghost {
color: var(--bulma-button-text-color);
}
.button.is-ghost:hover,
.button.is-ghost.is-hovered {
color: var(--bulma-button-ghost-hover-color);
}
</style>