hookehuyr

fix(bookDetail): 优化书籍详情页简介展开折叠检测逻辑

改用ResizeObserver替代原有的setTimeout与hasEllipsis检测方式,实现更精准的文本溢出判断;添加书籍简介内容变化监听,同步更新展开状态;在组件失活与卸载时正确清理观察者与动画帧计时器。
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
16 </div> 16 </div>
17 <div class="book-intro"> 17 <div class="book-intro">
18 <p class="book-post">{{ bookInfo.name }}</p> 18 <p class="book-post">{{ bookInfo.name }}</p>
19 - <div id="book-intro" :class="{ 'van-multi-ellipsis--l3': isToggle }" v-html="bookInfo.note"></div> 19 + <div id="book-intro" ref="bookIntroRef" :class="{ 'van-multi-ellipsis--l3': isToggle }" v-html="bookInfo.note"></div>
20 <template v-if="hasToggle"> 20 <template v-if="hasToggle">
21 <div v-if="isToggle" class="book-toggle-icon" @click="isToggle = false"> 21 <div v-if="isToggle" class="book-toggle-icon" @click="isToggle = false">
22 展开&nbsp; 22 展开&nbsp;
...@@ -121,9 +121,9 @@ ...@@ -121,9 +121,9 @@
121 </template> 121 </template>
122 122
123 <script setup> 123 <script setup>
124 -import { ref, onActivated, onMounted } from 'vue' 124 +import { ref, watch, nextTick, onActivated, onMounted, onDeactivated, onBeforeUnmount } from 'vue'
125 import { useRoute, useRouter, onBeforeRouteLeave } from 'vue-router' 125 import { useRoute, useRouter, onBeforeRouteLeave } from 'vue-router'
126 -import { _, storeToRefs, mainStore, showSuccessToast, hasEllipsis } from '@/utils/generatePackage' 126 +import { _, storeToRefs, mainStore, showSuccessToast } from '@/utils/generatePackage'
127 import { MyButton, VideoCard, NoticeOverlayModule, DonateFlower, ShortcutFixed } from '@/utils/generateModules' 127 import { MyButton, VideoCard, NoticeOverlayModule, DonateFlower, ShortcutFixed } from '@/utils/generateModules'
128 import { icon_video, icon_up, icon_down, icon_subscribed, icon_unsubscribe, no_image } from '@/utils/generateIcons' 128 import { icon_video, icon_up, icon_down, icon_subscribed, icon_unsubscribe, no_image } from '@/utils/generateIcons'
129 import { JSJ_FORM_MANDARIN, JSJ_FORM_LOCALISM } from '@/constant' 129 import { JSJ_FORM_MANDARIN, JSJ_FORM_LOCALISM } from '@/constant'
...@@ -157,13 +157,76 @@ const customStyle = ref({ ...@@ -157,13 +157,76 @@ const customStyle = ref({
157 }) 157 })
158 158
159 const donateInfo = ref({}) 159 const donateInfo = ref({})
160 +const bookIntroRef = ref(null)
161 +
162 +const hasToggle = ref(false); // 判断是否有展开文字,默认没有
163 +const isToggle = ref(true); // 判断展开状态,默认展开
164 +
165 +let introResizeObserver = null
166 +let introMeasureTimer = null
167 +
168 +const getIntroToggleStatus = () => {
169 + const el = bookIntroRef.value;
170 + if (!el || !el.clientWidth) {
171 + return false;
172 + }
173 +
174 + const clone = el.cloneNode(true);
175 + clone.removeAttribute('id');
176 + clone.style.position = 'fixed';
177 + clone.style.left = '-99999px';
178 + clone.style.top = '-99999px';
179 + clone.style.zIndex = '-1';
180 + clone.style.visibility = 'hidden';
181 + clone.style.pointerEvents = 'none';
182 + clone.style.height = 'auto';
183 + clone.style.maxHeight = 'none';
184 + clone.style.width = `${el.clientWidth}px`;
185 + clone.classList.add('van-multi-ellipsis--l3');
186 +
187 + document.body.appendChild(clone);
188 + const flag = clone.scrollHeight > clone.clientHeight;
189 + document.body.removeChild(clone);
190 +
191 + return flag;
192 +}
193 +
194 +const updateHasToggle = async () => {
195 + await nextTick();
196 + if (introMeasureTimer) {
197 + cancelAnimationFrame(introMeasureTimer);
198 + }
199 + introMeasureTimer = requestAnimationFrame(() => {
200 + hasToggle.value = getIntroToggleStatus();
201 + });
202 +}
203 +
204 +const bindIntroObserver = () => {
205 + if (!window.ResizeObserver || introResizeObserver || !bookIntroRef.value) return;
206 +
207 + introResizeObserver = new ResizeObserver(() => {
208 + updateHasToggle();
209 + });
210 + introResizeObserver.observe(bookIntroRef.value);
211 +}
212 +
213 +const clearIntroObserver = () => {
214 + if (introResizeObserver) {
215 + introResizeObserver.disconnect();
216 + introResizeObserver = null;
217 + }
218 +
219 + if (introMeasureTimer) {
220 + cancelAnimationFrame(introMeasureTimer);
221 + introMeasureTimer = null;
222 + }
223 +}
224 +
160 onMounted(async () => { 225 onMounted(async () => {
161 const { data } = await prepareDonateAPI(); 226 const { data } = await prepareDonateAPI();
162 donateInfo.value = data; 227 donateInfo.value = data;
163 - setTimeout(() => { 228 + bindIntroObserver();
164 - // 判断是否显示简介的展开图标 229 + updateHasToggle();
165 - hasToggle.value = hasEllipsis(`book-intro`);
166 - }, 500);
167 // TAG: 监听滚动到底部 230 // TAG: 监听滚动到底部
168 // 快捷访问组件高度动态调整 231 // 快捷访问组件高度动态调整
169 // window.onscroll = function () { 232 // window.onscroll = function () {
...@@ -202,9 +265,10 @@ window.onscroll = function () { ...@@ -202,9 +265,10 @@ window.onscroll = function () {
202 } 265 }
203 } 266 }
204 267
205 -// 判断是否显示简介的展开图标 268 +watch(() => bookInfo.value?.note, () => {
206 -const hasToggle = ref(false); // 判断是否有展开文字,默认没有 269 + isToggle.value = true;
207 -const isToggle = ref(true); // 判断展开状态,默认展开 270 + updateHasToggle();
271 +});
208 272
209 /** 273 /**
210 * 书籍订阅 274 * 书籍订阅
...@@ -327,6 +391,16 @@ onActivated(() => { // keepAlive 重置后执行回调 ...@@ -327,6 +391,16 @@ onActivated(() => { // keepAlive 重置后执行回调
327 // 更新是否实名认证和儿童信息 391 // 更新是否实名认证和儿童信息
328 const data = useDefaultPerf($route.query.id); 392 const data = useDefaultPerf($route.query.id);
329 userInfo = data.userInfo; 393 userInfo = data.userInfo;
394 + bindIntroObserver();
395 + updateHasToggle();
396 +});
397 +
398 +onDeactivated(() => {
399 + clearIntroObserver();
400 +});
401 +
402 +onBeforeUnmount(() => {
403 + clearIntroObserver();
330 }); 404 });
331 405
332 onBeforeRouteLeave(() => { 406 onBeforeRouteLeave(() => {
......