之前的过程中,我们都不怎么熟悉Eclipse的哪些API,样式也没发怎么去修改,现在我们要修改为用html的方式来编写.
准备一个AI助手聊天页面的html.css,js代码
效果如下所示。
1.快速demo
1.1.准备前端代码
确保准备的前端代码可以在浏览器正常运行,这里我们直接浏览器访问html的绝对路径
代码目录如下所示
1.1.1.chat.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AI助手</title>
<link rel="stylesheet" type="text/css" href="F:\work\com.hutao.search\src\resources\chat.css">
</head>
<body>
<div class="chat-container"><div class="messages" id="message-container"></div><div class="input-container"><input type="text" class="user-input" id="user-input" placeholder="输入消息..."><button class="send-button" onclick="handleUserInput()">发送</button></div>
</div><script src="F:\work\com.hutao.search\src\resources\chat.js"></script>
</body>
</html>
1.1.2.chat.js
const messageContainer = document.getElementById('message-container');const userInput = document.getElementById('user-input');function handleUserInput() {const userMessage = userInput.value.trim();if (userMessage === '') {return;}appendMessage(userMessage, 'user-message');// Simulate AI Assistant replyconst systemReply = 'AI助手: 你好呀,勇士!';appendMessage(systemReply, 'assistant-message');userInput.value = '';messageContainer.scrollTop = messageContainer.scrollHeight;}function appendMessage(message, messageType) {const messageElement = document.createElement('div');messageElement.classList.add('message', messageType);messageElement.textContent = message;messageContainer.appendChild(messageElement);}
1.1.3.chat.css
body {font-family: Arial, sans-serif;margin: 0;padding: 0;background-color: #f2f2f2;}.chat-container {max-width: 600px;margin: 20px auto;background-color: #fff;border-radius: 5px;box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);}.messages {min-height: 300px;max-height: 400px;overflow-y: scroll;padding: 10px;}.message {margin-bottom: 10px;padding: 10px;border-radius: 5px;}.user-message {background-color: #DCF8C6;align-self: flex-end;}.assistant-message {background-color: #E4E4E4;}.input-container {display: flex;align-items: center;padding: 10px;background-color: #f9f9f9;}.user-input {flex: 1;height: 40px;margin-right: 10px;padding: 5px;border: 1px solid #ccc;border-radius: 5px;}.send-button {padding: 5px 10px;background-color: #4CAF50;color: white;border: none;border-radius: 5px;cursor: pointer;}
2.改造后端代码ViewPart
将之前的ViewPart 进行改造,这里不在使用java来开发界面。我们用前端的html,css,js.
实现思路,就是将在嵌入浏览器组件 (Browser) 来加载 HTML 和 JavaScript 页面,从而实现你的需求。这种方式可以让你使用熟悉的前端 技术来构建界面。
package com.hutao.search.view;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;import org.eclipse.swt.SWT;
import org.eclipse.swt.browser.Browser;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.ui.part.ViewPart;public class AiView extends ViewPart {public static final String ID = "com.hutao.search.ai.plugin.aiview";@Overridepublic void createPartControl(Composite parent) {parent.setLayout(new GridLayout(1, false));// 创建 Browser 控件Browser browser = new Browser(parent, SWT.NONE);browser.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));// 加载 HTML 文件内容String htmlContent = loadHtmlFile("resources/chat.html");System.out.println(htmlContent);// 在 Browser 控件中显示 HTML 内容browser.setText(htmlContent);}/*** @description:加载html页面* @author:hutao* @mail:hutao1@epri.sgcc.com.cn* @date:2025年2月18日16:01:01*/private String loadHtmlFile(String resourcePath) {InputStream inputStream = getClass().getClassLoader().getResourceAsStream(resourcePath);if (inputStream == null) {return "<html><body><h1>无法加载资源</h1></body></html>";}StringBuilder content = new StringBuilder();try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"))) {String line;while ((line = reader.readLine()) != null) {content.append(line).append("\n");}} catch (IOException e) {e.printStackTrace();return "<html><body><h1>无法加载资源</h1></body></html>";}return content.toString();}@Overridepublic void setFocus() {}
}
3.Demo效果展示
左边为我们在正常浏览器里面运行的效果,而右边实在eclipse中运行的效果。不然看出,样式好像丢了一些。也就是将前端的代码,迁移到html中的时候,好像不是完全兼容的。
4.CSS样式丢失的问题
根据上图的对比,不难发现,背景色都丢了,我们通过浏览器查看到这个元素,复制一下这些样式
我们把这部分代码复制出来看看,然后放到我们的html代码中,
<div class="chat-container"><div class="messages" id="message-container"><div class="message user-message">xxx</div><div class="message assistant-message">AI助手: 你好呀,勇士!</div><div class="message user-message">xxx</div><div class="message assistant-message">AI助手: 你好呀,勇士!</div><div class="message user-message">xxx</div><div class="message assistant-message">AI助手: 你好呀,勇士!</div><div class="message user-message">xxx</div><div class="message assistant-message">AI助手: 你好呀,勇士!</div></div><div class="input-container"><input type="text" class="user-input" id="user-input" placeholder="输入消息..."><button class="send-button" onclick="handleUserInput()">发送</button></div></div>
如下图所示,通过代码直接写的,样式没问题,通过handleUserInput-》appendMessage-》追加的消息,丢失了背景色样式,
而在浏览器中,我们可以看到,写死的,和通过handleUserInput-》appendMessage-》都是正常的,观察代码,也发先,代码都是一致的,目前我们则怀疑,可能在eclipse的Browser通过我们的handleUserInput-》appendMessage-》追加的消息,可能不能是我们正常显示时候的代码了。
于是我尝试这样解决问题,那我直接用style,不用css来渲染,看看如何?结果样式正常了。
function appendMessage(message, messageType) {const messageElement = document.createElement('div');const messageElement = document.createElement('div');// 设置背景色和字体颜色,直接通过 style 属性应用if (messageType === 'user-message') {messageElement.style.backgroundColor = '#DCF8C6'; // 用户消息背景色messageElement.style.alignSelf = 'flex-end'; // 用户消息右对齐} else if (messageType === 'assistant-message') {messageElement.style.backgroundColor = '#E4E4E4'; // AI 助手背景色}// 直接应用原本的 CSS 样式messageElement.style.padding = '10px'; // 设置内边距messageElement.style.marginBottom = '10px'; // 设置下边距messageElement.style.borderRadius = '5px'; // 设置圆角messageElement.style.fontFamily = 'Arial, sans-serif'; // 设置字体messageElement.style.fontSize = '14px'; // 设置字体大小messageElement.style.lineHeight = '1.5'; // 设置行高messageElement.style.maxWidth = '100%'; // 限制宽度(可选)messageElement.style.display = 'block'; // 确保它是块级元素messageElement.textContent = message;messageContainer.appendChild(messageElement);
}
现在基本可以确定,问题出在handleUserInput-》appendMessage-》css渲染出现问题了,我尝试将下面的classList.add(‘message’, messageType)拆成两行,classList.add(‘message’),classList.add(messageType),结果样式可以正常了。
function appendMessage(message, messageType) {const messageElement = document.createElement('div');//messageElement.classList.add('message', messageType);messageElement.classList.add('message');messageElement.classList.add(messageType);messageElement.textContent = message;messageContainer.appendChild(messageElement);
}
现在就有一个问题,怎么去看生成的这些元素样式,毕竟不是在浏览器里面,浏览器我们可以F12查看。
这里这特了一早上,我最后研究了下日志打印来看看为啥上面CSS样式失效。
5.日志打印问题
接着上面的问题,以后出现这样的问题该怎么定位处理?毕竟这个不想浏览器一样,我们可以按F12,可以debug
通过上面我们一系列测试,我们最终锁定:通过handleUserInput-》appendMessage-》追加的消息,丢失了背景色样式,那么如果我们能通过一些有效的方法来看看,我们最终的页面html元素是否为下面这样。
<div class="messages" id="message-container"><div class="message user-message">aa</div><div class="message assistant-message">AI助手: 你好呀,勇士!</div>
</div>
5.1.alert弹框打印信息
alert是原始的js弹框,不用引入任何框架插件即可使用,后面为啥会说,为啥用原始js打印(还有插件引入的问题还没说,这里我引入css,js是使用绝对路径地址)
在合适的位置,使用alert,例如,这里我在调用appendMessage之后,去获取message-container元素,然后弹框
const messageContainer = document.getElementById('message-container');
alert(messageContainer.innerHTML);
此时我们就能发现问题了,我们通过appendMessage生成的最后html和我们预期的不一样,
实际生成的没样式
<div class="message">abc</div>
<div class="message">AI助手: 你好呀,勇士!</div>预期的生成的有样式
<div class="message user-message">aa</div>
<div class="message assistant-message">AI助手: 你好呀,勇士!</div>
然而,当我们把appendMessage中的classList.add(),拆成两次添加以后,就正常了。
//messageElement.classList.add('message', messageType);
messageElement.classList.add('message');
messageElement.classList.add(messageType);
楼主查阅了很多资料,觉得下面这个理由比较靠谱。
浏览器对标准的 JavaScript API 支持较好,classList.add 方法在现代浏览器中已经是一个标准且稳定的 API。然而,Eclipse 插件的运行环境可能和浏览器有所不同,可能存在对某些 JavaScript 特性支持不完全或者存在兼容性问题。
所以在调用一些前端的API,如果达不到我们的期望的时候,不妨用打印的办法看看。
5.2.使用console.log进行日志输出
如下图所示,只是使用console.log就没那么简单了,你会发现无论怎么打印都不会输出到控制台,毕竟以前我们打印输出,是输出到F12,但是这里不好意思,这里是IDE,不是浏览器,没有F12
下面来说,怎么让console.log进行日志输出
BrowserFunction 是 Eclipse SWT(Standard Widget Toolkit)库中的一个类,用于在 Java 代码和嵌入在 SWT Browser 组件中的 JavaScript 代码之间建立桥梁,允许 JavaScript 代码调用 Java 方法。
public class CustomFunction extends BrowserFunction {public CustomFunction(Browser browser, String name) {super(browser, name);}@Overridepublic Object function(Object[] arguments) {for (Object arg : arguments) {if (arg != null) {// 打印到Java控制台System.out.println(arg.toString()); }}return null;}
}
接着在我们的ViewPart中中的创建part的方法中,创建CustomFunction。
public class AiView extends ViewPart {public static final String ID = "com.hutao.search.ai.plugin.aiview";@Overridepublic void createPartControl(Composite parent) {parent.setLayout(new GridLayout(1, false));// 创建 Browser 控件Browser browser = new Browser(parent, SWT.NONE);browser.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));// 加载 HTML 文件内容String htmlContent = loadHtmlFile("resources/chat.html");System.out.println(htmlContent);// 在 Browser 控件中显示 HTML 内容//browser.setText(htmlContent);browser.setUrl("F:\\work\\com.hutao.search\\src\\resources\\chat.html");new CustomFunction(browser, "logToJava");}}
然后在我们的js文件(需要调用console.log 之前,重写console.log),最好js的第一行代码
console.log = function() {const messages = Array.prototype.slice.call(arguments);// 将所有参数连接成一个字符串并调用Java方法logToJava(messages.join(' '));
};
6.CSS,JS资源引用问题
上面的代码,我用的是绝对路径地址,但是这样明显不对的,这样就没办法迁移了,因此要换成相对地址
<script src="F:\work\com.hutao.search\src\resources\chat.js"></script>
<link rel="stylesheet" type="text/css" href="F:\work\com.hutao.search\src\resources\chat.css">
chat.html和chat.css等都在一个文件夹,因此我们用相对路径
<script src="chat.js"></script>
<link rel="stylesheet" type="text/css" href="chat.css">
修改完以后再看我们的浏览器的,没有受到影响。
但是在看我们的eclipse里面的。样式不仅丢了,点击发送也没任何反应。之前是CSS部分丢了,现在来看是CSS和JS,都全丢了
6.1.问题分析
在 Eclipse SWT 中的 Browser 控件加载 HTML 文件时,相对路径的资源(如 CSS 文件和 JS 文件)通常会出现问题。这是因为 Browser 控件使用的本地 Web 渲染引擎和我们普通的浏览器渲染不是一样的。因此,不能用浏览器去引用CSS,JS的思路去。
我们先不防获取一下这里的CSS,JS,HTML的路径看一下
URL jspath = getClass().getClassLoader().getResource("resources/chat.js");
URL csspath = getClass().getClassLoader().getResource("resources/chat.css");
URL htmlpath = getClass().getClassLoader().getResource("resources/chat.html");System.out.println(jspath.toString());
System.out.println(csspath.toString());
System.out.println(htmlpath.toString());
如下图所示,如果你没接触到OSGI这类插件式开发的框架,对于下面这个bundleresource的地址,一定是很蒙蔽的。
格式特点:以 bundleresource:// 开头,后面紧跟一串数字(如 729.fwk1177963719),这串数字是 OSGi 框架为每个 Bundle(可理解为一个插件)分配的唯一标识符,用于区分不同的插件。再后面就是资源在 Bundle 内的相对路径,如 resources/chat.js。
用途:这种地址主要用于在 OSGi 环境中定位 Bundle 内部的资源。在 Eclipse 插件开发中,每个插件都是一个 Bundle,插件内的资源(如 HTML、CSS、JavaScript 文件等)可以通过这种地址来引用,确保资源的独立性和隔离性。
6.2.引用方案
6.2.1.1html,css,js物理不分离
即将所有的html,css,js等资源,都写到同一个html文件中,这样就不存在引用问题,代价就是会导致最后html变得很大,如果你开发的插件足够代码多,引用的资源足够多,后期维护变成几百上千行代码的时候,很难维护。这个我就不举例写了。有兴趣自己尝试。
6.2.1.1html,css,jss逻辑不分离
相比于上面的方案,物理不分离,我们在物理上将这些资源文件分离。但是在逻辑上不分离,具体措施就是,开发写代码的时候,html,css,各写个的,但是最后代码执行的时候,将css,js,等注入到html,最后,和上面物理不分离的效果是一样的,区别是,上面的代码在开发的时候就已经是同一个文件了,而这个方法,是开发完毕以后,在代码运行的时候,将代码打包合并到一个html文件中。
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;import org.eclipse.swt.SWT;
import org.eclipse.swt.browser.Browser;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.ui.part.ViewPart;public class AiView extends ViewPart {public static final String ID = "com.hutao.search.ai.plugin.aiview";@Overridepublic void createPartControl(Composite parent) {parent.setLayout(new GridLayout(1, false));// 创建 Browser 控件Browser browser = new Browser(parent, SWT.NONE);browser.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));// 加载 HTML 文件内容String htmlContent = loadHtmlFile("resources/chat.html");// 在 Browser 控件中显示 HTML 内容browser.setText(htmlContent);new CustomFunction(browser, "logToJava");}/*** @description:加载html页面* @author:hutao* @mail:hutao1@epri.sgcc.com.cn* @date:2025年2月18日16:01:01*/private String loadHtmlFile(String resourcePath) {InputStream inputStream = getClass().getClassLoader().getResourceAsStream(resourcePath);if (inputStream == null) {return "<html><body><h1>无法加载资源</h1></body></html>";}StringBuilder content = new StringBuilder();try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) {String line;while ((line = reader.readLine()) != null) {content.append(line).append("\n");}} catch (IOException e) {e.printStackTrace();return "<html><body><h1>无法加载资源</h1></body></html>";}// 加载 CSS 文件并内联String cssContent = loadResourceAsString("resources/chat.css");String jsContent = loadResourceAsString("resources/chat.js");// 将 CSS 和 JS 插入到 HTML 内容中return content.toString().replace("<link rel=\"stylesheet\" type=\"text/css\" href=\"chat.css\">","<style>" + cssContent + "</style>").replace("<script src=\"chat.js\"></script>","<script>" + jsContent + "</script>");}/*** @description:加载资源文件,转成字符串* @author:hutao* @mail:hutao1@epri.sgcc.com.cn* @date:2025年2月19日11:01:01*/private String loadResourceAsString(String resourcePath) {InputStream inputStream = getClass().getClassLoader().getResourceAsStream(resourcePath);if (inputStream == null) {return ""; // 返回空字符串以避免 null}StringBuilder content = new StringBuilder();try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) {String line;while ((line = reader.readLine()) != null) {content.append(line).append("\n");}} catch (IOException e) {e.printStackTrace();}return content.toString();}@Overridepublic void setFocus() {}
}
至此结束,相信到了这里,你就已经能用前端的开发来开发eclipse的插件了。如果你用的是VUE,也别怕,因为VUE打包最后也是生成HTML,CSS,JS这些代码,只是引用的方式,可能需要做转换