Canvas绘制图片和区域(前端使用Canvas绘制图片,并在图片上绘制区域)

简介:在Web开发中,有时候我们需要在图片上进行一些交互式操作,比如绘制区域、标记等。这种场景下,我们可以使用HTML5的<canvas>元素来实现。Canvas 是 HTML5 提供的一种图形绘制接口,可以通过 JavaScript 在网页上绘制图形、动画和其他视觉效果。这里来记录一下,如何在一张图片上,绘制区域

先看一下效果:

如何使用Canvas在图片上绘制区域?


初始化Canvas

一. 首先,我们需要初始化三个canvas画布

initCanvas() {
  // 初始化canvas画布
  let canvasWrap = document.getElementsByClassName("canvas-wrap");
  this.wrapWidth = canvasWrap[0].clientWidth;
  this.wrapHeight = canvasWrap[0].clientHeight;

  this.imgCanvas = document.getElementById("imgCanvas");
  this.imgCtx = this.imgCanvas.getContext("2d");

  // 绘制canvas
  this.drawCanvas = document.getElementById("drawCanvas");
  this.drawCtx = this.drawCanvas.getContext("2d");

  // 保存绘制区域 saveCanvas
  this.saveCanvas = document.getElementById("saveCanvas");
  this.saveCtx = this.saveCanvas.getContext("2d");
}
  1. imgCanvas用于绘制原始图片
  2. drawCanvas用于临时绘制区域
  3. saveCanvas用于保存最终绘制的区域


二. 计算并设置canvas的宽高比例,以适应图片尺寸

initImgCanvas() {
  // 计算宽高比
  let ww = this.wrapWidth; // 画布宽度
  let wh = this.wrapHeight; // 画布高度 
  let iw = this.imgWidth; // 图片宽度
  let ih = this.imgHeight; // 图片高度

  if (iw / ih < ww / wh) {
    // 以高为主
    this.ratio = ih / wh;
    this.canvasHeight = wh;
    this.canvasWidth = (wh * iw) / ih;
  } else {
    // 以宽为主 
    this.ratio = iw / ww;
    this.canvasWidth = ww;
    this.canvasHeight = (ww * ih) / iw;
  }

  // 初始化画布大小
  this.imgCanvas.width = this.canvasWidth;
  this.imgCanvas.height = this.canvasHeight;
  this.drawCanvas.width = this.canvasWidth; 
  this.drawCanvas.height = this.canvasHeight;
  this.saveCanvas.width = this.canvasWidth;
  this.saveCanvas.height = this.canvasHeight;

  // 图片加载绘制
  let img = document.createElement("img");
  img.src = this.imgUrl;
  img.onload = () => {
    console.log("图片已加载");
    this.imgCtx.drawImage(img, 0, 0, this.canvasWidth, this.canvasHeight);
    this.renderDatas(); // 渲染原有数据
  };
}

这里先计算画布和图片的宽高比,根据比例关系决定以宽为主还是以高为主进行等比缩放。然后设置三个canvas的宽高,并在图片加载完成后将其绘制到imgCanvas上。renderDatas函数用于渲染已有的绘制数据(如果有的话)。


开始绘制

三. 绘制的主要逻辑

startDraw() {
  // 绘制区域
  if (this.isDrawing) return;
  this.isDrawing = true;
  // 绘制逻辑
  this.drawCanvas.addEventListener("click", this.drawImageClickFn);
  this.drawCanvas.addEventListener("dblclick", this.drawImageDblClickFn);
  this.drawCanvas.addEventListener("mousemove", this.drawImageMoveFn);
}

我们在drawCanvas上监听clickdblclickmousemove事件,分别对应点击、双击和鼠标移动三种绘制交互。


四. 点击事件用于开始一个新的区域绘制

