【PPTist】插入形状、插入图片、插入图表

一、插入形状

插入形状有两种情况,一种是插入固定的形状,
在这里插入图片描述

一种是插入自定义的形状。
插入固定的形状时,跟上一篇文章 绘制文本框 是一样一样的,都是调用的 mainStore.setCreatingElement() 方法,只不多传的类型不一样。还有插入线条,也是类似的。

mainStore.setCreatingElement({
  type: 'shape',
  data: shape,
})

所以咱们那接下来主要看插入自定义形状时的代码执行流程

1、点击
<Popover trigger="click" v-model:value="shapeMenuVisible" style="height: 100%;" :offset="10">
  <template #content>
    <PopoverMenuItem center @click="() => { drawCustomShape(); shapeMenuVisible = false }">自由绘制</PopoverMenuItem>
  </template>
  <IconDown class="arrow" />
</Popover>

src/views/Editor/CanvasTool/index.vue

// 绘制自定义任意多边形
const drawCustomShape = () => {
  mainStore.setCreatingCustomShapeState(true)
  shapePoolVisible.value = false
}

src/store/main.ts

setCreatingCustomShapeState(state: boolean) {
  this.creatingCustomShape = state
},

有了 creatingCustomShape,下面的组件就会显示

<ShapeCreateCanvas
  v-if="creatingCustomShape"
  @created="data => insertCustomShape(data)"
/>
2、mousedown

src/views/Editor/Canvas/ShapeCreateCanvas.vue
触发 created 方法

const addPoint = (e: MouseEvent) => {
  const { pageX, pageY } = getPoint(e)
  isMouseDown.value = true

  if (closed.value) emit('created', getCreateData())
  else points.value.push([pageX, pageY])

  document.onmouseup = () => {
    isMouseDown.value = false
  }
}
3、created

src/views/Editor/Canvas/index.vue
插入任意多边形

// 插入自定义任意多边形
const insertCustomShape = (data: CreateCustomShapeData) => {
  const {
    start,
    end,
    path,
    viewBox,
  } = data
  const position = formatCreateSelection({ start, end })
  if (position) {
    const supplement: Partial<PPTShapeElement> = {}
    if (data.fill) supplement.fill = data.fill
    if (data.outline) supplement.outline = data.outline
    // 创建形状元素
    createShapeElement(position, { path, viewBox }, supplement)
  }

  // 清除 creatingCustomShape
  mainStore.setCreatingCustomShapeState(false)
}
4、mousemove

src/views/Editor/Canvas/ShapeCreateCanvas.vue
如果鼠标按下,添加 points,就会形成折线的效果。
可以看到只要起点和终点比较近就算闭合了,防止对不上

const updateMousePosition = (e: MouseEvent) => {
  // 如果鼠标按下,则添加点
  if (isMouseDown.value) {
    const { pageX, pageY } = getPoint(e, true)
    points.value.push([pageX, pageY])
    mousePosition.value = null
    return
  }

  // 更新鼠标位置
  const { pageX, pageY } = getPoint(e)
  mousePosition.value = [pageX, pageY]

  // 判断是否闭合
  if (points.value.length >= 2) {
    const [firstPointX, firstPointY] = points.value[0]
    if (Math.abs(firstPointX - pageX) < 5 && Math.abs(firstPointY - pageY) < 5) {
      closed.value = true
    }
    else closed.value = false
  }
  else closed.value = false
}

根据鼠标位置 mousePosition 计算 path

const path = computed(() => {
  let d = ''
  for (let i = 0; i < points.value.length; i++) {
    const point = points.value[i]
    if (i === 0) d += `M ${point[0]} ${point[1]} `
    else d += `L ${point[0]} ${point[1]} `
  }
  if (points.value.length && mousePosition.value) {
    d += `L ${mousePosition.value[0]} ${mousePosition.value[1]}`
  }
  return d
})

模版中的 path 元素随之更新

<svg overflow="visible">
	<path
      :d="path" 
      stroke="#d14424" 
      :fill="closed ? 'rgba(226, 83, 77, 0.15)' : 'none'" 
      stroke-width="2" 
    ></path>
</svg>
5、取消绘制的按键绑定
const keydownListener = (e: KeyboardEvent) => {
  const key = e.key.toUpperCase()
  if (key === KEYS.ESC) close()
  if (key === KEYS.ENTER) create()
}
onMounted(() => {
  message.success('点击绘制任意形状,首尾闭合完成绘制,按 ESC 键或鼠标右键取消,按 ENTER 键提前完成', {
    duration: 0,
  })
  document.addEventListener('keydown', keydownListener)
})

