2026-03-17 14:49:50 +08:00

826 lines
23 KiB
Vue
Raw Permalink 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>
<view class="content">
<view class="navbox">
<view class="status_bar"></view>
<uni-nav-bar
left-icon="left"
title="患者列表"
@clickLeft="goBack"
color="#8B2316"
:border="false"
backgroundColor="#eee"
>
<template #right>
<view class="nav-right">
<uni-icons type="search" size="24" color="#8B2316" @click="searchPatients"></uni-icons>
<uni-icons type="plusempty" size="24" color="#8B2316" @click="goCode" style="margin-left: 30rpx;"></uni-icons>
</view>
</template>
</uni-nav-bar>
</view>
<view class="patient-list">
<view class="listbox alertContent" v-if="showAlert">
<image :src="delImg" class="del-img" mode="widthFix" @click="hideAlert"></image>
<image :src="alertImg" class="alert-img" mode="widthFix"></image>
</view>
<view class="listbox patient-building" v-if="patientDataLoading && !loadFinish">
<text class="patient-building-text">正在整理患者数据请稍候...</text>
</view>
<view class="listbox" v-show="patientList.length > 0">
<scroll-view
class="groups-scroll"
scroll-y="true"
:scroll-into-view="scrollIntoViewId"
:scroll-with-animation="true"
@scroll="onGroupsScroll"
@scrolltolower="onGroupsScrollToLower"
:lower-threshold="120"
v-show="loadFinish"
>
<view class="special-actions">
<view class="action-item" @click="addNewPatient">
<view class="action-icon new-patient">
<up-image :src="newPatientImg" width="60rpx" height="60rpx"></up-image>
</view>
<text class="action-text">新的患者<text class="new-patient-count" v-if="applyList.length > 0">(待审核{{ applyList.length }})</text></text>
<uni-icons type="right" size="20" color="#999"></uni-icons>
</view>
<view class="action-item" @click="managePatientGroups">
<view class="action-icon group-icon">
<view class="grid-icon">
<view class="grid-item"></view>
<view class="grid-item"></view>
<view class="grid-item"></view>
<view class="grid-item"></view>
</view>
</view>
<text class="action-text">患者分组 <text class="new-patient-count" v-if="patientList.length > 0">(随访{{ patientListTotal }})</text></text>
<uni-icons type="right" size="20" color="#999"></uni-icons>
</view>
</view>
<view class="group-section" v-for="group in patientGroups" :key="group.letter" :id="`anchor-${group.letter}`">
<view class="group-header">{{ group.letter }}</view>
<view class="patient-item" v-for="item in group.items" :key="item.uuid || item.id" @longpress="onPatientItemLongPress(item)" @click="goPatientDetail(item.uuid)">
<image
v-if="hasPatientAvatar(item)"
class="patient-avatar"
:src="getPatientAvatar(item)"
mode="aspectFill"
lazy-load="true"
@error="handlePatientAvatarError(item)"
@click.stop="goPatientDetail(item.uuid)"
></image>
<view v-else class="patient-avatar avatar-placeholder"></view>
<view class="patient-info">
<text class="patient-name">{{ item.nickname || item.realName }}</text>
</view>
<view class="patient-status">
<view class="edit-icon" @click.stop="editPatient(item.uuid)">
<view class="edit-pen"></view>
</view>
<text class="follow-date">随访于{{ item.joinDateYMD }}</text>
</view>
</view>
</view>
<view class="load-more" v-if="patientListLoadingMore">
<text class="load-more-text">加载中...</text>
</view>
<view class="load-more" v-else-if="!patientListHasMore && patientList.length > 0">
<text class="load-more-text">没有更多了</text>
</view>
</scroll-view>
<view class="letter-index" v-if="loadFinish && indexList.length > 0">
<view class="letter-item" v-for="letter in indexList" :key="letter" :class="{ active: activeLetter === letter }" @click="onLetterTap(letter)">
{{ letter }}
</view>
</view>
</view>
<view class="listbox" v-show="patientList.length === 0 && !patientDataLoading">
<view class="special-actions">
<view class="action-item" @click="addNewPatient">
<view class="action-icon new-patient">
<up-image :src="newPatientImg" width="60rpx" height="60rpx"></up-image>
</view>
<text class="action-text">新的患者<text class="new-patient-count" v-if="applyList.length > 0">(待审核{{ applyList.length }}人)</text></text>
<uni-icons type="right" size="20" color="#999"></uni-icons>
</view>
<view class="action-item" @click="managePatientGroups">
<view class="action-icon group-icon">
<view class="grid-icon">
<view class="grid-item"></view>
<view class="grid-item"></view>
<view class="grid-item"></view>
<view class="grid-item"></view>
</view>
</view>
<text class="action-text">患者分组</text>
<uni-icons type="right" size="20" color="#999"></uni-icons>
</view>
</view>
<empty></empty>
</view>
</view>
<view class="tab-bar">
<view class="tab-item" @click="goMessageTab">
<text class="tab-text">患者消息</text>
</view>
<view class="tab-item active">
<text class="tab-text">患者列表</text>
<view class="tab-dot" v-if="hasNewPatient"><uni-badge class="uni-badge-left-margin" :text="applyList.length" :offset="[-3, -3]" size="small"></uni-badge></view>
</view>
<view class="tab-item" @click="goPlanTab">
<text class="tab-text">随访计划</text>
</view>
</view>
</view>
<unidialog :visible="visible" :content="message" @close="visible=false" @confirm="deleteFollowUpFromList"></unidialog>
</template>
<script setup>
import { computed, nextTick, ref, shallowRef } from 'vue';
import { onBackPress, onLoad, onShow } from '@dcloudio/uni-app';
import dayjs from 'dayjs';
import pinyin from 'pinyin';
import api from '@/api/api.js';
import navTo from '@/utils/navTo.js';
import docUrl from '@/utils/docUrl.js';
import unidialog from '@/components/dialog/dialog.vue';
import newPatientImg from '@/static/new_patient.png';
import delImg from '@/static/iv_delete.png';
import alertImg from '@/static/patientgif.png';
const patientList = ref([]);
const patientDataLoaded = ref(false);
const patientDataLoading = ref(false);
const loadFinish = ref(false);
const showAlert = ref(false);
const applyList = ref([]);
const visible = ref(false);
const message = ref('');
const selectedPatientUuid = ref('');
const selectedPatientName = ref('');
const firstLetterCache = new Map();
const LETTER_ORDER = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('');
const GROUP_BUILD_CHUNK_SIZE = 180;
const PERF_TRACE = true;
const SCROLL_LOG_MIN_INTERVAL = 180;
const activeLetter = ref('');
const scrollIntoViewId = ref('');
const groupScrollRanges = ref([]);
const indexList = ref([]);
const avatarErrorMap = ref({});
const patientGroups = shallowRef([]);
const allPatientGroups = shallowRef([]);
const lastScrollTop = ref(0);
const lastScrollLogAt = ref(0);
const buildStartedAt = ref(0);
const fetchStartedAt = ref(0);
const patientListPage = ref(1);
const patientListPageSize = ref(300);
const patientListHasMore = ref(true);
const patientListLoadingMore = ref(false);
const canTriggerLowerLoad = ref(true);
const lastLowerTriggerTop = ref(0);
const LOWER_RESET_DISTANCE = 120;
const patientListTotal = ref(0);
const hasNewPatient = computed(() => applyList.value.length > 0);
const perfLog = (...args) => {
if (!PERF_TRACE) return;
console.log('[patientList/perf]', ...args);
};
const sortLetter = (a, b) => {
if (a === '#') return 1;
if (b === '#') return -1;
const ai = LETTER_ORDER.indexOf(a);
const bi = LETTER_ORDER.indexOf(b);
if (ai === -1 && bi === -1) return String(a).localeCompare(String(b));
if (ai === -1) return 1;
if (bi === -1) return -1;
return ai - bi;
};
const formatYMD = (input) => {
if (!input) return '';
const d = dayjs(input);
return d.isValid() ? d.format('YYYY-MM-DD') : '';
};
const getFirstLetter = (name = '') => {
const firstChar = String(name).trim().charAt(0);
if (!firstChar) return '#';
if (/^[A-Za-z]$/.test(firstChar)) return firstChar.toUpperCase();
if (firstLetterCache.has(firstChar)) return firstLetterCache.get(firstChar);
try {
const py = pinyin(firstChar, { style: pinyin.STYLE_NORMAL })?.[0]?.[0] || '';
const letter = py ? py.charAt(0).toUpperCase() : '#';
firstLetterCache.set(firstChar, letter);
return letter;
} catch (error) {
return '#';
}
};
const getAvatarKey = (item = {}) => String(item.uuid || item.id || item.realName || item.nickname || '');
const normalizeAvatarUrl = (photo) => {
const raw = String(photo || '').trim();
if (!raw) return '';
if (/^https?:\/\//i.test(raw)) return raw;
return `${docUrl}${raw}`;
};
const hasPatientAvatar = (item = {}) => {
const key = getAvatarKey(item);
if (!item.avatarUrl) return false;
if (key && avatarErrorMap.value[key]) return false;
return true;
};
const getPatientAvatar = (item = {}) => {
return item.avatarUrl || '';
};
const handlePatientAvatarError = (item) => {
if (!item) return;
const key = getAvatarKey(item);
if (!key) return;
avatarErrorMap.value = { ...avatarErrorMap.value, [key]: true };
};
const rebuildIndexList = () => {
indexList.value = allPatientGroups.value
.map(g => String(g?.letter || '').toUpperCase())
.filter((letter, index, arr) => (letter === '#' || /^[A-Z]$/.test(letter)) && arr.indexOf(letter) === index)
.sort(sortLetter);
activeLetter.value = indexList.value[0] || '';
};
const rpxToPx = (rpx) => {
const { screenWidth } = uni.getSystemInfoSync();
return Math.round((screenWidth / 750) * rpx);
};
const buildGroupScrollRanges = () => {
const actionHeight = rpxToPx(120);
const headerHeight = rpxToPx(80);
const itemHeight = rpxToPx(150);
let cursor = actionHeight * 2;
groupScrollRanges.value = patientGroups.value.map(group => {
const groupHeight = headerHeight + (group.items?.length || 0) * itemHeight;
const range = { letter: group.letter, start: cursor, end: cursor + groupHeight };
cursor += groupHeight;
return range;
});
};
const onGroupsScroll = (e) => {
const top = Number(e?.detail?.scrollTop || 0);
if (!canTriggerLowerLoad.value && top < lastLowerTriggerTop.value - LOWER_RESET_DISTANCE) {
canTriggerLowerLoad.value = true;
}
if (!groupScrollRanges.value.length) return;
let current = groupScrollRanges.value[0].letter;
for (let i = 0; i < groupScrollRanges.value.length; i++) {
const range = groupScrollRanges.value[i];
if (top >= range.start) {
current = range.letter;
} else {
break;
}
}
activeLetter.value = current;
const now = Date.now();
if (now - lastScrollLogAt.value >= SCROLL_LOG_MIN_INTERVAL) {
perfLog('scroll', {
top,
deltaTop: top - lastScrollTop.value,
activeLetter: current,
groupCount: patientGroups.value.length,
patientCount: patientList.value.length
});
lastScrollLogAt.value = now;
lastScrollTop.value = top;
}
};
const onLetterTap = (letter) => {
if (!letter) return;
activeLetter.value = letter;
const targetId = `anchor-${letter}`;
if (scrollIntoViewId.value === targetId) {
scrollIntoViewId.value = '';
nextTick(() => {
scrollIntoViewId.value = targetId;
});
return;
}
scrollIntoViewId.value = targetId;
};
const waitForNextFrame = () => new Promise(resolve => setTimeout(resolve, 0));
const buildGroupsFromPatients = async () => {
buildStartedAt.value = Date.now();
perfLog('build:start', { count: patientList.value.length });
avatarErrorMap.value = {};
const map = new Map();
const source = patientList.value || [];
for (let i = 0; i < source.length; i++) {
const p = source[i] || {};
const normalized = { ...p, avatarUrl: normalizeAvatarUrl(p.photo), joinDateYMD: formatYMD(p.join_date) };
const first = getFirstLetter(normalized.nickname || normalized.realName);
const letter = /^[A-Z]$/.test(first) ? first : '#';
if (!map.has(letter)) map.set(letter, []);
map.get(letter).push(normalized);
if (i > 0 && i % GROUP_BUILD_CHUNK_SIZE === 0) {
await waitForNextFrame();
}
}
const letters = Array.from(map.keys()).sort(sortLetter);
allPatientGroups.value = letters.map(l => ({ letter: l, items: map.get(l) }));
patientGroups.value = allPatientGroups.value;
rebuildIndexList();
buildGroupScrollRanges();
perfLog('build:done', {
costMs: Date.now() - buildStartedAt.value,
groupCount: patientGroups.value.length,
patientCount: patientList.value.length
});
};
const getApplyList = async () => {
try {
const res = await api.applyList();
if (res && res.code === 200) applyList.value = res.data || [];
} catch (error) {}
};
const isApiSuccess = (res) => Number(res?.code) === 1 || Number(res?.code) === 200;
const parsePagedPatients = (res) => {
const data = res?.data;
if (Array.isArray(data)) {
return { list: data, total: undefined, pageNum: undefined, pages: undefined, isLastPage: undefined };
}
const list =
(Array.isArray(data?.list) && data.list) ||
(Array.isArray(data?.records) && data.records) ||
(Array.isArray(data?.rows) && data.rows) ||
(Array.isArray(data?.data) && data.data) ||
[];
const total = Number(
data?.total ??
data?.count ??
data?.totalCount ??
undefined
);
const pageNum = Number(data?.pageNum ?? data?.page ?? data?.current ?? undefined);
const pages = Number(data?.pages ?? data?.pageCount ?? undefined);
const isLastPage =
typeof data?.isLastPage === 'boolean'
? data.isLastPage
: undefined;
return {
list,
total: Number.isFinite(total) ? total : undefined
,
pageNum: Number.isFinite(pageNum) ? pageNum : undefined,
pages: Number.isFinite(pages) ? pages : undefined,
isLastPage
};
};
const mergePatientListUnique = (base = [], incoming = []) => {
if (!incoming.length) return base;
const seen = new Set();
const merged = [];
const pushUnique = (item) => {
const key = String(item?.uuid || item?.id || item?.mobile || '');
if (!key) {
merged.push(item);
return;
}
if (seen.has(key)) return;
seen.add(key);
merged.push(item);
};
for (let i = 0; i < base.length; i++) pushUnique(base[i]);
for (let i = 0; i < incoming.length; i++) pushUnique(incoming[i]);
return merged;
};
const applyPatientListData = async (list, total) => {
patientList.value = list || [];
patientListTotal.value = total || 0;
await buildGroupsFromPatients();
loadFinish.value = true;
patientDataLoaded.value = true;
if (patientListTotal.value > 1000) {
const hasAlert = uni.getStorageSync('hasAlert');
showAlert.value = !hasAlert;
}
};
const patientListByGBKPage = async ({ showLoading = true, reset = false } = {}) => {
if (patientDataLoading.value || patientListLoadingMore.value) return;
if (!reset && !patientListHasMore.value) return;
if (reset) {
patientDataLoading.value = true;
loadFinish.value = false;
patientListPage.value = 1;
patientListHasMore.value = true;
canTriggerLowerLoad.value = true;
lastLowerTriggerTop.value = 0;
} else {
patientListLoadingMore.value = true;
}
fetchStartedAt.value = Date.now();
perfLog('api:start', { showLoading, reset, page: patientListPage.value, pageSize: patientListPageSize.value });
if (showLoading) uni.showLoading({ title: '加载中...', mask: true });
try {
const requestPage = patientListPage.value;
const res = await api.patientListByGBKPage({
page: requestPage,
pageSize: patientListPageSize.value,
});
if (isApiSuccess(res)) {
const { list, total, pageNum, pages, isLastPage } = parsePagedPatients(res);
const merged = reset ? list : mergePatientListUnique(patientList.value, list);
await applyPatientListData(merged, total);
if (typeof isLastPage === 'boolean') {
patientListHasMore.value = !isLastPage;
} else if (typeof pages === 'number' && typeof pageNum === 'number') {
patientListHasMore.value = pageNum < pages;
} else if (typeof total === 'number') {
patientListHasMore.value = merged.length < total;
} else {
patientListHasMore.value = list.length >= patientListPageSize.value;
}
if (patientListHasMore.value) {
if (typeof pageNum === 'number') {
patientListPage.value = pageNum + 1;
} else {
patientListPage.value = requestPage + 1;
}
}
perfLog('api:done', {
totalMs: Date.now() - fetchStartedAt.value,
requestPage,
page: typeof pageNum === 'number' ? pageNum : (reset ? 1 : patientListPage.value - 1),
pageDataCount: list.length,
mergedCount: merged.length,
hasMore: patientListHasMore.value
});
} else {
perfLog('api:bad-code', { code: res.code });
}
} catch (error) {
perfLog('api:error', error?.message || error);
throw error;
} finally {
patientDataLoading.value = false;
patientListLoadingMore.value = false;
if (showLoading) uni.hideLoading();
}
};
const onGroupsScrollToLower = () => {
void patientListByGBKPage({ showLoading: false, reset: false });
};
const goPatientDetail = (uuid) => {
navTo({ url: `/pages_app/patientDetail/patientDetail?uuid=${uuid}` });
};
const editPatient = (uuid) => {
navTo({ url: `/pages_app/patientSetting/patientSetting?uuid=${uuid}` });
};
const searchPatients = () => {
navTo({ url: '/pages_app/searchPatient/searchPatient' });
};
const addNewPatient = () => {
navTo({ url: '/pages_app/myPatient/myPatient?from=patientMsg' });
};
const managePatientGroups = () => {
navTo({ url: '/pages_app/patientGroup/patientGroup?from=patientMsg' });
};
const goCode = () => {
navTo({ url: '/pages_app/myCode/myCode' });
};
const hideAlert = () => {
showAlert.value = false;
uni.setStorageSync('hasAlert', true);
};
const onPatientItemLongPress = (item) => {
if (!item?.uuid) return;
selectedPatientUuid.value = item.uuid;
selectedPatientName.value = item.nickname || item.realName;
message.value = `确认解除[${selectedPatientName.value || '该患者'}]的随访关系?`;
visible.value = true;
};
const deleteFollowUpFromList = async () => {
if (!selectedPatientUuid.value) {
visible.value = false;
return;
}
const res = await api.cancelRes({ patientUuid: selectedPatientUuid.value });
if (isApiSuccess(res)) {
uni.showToast({ title: '解除成功', icon: 'none' });
await patientListByGBKPage({ showLoading: true, reset: true });
}
visible.value = false;
};
const goBack = () => {
plus.runtime.quit();
};
const goMessageTab = () => {
navTo({ url: '/pages_app/patientMsg/patientMsg' });
};
const goPlanTab = () => {
navTo({ url: '/pages_app/patientMsg/patientPlan' });
};
onBackPress(() => {
plus.runtime.quit();
return true;
});
onLoad(async () => {
perfLog('lifecycle:onLoad');
getApplyList();
void patientListByGBKPage({ showLoading: true, reset: true });
});
onShow(async () => {
perfLog('lifecycle:onShow', { patientDataLoaded: patientDataLoaded.value });
getApplyList();
if (!patientDataLoaded.value) {
void patientListByGBKPage({ showLoading: true, reset: true });
}
});
</script>
<style lang="scss" scoped>
.content {
background-color: #f5f5f5;
height: 100vh;
overflow-y: hidden;
}
.nav-right {
display: flex;
align-items: center;
}
.tab-bar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
height: 100rpx;
background-color: #f8f8f8;
display: flex;
align-items: center;
border-top: 1rpx solid #e0e0e0;
z-index: 999;
}
.tab-item {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
position: relative;
height: 100%;
}
.tab-item:nth-child(2) {
border: 2rpx solid #ccc;
border-top: none;
border-bottom: none;
}
.tab-item .tab-text {
font-size: 32rpx;
color: #999;
}
.tab-item.active .tab-text {
color: #8B2316;
font-weight: 500;
}
.tab-dot {
position: absolute;
top: 18rpx;
right: 28rpx;
}
.patient-list {
width: 100%;
margin-top: calc(var(--status-bar-height) + 44px);
height: calc(100vh - var(--status-bar-height) - 44px - 103rpx);
overflow: hidden;
background-color: #fff;
display: flex;
flex-direction: column;
}
.listbox {
flex: 1;
min-height: 0;
}
.patient-building {
display: flex;
align-items: center;
justify-content: center;
}
.patient-building-text {
font-size: 28rpx;
color: #999;
}
.groups-scroll {
height: 100%;
}
.load-more {
display: flex;
align-items: center;
justify-content: center;
height: 80rpx;
}
.load-more-text {
font-size: 24rpx;
color: #999;
}
.special-actions {
background-color: #fff;
border-bottom: 1rpx solid #f0f0f0;
}
.action-item {
display: flex;
align-items: center;
padding: 30rpx;
border-bottom: 1rpx solid #f0f0f0;
}
.action-icon {
width: 60rpx;
height: 60rpx;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin-right: 20rpx;
}
.new-patient,
.group-icon {
background-color: #8B2316;
}
.grid-icon {
display: grid;
grid-template-columns: 1fr 1fr;
grid-template-rows: 1fr 1fr;
gap: 2rpx;
width: 32rpx;
height: 32rpx;
}
.grid-item {
background-color: #fff;
border-radius: 2rpx;
}
.action-text {
flex: 1;
font-size: 32rpx;
color: #333;
}
.new-patient-count {
color: red;
font-size: 32rpx;
margin-left: 10rpx;
}
.group-header {
padding: 20rpx 30rpx;
background-color: #e4e4e4;
font-size: 36rpx;
color: #666;
}
.patient-item {
display: flex;
align-items: center;
padding: 30rpx 80rpx 20rpx 30rpx;
border-bottom: 1rpx solid #f0f0f0;
background-color: #fff;
}
.patient-avatar {
width: 90rpx;
height: 90rpx;
border-radius: 10rpx;
margin-right: 20rpx;
}
.avatar-placeholder {
position: relative;
background: #e0e0e0;
border-radius: 50%;
}
.avatar-placeholder::before {
content: '';
position: absolute;
width: 28rpx;
height: 28rpx;
border-radius: 50%;
background: #ffffff;
top: 16rpx;
left: 31rpx;
}
.avatar-placeholder::after {
content: '';
position: absolute;
width: 56rpx;
height: 30rpx;
border-radius: 28rpx 28rpx 12rpx 12rpx;
background: #ffffff;
left: 17rpx;
bottom: 14rpx;
}
.patient-info {
flex: 1;
}
.patient-name {
font-size: 32rpx;
color: #333;
font-weight: 500;
}
.patient-status {
display: flex;
align-items: flex-end;
flex-direction: column;
}
.edit-icon {
width: 40rpx;
height: 40rpx;
display: flex;
align-items: center;
justify-content: center;
}
.edit-pen {
position: relative;
width: 24rpx;
height: 6rpx;
background: #8B2316;
border-radius: 3rpx;
transform: rotate(-35deg);
}
.edit-pen::before {
content: '';
position: absolute;
left: -8rpx;
top: 0;
width: 0;
height: 0;
border-top: 3rpx solid transparent;
border-bottom: 3rpx solid transparent;
border-right: 8rpx solid #8B2316;
}
.edit-pen::after {
content: '';
position: absolute;
right: -5rpx;
top: 0;
width: 5rpx;
height: 6rpx;
background: #d9d9d9;
border-radius: 0 3rpx 3rpx 0;
}
.follow-date {
margin-top: 8rpx;
font-size: 24rpx;
color: #666;
}
.letter-index {
position: fixed;
right: 10rpx;
top: 50%;
transform: translateY(-50%);
background-color: rgba(255, 255, 255, 0.9);
border-radius: 20rpx;
padding: 10rpx 5rpx;
z-index: 999;
}
.letter-item {
padding: 8rpx 12rpx;
font-size: 24rpx;
color: #666;
text-align: center;
border-radius: 10rpx;
}
.letter-item.active {
color: #8B2316;
font-weight: 600;
}
.alertContent {
z-index: 99;
position: fixed;
bottom: 100rpx;
width: 100%;
top: calc(var(--status-bar-height) + 44px);
pointer-events: none;
}
.alert-img {
width: 100%;
}
.del-img {
position: absolute;
top: 280rpx;
z-index: 999;
right: 100rpx;
width: 48rpx;
height: 48rpx;
pointer-events: auto;
}
</style>