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>
Showing
1 changed file
with
97 additions
and
57 deletions
| ... | @@ -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(/ /g, ' ') // 空格 | 406 | content = content.replace(/ /g, ' ') // 空格 |
| 326 | content = content.replace(/&/g, '&') // & | 407 | content = content.replace(/&/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 { | ... | ... |
-
Please register or login to post a comment