以及鼠标右键也会取消绘制

@contextmenu.stop.prevent="close()"
const close = () => {
  mainStore.setCreatingCustomShapeState(false)
}

二、插入图片

src/views/Editor/CanvasTool/index.vue
插入图片也是一个自定义组件

<FileInput @change="files => insertImageElement(files)">
  <IconPicture class="handler-item" v-tooltip="'插入图片'" />
</FileInput>

这个组件里面实现上传功能的是 input 标签

<input 
  class="input"
  type="file" 
  name="upload" 
  ref="inputRef" 
  :accept="accept" 
  @change="$event => handleChange($event)"
>

上传之后插入图片元素

const insertImageElement = (files: FileList) => {
  const imageFile = files[0]
  if (!imageFile) return
  getImageDataURL(imageFile).then(dataURL => createImageElement(dataURL))
}

src/utils/image.ts

获取图片宽高的方法,相比大家都挺熟悉的

/**
 * 获取图片的原始宽高
 * @param src 图片地址
 */
export const getImageSize = (src: string): Promise<ImageSize> => {
  return new Promise(resolve => {
    const img = document.createElement('img')
    img.src = src
    img.style.opacity = '0'
    document.body.appendChild(img)

    img.onload = () => {
      const imgWidth = img.clientWidth
      const imgHeight = img.clientHeight
    
      img.onload = null
      img.onerror = null

      document.body.removeChild(img)

      resolve({ width: imgWidth, height: imgHeight })
    }

    img.onerror = () => {
      img.onload = null
      img.onerror = null
    }
  })
}

获取图片宽高之后,创建图片元素,通过 lefttop 将图片水平垂直居中
src/hooks/useCreateElement.ts

/**
 * 创建图片元素
 * @param src 图片地址
 */
const createImageElement = (src: string) => {
  getImageSize(src).then(({ width, height }) => {
    const scale = height / width

    if (scale < viewportRatio.value && width > VIEWPORT_SIZE) {
      width = VIEWPORT_SIZE
      height = width * scale
    }
    else if (height > VIEWPORT_SIZE * viewportRatio.value) {
      height = VIEWPORT_SIZE * viewportRatio.value
      width = height / scale
    }

    createElement({
      type: 'image',
      id: nanoid(10),
      src,
      width,
      height,
      left: (VIEWPORT_SIZE - width) / 2,
      top: (VIEWPORT_SIZE * viewportRatio.value - height) / 2,
      fixedRatio: true,
      rotate: 0,
    })
  })
}

复习一下创建元素的方法,会把元素放到当前幻灯片的元素列表中

// 创建(插入)一个元素并将其设置为被选中元素
const createElement = (element: PPTElement, callback?: () => void) => {
  // 添加元素到元素列表
  slidesStore.addElement(element)
  // 设置被选中元素列表
  mainStore.setActiveElementIdList([element.id])

  if (creatingElement.value) mainStore.setCreatingElement(null)

  setTimeout(() => {
    // 设置编辑器区域为聚焦状态
    mainStore.setEditorareaFocus(true)
  }, 0)

  if (callback) callback()

  // 添加历史快照
  addHistorySnapshot()
}

三、插入图表

插入图表的方法,其实也差不多,就是往当前的幻灯片里添加一个图表对象。不过这里就不讲前面怎么添加元素了,讲讲后面怎么展示元素吧。先来看一下图表元素的数据:

const newElement: PPTChartElement = {
  type: 'chart',
  id: nanoid(10),
  chartType: CHART_TYPES[type],
  left: 300,
  top: 81.25,
  width: 400,
  height: 400,
  rotate: 0,
  themeColor: [theme.value.themeColor],
  gridColor: theme.value.fontColor,
  data: {
    labels: ['类别1', '类别2', '类别3', '类别4', '类别5'],
    legends: ['系列1'],
    series: [
      [12, 19, 5, 2, 18],
    ],
  },
}

是这个元素对元素列表进行循环的
src/views/Editor/Canvas/index.vue

<EditableElement 
  v-for="(element, index) in elementList" 
  :key="element.id"
  :elementInfo="element"
  :elementIndex="index + 1"
  :isMultiSelect="activeElementIdList.length > 1"
  :selectElement="selectElement"
  :openLinkDialog="openLinkDialog"
  v-show="!hiddenElementIdList.includes(element.id)"
/>

src/views/Editor/Canvas/EditableElement.vue
这个组件中通过动态组件的方式控制显示哪个元素

<component
  :is="currentElementComponent"
  :elementInfo="elementInfo"
  :selectElement="selectElement"
  :contextmenus="contextmenus"
