fix: refine dark mode and table scrolling
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onBeforeUnmount, onMounted, reactive, ref } from "vue";
|
||||
import { computed, nextTick, onBeforeUnmount, onMounted, reactive, ref } from "vue";
|
||||
import { ElMessage } from "element-plus";
|
||||
import apiClient, { getApiErrorMessage } from "@/api/client";
|
||||
import { useViewport } from "@/composables/useViewport";
|
||||
@@ -37,6 +37,10 @@ const rooms = ref<LiveRoom[]>([]);
|
||||
const selectedRooms = ref<LiveRoom[]>([]);
|
||||
const loadError = ref("");
|
||||
const { isMobile } = useViewport();
|
||||
const roomsTableShellRef = ref<HTMLElement | null>(null);
|
||||
const roomsTableProxyRef = ref<HTMLElement | null>(null);
|
||||
const showRoomsTableProxyScroll = ref(false);
|
||||
const roomsTableProxyInnerWidth = ref(0);
|
||||
|
||||
const createForm = reactive({
|
||||
url: "",
|
||||
@@ -106,6 +110,142 @@ const recordDialogWidth = computed(() => (isMobile.value ? "min(100vw - 24px, 44
|
||||
const settingsDialogWidth = computed(() => (isMobile.value ? "min(100vw - 24px, 760px)" : "840px"));
|
||||
const deleteDialogWidth = computed(() => (isMobile.value ? "min(100vw - 24px, 560px)" : "clamp(480px, 46vw, 560px)"));
|
||||
let autoRefreshTimer: number | null = null;
|
||||
let roomsTableScrollWrap: HTMLElement | null = null;
|
||||
let roomsTableResizeObserver: ResizeObserver | null = null;
|
||||
let observedRoomsTableShell: HTMLElement | null = null;
|
||||
let observedRoomsTableWrap: HTMLElement | null = null;
|
||||
let syncingRoomsTableProxy = false;
|
||||
let syncingRoomsTableBody = false;
|
||||
|
||||
function cleanupRoomsTableScrollSync() {
|
||||
if (roomsTableScrollWrap) {
|
||||
roomsTableScrollWrap.removeEventListener("scroll", handleRoomsTableBodyScroll);
|
||||
roomsTableScrollWrap = null;
|
||||
}
|
||||
|
||||
roomsTableResizeObserver?.disconnect();
|
||||
roomsTableResizeObserver = null;
|
||||
observedRoomsTableShell = null;
|
||||
observedRoomsTableWrap = null;
|
||||
}
|
||||
|
||||
function getRoomsTableScrollWrap() {
|
||||
const shell = roomsTableShellRef.value;
|
||||
|
||||
if (!shell) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return shell.querySelector<HTMLElement>(".el-table__body-wrapper .el-scrollbar__wrap") ??
|
||||
shell.querySelector<HTMLElement>(".el-scrollbar__wrap") ??
|
||||
shell.querySelector<HTMLElement>(".el-table__body-wrapper");
|
||||
}
|
||||
|
||||
function handleRoomsTableBodyScroll() {
|
||||
if (syncingRoomsTableProxy) {
|
||||
return;
|
||||
}
|
||||
|
||||
const proxy = roomsTableProxyRef.value;
|
||||
|
||||
if (!proxy || !roomsTableScrollWrap) {
|
||||
return;
|
||||
}
|
||||
|
||||
syncingRoomsTableBody = true;
|
||||
proxy.scrollLeft = roomsTableScrollWrap.scrollLeft;
|
||||
window.requestAnimationFrame(() => {
|
||||
syncingRoomsTableBody = false;
|
||||
});
|
||||
}
|
||||
|
||||
function handleRoomsTableProxyScroll() {
|
||||
if (syncingRoomsTableBody || !roomsTableScrollWrap) {
|
||||
return;
|
||||
}
|
||||
|
||||
const proxy = roomsTableProxyRef.value;
|
||||
|
||||
if (!proxy) {
|
||||
return;
|
||||
}
|
||||
|
||||
syncingRoomsTableProxy = true;
|
||||
roomsTableScrollWrap.scrollLeft = proxy.scrollLeft;
|
||||
window.requestAnimationFrame(() => {
|
||||
syncingRoomsTableProxy = false;
|
||||
});
|
||||
}
|
||||
|
||||
function ensureRoomsTableResizeObserver() {
|
||||
if (typeof ResizeObserver === "undefined") {
|
||||
return;
|
||||
}
|
||||
|
||||
const shell = roomsTableShellRef.value;
|
||||
const wrap = roomsTableScrollWrap;
|
||||
|
||||
if (roomsTableResizeObserver && observedRoomsTableShell === shell && observedRoomsTableWrap === wrap) {
|
||||
return;
|
||||
}
|
||||
|
||||
roomsTableResizeObserver?.disconnect();
|
||||
roomsTableResizeObserver = new ResizeObserver(() => {
|
||||
void syncRoomsTableProxyScroll();
|
||||
});
|
||||
|
||||
if (shell) {
|
||||
roomsTableResizeObserver.observe(shell);
|
||||
}
|
||||
|
||||
if (wrap) {
|
||||
roomsTableResizeObserver.observe(wrap);
|
||||
}
|
||||
|
||||
observedRoomsTableShell = shell;
|
||||
observedRoomsTableWrap = wrap;
|
||||
}
|
||||
|
||||
async function syncRoomsTableProxyScroll() {
|
||||
await nextTick();
|
||||
|
||||
if (isMobile.value) {
|
||||
showRoomsTableProxyScroll.value = false;
|
||||
roomsTableProxyInnerWidth.value = 0;
|
||||
cleanupRoomsTableScrollSync();
|
||||
return;
|
||||
}
|
||||
|
||||
const wrap = getRoomsTableScrollWrap();
|
||||
|
||||
if (roomsTableScrollWrap !== wrap) {
|
||||
roomsTableScrollWrap?.removeEventListener("scroll", handleRoomsTableBodyScroll);
|
||||
roomsTableScrollWrap = wrap;
|
||||
roomsTableScrollWrap?.addEventListener("scroll", handleRoomsTableBodyScroll, { passive: true });
|
||||
}
|
||||
|
||||
const proxy = roomsTableProxyRef.value;
|
||||
|
||||
if (!wrap) {
|
||||
showRoomsTableProxyScroll.value = false;
|
||||
roomsTableProxyInnerWidth.value = 0;
|
||||
ensureRoomsTableResizeObserver();
|
||||
return;
|
||||
}
|
||||
|
||||
const scrollWidth = Math.max(wrap.scrollWidth, wrap.firstElementChild?.scrollWidth ?? 0);
|
||||
const clientWidth = wrap.clientWidth;
|
||||
const canScrollHorizontally = scrollWidth > clientWidth + 1;
|
||||
|
||||
roomsTableProxyInnerWidth.value = scrollWidth;
|
||||
showRoomsTableProxyScroll.value = canScrollHorizontally;
|
||||
|
||||
if (canScrollHorizontally && proxy && Math.abs(proxy.scrollLeft - wrap.scrollLeft) > 1) {
|
||||
proxy.scrollLeft = wrap.scrollLeft;
|
||||
}
|
||||
|
||||
ensureRoomsTableResizeObserver();
|
||||
}
|
||||
|
||||
async function loadRooms() {
|
||||
loading.value = true;
|
||||
@@ -114,6 +254,7 @@ async function loadRooms() {
|
||||
try {
|
||||
const { data } = await apiClient.get<LiveRoom[]>("/live-rooms");
|
||||
rooms.value = data;
|
||||
void syncRoomsTableProxyScroll();
|
||||
} catch (error) {
|
||||
loadError.value = getApiErrorMessage(error, "直播间列表加载失败,请稍后重试。");
|
||||
} finally {
|
||||
@@ -148,6 +289,7 @@ async function autoRefreshRooms() {
|
||||
const { data } = await apiClient.get<LiveRoom[]>("/live-rooms");
|
||||
rooms.value = data;
|
||||
loadError.value = "";
|
||||
void syncRoomsTableProxyScroll();
|
||||
} catch {
|
||||
// Keep the current view stable; the background poller will try again.
|
||||
}
|
||||
@@ -594,13 +736,17 @@ function nullableStringFromSelect(value: string | number) {
|
||||
|
||||
onMounted(async () => {
|
||||
await loadRooms();
|
||||
void syncRoomsTableProxyScroll();
|
||||
startAutoRefresh();
|
||||
document.addEventListener("visibilitychange", handleVisibilityChange);
|
||||
window.addEventListener("resize", syncRoomsTableProxyScroll);
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
stopAutoRefresh();
|
||||
document.removeEventListener("visibilitychange", handleVisibilityChange);
|
||||
window.removeEventListener("resize", syncRoomsTableProxyScroll);
|
||||
cleanupRoomsTableScrollSync();
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -745,7 +891,16 @@ onBeforeUnmount(() => {
|
||||
</article>
|
||||
</div>
|
||||
|
||||
<div v-else class="table-scroll-shell rooms-table-shell">
|
||||
<div v-else ref="roomsTableShellRef" class="table-scroll-shell rooms-table-shell">
|
||||
<div
|
||||
v-if="showRoomsTableProxyScroll"
|
||||
ref="roomsTableProxyRef"
|
||||
class="rooms-table-proxy-scroll"
|
||||
@scroll.passive="handleRoomsTableProxyScroll"
|
||||
>
|
||||
<div class="rooms-table-proxy-scroll__inner" :style="{ width: `${roomsTableProxyInnerWidth}px` }"></div>
|
||||
</div>
|
||||
|
||||
<el-table
|
||||
:data="rooms"
|
||||
v-loading="loading"
|
||||
@@ -1246,8 +1401,23 @@ onBeforeUnmount(() => {
|
||||
}
|
||||
|
||||
.rooms-table-shell {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.rooms-table-proxy-scroll {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 5;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
margin-bottom: 10px;
|
||||
padding-bottom: 6px;
|
||||
background: var(--surface);
|
||||
scrollbar-width: thin;
|
||||
}
|
||||
|
||||
.rooms-table-proxy-scroll__inner {
|
||||
height: 1px;
|
||||
}
|
||||
|
||||
.rooms-table {
|
||||
|
||||
Reference in New Issue
Block a user