|
@@ -0,0 +1,209 @@
|
|
|
+import puppeteer from "puppeteer";
|
|
|
+import fs from "fs-extra";
|
|
|
+import path from "path";
|
|
|
+import axios from "axios";
|
|
|
+import FormData from "form-data";
|
|
|
+
|
|
|
+/**
|
|
|
+ * 生成柱状图并上传
|
|
|
+ * @param {Object} data - 图表数据
|
|
|
+ * @returns {Promise<String>} - 返回图片URL
|
|
|
+ */
|
|
|
+export const generateBarChart = async (data) => {
|
|
|
+ try {
|
|
|
+ console.log("开始生成图表...");
|
|
|
+ console.log("数据:", data);
|
|
|
+
|
|
|
+ // 创建临时目录
|
|
|
+ const tempDir = path.join(process.cwd(), "temp");
|
|
|
+ await fs.ensureDir(tempDir);
|
|
|
+ const tempFilePath = path.join(tempDir, `temp_chart_${Date.now()}.png`);
|
|
|
+
|
|
|
+ // 获取 plotly.js 的绝对路径
|
|
|
+ const plotlyPath = path.join(
|
|
|
+ process.cwd(),
|
|
|
+ "src",
|
|
|
+ "public",
|
|
|
+ "js",
|
|
|
+ "plotly-latest.min.js"
|
|
|
+ );
|
|
|
+ const plotlyContent = await fs.readFile(plotlyPath, "utf-8");
|
|
|
+
|
|
|
+ // 创建浏览器实例
|
|
|
+ const browser = await puppeteer.launch({
|
|
|
+ headless: "new",
|
|
|
+ args: ["--no-sandbox", "--disable-setuid-sandbox"],
|
|
|
+ });
|
|
|
+
|
|
|
+ try {
|
|
|
+ const page = await browser.newPage();
|
|
|
+
|
|
|
+ // 准备图表数据
|
|
|
+ const chartDataset = data.data[0];
|
|
|
+ const trace = {
|
|
|
+ x: chartDataset.xData,
|
|
|
+ y: chartDataset.yData,
|
|
|
+ type: "bar",
|
|
|
+ marker: {
|
|
|
+ color: "#588CF0",
|
|
|
+ },
|
|
|
+ line: {
|
|
|
+ color: "#588CF0",
|
|
|
+ },
|
|
|
+ name: chartDataset.title || "数据",
|
|
|
+ hovertemplate: `${data.xaixs}: %{x} <br> ${data.yaixs}: %{y} <br>`,
|
|
|
+ };
|
|
|
+
|
|
|
+ // 准备布局配置
|
|
|
+ const layout = {
|
|
|
+ title: {
|
|
|
+ text: chartDataset.title,
|
|
|
+ font: {
|
|
|
+ size: 16,
|
|
|
+ weight: "bold",
|
|
|
+ },
|
|
|
+ },
|
|
|
+ xaxis: {
|
|
|
+ title: data.xaixs || "X轴",
|
|
|
+ gridcolor: "rgb(255,255,255)",
|
|
|
+ type: data.xaixs === "机组" ? "category" : undefined,
|
|
|
+ tickcolor: "rgb(255,255,255)",
|
|
|
+ backgroundcolor: "#e5ecf6",
|
|
|
+ },
|
|
|
+ yaxis: {
|
|
|
+ title: data.yaixs || "Y轴",
|
|
|
+ gridcolor: "rgb(255,255,255)",
|
|
|
+ tickcolor: "rgb(255,255,255)",
|
|
|
+ backgroundcolor: "#e5ecf6",
|
|
|
+ title_standoff: 100,
|
|
|
+ },
|
|
|
+ margin: {
|
|
|
+ l: 50,
|
|
|
+ r: 50,
|
|
|
+ t: 50,
|
|
|
+ b: 50,
|
|
|
+ },
|
|
|
+ plot_bgcolor: "#e5ecf6",
|
|
|
+ gridcolor: "#fff",
|
|
|
+ bgcolor: "#e5ecf6",
|
|
|
+ autosize: true,
|
|
|
+ };
|
|
|
+
|
|
|
+ // 如果 Y 轴是 "温度偏差",添加两条红色虚线
|
|
|
+ if (data.yaixs === "温度偏差") {
|
|
|
+ layout.shapes = [
|
|
|
+ {
|
|
|
+ type: "line",
|
|
|
+ xref: "paper",
|
|
|
+ yref: "y",
|
|
|
+ x0: 0,
|
|
|
+ x1: 1,
|
|
|
+ y0: 5,
|
|
|
+ y1: 5,
|
|
|
+ line: {
|
|
|
+ color: "red",
|
|
|
+ width: 2,
|
|
|
+ dash: "dash",
|
|
|
+ },
|
|
|
+ hovertext: "上限: 5°C",
|
|
|
+ hoverinfo: "text",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ type: "line",
|
|
|
+ xref: "paper",
|
|
|
+ yref: "y",
|
|
|
+ x0: 0,
|
|
|
+ x1: 1,
|
|
|
+ y0: -5,
|
|
|
+ y1: -5,
|
|
|
+ line: {
|
|
|
+ color: "red",
|
|
|
+ width: 2,
|
|
|
+ dash: "dash",
|
|
|
+ },
|
|
|
+ hovertext: "下限: -5°C",
|
|
|
+ hoverinfo: "text",
|
|
|
+ },
|
|
|
+ ];
|
|
|
+ layout.hovermode = "x unified";
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果是机组数据,设置刻度值
|
|
|
+ if (data.xaixs === "机组" || data.xaixs === "机组名称") {
|
|
|
+ layout.xaxis.tickvals = chartDataset.xData;
|
|
|
+ layout.xaxis.ticktext = chartDataset.xData;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 创建HTML内容
|
|
|
+ const htmlContent = `
|
|
|
+ <!DOCTYPE html>
|
|
|
+ <html>
|
|
|
+ <head>
|
|
|
+ <script>${plotlyContent}</script>
|
|
|
+ <style>
|
|
|
+ body { margin: 0; }
|
|
|
+ #chart { width: 800px; height: 600px; }
|
|
|
+ </style>
|
|
|
+ </head>
|
|
|
+ <body>
|
|
|
+ <div id="chart"></div>
|
|
|
+ <script>
|
|
|
+ window.onload = function() {
|
|
|
+ const trace = ${JSON.stringify(trace)};
|
|
|
+ const layout = ${JSON.stringify(layout)};
|
|
|
+ Plotly.newPlot('chart', [trace], layout).then(() => {
|
|
|
+ window.chartRendered = true;
|
|
|
+ });
|
|
|
+ };
|
|
|
+ </script>
|
|
|
+ </body>
|
|
|
+ </html>
|
|
|
+ `;
|
|
|
+
|
|
|
+ // 设置页面内容
|
|
|
+ await page.setContent(htmlContent, {
|
|
|
+ waitUntil: "networkidle0",
|
|
|
+ });
|
|
|
+
|
|
|
+ // 等待图表渲染完成
|
|
|
+ await page.waitForFunction(() => window.chartRendered === true, {
|
|
|
+ timeout: 60000,
|
|
|
+ });
|
|
|
+
|
|
|
+ // 截图并保存到临时文件
|
|
|
+ const chartElement = await page.$("#chart");
|
|
|
+ await chartElement.screenshot({
|
|
|
+ path: tempFilePath,
|
|
|
+ type: "png",
|
|
|
+ });
|
|
|
+
|
|
|
+ // 上传图片到服务器
|
|
|
+ const formData = new FormData();
|
|
|
+ formData.append("file", fs.createReadStream(tempFilePath));
|
|
|
+ formData.append("type", "chart");
|
|
|
+ formData.append("engineCode", data.engineCode);
|
|
|
+ formData.append("analysisTypeCode", data.analysisTypeCode);
|
|
|
+
|
|
|
+ const uploadResponse = await axios.post(
|
|
|
+ "http://192.168.50.233:6900/upload", //minio 地址上传
|
|
|
+ formData,
|
|
|
+ {
|
|
|
+ headers: {
|
|
|
+ ...formData.getHeaders(),
|
|
|
+ },
|
|
|
+ }
|
|
|
+ );
|
|
|
+
|
|
|
+ // 删除临时文件
|
|
|
+ await fs.remove(tempFilePath);
|
|
|
+
|
|
|
+ console.log("图表生成并上传成功:", uploadResponse.data.url);
|
|
|
+ return uploadResponse.data.url;
|
|
|
+ } finally {
|
|
|
+ await browser.close();
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error("生成图表失败:", error);
|
|
|
+ throw error;
|
|
|
+ }
|
|
|
+};
|