|
@@ -0,0 +1,220 @@
|
|
|
+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 generateLineChart = 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_line_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 sortedData = data.data.sort((a, b) => {
|
|
|
+ if (
|
|
|
+ a.engineCode === data.fieldEngineCode &&
|
|
|
+ b.engineCode !== data.fieldEngineCode
|
|
|
+ ) {
|
|
|
+ return 1;
|
|
|
+ }
|
|
|
+ if (
|
|
|
+ a.engineCode !== data.fieldEngineCode &&
|
|
|
+ b.engineCode === data.fieldEngineCode
|
|
|
+ ) {
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+ return 0;
|
|
|
+ });
|
|
|
+
|
|
|
+ const finalData = [];
|
|
|
+ sortedData.forEach((turbine) => {
|
|
|
+ const color =
|
|
|
+ turbine.engineCode === data.fieldEngineCode ? "#406DAB" : "#D3D3D3";
|
|
|
+ const chartConfig = {
|
|
|
+ x: turbine.xData,
|
|
|
+ y: turbine.yData,
|
|
|
+ name: turbine.engineName,
|
|
|
+ mode: "lines",
|
|
|
+ fill: "none",
|
|
|
+ line: { color },
|
|
|
+ marker: { color },
|
|
|
+ hovertemplate: `${data.xaixs}: %{x} <br> ${data.yaixs}: %{y} <br>`,
|
|
|
+ };
|
|
|
+ finalData.push(chartConfig);
|
|
|
+ });
|
|
|
+
|
|
|
+ // 添加合同功率曲线
|
|
|
+ if (
|
|
|
+ data.contract_Cp_curve_yData &&
|
|
|
+ data.contract_Cp_curve_yData.length > 0
|
|
|
+ ) {
|
|
|
+ finalData.push({
|
|
|
+ x: data.contract_Cp_curve_xData,
|
|
|
+ y: data.contract_Cp_curve_yData,
|
|
|
+ mode: "lines+markers",
|
|
|
+ name: "合同功率曲线",
|
|
|
+ line: {
|
|
|
+ color: "red",
|
|
|
+ width: 1,
|
|
|
+ },
|
|
|
+ marker: { color: "red", size: 4 },
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ // 准备布局配置
|
|
|
+ const layout = {
|
|
|
+ title: {
|
|
|
+ text: data.title || "图表",
|
|
|
+ font: {
|
|
|
+ size: 16,
|
|
|
+ weight: "bold",
|
|
|
+ },
|
|
|
+ },
|
|
|
+ xaxis: {
|
|
|
+ title: data.xaixs || "X轴",
|
|
|
+ gridcolor: "rgb(255,255,255)",
|
|
|
+ tickcolor: "rgb(255,255,255)",
|
|
|
+ backgroundcolor: "#e5ecf6",
|
|
|
+ },
|
|
|
+ yaxis: {
|
|
|
+ title: data.yaixs || "Y轴",
|
|
|
+ gridcolor: "rgb(255,255,255)",
|
|
|
+ tickcolor: "rgb(255,255,255)",
|
|
|
+ backgroundcolor: "#e5ecf6",
|
|
|
+ },
|
|
|
+ margin: {
|
|
|
+ l: 50,
|
|
|
+ r: 50,
|
|
|
+ t: 50,
|
|
|
+ b: 50,
|
|
|
+ },
|
|
|
+ autosize: true,
|
|
|
+ plot_bgcolor: "#e5ecf6",
|
|
|
+ gridcolor: "#fff",
|
|
|
+ bgcolor: "#e5ecf6",
|
|
|
+ };
|
|
|
+
|
|
|
+ // 设置坐标轴范围
|
|
|
+ const getChartSetUp = (axisTitle) => {
|
|
|
+ return data.setUpImgData?.find((item) => item.text.includes(axisTitle));
|
|
|
+ };
|
|
|
+
|
|
|
+ const xChartSetUp = getChartSetUp(layout.xaxis.title);
|
|
|
+ if (xChartSetUp) {
|
|
|
+ layout.xaxis.dtick = xChartSetUp.dtick;
|
|
|
+ layout.xaxis.range = [xChartSetUp.min, xChartSetUp.max];
|
|
|
+ }
|
|
|
+
|
|
|
+ const yChartSetUp = getChartSetUp(layout.yaxis.title);
|
|
|
+ if (yChartSetUp) {
|
|
|
+ layout.yaxis.dtick = yChartSetUp.dtick;
|
|
|
+ layout.yaxis.range = [yChartSetUp.min, yChartSetUp.max];
|
|
|
+ }
|
|
|
+
|
|
|
+ // 创建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 data = ${JSON.stringify(finalData)};
|
|
|
+ const layout = ${JSON.stringify(layout)};
|
|
|
+ Plotly.newPlot('chart', data, layout, {
|
|
|
+ responsive: true
|
|
|
+ }).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://localhost:6900/upload",
|
|
|
+ // formData,
|
|
|
+ // {
|
|
|
+ // headers: {
|
|
|
+ // ...formData.getHeaders(),
|
|
|
+ // },
|
|
|
+ // }
|
|
|
+ // );
|
|
|
+
|
|
|
+ // 删除临时文件
|
|
|
+ await fs.remove(tempFilePath);
|
|
|
+ // console.log("折线图生成并上传成功:", uploadResponse.data.url);
|
|
|
+ // return uploadResponse.data.url;
|
|
|
+ return formData;
|
|
|
+ } finally {
|
|
|
+ await browser.close();
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error("生成折线图失败:", error);
|
|
|
+ throw error;
|
|
|
+ }
|
|
|
+};
|