drawImageClickFn(e) {
  let drawCtx = this.drawCtx;
  if (e.offsetX || e.layerX) {
    let pointX = e.offsetX == undefined ? e.layerX : e.offsetX;
    let pointY = e.offsetY == undefined ? e.layerY : e.offsetY;
    let lastPoint = this.drawingPoints[this.drawingPoints.length - 1] || [];
    if (lastPoint[0] !== pointX || lastPoint[1] !== pointY) {
      this.drawingPoints.push([pointX, pointY]);
    }
  }
}

这里获取鼠标点击的坐标,并将其推入drawingPoints数组中,用于临时保存当前绘制区域的点坐标。


五. 鼠标移动事件用于实时绘制区域

drawImageMoveFn(e) {
  let drawCtx = this.drawCtx;
  if (e.offsetX || e.layerX) {
    let pointX = e.offsetX == undefined ? e.layerX : e.offsetX;
    let pointY = e.offsetY == undefined ? e.layerY : e.offsetY;
    // 绘制
    drawCtx.clearRect(0, 0, this.canvasWidth, this.canvasHeight);

    // 绘制点
    drawCtx.fillStyle = "blue";
    this.drawingPoints.forEach((item, i) => {
      drawCtx.beginPath();
      drawCtx.arc(...item, 6, 0, 180);
      drawCtx.fill(); //填充
    });

    // 绘制动态区域
    drawCtx.save();
    drawCtx.beginPath();
    this.drawingPoints.forEach((item, i) => {
      drawCtx.lineTo(...item);
    });
    drawCtx.lineTo(pointX, pointY);
    drawCtx.lineWidth = "3";
    drawCtx.strokeStyle = "blue";
    drawCtx.fillStyle = "rgba(255, 0, 0, 0.3)";
    drawCtx.stroke();
    drawCtx.fill(); //填充
    drawCtx.restore();
  }
}

这里先清空drawCanvas,然后遍历drawingPoints数组,绘制已经点击的点。接着再绘制一个动态区域,即从第一个点开始,连线到当前鼠标位置,形成一个闭合多边形区域。


六. 双击事件用于完成当前区域的绘制

drawImageDblClickFn(e) {
  let drawCtx = this.drawCtx;
  let saveCtx = this.saveCtx;
  if (e.offsetX || e.layerX) {
    let pointX = e.offsetX == undefined ? e.layerX : e.offsetX;
    let pointY = e.offsetY == undefined ? e.layerY : e.offsetY;
    let lastPoint = this.drawingPoints[this.drawingPoints.length - 1] || [];
    if (lastPoint[0] !== pointX || lastPoint[1] !== pointY) {
      this.drawingPoints.push([pointX, pointY]);
    }
  }
  // 清空绘制图层
  drawCtx.clearRect(0, 0, this.canvasWidth, this.canvasHeight);
  // 绘制区域至保存图层
  this.drawSaveArea(this.drawingPoints);

  this.drawedPoints.push(this.drawingPoints);
  this.drawingPoints = [];
  this.isDrawing = false;

  // 绘制结束逻辑
  this.drawCanvas.removeEventListener("click", this.drawImageClickFn);
  this.drawCanvas.removeEventListener("dblclick", this.drawImageDblClickFn);
  this.drawCanvas.removeEventListener("mousemove", this.drawImageMoveFn);
}

双击时,先获取双击的坐标点,并将其推入drawingPoints数组中。然后清空drawCanvas,并调用drawSaveArea方法,将当前绘制区域渲染到saveCanvas上。


七. 遍历区域点坐标的方法

drawSaveArea(points) {
  if (points.length === 0) return;
  this.saveCtx.save();
  this.saveCtx.beginPath();
  points.forEach((item, i) => {
    this.saveCtx.lineTo(...item);
  });
  this.saveCtx.closePath();
  this.saveCtx.lineWidth = "2";
  this.saveCtx.fillStyle = "rgba(255,0, 255, 0.3)";
  this.saveCtx.strokeStyle = "red";
  this.saveCtx.stroke();
  this.saveCtx.fill(); 
  this.saveCtx.restore();
}

