背景
客户网站由VUE开发,并已经上线,需要嵌入在线客服,支持实时消息提醒 文字图片交互功能。大概功能如下图所示:
解决思路:
因为React. 使用Nextjs 框架开发,会编译成静态HTML,所以将编译后的HTML直接嵌入到原系统即可。还有VUE和React 结合的第三方框架,开发和客户的接入成本较高,暂不考虑。
客户接入要简单并不能影响现有功能,而且需要和现有页面通信,需要嵌入到同一页,不能用iframe。最好是一键安装。假设以下页面为客户现有页面,最终安装效果如下:客户只需要在原网页中加入一句js 代码即可:
<!DOCTYPE html>
<html lang="en">
<head>
<title>动态加载示例</title>
<meta charset="UTF-8">
<script src="./imInstaller.js"></script>
</head>
<body>
<h1>点右下游客服图标!</h1>
<div id="root">
<div id="__next">
<div id="content-container">
</div>
</div>
</div>
</body>
</html>
具体方案步骤
- 首先用Nextjs将React 组件生成HTML,next.config.mjs 配置如下:HTML文件会生成至dist/pc 目录下
/** @type { import("next").NextConfig } */
const nextConfig = {
...
assetPrefix: process.env.NEXT_PUBLIC_ASSET_PREFIX,
reactStrictMode: false,
// 编译文件的输出目录
distDir: "dist/pc",
output: "export"
...
};
export default nextConfig;
export default function Page() {
return (
<div id="content-container">
<MyComponent /> //任意组件,演示效果
</div>
);
}
直接生成后部署到nginx,nginx 的配置如下:
server {
listen 80;
server_name www.sparrowzoo.com sparrowzoo.com upload.sparrowzoo.com
charset utf_8;
location /react {
try_files $uri $uri.html $uri/ =404;//自动访问.html结尾的文件 比如/a会自动访问至/a.html
alias /workspace/sparrow-js/react-next-14/dist/pc;
expires 1h;
}
}
imInstaller.js
function loadjQuery(callback) {
if (window.jQuery) {
callback();
return;
}
const script = document.createElement("script");
script.src = "https://code.jquery.com/jquery-3.7.1.min.js";
script.onload = callback;
script.onerror = function () {
console.error("加载失败,请检查网络或 CDN 状态");
};
document.head.appendChild(script);
}
// 加载 jQuery 并初始化功能
loadjQuery(function () {
var root = "./";
var htmlUrl = root + "/12talk";
function loadHTML(url, containerId) {
const container = $(`#${containerId}`);
container.css({ position: "absolute", right: "4rem", bottom: "4rem" });
// 显示加载状态
$.ajax({
url: url,
method: "GET",
cache: false, // 禁用缓存
dataType: "html",
})
.done(function (html) {
html = html.replace("</body>", "");
html = html.replace("</html>", "");
container.append(html); // 外部样式不
reloadAssets(container);
})
.fail(function (jqXHR, textStatus) {
console.error("加载失败:", textStatus);
container.html('<p class="error">内容加载失败,请刷新重试</p>');
});
}
// 资源重载处理
function reloadAssets(container) {
//处理样式表;
container.find('link[rel="stylesheet"]').each(function () {
$(this).clone().appendTo("head");
});
}
$(document).ready(() => {
loadHTML(htmlUrl, "content-container");
});
});
部署上线后发现问题:
IM可以正常访问,但将原页面效果直接覆盖,这是万万不可接受的
分析原因
- Nextjs 生成的HTML默认为完整的HTML结构,包括 标签
通过jquery
container.append(html);
之后会直接覆盖原页面。
进行如下调整,将React 页面生成页面中的html body 去掉,只保留待嵌入的html代码片断,需要将项目的路由改为Route Groups 详见nextjs.org/docs/app/bu…
调整后的project structure 如下
Im 下的layout 不会受root layout. 影响,即im 目录下只生成html代码片断,不会生成完整html 代码,chat 目录下会生成完整html不受该配置影响。
修改后im目录下的layout.tsx
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<>{children}</>
);
}
注意:本地运行可能会报错,提示
Missing Required HTML Tag
The following tags are missing in the Root Layout: , . Read more at nextjs.org/docs/messag… 不用理会,部署线上可保证运行正常
经过以上修改之后依然会覆盖原页面内容,但以上修改依然有效。进一步分析
在不知道问题的时侯,最没有办法的办法 就是排除法:将MyComponet 中的代码删至最简
export default function Demo() {
return (
<div>
<h1>Welcome to 12talk</h1>
<p>This is a demo page for 12talk.</p>
</div>
);
}
依然会覆盖,说明不是自定义组件的问题,也不是jquery加载的问题,可能是nextjs 生成机制html的机制,会默认将生成的html片断嵌到html. body下的div 下。这是nextjs 和react 的生成机制导致的,所以不管原页长什么样。最终都会变成
<html>
<body>
<div id="__next">
你的内容
</div>
</body>
</html
所以原页面内容被覆盖。
知道原因后做如下调整
"use client";
import React, {useEffect} from "react";
import dynamic from "next/dynamic";
import {createRoot} from "react-dom/client";
const JQueryComponent = dynamic(() => import("./back"), {
ssr: false, // 仅客户端渲染
});
export default function Page() {
useEffect(() => {
// 手动挂载到指定容器
//修改默认挂载方式,挂载到指定的html标签上,不会覆盖原内容
const container = document.getElementById("content-container");
if (container) {
const root = createRoot(container);
root.render(<JQueryComponent />);
}
}, []);
return null; // 返回空值,避免自动生成默认 div,
//注意这句话,next js 如果发现有内容就会默认加到body下相当于document.body.innerHTML="<div>你的内容</div>"
//这里返回null非常 重要
}
将生成好的html 片断部署至nginx.并将public index.html一并部署(为了演示使用,假设为客户现存页面)
并按以上imInstall.js 安装代码执行即可。
至此完美解决
最终总结
- IM侧实现较为复杂,需要了解react 和nextjs 的核心机制,可能遇到一些线上线下的部署的坑,但理论上是讲得通的
- 对于客户端使用非常简单,只需要一行代码即可安装!
<!DOCTYPE html>
<html lang="en">
<head>
<title>动态加载示例</title>
<meta charset="UTF-8">
<script src="./imInstaller.js"></script>
</head>
<body>
<h1>点右下游客服图标!</h1>
<div id="root">
<div id="__next">
<div id="content-container">
</div>
</div>
</div>
</body>
</html>