hookehuyr

test(rich-text): 完善 v-html 测试页面功能

- 添加 HTML 实体解析( , &, <, >, ")
- 实现 a 标签替换为 div + data-href 属性
- 添加 PDF 文件链接点击处理(useFileOperation)
- 新增 transformElement 图片处理测试开关
- 清理所有调试 console.log 语句

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
...@@ -50,6 +50,9 @@ ...@@ -50,6 +50,9 @@
50 <view class="action-btn success" @tap="loadFromAPI">模拟API加载</view> 50 <view class="action-btn success" @tap="loadFromAPI">模拟API加载</view>
51 <view class="action-btn danger" @tap="loadRealApiData">📄 真实API</view> 51 <view class="action-btn danger" @tap="loadRealApiData">📄 真实API</view>
52 <view class="action-btn info" @tap="loadRealApiDataWithDiv">📄 真实API(a→div)</view> 52 <view class="action-btn info" @tap="loadRealApiDataWithDiv">📄 真实API(a→div)</view>
53 + <view class="action-btn" :class="transformEnabled ? 'enabled' : ''" @tap="toggleTransform">
54 + {{ transformEnabled ? '✅' : '⚪' }} 图片自动处理
55 + </view>
53 </view> 56 </view>
54 57
55 <!-- 对比说明 --> 58 <!-- 对比说明 -->
...@@ -88,6 +91,7 @@ const switchCount = ref(0) ...@@ -88,6 +91,7 @@ const switchCount = ref(0)
88 const currentContentType = ref('未加载') 91 const currentContentType = ref('未加载')
89 const contentLength = ref(0) 92 const contentLength = ref(0)
90 const lastAction = ref('页面初始化') 93 const lastAction = ref('页面初始化')
94 +const transformEnabled = ref(false) // 是否启用 transformElement
91 95
92 // 测试 HTML 内容 96 // 测试 HTML 内容
93 const testHtml = ref('') 97 const testHtml = ref('')
...@@ -306,6 +310,89 @@ const loadRealApiData = () => { ...@@ -306,6 +310,89 @@ const loadRealApiData = () => {
306 }, 500) 310 }, 500)
307 } 311 }
308 312
313 +// 切换 transformElement
314 +const toggleTransform = () => {
315 + transformEnabled.value = !transformEnabled.value
316 +
317 + if (transformEnabled.value) {
318 + // 启用 transformElement
319 + Taro.options.html.transformElement = (el) => {
320 + const nodeName = el.nodeName?.toLowerCase() || ''
321 + const tagName = el.tagName?.toLowerCase() || ''
322 +
323 + // 处理 img/image 标签
324 + if (nodeName === 'img' || tagName === 'img' || nodeName === 'image' || tagName === 'image') {
325 + const src = el.getAttribute('src') || ''
326 +
327 + // 检查父元素链
328 + let parent = el.parentElement
329 + let isInsideLink = false
330 + let parentChain = []
331 +
332 + while (parent) {
333 + const pName = parent.nodeName?.toLowerCase() || ''
334 + const pDataIsLink = parent.getAttribute?.('data-is-link')
335 + const pClassList = parent.classList?.value || ''
336 +
337 + parentChain.push(pName)
338 +
339 + if (pName === 'a' || pDataIsLink === 'true' || pClassList.includes('_file_list')) {
340 + isInsideLink = true
341 + break
342 + }
343 +
344 + parent = parent.parentElement
345 + }
346 +
347 + if (isInsideLink) {
348 + // 在链接内,不处理(图标),但必须返回元素让它显示
349 + return el // 直接返回,不做任何修改
350 + }
351 +
352 + // 不在链接内,处理(内容图片)
353 + el.setAttribute('mode', 'widthFix')
354 + el.setAttribute('style', 'width: 100%;')
355 + }
356 +
357 + // 所有元素都必须返回,否则会被过滤掉不显示
358 + return el
359 + }
360 +
361 + lastAction.value = '已启用 transformElement(外部图片自动 widthFix)'
362 + Taro.showToast({
363 + title: '已启用图片自动处理',
364 + icon: 'success'
365 + })
366 +
367 + // 重新加载当前内容以应用 transform
368 + if (testHtml.value) {
369 + const currentContent = testHtml.value
370 + testHtml.value = ''
371 + nextTick(() => {
372 + testHtml.value = currentContent
373 + })
374 + }
375 + } else {
376 + // 禁用 transformElement
377 + Taro.options.html.transformElement = undefined
378 +
379 + lastAction.value = '已禁用 transformElement'
380 + Taro.showToast({
381 + title: '已禁用图片自动处理',
382 + icon: 'none'
383 + })
384 +
385 + // 重新加载当前内容
386 + if (testHtml.value) {
387 + const currentContent = testHtml.value
388 + testHtml.value = ''
389 + nextTick(() => {
390 + testHtml.value = currentContent
391 + })
392 + }
393 + }
394 +}
395 +
309 // 加载真实 API 数据(<a> 替换为 <div> 版本) 396 // 加载真实 API 数据(<a> 替换为 <div> 版本)
310 const loadRealApiDataWithDiv = () => { 397 const loadRealApiDataWithDiv = () => {
311 lastAction.value = '加载真实 API 数据(a→div)...' 398 lastAction.value = '加载真实 API 数据(a→div)...'
...@@ -315,12 +402,6 @@ const loadRealApiDataWithDiv = () => { ...@@ -315,12 +402,6 @@ const loadRealApiDataWithDiv = () => {
315 // 将 <a> 标签替换为 <div> 标签,保留 href 为 data-href 402 // 将 <a> 标签替换为 <div> 标签,保留 href 为 data-href
316 let content = realApiData.post_content 403 let content = realApiData.post_content
317 404
318 - console.log('[VHtmlTest] 原始内容中的 <a> 标签示例:')
319 - const aTagMatch = content.match(/<a[^>]*>/)
320 - if (aTagMatch) {
321 - console.log('[VHtmlTest]', aTagMatch[0])
322 - }
323 -
324 // 替换 HTML 实体 405 // 替换 HTML 实体
325 content = content.replace(/&nbsp;/g, ' ') // 空格 406 content = content.replace(/&nbsp;/g, ' ') // 空格
326 content = content.replace(/&amp;/g, '&') // & 407 content = content.replace(/&amp;/g, '&') // &
...@@ -334,15 +415,8 @@ const loadRealApiDataWithDiv = () => { ...@@ -334,15 +415,8 @@ const loadRealApiDataWithDiv = () => {
334 content = content.replace(/href=/g, 'data-href=') 415 content = content.replace(/href=/g, 'data-href=')
335 content = content.replace(/<\/a>/g, '</div>') 416 content = content.replace(/<\/a>/g, '</div>')
336 417
337 - console.log('[VHtmlTest] 替换后的 div 标签示例:')
338 - const divTagMatch = content.match(/<div[^>]*data-is-link="true"[^>]*>/)
339 - if (divTagMatch) {
340 - console.log('[VHtmlTest]', divTagMatch[0])
341 - }
342 -
343 // 统计 data-href 数量 418 // 统计 data-href 数量
344 const hrefCount = (content.match(/data-href=/g) || []).length 419 const hrefCount = (content.match(/data-href=/g) || []).length
345 - console.log('[VHtmlTest] 找到 data-href 数量:', hrefCount)
346 420
347 testHtml.value = content 421 testHtml.value = content
348 currentContentType.value = `真实文章(<a>→<div>): ${realApiData.post_title}` 422 currentContentType.value = `真实文章(<a>→<div>): ${realApiData.post_title}`
...@@ -354,8 +428,7 @@ const loadRealApiDataWithDiv = () => { ...@@ -354,8 +428,7 @@ const loadRealApiDataWithDiv = () => {
354 nextTick(() => { 428 nextTick(() => {
355 bindImageEvents() 429 bindImageEvents()
356 // 绑定文件链接点击事件 430 // 绑定文件链接点击事件
357 - const linkCount = bindFileLinkEvents() 431 + bindFileLinkEvents()
358 - console.log('[VHtmlTest] 绑定了', linkCount, '个文件链接')
359 }) 432 })
360 433
361 Taro.showToast({ 434 Taro.showToast({
...@@ -368,12 +441,10 @@ const loadRealApiDataWithDiv = () => { ...@@ -368,12 +441,10 @@ const loadRealApiDataWithDiv = () => {
368 // 绑定图片长按预览事件(类似 activityDetail 页面的实现) 441 // 绑定图片长按预览事件(类似 activityDetail 页面的实现)
369 const bindImageEvents = () => { 442 const bindImageEvents = () => {
370 const imgs = $('#taro_html').children('.h5-p').children('.h5-img') 443 const imgs = $('#taro_html').children('.h5-p').children('.h5-img')
371 - console.log('[VHtmlTest] 找到图片数量:', imgs.length)
372 444
373 imgs.forEach(function (img) { 445 imgs.forEach(function (img) {
374 $(img).on('longpress', function () { 446 $(img).on('longpress', function () {
375 const src = $(img).attr('src') 447 const src = $(img).attr('src')
376 - console.log('[VHtmlTest] 图片长按事件:', src)
377 448
378 Taro.previewImage({ 449 Taro.previewImage({
379 urls: [src], 450 urls: [src],
...@@ -381,11 +452,10 @@ const bindImageEvents = () => { ...@@ -381,11 +452,10 @@ const bindImageEvents = () => {
381 indicator: 'default', 452 indicator: 'default',
382 loop: false, 453 loop: false,
383 success: res => { 454 success: res => {
384 - console.log('[VHtmlTest] 预览成功:', res)
385 lastAction.value = '图片预览触发' 455 lastAction.value = '图片预览触发'
386 }, 456 },
387 fail: err => { 457 fail: err => {
388 - console.error('[VHtmlTest] 预览失败:', err) 458 + // 预览失败
389 } 459 }
390 }) 460 })
391 }) 461 })
...@@ -398,45 +468,22 @@ const bindImageEvents = () => { ...@@ -398,45 +468,22 @@ const bindImageEvents = () => {
398 const bindFileLinkEvents = () => { 468 const bindFileLinkEvents = () => {
399 // 先检查 #taro_html 容器是否存在 469 // 先检查 #taro_html 容器是否存在
400 const container = $('#taro_html') 470 const container = $('#taro_html')
401 - console.log('[VHtmlTest] 容器存在?', container.length > 0)
402 -
403 - // 检查容器内所有 div
404 - const allDivs = container.find('div')
405 - console.log('[VHtmlTest] 容器内所有 div 数量:', allDivs.length)
406 471
407 // 检查带 data-is-link 属性的 div 472 // 检查带 data-is-link 属性的 div
408 const fileLinks = container.find('div[data-is-link="true"]') 473 const fileLinks = container.find('div[data-is-link="true"]')
409 - console.log('[VHtmlTest] 找到文件链接数量 (data-is-link=true):', fileLinks.length)
410 474
411 // 如果找不到,尝试用 class 查找 475 // 如果找不到,尝试用 class 查找
412 if (fileLinks.length === 0) { 476 if (fileLinks.length === 0) {
413 - console.log('[VHtmlTest] 尝试用 _file_list class 查找...')
414 const classLinks = container.find('._file_list') 477 const classLinks = container.find('._file_list')
415 - console.log('[VHtmlTest] 找到 _file_list 数量:', classLinks.length)
416 -
417 - // 检查这些元素的属性
418 - classLinks.each(function (idx, el) {
419 - console.log('[VHtmlTest] _file_list[' + idx + '] 属性:', {
420 - tagName: el.tagName,
421 - className: el.className,
422 - 'data-href': el.getAttribute('data-href'),
423 - 'data-is-link': el.getAttribute('data-is-link'),
424 - href: el.getAttribute('href'),
425 - innerHTML: el.innerHTML.substring(0, 100)
426 - })
427 - })
428 478
429 // 如果用 class 找到了,绑定到这些元素上 479 // 如果用 class 找到了,绑定到这些元素上
430 classLinks.each(function (idx, el) { 480 classLinks.each(function (idx, el) {
431 const $el = $(el) 481 const $el = $(el)
432 const dataHref = $el.attr('data-href') 482 const dataHref = $el.attr('data-href')
433 - console.log('[VHtmlTest] _file_list[' + idx + '] data-href:', dataHref)
434 483
435 if (dataHref) { 484 if (dataHref) {
436 $el.on('tap', function () { 485 $el.on('tap', function () {
437 const fileName = $el.find('span span span').first().text() || 'document.pdf' 486 const fileName = $el.find('span span span').first().text() || 'document.pdf'
438 - console.log('[VHtmlTest] 文件链接点击:', { dataHref, fileName })
439 -
440 lastAction.value = `打开文件: ${fileName}` 487 lastAction.value = `打开文件: ${fileName}`
441 viewFile({ 488 viewFile({
442 downloadUrl: dataHref, 489 downloadUrl: dataHref,
...@@ -454,13 +501,9 @@ const bindFileLinkEvents = () => { ...@@ -454,13 +501,9 @@ const bindFileLinkEvents = () => {
454 const $link = $(link) 501 const $link = $(link)
455 const href = $link.attr('data-href') 502 const href = $link.attr('data-href')
456 503
457 - console.log('[VHtmlTest] 文件链接 data-href:', href)
458 -
459 $link.on('tap', function () { 504 $link.on('tap', function () {
460 const fileName = $link.find('span span span').first().text() || 'document.pdf' 505 const fileName = $link.find('span span span').first().text() || 'document.pdf'
461 506
462 - console.log('[VHtmlTest] 文件链接点击:', { href, fileName })
463 -
464 if (href) { 507 if (href) {
465 lastAction.value = `打开文件: ${fileName}` 508 lastAction.value = `打开文件: ${fileName}`
466 509
...@@ -469,8 +512,6 @@ const bindFileLinkEvents = () => { ...@@ -469,8 +512,6 @@ const bindFileLinkEvents = () => {
469 downloadUrl: href, 512 downloadUrl: href,
470 fileName: fileName 513 fileName: fileName
471 }) 514 })
472 - } else {
473 - console.warn('[VHtmlTest] 链接没有 data-href 属性')
474 } 515 }
475 }) 516 })
476 }) 517 })
...@@ -479,22 +520,14 @@ const bindFileLinkEvents = () => { ...@@ -479,22 +520,14 @@ const bindFileLinkEvents = () => {
479 } 520 }
480 521
481 // 监听 testHtml 变化 522 // 监听 testHtml 变化
482 -watch(testHtml, (newVal, oldVal) => { 523 +watch(testHtml, (_newVal, _oldVal) => {
483 - console.log('[VHtmlTest] testHtml 变化:', {
484 - oldLength: oldVal?.length || 0,
485 - newLength: newVal?.length || 0
486 - })
487 renderTest.value = true 524 renderTest.value = true
488 }) 525 })
489 526
490 // 初始化 527 // 初始化
491 onMounted(() => { 528 onMounted(() => {
492 - console.log('[VHtmlTest] 组件已挂载')
493 -
494 // 加载默认测试内容 529 // 加载默认测试内容
495 switchTestContent(1) 530 switchTestContent(1)
496 -
497 - console.log('[VHtmlTest] 初始化完成,v-html 应该已绑定到响应式变量 testHtml')
498 }) 531 })
499 </script> 532 </script>
500 533
...@@ -660,6 +693,8 @@ onMounted(() => { ...@@ -660,6 +693,8 @@ onMounted(() => {
660 border-radius: 8rpx; 693 border-radius: 8rpx;
661 font-size: 28rpx; 694 font-size: 28rpx;
662 text-align: center; 695 text-align: center;
696 + background: #f0f0f0;
697 + color: #333;
663 698
664 &.primary { 699 &.primary {
665 background: #1989fa; 700 background: #1989fa;
...@@ -686,6 +721,11 @@ onMounted(() => { ...@@ -686,6 +721,11 @@ onMounted(() => {
686 background: #7232dd; 721 background: #7232dd;
687 color: #fff; 722 color: #fff;
688 } 723 }
724 +
725 + &.enabled {
726 + background: #07c160;
727 + color: #fff;
728 + }
689 } 729 }
690 730
691 .compare-note { 731 .compare-note {
......