drawSaveArea方法会遍历当前区域的所有点坐标,并在saveCanvas上绘制一个闭合的多边形区域,边框为红色,填充为半透明的紫色。接下来,将当前绘制区域的点坐标数组drawingPoints推入drawedPoints数组中,用于保存所有已绘制的区域数据。然后,重置drawingPointsisDrawing的状态,并移除所有绘制事件的监听器。

至此,一个区域的绘制就完成了。如果需要继续绘制新的区域,只需再次调用startDraw方法即可。


保存和渲染数据

八. 保存数据:我们需要将绘制的区域数据保存下来,以及从已有数据中渲染出区域

savePoints() {
  // 将画布坐标数据转换成提交数据
  let objectPoints = [];
  objectPoints = this.drawedPoints.map((area) => {
    let polygon = {};
    area.forEach((point, i) => {
      polygon[`x${i + 1}`] = Math.round(point[0] * this.ratio);
      polygon[`y${i + 1}`] = Math.round(point[1] * this.ratio);
    });
    return {
      polygon: polygon,
    };
  });
  this.submitData = objectPoints;
  console.log("最终提交数据", objectPoints);
}

这里遍历所有已绘制的区域drawedPoints,将每个区域的点坐标根据ratio进行缩放(实际图片尺寸),并转换成一个polygon对象的形式,最终保存在submitData中。


九. 渲染数据

renderDatas() {
  // 将提交数据数据转换成画布坐标
  this.drawedPoints = this.submitData.map((item) => {
    let polygon = item.polygon;
    let points = [];
    for (let i = 1; i < Object.keys(polygon).length / 2 + 1; i++) {
      if (!isNaN(polygon[`x${i}`]) && !isNaN(polygon[`y${i}`])) {
        points.push([
          polygon[`x${i}`] / this.ratio,
          polygon[`y${i}`] / this.ratio,
        ]);
      }
    }
    this.drawSaveArea(points);
    return points;
  });
}

渲染数据的逻辑是,遍历submitData中的每个polygon对象,根据ratio将其坐标值转换成canvas的坐标值,并调用drawSaveArea方法将其渲染到saveCanvas上。至此,我们就完成了在canvas上绘制图片区域的全部逻辑。可以根据具体需求进行相应的调整和扩展。


十. 执行过程

具体全部的执行顺序如下:

  1. 初始化Canvas
    • 调用initCanvas()方法初始化三个Canvas画布
    • 调用initImgCanvas()方法计算并设置画布宽高比例,加载并绘制图片
  2. 开始绘制
    • 调用startDraw()方法
    • 监听drawCanvasclickdblclickmousemove事件
    • 点击时,在drawImageClickFn中记录点坐标
    • 移动时,在drawImageMoveFn中实时绘制区域
    • 双击时,在drawImageDblClickFn中完成当前区域绘制,保存至saveCanvas
  3. 保存和渲染数据
    • 调用savePoints()方法,将绘制区域的点坐标数据转换并保存到submitData
    • 调用renderDatas()方法,将submitData中的数据转换并渲染到saveCanvas

简单来说,就是先初始化画布,然后开始绘制区域的交互,最后保存和渲染数据。


十一. 当然,如果想使用原生JS实现,可以改成像下面这样

let canvasWrap, wrapWidth, wrapHeight, imgCanvas, imgCtx, drawCanvas, drawCtx, saveCanvas, saveCtx;
let ratio, canvasWidth, canvasHeight, imgWidth, imgHeight, imgUrl;
let isDrawing = false;
let drawingPoints = [];
let drawedPoints = [];
let submitData = [];

// 1. 初始化Canvas画布
function initCanvas() {
  // 获取canvas容器元素并设置宽高
  canvasWrap = document.getElementsByClassName("canvas-wrap")[0];
  wrapWidth = canvasWrap.clientWidth;
  wrapHeight = canvasWrap.clientHeight;

  // 获取canvas元素并获取2D绘图上下文
  imgCanvas = document.getElementById("imgCanvas");
  imgCtx = imgCanvas.getContext("2d");
  drawCanvas = document.getElementById("drawCanvas");
  drawCtx = drawCanvas.getContext("2d");
  saveCanvas = document.getElementById("saveCanvas");
  saveCtx = saveCanvas.getContext("2d");
}