></component>
const currentElementComponent = computed<unknown>(() => {
  const elementTypeMap = {
    [ElementTypes.IMAGE]: ImageElement,
    [ElementTypes.TEXT]: TextElement,
    [ElementTypes.SHAPE]: ShapeElement,
    [ElementTypes.LINE]: LineElement,
    [ElementTypes.CHART]: ChartElement,
    [ElementTypes.TABLE]: TableElement,
    [ElementTypes.LATEX]: LatexElement,
    [ElementTypes.VIDEO]: VideoElement,
    [ElementTypes.AUDIO]: AudioElement,
  }
  return elementTypeMap[props.elementInfo.type] || null
})

我们的目标就是 ChartElementsrc/views/components/element/ChartElement/index.vue
然后图表那一小块是这个:src/views/components/element/ChartElement/Chart.vue,图表是通过 chartist 库实现的

import { BarChart, LineChart, PieChart } from 'chartist'

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

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

相关文章

Elasticsearch—索引库操作(增删查改)

Elasticsearch中Index就相当于MySQL中的数据库表 Mapping映射就类似表的结构。 因此我们想要向Elasticsearch中存储数据,必须先创建Index和Mapping 1. Mapping映射属性 Mapping是对索引库中文档的约束&#xff0c;常见的Mapping属性包括&#xff1a; type&#xff1a;字段数据类…

ROS Action接口

实现自主导航是使用Action接口的主要目的 在实际使用navigation导航系统的时候&#xff0c;机器人需要自主进行导航。不能每次都手动设置导航的目标点。所以需要编写程序代码来实现导航控制。这就需要使用到navigation的导航接口。Navigation的这个导航接口有好几个。Rose官方…

macOS 安装tomcat9

macOS 安装tomcat9 URL&#xff1a;https://tomcat.apache.org/download-90.cgi 解压之后放到指定目录 /Users/lanren/install/tomcat-9 自己取个名字就行 给权限&#xff1a; ① 先进行权限修改&#xff1a;终端输入sudo chmod 755 /Users/lanren/install/tomcat-9/bin/…

PatchTST:通道独立的、切片的 时序 Transformer

出处&#xff1a;ICLR 2023 代码链接&#xff1a;yuqinie98/PatchTST: An offical implementation of PatchTST: "A Time Series is Worth 64 Words: Long-term Forecasting with Transformers." (ICLR 2023) https://arxiv.org/abs/2211.14730 一 模型主要思想及…

QT c++ 样式 设置 按钮(QPushButton)的渐变色美化

上一篇文章中描述了标签的渐变色美化,本文描述按钮的渐变色美化。 1.头文件 #ifndef WIDGET_H #define WIDGET_H #include <QWidget> //#include "CustomButton.h"#include <QVBoxLayout> #include <QLinearGradient> #include <QPushButton&…

OPT: Open Pre-trained Transformer语言模型

摘要 大规模语言模型通常需要数十万计算日的训练时间&#xff0c;展现了在零样本和小样本学习中的显著能力。鉴于其计算成本之高&#xff0c;这些模型在没有大量资本投入的情况下难以复现。对于那些通过API提供的少数模型&#xff0c;研究者无法获取完整的模型权重&#xff0c…

力扣257(关于回溯算法)二叉树的所有路径

257. 二叉树的所有路径 一.问题描述 已解答 简单 相关标签 相关企业 给你一个二叉树的根节点 root &#xff0c;按 任意顺序 &#xff0c;返回所有从根节点到叶子节点的路径。 叶子节点 是指没有子节点的节点。 示例 1&#xff1a; 输入&#xff1a;root [1,2,3,null,5…

《OpenCV计算机视觉实战项目》——银行卡号识别

文章目录 项目任务及要求项目实现思路项目实现及代码导入模块设置参数对模版图像中数字的定位处理银行卡的图像处理读取输入图像&#xff0c;预处理找到数字边框使用模版匹配&#xff0c;计算匹配得分 画出并打印结果 项目任务及要求 任务书&#xff1a; 要为某家银行设计一套…

Python学习(三)基础入门(数据类型、变量、条件判断、模式匹配、循环)

目录 一、第一个 Python 程序1.1 命令行模式、Python 交互模式1.2 Python的执行方式1.3 SyntaxError 语法错误1.4 输入和输出 二、Python 基础2.1 Python 语法2.2 数据类型1&#xff09;Number 数字2&#xff09;String 字符串3&#xff09;List 列表4&#xff09;Tuple 元组5&…

系统思考—要素连接

