You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Intro.js引导步骤中渲染HTML而非纯文本(Symfony+EasyAdmin项目场景)

如何在Intro.js引导步骤中渲染HTML而非纯文本(Symfony+EasyAdmin项目场景)

嘿,我之前也碰到过几乎一模一样的问题!这个问题其实分两部分:一是EasyAdmin详情页的HTML渲染,二是Intro.js本身的HTML解析配置,咱们一个个解决:

1. 解决EasyAdmin详情页iframe显示为纯文本的问题

EasyAdmin默认会对所有字段内容进行HTML转义,防止XSS风险,但这就导致你的iframe代码被当成纯文本展示了。咱们只需要在CRUD控制器的字段配置中,给intro字段关闭HTML转义就行:

修改你的Page(或Step)的CRUD控制器

如果是在Page实体的CRUD控制器中配置详情页的steps展示:

// src/Controller/Admin/PageCrudController.php
public function configureFields(string $pageName): iterable
{
    // ... 其他字段配置
    if ($pageName === Crud::PAGE_DETAIL) {
        yield CollectionField::new('steps', 'Steps')
            ->setEntryIsComplex(true)
            ->setEntryOptions([
                'fields' => [
                    Field::new('title'),
                    Field::new('element'),
                    // 关键:关闭intro字段的HTML转义
                    Field::new('intro')->setEscapeHtml(false),
                ],
            ]);
    } else {
        // 你的表单页原有配置保持不变
        yield CollectionField::new('steps', 'Steps')
            ->setEntryType(IntroStepType::class)
            ->allowAdd()
            ->allowDelete()
            ->renderExpanded()
            ->addJsFiles(Asset::fromEasyAdminAssetPackage('field-text-editor.js')->onlyOnForms())
            ->addCssFiles(Asset::fromEasyAdminAssetPackage('field-text-editor.css')->onlyOnForms());
    }
}

如果是在Step实体的独立CRUD控制器中:

// src/Controller/Admin/StepCrudController.php
public function configureFields(string $pageName): iterable
{
    yield Field::new('title');
    yield Field::new('element');
    // 直接给intro字段关闭转义
    yield Field::new('intro')->setEscapeHtml(false);
}

这样修改后,EasyAdmin的详情页就能正确渲染iframe了。

2. 解决Intro.js引导步骤中HTML不渲染的问题

Intro.js默认会把步骤的intro内容当成纯文本处理,咱们只需要在Stimulus控制器里给Intro.js添加一个配置,告诉它允许解析HTML内容:

修改你的Stimulus控制器

你可以选择全局关闭所有步骤的HTML转义,或者给单个步骤配置,推荐全局配置更简单:

// 你的stimulus控制器文件
import { Controller } from '@hotwired/stimulus';
import introJs from 'intro.js';
import '../vendor/intro.js/introjs.css';

export default class extends Controller {
    async connect() {
        this.intro = introJs();
        // 给基础配置加上escape: false,全局允许HTML渲染
        this.baseOptions = {
            showBullets: false,
            showPrevButton: true,
            nextLabel: 'Next',
            prevLabel: 'Previous',
            escape: false, // 关键:关闭HTML转义,让Intro.js渲染HTML内容
        };
        this.intro.setOptions(this.baseOptions);

        const params = new URLSearchParams(window.location.search);
        if ('true' === params.get('tutorial')) {
            await this.startTutorial();
        }
    }

    // 剩下的方法保持不变,startTutorial里的options会继承baseOptions的escape配置
    async startTutorial() {
        const path = encodeURIComponent(window.location.pathname);
        try {
            const response = await fetch(`/tutorial?path=${path}`);
            if (!response.ok) {
                console.error('Error loading tutorial data:', response.status, response.statusText);
                return;
            }
            const data = await response.json();
            if (!data.steps?.length) return;
            const steps = data.steps.filter(step => !step.element || document.querySelector(step.element));
            if (steps.length === 0) return;
            const options = {
                ...this.baseOptions,
                steps,
                doneLabel: data.next ? 'Next' : 'Done',
            };
            this.intro.setOptions(options);
            // ... 剩下的代码不变
        } catch (error) {
            console.error('Network error:', error);
        }
    }

    // 其他方法不变
    removeTutorialQueryParam() {
        const url = new URL(window.location);
        url.searchParams.delete('tutorial');
        window.history.replaceState({}, document.title, url);
    }
}

如果只需要给特定步骤开启HTML渲染,也可以在startTutorial里对steps数组做映射:

const steps = data.steps.filter(step => !step.element || document.querySelector(step.element))
    .map(step => ({
        ...step,
        escape: false // 给单个步骤设置允许HTML渲染
    }));

重要安全提醒

关闭HTML转义后,要确保只有可信的用户(比如管理员)能编辑intro字段,避免恶意用户插入危险的HTML/JS代码导致XSS攻击。如果需要对外公开,建议对输入的内容做过滤,比如只允许来自YouTube的iframe标签。

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.08 08:28:02