// 2. 初始化图片Canvas
function initImgCanvas() {
  // 计算画布和图片的宽高比
  let ww = wrapWidth;
  let wh = wrapHeight;
  let iw = imgWidth;
  let ih = imgHeight;

  if (iw / ih < ww / wh) {
    ratio = ih / wh;
    canvasHeight = wh;
    canvasWidth = (wh * iw) / ih;
  } else {
    ratio = iw / ww;
    canvasWidth = ww;
    canvasHeight = (ww * ih) / iw;
  }

  // 设置三个canvas的宽高
  imgCanvas.width = canvasWidth;
  imgCanvas.height = canvasHeight;
  drawCanvas.width = canvasWidth;
  drawCanvas.height = canvasHeight;
  saveCanvas.width = canvasWidth;
  saveCanvas.height = canvasHeight;

  // 加载图片并绘制到imgCanvas上
  let img = document.createElement("img");
  img.src = imgUrl;
  img.onload = () => {
    console.log("图片已加载");
    imgCtx.drawImage(img, 0, 0, canvasWidth, canvasHeight);
    renderDatas(); // 渲染已有数据
  };
}

// 3. 开始绘制
function startDraw() {
  if (isDrawing) return;
  isDrawing = true;
  // 监听drawCanvas的click、dblclick和mousemove事件
  drawCanvas.addEventListener("click", drawImageClickFn);
  drawCanvas.addEventListener("dblclick", drawImageDblClickFn);
  drawCanvas.addEventListener("mousemove", drawImageMoveFn);
}

// 4. 清空所有绘制区域
function clearAll() {
  saveCtx.clearRect(0, 0, canvasWidth, canvasHeight);
  drawedPoints = [];
}

// 5. 获取并加载图片
function getImage() {
  imgUrl = "需要渲染的图片地址";
  imgWidth = 200;
  imgHeight = 300;
  imgUrl && initImgCanvas();
}

// 6. 点击事件,记录点坐标
function drawImageClickFn(e) {
  if (e.offsetX || e.layerX) {
    let pointX = e.offsetX == undefined ? e.layerX : e.offsetX;
    let pointY = e.offsetY == undefined ? e.layerY : e.offsetY;
    let lastPoint = drawingPoints[drawingPoints.length - 1] || [];
    if (lastPoint[0] !== pointX || lastPoint[1] !== pointY) {
      drawingPoints.push([pointX, pointY]);
    }
  }
}

// 7. 鼠标移动事件,实时绘制区域
function drawImageMoveFn(e) {
  if (e.offsetX || e.layerX) {
    let pointX = e.offsetX == undefined ? e.layerX : e.offsetX;
    let pointY = e.offsetY == undefined ? e.layerY : e.offsetY;
    drawCtx.clearRect(0, 0, canvasWidth, canvasHeight);

    drawCtx.fillStyle = "blue";
    drawingPoints.forEach((item, i) => {
      drawCtx.beginPath();
      drawCtx.arc(...item, 6, 0, 180);
      drawCtx.fill();
    });

    drawCtx.save();
    drawCtx.beginPath();
    drawingPoints.forEach((item, i) => {
      drawCtx.lineTo(...item);
    });
    drawCtx.lineTo(pointX, pointY);
    drawCtx.lineWidth = "3";
    drawCtx.strokeStyle = "blue";
    drawCtx.fillStyle = "rgba(255, 0, 0, 0.3)";
    drawCtx.stroke();
    drawCtx.fill();
    drawCtx.restore();
  }
}