“改变你的思维&#xff0c;就能改变你的世界”— 诺曼皮尔 世界上的所有事物&#xff0c;都在规律的支配下&#xff0c;以系统的方式运转。显性的部分是我们能看到的“要素”&#xff0c;而那些看不见的力量&#xff0c;正是推动系统运作的要素之间的相互作用。更隐秘的&…

云原生(1)

作业&#xff1a; 1、shell 脚本写出检测 /tmp/size.log 文件如果存在显示它的内容&#xff0c;不存在则创建一个文件将创建时间写入。 2、写一个 shel1 脚本,实现批量添加 20个用户,用户名为user01-20,密码为user 后面跟5个随机字符。 3、编写个shel 脚本将/usr/local 日录下大…

【IO编程】文件IO的API

这篇文章在 文章&#xff1a;【文件I/O】文件持久化 的基础之上&#xff0c;更进一步的描述了文件IO中更多更详细的API详解。 文件IO 文件IO操作是必须要理解的环节之一&#xff0c;因为 s&#xff1a;套接字文件 &#xff1b;p: 管道文件 —> 都需要通过文件IO来进行打开…

【数据库】Unity 使用 Sqlite 数据库

1.找到需要三个 DLL Mono.Data.Sqlite.dllSystem.Data.dllsqlite3.dll 上面两个dll可在本地unity安装目录找到&#xff1a; C:\Program Files\Unity\Hub\Editor\2022.3.xxf1c1\Editor\Data\MonoBleedingEdge\lib\mono\unityjit-win32 下面dll可在sqlite官网下载到&#xff…

省级-农业科技创新(农业科技专利)数据(2010-2022年)-社科数据

省级-农业科技创新&#xff08;农业科技专利&#xff09;数据&#xff08;2010-2022年&#xff09;-社科数据https://download.csdn.net/download/paofuluolijiang/90028570 https://download.csdn.net/download/paofuluolijiang/90028570 数据 年份、省份、农业科技专利数量…

51单片机——定时器中断(重点)

STC89C5X含有3个定时器&#xff1a;定时器0、定时器1、定时器2 注意&#xff1a;51系列单片机一定有基本的2个定时器&#xff08;定时器0和定时器1&#xff09;&#xff0c;但不全有3个中断&#xff0c;需要查看芯片手册&#xff0c;通常我们使用的是基本的2个定时器&#xff…

计算机的错误计算(二百零九)

摘要 利用两个大模型判断 是否为有理数&#xff1f;其值是多少&#xff1f;由实验知&#xff0c;其中一个大模型判断错误&#xff0c;说不是有理数&#xff1b;至于其值&#xff0c;该大模型选了一个错误的数值。 例1. e^(45*ln(24.8))是有理数吗&#xff1f;其值是多少&am…

Facebook 隐私变革之路:回顾与展望

在数字时代&#xff0c;个人隐私的保护一直是社交平台面临的重大挑战之一。作为全球最大的社交网络平台&#xff0c;Facebook&#xff08;现为Meta&#xff09;在处理用户隐私方面的变革&#xff0c;历经了多次调整与完善。本文将回顾Facebook在隐私保护方面的历程&#xff0c;…

第432场周赛:跳过交替单元格的之字形遍历、机器人可以获得的最大金币数、图的最大边权的最小值、统计 K 次操作以内得到非递减子数组的数目

Q1、跳过交替单元格的之字形遍历 1、题目描述 给你一个 m x n 的二维数组 grid&#xff0c;数组由 正整数 组成。 你的任务是以 之字形 遍历 grid&#xff0c;同时跳过每个 交替 的单元格。 之字形遍历的定义如下&#xff1a; 从左上角的单元格 (0, 0) 开始。在当前行中向…

GitLab CI/CD使用runner实现自动化部署前端Vue2 后端.Net 7 Zr.Admin项目

1、查看gitlab版本 建议安装的runner版本和gitlab保持一致 2、查找runner 执行 yum list gitlab-runner --showduplicates | sort -r 找到符合gitlab版本的runner&#xff0c;我这里选择 14.9.1版本 如果执行出现找不到下载源&#xff0c;添加官方仓库 执行 curl -L &quo…

机器学习基础-机器学习的常用学习方法

目录 半监督学习的概念 规则学习的概念 基本概念 机器学习里的规则 逻辑规则 规则集 充分性与必要性 冲突消解 命题逻辑 → 命题规则 序贯覆盖 单条规则学习 剪枝优化 强化学习的概念 1. 强化学习对应了四元组 2. 强化学习的目标 强化学习常用马尔可夫决策过程…