// 8. 双击事件,完成当前区域绘制
function drawImageDblClickFn(e) {
  if (e.offsetX || e.layerX) {
    let pointX = e.offsetX == undefined ? e.layerX : e.offsetX;
    let pointY = e.offsetY == undefined ? e.layerY : e.offsetY;
    let lastPoint = drawingPoints[drawingPoints.length - 1] || [];
    if (lastPoint[0] !== pointX || lastPoint[1] !== pointY) {
      drawingPoints.push([pointX, pointY]);
    }
  }
  drawCtx.clearRect(0, 0, canvasWidth, canvasHeight);
  drawSaveArea(drawingPoints);

  drawedPoints.push(drawingPoints);
  drawingPoints = [];
  isDrawing = false;

  drawCanvas.removeEventListener("click", drawImageClickFn);
  drawCanvas.removeEventListener("dblclick", drawImageDblClickFn);
  drawCanvas.removeEventListener("mousemove", drawImageMoveFn);
}

// 9. 绘制区域到saveCanvas
function drawSaveArea(points) {
  if (points.length === 0) return;
  saveCtx.save();
  saveCtx.beginPath();
  points.forEach((item, i) => {
    saveCtx.lineTo(...item);
  });
  saveCtx.closePath();
  saveCtx.lineWidth = "2";
  saveCtx.fillStyle = "rgba(255,0, 255, 0.3)";
  saveCtx.strokeStyle = "red";
  saveCtx.stroke();
  saveCtx.fill();
  saveCtx.restore();
}

// 10. 保存绘制数据
function savePoints() {
  let objectPoints = [];
  objectPoints = drawedPoints.map((area) => {
    let polygon = {};
    area.forEach((point, i) => {
      polygon[`x${i + 1}`] = Math.round(point[0] * ratio);
      polygon[`y${i + 1}`] = Math.round(point[1] * ratio);
    });
    return {
      polygon: polygon,
    };
  });
  submitData = objectPoints;
  console.log("最终提交数据", objectPoints);
}

// 11. 渲染已有数据
function renderDatas() {
  drawedPoints = submitData.map((item) => {
    let polygon = item.polygon;
    let points = [];
    for (let i = 1; i < Object.keys(polygon).length / 2 + 1; i++) {
      if (!isNaN(polygon[`x${i}`]) && !isNaN(polygon[`y${i}`])) {
        points.push([
          polygon[`x${i}`] / ratio, // 根据ratio换算canvas坐标
          polygon[`y${i}`] / ratio,
        ]);
      }
    }
    drawSaveArea(points); // 调用drawSaveArea将区域绘制到saveCanvas上
    return points;
  });
}

// 使用方式
initCanvas(); // 1. 初始化Canvas画布
getImage(); // 5. 获取并加载图片 
startDraw(); // 3. 开始绘制

renderDatas函数的作用是将已有的绘制数据(submitData)转换成canvas坐标,

并调用drawSaveArea方法将其渲染到saveCanvas上。

该函数遍历submitData中的每个polygon对象,

根据ratio将其坐标值转换成canvas的坐标值,

然后调用drawSaveArea方法绘制该区域。

最终返回一个包含所有区域点坐标的数组drawedPoints

最后,需要按顺序调用initCanvas() -> getImage() -> startDraw()等方法,分别完成初始化画布、加载图片和开始绘制的功能。

十二. 全部代码

全部的vuejs代码和原生js代码直接点我头像,私我,获取全部代码。
 

创作不易,感觉有用,就一键三连,感谢(●'◡'●)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/610651.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

GPU云渲染平台选择应该看什么?瑞云渲染带你了解

在选择GPU云渲染平台时&#xff0c;关键的考量因素包括渲染速度、支持的软件兼容性、价格性价比以及用户服务质量。了解这些指标将帮助用户在众多选项中做出明智的选择&#xff0c;以优化渲染效率和成本效益。 一、GPU与渲染有什么关系 GPU与渲染有着密切的关系&#xff0c;在…

如何创建window11虚拟机,超详细!!!

为什么要创建window11虚拟机&#xff0c;有了虚拟机&#xff0c;可以避免一些软件的危害&#xff0c;我们可以在虚拟机里&#xff0c;测试软件的安全性。当然你也可以在虚拟机里体验下流氓软件带来的快乐。众所周知&#xff0c;流氓软件会修改默认浏览器&#xff0c;将几款流氓…

影刀进行shopee商品排名零代码爬取

需要研究shopee平台的排名更新时间段和周期&#xff0c;几分钟用影刀写了一个爬取应用&#xff0c;每10分钟进行一次排名爬取&#xff08;以fan‘风扇’为例&#xff09;&#xff0c;0代码爬取。 打开’fan’关键词搜索网页&#xff1b;等待网页加载&#xff1b;滚动进一步加载…

实体门店超-常规营销获客:218套落地方案/打造引流/锁客/复购/裂变营销

课程内容&#xff1a; 1 记住&#xff0c;生意不好不一定是你产品出了问题,mp4 2 生意人为什么要从产品思维向流量思维转型&#xff0c;社区超市每月多5万.mp4 3 实体老板不懂鱼塘理论只能等死&#xff0c;美业1招锁定275名年用户卡,mp4 4 餐饮赢销八部&#xff0c;帮你引爆…

基于springboot实现智慧图书管理系统项目【项目源码+论文说明】

基于springboot实现智慧图书管理系统演示 摘要 如今社会上各行各业&#xff0c;都在用属于自己专用的软件来进行工作&#xff0c;互联网发展到这个时候&#xff0c;人们已经发现离不开了互联网。互联网的发展&#xff0c;离不开一些新的技术&#xff0c;而新技术的产生往往是为…

结合创新!通道注意力+UNet,实现高精度分割

在U-Net网络中加入通道注意力机制能显著提升模型的性能&#xff01; 具体点说是在U-Net的卷积层之后添加一个通道注意力模块&#xff0c;这样这个模块可以学习不同通道之间的权重&#xff0c;并根据这些权重对通道进行加权&#xff0c;从而增强重要通道的特征表示。 这种结合…

如何在家轻松赚钱,分享五个在家赚钱的好点子

在家轻松赚钱可以考虑以下几种方式 1. 网络任务 网上可以做下百度的致米宝库&#xff0c;一个月有个一千多块钱&#xff0c;我感觉还行&#xff0c;比较适合新手小白的&#xff0c;自由操作即可。 2. 写作和翻译 如果你擅长写作或翻译&#xff0c;可以在各种写作平台或翻译平…

java图片水印字体乱码问题

问题描述&#xff1a;在linux Centos-7.5_64bit系统的其他服务器上不乱码&#xff0c;在部署项目的正式服务器乱码 水印字体设置是 微软雅黑 Font wordFont new Font("微软雅黑", Font.ITALIC,(srcImgHeightsrcImgWidth)/50); 一.Springboot项目&#xff0c;部署在…

Veeam - 数据保护和管理解决方案_Windows平台部署备份还原VMware手册

Veeam - - 数据保护和管理解决方案 Veeam Backup & Replication Console Veeam Data Platform Veeam Backup & Replication是一款强大的虚拟机备份、恢复和复制解决方案 安全备份、干净恢复和数据弹性 — 即时交付 在混合云中随时随地管理、控制、备份和恢复您的所有数…

websocket最大数量的限制问题

更多ruoyi-nbcio功能请看演示系统 gitee源代码地址 前后端代码&#xff1a; https://gitee.com/nbacheng/ruoyi-nbcio 演示地址&#xff1a;RuoYi-Nbcio后台管理系统 http://218.75.87.38:9666/ 更多nbcio-boot功能请看演示系统 gitee源代码地址 后端代码&#xff1a; h…

【SRC-CPP-OpenCV】给图片更换背景色

文章目录 Part.I IntroductionPart.II Main_bodyChap.I 源码简析Chap.II 效果展示 Part.III 源码Reference Part.I Introduction 本文将介绍如何用 OpenCV 更换图片的背景色&#xff08;附有完整代码&#xff09;。 Part.II Main_body Chap.I 源码简析 配置部分&#xff1a…

嵌入式学习<1>:建立工程、GPIO和keil仿真

嵌入式学习_part1 本部分笔记用于学习记录&#xff0c;笔记源头 >>b站江科大_STM32入门教程_新建工程 建立工程、GPIO 开发环境&#xff1a;keil MDK、STM32F103C8T6 1 &#xff09;建立工程 &#xff08;1&#xff09;基于寄存器开发、基于标准库 或者 基于HAL库开…

【Linux】编写一个简易的shell

思维导图 学习目标 将简易的shell代码进行编写。 一、阐述shell的基本思路 在进程程序替换中&#xff0c;我们可以将一个指令交给子进程&#xff0c;让子进程去完成这个指令。如果这个命令是一个内建命令&#xff0c;我们需要将这个命令交给bash进行处理。 大致思路是&#xf…

语言基础 /CC++ 可变参函数设计与实践,变参函数的实现、使用、替代方法

文章目录 概述适用于做可变参数的数据类型格式化字符串输出用int做变长参数类型用结构体指针做变长参数类型用double做变长参数类型用结构体直接做变长参数类型 变参函数与宏定义符号 ... 不能透传符号 ... 不接受ap做参数_VA_ARGS_ 代表可变参数 回调可变参数函数取代变参函数…

【数据库原理及应用】期末复习汇总高校期末真题试卷05

试卷 一、选择题 1.( )是存储在计算机内有结构的数据的集合。 A.数据库系统 B.数据库 C.数据库管理系统 D.数据结构 2.数据库的三级模式结构中&#xff0c;数据库对象—视图是( ) A.外模式 B.内模式 C.存储模式 D.模式 3.在下列关于关系表的陈述中&#xff0c;错误的是(…

Zabbix监控中文乱码问题解决方法

一、问题描述 1.查看Zabbix仪表盘 在Zabbix的监控仪表盘界面&#xff0c;字体显示为“方框”&#xff0c;无法查看到具体的性能指标名称。 2.问题分析 Zabbix的web端没有中文字库&#xff0c;导致切换到中文页面&#xff0c;中文成了乱码这个问题&#xff0c;我们最需要把中文…

使用LlamaIndex构建能对文档进行推理;大模型自动执行基于浏览器的工作流;ElevenLabs宣布进军音乐创作领域

✨ 1: Building Agentic RAG with LlamaIndex 由Jerry Liu教授、专注于使用LlamaIndex构建能对文档进行推理和回答复杂问题的代理研究型RAG的新课程 我很高兴向大家介绍“使用&#xff08;RAG&#xff09;与Llamalndex构建主动性研究助理代理”的课程&#xff0c;这是由Llama…

XSS-Labs 靶场通过解析(下)

前言 XSS-Labs靶场是一个专门用于学习和练习跨站脚本攻击&#xff08;XSS&#xff09;技术的在线平台。它提供了一系列的实验场景和演示&#xff0c;帮助安全研究人员、开发人员和安全爱好者深入了解XSS攻击的原理和防御方法。 XSS-Labs靶场的主要特点和功能包括&#xff1a;…

关联系统-整车控制器VCU

整车驱动原理 如上图所示&#xff0c;电池组输出直流电给DC/AC逆变器&#xff0c;逆变器将直流电转化为交流电输入给电机&#xff0c;电机在电磁力的作用下转动&#xff0c;通过传动机构将驱动力输送到车轮&#xff0c;其中整车控制器VCU可以根据用户油门/刹车的输入控制输出功…

国际化业务、全球化团队沟通难?浅析跨文化沟通的挑战和应对措施

在全球化背景下&#xff0c;发展出海业务相比以往更具有巨大的前景和潜力&#xff0c;是企业寻找“第二增长点”和提升综合实力的优先选择。近几年“中企出海”大热&#xff0c;中国企业在世界各地开展业务拓展国际市场&#xff0c;获得更加国际化的营商经验与客户资源。与此同…
最新文章